fix(web): name checkboxes, radios, switches, cells, and shadow-hosted buttons from content, and a reset input by its value

Merged
OK
1mo ago
+157-22
  • Repro through the runner, on /roles with a <div role="switch">Dark mode</div>: getByRole('switch', { name: 'Dark mode' }) resolves one node through Playwright's role selector, and toHaveAccessibleName('Dark mode') on the same locator fails with observed: name "" (match count 1). The agent tree lists an unnamed switch. Same for role="checkbox", role="radio", <td>, role="gridcell", a custom element with role="button" whose text is behind its shadow root, and <input type="reset" value="Clear">.
  • Cause: accessibleName in packages/web/src/in-page/read-semantics.ts named from content for a hand-picked role list, childrenNameOf read light childNodes only, and reset was mapped to button but only button and submit took the value.
  • The role list is now accname's, the same list Playwright's roleUtils.allowsNameFromContent uses, plus listitem, status, and alert, which the tree already named. NAME_FROM_CONTENT_ROLES in packages/web/src/in-page/read-semantics.ts.
  • Content collection follows Playwright's walk (contentChildrenOf): a <slot> reads its assigned nodes, a host reads its unslotted light children and then its shadow tree, a closed root from the record the init script keeps. A <slot> is display: contents, so both readers space it like a block (* Custom, not *Custom).
  • submit and reset read their value, else Submit or Reset, as Chrome and Playwright do.
  • Tests: packages/web/tests/integration/read-node-names.test.ts asserts the tree name and Playwright's getByRole(role, { name, exact: true }) land on the same element for 15 cases; packages/e2e/tests/integration/web-platform.test.ts runs toHaveCount(1) and toHaveAccessibleName(name) on one locator through the real runner for six of them.
  • Docs: docs/reference/screen.mdx names the roles and the shadow tree rule.

Out of scope, found on the way: the reader reads a scope-less <th> as columnheader where Playwright reads it as rowheader when a filled <td> sits next to it. The fixture uses scope="col" to stay clear of that.


Devin Review