Skip to content

feat(selectors): replace pierceFrames() with frameLocator() - #42395

Merged
Dmitry Gozman (dgozman) merged 4 commits into
microsoft:mainfrom
dgozman:any-frame
Aug 28, 2026
Merged

feat(selectors): replace pierceFrames() with frameLocator()#42395
Dmitry Gozman (dgozman) merged 4 commits into
microsoft:mainfrom
dgozman:any-frame

Conversation

@dgozman

@dgozman Dmitry Gozman (dgozman) commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • page.frameLocator() / frame.frameLocator() without a selector replace pierceFrames(): they pick the frame the match starts in, and the rest of the selector resolves inside that single frame like a regular locator.
  • Dropping mid-chain piercing removes its restrictions — composite locators, *-capture and mid-chain nth now work — and replaces the recursive piercing resolver with a per-frame run of the existing chained resolution.
  • Removes the pierceFrames context option and the corresponding test option, along with the no-pierce-frames opt-out token.
  • Codegen now prefers a bare frameLocator() prefix whenever it saves at least two frameLocator(selector) calls and still pinpoints the target frame, falling back to the full chain otherwise.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Hi, I'm the Playwright bot and I took a first look at the CI failures here.

🟡 One failure, can't pin it on this PR either way

[playwright-test] › ui-mode-test-network-tab.spec.ts:198 › should pretty-print XML request body failed once on windows-latest-node22. It's a UI-mode network-tab test that never touches anyFrame()/selectors, its subject is unrelated to this PR, but it has a spotless history so I can't confirm it as a known flake. Calling it uncertain.

Details

This PR is a selector-engine refactor (anyFrame() replacing pierceFrames(), plus injectedScript/selectorParser/locator generation). The one real failure doesn't exercise any of that.

Uncertain

  • [playwright-test] › ui-mode-test-network-tab.spec.ts:198 › should pretty-print XML request body (windows-latest-node22) — the assertion getByRole('tabpanel', { name: 'Payload' }).locator('.CodeMirror-code .CodeMirror-line') resolved to 0 elements for the full 75s timeout — the Payload panel never rendered its content. That's a plain locator, not anyFrame(), and the selector change works across the rest of the run (51291 passed); a broken selector engine would fail broadly, not once on Windows. Everything about the signature (Windows-only, timeout, empty panel) leans UI-mode rendering flake — but I can't prove it: across the results DB this test is failed 1 / 1009 runs, and that single failure is this run. No prior failure anywhere the PR can't be responsible, so per our flake rule it stays uncertain rather than confirmed flake. A re-run on the Windows shard would settle it.

Not triaged

  • The 4 flaky results (video.spec.ts:699, chromium.spec.ts:436, workers.spec.ts:191, beforeunload.spec.ts:130) all passed on retry and are unrelated to selectors — nothing to chase.

Triaged by the Playwright bot - agent run

@github-actions

This comment has been minimized.

Comment thread packages/playwright-core/src/server/frameSelectors.ts
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread packages/isomorphic/selectorParser.ts Outdated
Comment thread tests/page/locator-any-frame.spec.ts Outdated
Comment thread packages/playwright-core/src/server/recorder/recorderUtils.ts
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

page.anyFrame() and frame.anyFrame() start the match in any frame of the
subtree, and the rest of the selector resolves inside that single frame,
just like a regular locator. This drops the mid-chain frame piercing that
pierceFrames() did, together with the restrictions it required: composite
locators, *-capture and mid-chain nth are all allowed now.

Also removes the pierceFrames context and test option. Codegen prefers an
anyFrame() prefix whenever it saves at least two frameLocator() calls and
still pinpoints the target frame.
A nested "any-frame" token used to reach the injected control engine and
match nothing, so page.locator('button').or(page.anyFrame().locator('a'))
silently dropped the second operand.

The token applies to the whole locator, so an operand never carries one:

  page.anyFrame().locator('.classname').or(page.getByRole('button'))

matches ".classname or button" inside every frame. Skip a leading
"any-frame" when matching the nested frame prefix, so that an operand may
still repeat the frame chain, and reject any leftover token.
parseSelector() strips the frame prefix a nested selector shares with the
outer one, but left "capture" pointing at the old part index, so

  frameLocator('#f').locator('body')
      .locator(frameLocator('#f').locator('*css=section >> span'))

resolved to the span instead of the section. Shift the capture along with
the removed parts, and reject a capture that falls inside the prefix.
Calling page.frameLocator() or frame.frameLocator() without a selector now
starts the match in any frame of the subtree, replacing anyFrame(). The
selector format and the behavior are unchanged - only the API shape.

Locator descriptions and codegen render the token as frameLocator() in all
languages, which also makes it round-trip through the locator parser, unlike
the bare any_frame/AnyFrame property forms.
@dgozman Dmitry Gozman (dgozman) changed the title feat(selectors): replace pierceFrames() with anyFrame() feat(selectors): replace pierceFrames() with frameLocator() Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Test results for "MCP"

1 failed
❌ [firefox] › mcp/cli-core.spec.ts:115 › hover @mcp-windows-latest-firefox

8258 passed, 1361 skipped


Merge workflow run.

@github-actions

Copy link
Copy Markdown
Contributor

Test results for "tests 1"

6 flaky ⚠️ [chromium-library] › library/video.spec.ts:736 › screencast › should work with video+trace `@frozen-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:736 › screencast › should work with video+trace `@chromium-ubuntu-22.04-arm-node20`
⚠️ [chromium-library] › library/video.spec.ts:699 › screencast › should capture full viewport on hidpi `@realtime-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:736 › screencast › should work with video+trace `@chromium-ubuntu-22.04-node24`
⚠️ [chromium-library] › library/video.spec.ts:736 › screencast › should work with video+trace `@chromium-ubuntu-22.04-node20`
⚠️ [playwright-test] › ui-mode-trace.spec.ts:827 › should update state on subsequent run `@ubuntu-latest-node20`

51218 passed, 1237 skipped


Merge workflow run.

@github-actions

Copy link
Copy Markdown
Contributor

Hi, I'm the Playwright bot and I took a look at the CI failures here.

🟢 The one failure is a known flake — this PR is clear

[firefox] › mcp/cli-core.spec.ts:115 › hover (@mcp-windows-latest-firefox) is a pre-existing flake, unrelated to the selector-engine work in this PR. The latest "tests 1" run is clean (6 flaky, all passed on retry). Nothing here looks caused by your change.

Details

This PR replaces pierceFrames() with frameLocator() — selector engine, frame resolution and codegen. The failing MCP test drives a CLI hover command and asserts on the CLI's stdout; it never exercises frameLocator()/anyFrame() or selector resolution, so the diff doesn't reach it.

Pre-existing flake / infra

  • [firefox] › mcp/cli-core.spec.ts:115 › hover (@mcp-windows-latest-firefox) — flake with a clear timing signature: the assertion reads an empty/undefined CLI stdout (Received has value: undefined on this run; Expected substring: "mouse move 50 50" / Received string: "" on the older ones), i.e. the CLI output hadn't arrived yet. Windows + Firefox only. Across the test-results DB it failed 7 of 714 runs (~1%) on this bot, and 6 of those 7 are on SHAs this PR can't be responsible formain pushes (12b611da, bee00ad3), PR fix(test runner): match --test-list entries against the test file path #42301 (fix-42298), release-1.62 (83768264), PR feat(webkit): roll to r2339 #41975 (webkit roll) and PR feat(selectors): add pierceFrames context option #42033 (pierce-frames-option). The whole cli-core.spec.ts file is flaky on this bot (click link 28/714, dialog-accept 16/713, dialog-dismiss 14/713), which is the same Windows+Firefox CLI-timing noise. MCP CLI mouse dispatch doesn't touch selector resolution.

Not triaged

  • The 6 flaky results in the "tests 1" run (video.spec.ts:736/:699, ui-mode-trace.spec.ts:827) all passed on retry and don't touch selectors — nothing to chase.

Triaged by the Playwright bot - agent run

@dgozman
Dmitry Gozman (dgozman) merged commit 6748802 into microsoft:main Aug 28, 2026
44 of 45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants