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();
|
||||
```
|
||||
|
||||
**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
|
||||
- returns: <[FrameLocator]>
|
||||
|
||||
|
|
@ -36,9 +87,24 @@ in that iframe.
|
|||
### param: FrameLocator.frameLocator.selector = %%-find-selector-%%
|
||||
|
||||
|
||||
## method: FrameLocator.last
|
||||
- returns: <[FrameLocator]>
|
||||
|
||||
Returns locator to the last matching frame.
|
||||
|
||||
|
||||
## method: FrameLocator.locator
|
||||
- returns: <[Locator]>
|
||||
|
||||
The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||
|
||||
### 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
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -252,18 +252,30 @@ export class Locator implements api.Locator {
|
|||
|
||||
export class FrameLocator implements api.FrameLocator {
|
||||
private _frame: Frame;
|
||||
private _selector: string;
|
||||
private _frameSelector: string;
|
||||
|
||||
constructor(frame: Frame, selector: string) {
|
||||
this._frame = frame;
|
||||
this._selector = selector + ' >> control=enter-frame';
|
||||
this._frameSelector = selector;
|
||||
}
|
||||
|
||||
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 {
|
||||
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();
|
||||
* ```
|
||||
*
|
||||
* **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 {
|
||||
/**
|
||||
* 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
|
||||
* that iframe.
|
||||
|
|
@ -13490,11 +13508,22 @@ export interface 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.
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 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 }) => {
|
||||
await routeIframe(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 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