api(frame-locator): allow nth, first, last (#10195)

This commit is contained in:
Pavel Feldman 2021-11-09 14:14:20 -08:00 committed by GitHub
parent d25b0f70bc
commit 4e90eb9406
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 149 additions and 5 deletions

View file

@ -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]>

View file

@ -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.

View file

@ -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}`);
}
}

View file

@ -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;
}
/**

View file

@ -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');
});