api(frame-locator): allow nth, first, last (#10195)
This commit is contained in:
parent
d25b0f70bc
commit
4e90eb9406
|
|
@ -27,6 +27,57 @@ var locator = page.FrameLocator("#my-frame").Locator("text=Submit");
|
||||||
await locator.ClickAsync();
|
await locator.ClickAsync();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Strictness**
|
||||||
|
|
||||||
|
Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches given selector.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Throws if there are several frames in DOM:
|
||||||
|
await page.frameLocator('.result-frame').locator('button').click();
|
||||||
|
|
||||||
|
// Works because we explicitly tell locator to pick the first frame:
|
||||||
|
await page.frameLocator('.result-frame').first().locator('button').click();
|
||||||
|
```
|
||||||
|
|
||||||
|
```python async
|
||||||
|
# Throws if there are several frames in DOM:
|
||||||
|
await page.frame_locator('.result-frame').locator('button')..click()
|
||||||
|
|
||||||
|
# Works because we explicitly tell locator to pick the first frame:
|
||||||
|
await page.frame_locator('.result-frame').first.locator('button')..click()
|
||||||
|
```
|
||||||
|
|
||||||
|
```python sync
|
||||||
|
# Throws if there are several frames in DOM:
|
||||||
|
page.frame_locator('.result-frame').locator('button').click()
|
||||||
|
|
||||||
|
# Works because we explicitly tell locator to pick the first frame:
|
||||||
|
page.frame_locator('.result-frame').first.locator('button').click()
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Throws if there are several frames in DOM:
|
||||||
|
page.frame_locator(".result-frame").locator("button").click();
|
||||||
|
|
||||||
|
// Works because we explicitly tell locator to pick the first frame:
|
||||||
|
page.frame_locator(".result-frame").first().locator("button").click();
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Throws if there are several frames in DOM:
|
||||||
|
await page.FrameLocator(".result-frame").Locator("button").ClickAsync();
|
||||||
|
|
||||||
|
// Works because we explicitly tell locator to pick the first frame:
|
||||||
|
await page.FrameLocator(".result-frame").First.Locator("button").ClickAsync();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## method: FrameLocator.first
|
||||||
|
- returns: <[FrameLocator]>
|
||||||
|
|
||||||
|
Returns locator to the first matching frame.
|
||||||
|
|
||||||
|
|
||||||
## method: FrameLocator.frameLocator
|
## method: FrameLocator.frameLocator
|
||||||
- returns: <[FrameLocator]>
|
- returns: <[FrameLocator]>
|
||||||
|
|
||||||
|
|
@ -36,9 +87,24 @@ in that iframe.
|
||||||
### param: FrameLocator.frameLocator.selector = %%-find-selector-%%
|
### param: FrameLocator.frameLocator.selector = %%-find-selector-%%
|
||||||
|
|
||||||
|
|
||||||
|
## method: FrameLocator.last
|
||||||
|
- returns: <[FrameLocator]>
|
||||||
|
|
||||||
|
Returns locator to the last matching frame.
|
||||||
|
|
||||||
|
|
||||||
## method: FrameLocator.locator
|
## method: FrameLocator.locator
|
||||||
- returns: <[Locator]>
|
- returns: <[Locator]>
|
||||||
|
|
||||||
The method finds an element matching the specified selector in the FrameLocator's subtree.
|
The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||||
|
|
||||||
### param: FrameLocator.locator.selector = %%-find-selector-%%
|
### param: FrameLocator.locator.selector = %%-find-selector-%%
|
||||||
|
|
||||||
|
|
||||||
|
## method: FrameLocator.nth
|
||||||
|
- returns: <[FrameLocator]>
|
||||||
|
|
||||||
|
Returns locator to the n-th matching frame.
|
||||||
|
|
||||||
|
### param: FrameLocator.nth.index
|
||||||
|
- `index` <[int]>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
id: library
|
id: library
|
||||||
title: "Overview"
|
title: "Library"
|
||||||
---
|
---
|
||||||
|
|
||||||
Playwright can either be used as a part of the [Playwright Test](./intro.md), or as a Playwright Library (this guide). If you are working on an application that utilizes Playwright capabilities or you are using Playwright with another test runner, read on.
|
Playwright can either be used as a part of the [Playwright Test](./intro.md), or as a Playwright Library (this guide). If you are working on an application that utilizes Playwright capabilities or you are using Playwright with another test runner, read on.
|
||||||
|
|
|
||||||
|
|
@ -252,18 +252,30 @@ export class Locator implements api.Locator {
|
||||||
|
|
||||||
export class FrameLocator implements api.FrameLocator {
|
export class FrameLocator implements api.FrameLocator {
|
||||||
private _frame: Frame;
|
private _frame: Frame;
|
||||||
private _selector: string;
|
private _frameSelector: string;
|
||||||
|
|
||||||
constructor(frame: Frame, selector: string) {
|
constructor(frame: Frame, selector: string) {
|
||||||
this._frame = frame;
|
this._frame = frame;
|
||||||
this._selector = selector + ' >> control=enter-frame';
|
this._frameSelector = selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
locator(selector: string): Locator {
|
locator(selector: string): Locator {
|
||||||
return new Locator(this._frame, this._selector + ' >> ' + selector);
|
return new Locator(this._frame, this._frameSelector + ' >> control=enter-frame >> ' + selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
frameLocator(selector: string): FrameLocator {
|
frameLocator(selector: string): FrameLocator {
|
||||||
return new FrameLocator(this._frame, this._selector + ' >> ' + selector);
|
return new FrameLocator(this._frame, this._frameSelector + ' >> control=enter-frame >> ' + selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
first(): FrameLocator {
|
||||||
|
return new FrameLocator(this._frame, this._frameSelector + ' >> nth=0');
|
||||||
|
}
|
||||||
|
|
||||||
|
last(): FrameLocator {
|
||||||
|
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=-1`);
|
||||||
|
}
|
||||||
|
|
||||||
|
nth(index: number): FrameLocator {
|
||||||
|
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=${index}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
packages/playwright-core/types/types.d.ts
vendored
29
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -13481,8 +13481,26 @@ export interface FileChooser {
|
||||||
* await locator.click();
|
* await locator.click();
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
* **Strictness**
|
||||||
|
*
|
||||||
|
* Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
|
||||||
|
* given selector.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* // Throws if there are several frames in DOM:
|
||||||
|
* await page.frameLocator('.result-frame').locator('button').click();
|
||||||
|
*
|
||||||
|
* // Works because we explicitly tell locator to pick the first frame:
|
||||||
|
* await page.frameLocator('.result-frame').first().locator('button').click();
|
||||||
|
* ```
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
export interface FrameLocator {
|
export interface FrameLocator {
|
||||||
|
/**
|
||||||
|
* Returns locator to the first matching frame.
|
||||||
|
*/
|
||||||
|
first(): FrameLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
|
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
|
||||||
* that iframe.
|
* that iframe.
|
||||||
|
|
@ -13490,11 +13508,22 @@ export interface FrameLocator {
|
||||||
*/
|
*/
|
||||||
frameLocator(selector: string): FrameLocator;
|
frameLocator(selector: string): FrameLocator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns locator to the last matching frame.
|
||||||
|
*/
|
||||||
|
last(): FrameLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The method finds an element matching the specified selector in the FrameLocator's subtree.
|
* The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||||
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
|
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
|
||||||
*/
|
*/
|
||||||
locator(selector: string): Locator;
|
locator(selector: string): Locator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns locator to the n-th matching frame.
|
||||||
|
* @param index
|
||||||
|
*/
|
||||||
|
nth(index: number): FrameLocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,24 @@ async function routeIframe(page: Page) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function routeAmbiguous(page: Page) {
|
||||||
|
await page.route('**/empty.html', route => {
|
||||||
|
route.fulfill({
|
||||||
|
body: `<iframe src="iframe-1.html"></iframe>
|
||||||
|
<iframe src="iframe-2.html"></iframe>
|
||||||
|
<iframe src="iframe-3.html"></iframe>`,
|
||||||
|
contentType: 'text/html'
|
||||||
|
}).catch(() => {});
|
||||||
|
});
|
||||||
|
await page.route('**/iframe-*', route => {
|
||||||
|
const path = new URL(route.request().url()).pathname.slice(1);
|
||||||
|
route.fulfill({
|
||||||
|
body: `<html><button>Hello from ${path}</button></html>`,
|
||||||
|
contentType: 'text/html'
|
||||||
|
}).catch(() => {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it('should work for iframe', async ({ page, server }) => {
|
it('should work for iframe', async ({ page, server }) => {
|
||||||
await routeIframe(page);
|
await routeIframe(page);
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
@ -198,3 +216,22 @@ it('locator.frameLocator should work for iframe', async ({ page, server }) => {
|
||||||
await expect(button).toHaveText('Hello iframe');
|
await expect(button).toHaveText('Hello iframe');
|
||||||
await button.click();
|
await button.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('locator.frameLocator should throw on ambiguity', async ({ page, server }) => {
|
||||||
|
await routeAmbiguous(page);
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
const button = page.locator('body').frameLocator('iframe').locator('button');
|
||||||
|
const error = await button.waitFor().catch(e => e);
|
||||||
|
expect(error.message).toContain('Error: strict mode violation: "body >> iframe" resolved to 3 elements');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('locator.frameLocator should not throw on first/last/nth', async ({ page, server }) => {
|
||||||
|
await routeAmbiguous(page);
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
const button1 = page.locator('body').frameLocator('iframe').first().locator('button');
|
||||||
|
await expect(button1).toHaveText('Hello from iframe-1.html');
|
||||||
|
const button2 = page.locator('body').frameLocator('iframe').nth(1).locator('button');
|
||||||
|
await expect(button2).toHaveText('Hello from iframe-2.html');
|
||||||
|
const button3 = page.locator('body').frameLocator('iframe').last().locator('button');
|
||||||
|
await expect(button3).toHaveText('Hello from iframe-3.html');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue