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(); 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]>

View file

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

View file

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

View file

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

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