fix(web): an exact label query composes exactly as a has filter or a scope

Merged
OK
1mo ago
+265-39
  • rows.filter({ has: screen.getByLabel('Name', { exact: true }) }) matched a row whose input is labelled Last Name too, so .first().getByRole('button', { name: 'Remove' }).tap() removed the wrong row. Standalone, the same query matched one input.
  • Cause: project() in packages/web/src/locators.ts built the composable form of an exact label query as Playwright's substring getByLabel, since Playwright's chain had no selector for the reader's exact label predicate. The standalone path kept the predicate; the composed path lost it.
  • Fix: a third selector engine, e2e-label=<json> (packages/web/src/label-selector.ts), runs the in-page reader on the labelable controls under its root, closed shadow roots included, and keeps those whose labels equal the value after whitespace normalization. That is the same comparison the surface makes standalone, so a has filter or a scope on an exact label now matches exactly what the standalone query matches.
  • Playwright's own getByLabel(..., { exact: true }) was the shorter route and I did not take it: it reads a label's aria-hidden text, so Display name* would match standalone and miss inside has. The new test pins that case.
  • SELECTOR_ENGINES (packages/web/src/selector-engines.ts) is now the list both registration paths (launched browser, CDP context) read.
flowchart LR
  A["getByLabel('Name', { exact: true })"] --> B{composed?}
  B -- "standalone" --> C["e2e-roots=&lt;labelable&gt; then surface keeps labels == 'Name'"]
  B -- "has / scope, before" --> D["e2e-roots= >> getByLabel('Name', substring)"]
  B -- "has / scope, after" --> E["e2e-label={value:'Name',...} runs the reader in the page"]
const rows = screen.getByRole('listitem');
await rows
  .filter({ has: screen.getByLabel('Name', { exact: true }) })
  .first()
  .getByRole('button', { name: 'Remove' })
  .tap();

New browser-backed test: packages/web/tests/integration/label-composition.test.ts on a /rows fixture (Last Name, Name* with an aria-hidden marker, and a label carrying " and >>).


Devin Review