fix(web): locators resolve inside closed shadow roots the reader observes

Merged
OK
1mo ago
+1826-1078

The observation has pierced closed shadow roots for a while, so a failure printed nodes no screen query could reach.

  • An e2e-roots= selector engine hands Playwright's own role, text, label, and test-id engines every closed root the page attached under the query root, so each element matches exactly once and the matching rules inside a root are the ones outside it. A page without closed roots pays nothing: the init script counts attached roots and the engine returns the query root alone at zero.
  • The six query kinds declare their candidates in one table in locators.ts; the exact-label path is no longer a second projection.
  • read-node.ts is 153 lines (from 1188): the closed-root scripts live in closed-shadow.ts, the in-page reader in in-page/read-semantics.ts.
  • Limits, documented: web.locator(css), frameLocator, and filter({ hasText }) keep Playwright's reach and stop at a closed root; has crosses it. Matches outside closed roots come first, then each root's; a test pins that order against the reader's.
  • Tests: closed-shadow-locators.test.ts (every query kind inside a root, nesting both ways, the visible flag, index, scope, has), the flipped lifecycle assertion, the engine source unit tests. The shadow-dom-form twin lands here, and the agentic scenario loses a gap that was already stale. Changeset @e2edev/web patch.
  • Two reads the reader stopped at the document, from review: an aria-labelledby id now resolves in the control's own tree root, so an exact getByLabel finds a labelled-by control inside an open or closed root and the observed tree names it; focus follows each root's active element through open roots and the recorded closed ones, so toBeFocused holds for a control inside a shadow root. Covered on /closed-form and in the reader names test with a same-id decoy in the light DOM.

Devin Review