microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
65.75k stars 3.58k forks source link

[Feature] automatically handle frames #12201

Closed DetachHead closed 1 year ago

DetachHead commented 2 years ago

currently to select an element that's located within a frame, you have to select the frame first:

await (await page.frame("iframe")).$("foo")

it would be cool if playwright could just recurse through the frames to find the element matching this selector, so i can simply go

await page.$("foo")

similar to how the chrome devtools work

KotlinIsland commented 2 years ago

This would be very based, and also consistent with how other web automation tools and libraries work.

dgozman commented 2 years ago

@DetachHead You can use frame locators to avoid multiple awaits and make things non-flaky:

await page.frameLocator('iframe').locator('foo').click();

it would be cool if playwright could just recurse through the frames to find the element matching this selector

In addition to this being technically challenging, it would also be surprising to most users. I can see this being opt-in, but with frame locators being available we will not prioritize it most likely.

similar to how the chrome devtools work

Perhaps I don't understand what you mean, but I don't think you can do $('foo') across frames in developer tools.

KotlinIsland commented 2 years ago

@dgozman

Perhaps I don't understand what you mean, but I don't think you can do $('foo') across frames in developer tools.

In chrome devtools console $() only searches in the currently selected frame, but the searchbox will search all frames.

UFT (🤮) searches through all frames.

SeleniumLibrary has keywords for searching through all frames

it would also be surprising to most users

In my experience I've only ever seen people be confused when a perfectly valid selector(page.$("#my-div")) doesn't return anything because the correct frame wasn't extracted first.

dgozman commented 2 years ago

We can consider an opt-in mode like locator('button', { allFrames: true }) or similar. This needs some technical experiments though, not sure how robust such a solution would be.

DetachHead commented 2 years ago

the current behavior is especially confusing because even the element explorer in the inspector searches through all frames, so in debug mode it highlights elements even though the script is unable to find them.

so the logic required to do this seems to already be present in the inspector

klhex commented 2 years ago

@dgozman

@DetachHead You can use frame locators to avoid multiple awaits and make things non-flaky:

await page.frameLocator('iframe').locator('foo').click();

I (being not an expert on this, so maybe I am missing something) see two issues with this:

  1. Quite some commercial web-based applications seem to be a) using iframes fairly intensively and b) using generic IDs and names for their iframes, meaning that those IDs and names change between each new login into the application. For this reason, so far we had to use alternative test automation frameworks instead of Playwright when we had to do test automation with such applications. (Luckily, this is the exception and in our day work we're testing our own app that works perfectly fine with PW.)

  2. The need to specify technical details for element selection goes against the idea of the text- and layout-based selectors. If you are using those selectors (and in our team where we focus on e2e tests we're always striving to use them whenever possible) you usually expect the following principle to apply, I'd say: "If I can see a specific text on the screen I am able to select the underlying element with those locators without having to look at the page's source code."

And, indeed, PW's current behavior is somewhat confusing, like @DetachHead mentioned earlier. Here's a real-world, current example with regards to a commercial application as mentioned before:

Some elements on its main screen (left column) can be selected perfectly fine w/ text-based selectors as we're used to. Others, which are part of various iframes, cannot. The text-based selector doesn't find them (it simply runs into a timeout), although the used text-based selectors (text=...) are actually provided by PW inspector's Explore functionality:

await page.locator('text=Category: Normal Change (2 Elemente)').click();

(Interestingly, when using "step over" in PW Inspector for that command, PW highlights the correct element in blue but nevertheless is unable to actually select it.)

Using PW Codegen gives us the following command:

await page.frameLocator('iframe[name="mif-comp-ext-gen-top295-625151"]').locator('text=Category: Normal Change (2 Elemente)').click();

Unfortunately, this one cannot be used since the iframe's name (as well as ID) contains generic parts which change upon every new login.

That means on the one hand we cannot use our beloved text-/layout-based selectors because we have to deal with elements within an iframe and on the other hand we cannot switch to the concerned iframe using the suggested method because of generic iframe names/IDs.

We'd love to be able to use text-/layout-based selectors also in cases where elements are located in iframes w/o having to research details in the page's source code.

it would be cool if playwright could just recurse through the frames to find the element matching this selector

In addition to this being technically challenging, it would also be surprising to most users. I can see this being opt-in, but with frame locators being available we will not prioritize it most likely.

I hope that my example regarding iframes and applications which use generic element names and IDs will help prioritizing it. 😃

pavelfeldman commented 1 year ago

Why was this issue closed?

We are prioritizing the features based on the upvotes, recency and activity in the issue thread. It looks like this issue only has a handful of upvotes, has not been touched recently and/or we lack sufficient feedback to act on it. We are closing issues like this one to keep our bug database maintainable. Please feel free to open a new issue and link this one to it if you think this is a mistake.

DetachHead commented 1 year ago

@pavelfeldman can this please be re-opened? there are many people who want this functionality, and considering selectors are deprecated in favor of locators, it's now even more of an issue than it used to be, since locators can be defined before their element is visible.

when using selectors, the issue is trivial to work around because you can just iterate over all the frames until you find the one that contains the element you're looking for:

await page.click("button")
const selector = "some element that's in an unknown iframe"
await page.waitForSelector(selector)
const element = findElementInFrame(selector) // function that iterates over all the frames until the selector is found

but with locators you need a way to do this every time the locator is used:

// defining a locator for an element that doesn't exist yet. my findElementInFrame function can't be used here
const locator = page.locator("some element that's in an unknown iframe")
await page.click("button")
await locator.waitFor()