feat: enterFrame/exitFrame (#29992)

This introduces `Locator.enterFrame()` and `FrameLocator.exitFrame()` to
convert between locator and frame locator.

Fixes #29336.
This commit is contained in:
Dmitry Gozman 2024-03-18 13:42:08 -07:00 committed by GitHub
parent a6d1fb93de
commit 70e6cdac57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 151 additions and 11 deletions

View File

@ -74,28 +74,59 @@ await page.FrameLocator(".result-frame").First.getByRole(AriaRole.Button).ClickA
**Converting Locator to FrameLocator**
If you have a [Locator] object pointing to an `iframe` it can be converted to [FrameLocator] using [`:scope`](https://developer.mozilla.org/en-US/docs/Web/CSS/:scope) CSS selector:
If you have a [Locator] object pointing to an `iframe` it can be converted to [FrameLocator] using [`method: Locator.enterFrame`].
**Converting FrameLocator to Locator**
If you have a [FrameLocator] object it can be converted to [Locator] pointing to the same `iframe` using [`method: FrameLocator.exitFrame`].
## method: FrameLocator.exitFrame
* since: v1.43
- returns: <[Locator]>
Returns a [Locator] object pointing to the same `iframe` as this frame locator.
Useful when you have a [FrameLocator] object obtained somewhere, and later on would like to interact with the `iframe` element.
**Usage**
```js
const frameLocator = locator.frameLocator(':scope');
const frameLocator = page.frameLocator('iframe[name="embedded"]');
// ...
const locator = frameLocator.exitFrame();
await expect(locator).toBeVisible();
```
```java
Locator frameLocator = locator.frameLocator(':scope');
FrameLocator frameLocator = page.frameLocator("iframe[name=\"embedded\"]");
// ...
Locator locator = frameLocator.exitFrame();
assertThat(locator).isVisible();
```
```python async
frameLocator = locator.frame_locator(":scope")
frame_locator = page.frame_locator("iframe[name=\"embedded\"]")
# ...
locator = frame_locator.exit_frame
await expect(locator).to_be_visible()
```
```python sync
frameLocator = locator.frame_locator(":scope")
frame_locator = page.frame_locator("iframe[name=\"embedded\"]")
# ...
locator = frame_locator.exit_frame
expect(locator).to_be_visible()
```
```csharp
var frameLocator = locator.FrameLocator(":scope");
var frameLocator = Page.FrameLocator("iframe[name=\"embedded\"]");
// ...
var locator = frameLocator.ExitFrame;
await Expect(locator).ToBeVisibleAsync();
```
## method: FrameLocator.first
* since: v1.17
- returns: <[FrameLocator]>

View File

@ -747,6 +747,51 @@ Resolves given locator to the first matching DOM element. If there are no matchi
Resolves given locator to all matching DOM elements. If there are no matching elements, returns an empty list.
## method: Locator.enterFrame
* since: v1.43
- returns: <[FrameLocator]>
Returns a [FrameLocator] object pointing to the same `iframe` as this locator.
Useful when you have a [Locator] object obtained somewhere, and later on would like to interact with the content inside the frame.
**Usage**
```js
const locator = page.locator('iframe[name="embedded"]');
// ...
const frameLocator = locator.enterFrame();
await frameLocator.getByRole('button').click();
```
```java
Locator locator = page.locator("iframe[name=\"embedded\"]");
// ...
FrameLocator frameLocator = locator.enterFrame();
frameLocator.getByRole(AriaRole.BUTTON).click();
```
```python async
locator = page.locator("iframe[name=\"embedded\"]")
# ...
frame_locator = locator.enter_frame
await frame_locator.get_by_role("button").click()
```
```python sync
locator = page.locator("iframe[name=\"embedded\"]")
# ...
frame_locator = locator.enter_frame
frame_locator.get_by_role("button").click()
```
```csharp
var locator = Page.Locator("iframe[name=\"embedded\"]");
// ...
var frameLocator = locator.EnterFrame;
await frameLocator.GetByRole(AriaRole.Button).ClickAsync();
```
## async method: Locator.evaluate
* since: v1.14
- returns: <[Serializable]>

View File

@ -192,6 +192,10 @@ export class Locator implements api.Locator {
return await this._frame.$$(this._selector);
}
enterFrame() {
return new FrameLocator(this._frame, this._selector);
}
first(): Locator {
return new Locator(this._frame, this._selector + ' >> nth=0');
}
@ -404,6 +408,10 @@ export class FrameLocator implements api.FrameLocator {
return this.locator(getByRoleSelector(role, options));
}
exitFrame() {
return new Locator(this._frame, this._frameSelector);
}
frameLocator(selector: string): FrameLocator {
return new FrameLocator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selector);
}

View File

@ -11460,6 +11460,24 @@ export interface Locator {
*/
elementHandles(): Promise<Array<ElementHandle>>;
/**
* Returns a {@link FrameLocator} object pointing to the same `iframe` as this locator.
*
* Useful when you have a {@link Locator} object obtained somewhere, and later on would like to interact with the
* content inside the frame.
*
* **Usage**
*
* ```js
* const locator = page.locator('iframe[name="embedded"]');
* // ...
* const frameLocator = locator.enterFrame();
* await frameLocator.getByRole('button').click();
* ```
*
*/
enterFrame(): FrameLocator;
/**
* Set a value to the input field.
*
@ -17761,14 +17779,32 @@ export interface FileChooser {
* **Converting Locator to FrameLocator**
*
* If you have a {@link Locator} object pointing to an `iframe` it can be converted to {@link FrameLocator} using
* [`:scope`](https://developer.mozilla.org/en-US/docs/Web/CSS/:scope) CSS selector:
* [locator.enterFrame()](https://playwright.dev/docs/api/class-locator#locator-enter-frame).
*
* ```js
* const frameLocator = locator.frameLocator(':scope');
* ```
* **Converting FrameLocator to Locator**
*
* If you have a {@link FrameLocator} object it can be converted to {@link Locator} pointing to the same `iframe`
* using [frameLocator.exitFrame()](https://playwright.dev/docs/api/class-framelocator#frame-locator-exit-frame).
*/
export interface FrameLocator {
/**
* Returns a {@link Locator} object pointing to the same `iframe` as this frame locator.
*
* Useful when you have a {@link FrameLocator} object obtained somewhere, and later on would like to interact with the
* `iframe` element.
*
* **Usage**
*
* ```js
* const frameLocator = page.frameLocator('iframe[name="embedded"]');
* // ...
* const locator = frameLocator.exitFrame();
* await expect(locator).toBeVisible();
* ```
*
*/
exitFrame(): Locator;
/**
* Returns locator to the first matching frame.
*/

View File

@ -21,7 +21,7 @@ import { test as it, expect } from './pageTest';
async function routeIframe(page: Page) {
await page.route('**/empty.html', route => {
route.fulfill({
body: '<iframe src="iframe.html"></iframe>',
body: '<iframe src="iframe.html" name="frame1"></iframe>',
contentType: 'text/html'
}).catch(() => {});
});
@ -298,3 +298,23 @@ it('should work with COEP/COOP/CORP isolated iframe', async ({ page, server, bro
await page.frameLocator('iframe').getByRole('button').click();
expect(await page.frames()[1].evaluate(() => window['__clicked'])).toBe(true);
});
it('locator.enterFrame should work', async ({ page, server }) => {
await routeIframe(page);
await page.goto(server.EMPTY_PAGE);
const locator = page.locator('iframe');
const frameLocator = locator.enterFrame();
const button = frameLocator.locator('button');
expect(await button.innerText()).toBe('Hello iframe');
await expect(button).toHaveText('Hello iframe');
await button.click();
});
it('frameLocator.exitFrame should work', async ({ page, server }) => {
await routeIframe(page);
await page.goto(server.EMPTY_PAGE);
const frameLocator = page.frameLocator('iframe');
const locator = frameLocator.exitFrame();
await expect(locator).toBeVisible();
expect(await locator.getAttribute('name')).toBe('frame1');
});