Skip to content

Conversation

ankur22
Copy link
Contributor

@ankur22 ankur22 commented Aug 20, 2025

What?

This adds a few new APIs. It would have been difficult to implement frameLocator on its own without the other two APIs:

  1. It implements frameLocator which allows the user to interact with elements within iframes with locator like APIs.
  2. On top of that it implements locator.contentFrame which returns a frameLocator, allowing the user to actually retrieve a frameLocator to start working with elements from with an iframe.
  3. It implements frameLocator.locator allowing users to actually select elements in an iframe.

Why?

Working with locator like APIs is important for maintainable, less flakey tests. It's also a preferred way of working when interacting with iframes in Playwright. We're currently on a drive to improve k6 browser's parity to make migrating from Playwright to k6 easier.

Essentially, locator and frameLocator APIs will retrieve the latest version of an element or perform an action on the latest version of an element when such APIs are called. Currently websites are very dynamic and we always want to try to work with the latest version of the DOM when trying to retrieve data or perform actions. Working with page$ and frame.contentFrame are currently the only way to interact with elements in an iframe, but these are flakey since they work with old versions of the DOM.

The end result of this implementation also allows us to easily chain selectors. Before we used to have to do (imagine having to navigate to multiple nested iframes):

const frameB = await page.$('#frameB');
const frameBContent = await frameB.contentFrame(); // Takes a snapshot of the DOM

const frameC = await frameBContent.$('#frameB');
const frameCContent = await frameC.contentFrame(); // Takes a snapshot of the DOM

await frameCContent.locator("#increment", nil).click();

const count = await frameCContent.locator("#count").textContent();

and now we can do:

const frameCContent = page.locator('#frameB').contentFrame().locator('#frameC').contentFrame();

await frameCContent.locator("#increment", nil).click(); // Works with the latest DOM and performs actionability checks.

const count = await frameCContent.locator("#count").textContent(); // Works with the latest DOM and performs actionability checks.

Checklist

  • I have performed a self-review of my code.
  • I have commented on my code, particularly in hard-to-understand areas.
  • I have added tests for my changes.
  • I have run linter and tests locally (make check) and all pass.

Checklist: Documentation (only for k6 maintainers and if relevant)

Please do not merge this PR until the following items are filled out.

Related PR(s)/Issue(s)

#5032

@ankur22 ankur22 force-pushed the add/locator-frameLocator branch 2 times, most recently from 2dcf9c9 to e4ab9db Compare August 20, 2025 15:21
@ankur22 ankur22 changed the base branch from master to fix/click August 20, 2025 15:21
@ankur22 ankur22 force-pushed the add/locator-frameLocator branch 2 times, most recently from 7e9fbf6 to 973a292 Compare August 20, 2025 15:27
@ankur22 ankur22 force-pushed the add/locator-frameLocator branch 2 times, most recently from b14b4dd to e38c7aa Compare August 22, 2025 15:40
Base automatically changed from fix/click to master August 22, 2025 15:47
@ankur22 ankur22 force-pushed the add/locator-frameLocator branch 5 times, most recently from 8f8245b to 5be0b4c Compare August 26, 2025 15:12
@ankur22 ankur22 changed the base branch from master to fix/exec-context-adoption August 26, 2025 15:12
@ankur22 ankur22 changed the title Add/locator frame locator Add locator.frameLocator Aug 26, 2025
@ankur22 ankur22 changed the title Add locator.frameLocator Add frameLocator and locator.contentFrame Aug 26, 2025
@ankur22 ankur22 changed the title Add frameLocator and locator.contentFrame Add frameLocator, frameLocator.locator and locator.contentFrame Aug 26, 2025
@ankur22 ankur22 added this to the v1.3.0 milestone Aug 27, 2025
@ankur22 ankur22 marked this pull request as ready for review August 27, 2025 19:52
@ankur22 ankur22 requested a review from a team as a code owner August 27, 2025 19:52
@ankur22 ankur22 requested review from AgnesToulet, inancgumus and joanlopez and removed request for a team August 27, 2025 19:52
@ankur22 ankur22 force-pushed the add/locator-frameLocator branch from 5976239 to 0929454 Compare August 28, 2025 08:30
// This is a valid response from waitForSelector. It means that the element
// was either hidden or detached.
if iframeHandle == nil {
return nil, "", errors.New("check if element is visible")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Use new visible error from #5111

@ankur22 ankur22 force-pushed the add/locator-frameLocator branch from 5a61856 to 51c6ca0 Compare September 5, 2025 14:19
This new selector in the parser will only be used as a directive to let
other parts of the codebase know that we need to enter into a frame
which could be from a different origin.
First we need to see if the parsed selector has a directive which calls
for a step into an iframe or frame.
Now we can split when we find a directive in the parsed selector. We
want to be able to step into the iframe/frame first (beforeFrame) and
then continue onto afterFrame.
After we have split the selectors on a frame, we need to rebuild them
so that we can use them in the next recursive call.
When we wait for a selector, we need to be able to step into an
iframe/frame when a directive in the selector is found. This change
performs those steps. It will split the selector, retrieve the document
for the iframe/frame and then continue the second selector split after
stepping into the iframe/frame.
We need to step into iframe/frame when working with count too. This
will split on the frame directive, step into the frame by splitting the
selector in two, and using the first split to do the step in. The
second split of the selector will be used after stepping into the frame
Since Query is used by isVisible and isHidden, which are part of
locator too, we need to step into iframe/frame like we do for count and
waitForSelector.
This is the common code that is needed when stepping into the
iframe/frame.
This will be extended to add a test with CORS
When a frame is hidden, waitForSelector may return nil response. This
is a valid case, but one that we should retry on when it is used
internally.
@ankur22 ankur22 force-pushed the add/locator-frameLocator branch from 51c6ca0 to dedb00f Compare September 5, 2025 14:23
@ankur22 ankur22 merged commit 45d9324 into master Sep 5, 2025
37 checks passed
@ankur22 ankur22 deleted the add/locator-frameLocator branch September 5, 2025 15:12
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.

3 participants