feat(locators): implement last,nth (#7870)

This commit is contained in:
Pavel Feldman 2021-07-27 15:58:18 -07:00 committed by GitHub
parent b9aad5eb86
commit cc43f9339f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 7 deletions

View File

@ -534,6 +534,11 @@ Returns whether the element is [visible](./actionability.md#visible).
### option: Locator.isVisible.timeout = %%-input-timeout-%%
## method: Locator.last
- returns: <[Locator]>
Returns locator to the last matching element.
## method: Locator.locator
- returns: <[Locator]>
@ -542,6 +547,14 @@ The method finds an element matching the specified selector in the `Locator`'s s
### param: Locator.locator.selector = %%-find-selector-%%
## method: Locator.nth
- returns: <[Locator]>
Returns locator to the n-th matching element.
### param: Locator.nth.index
- `index` <[int]>
## async method: Locator.press
Focuses the element, and then uses [`method: Keyboard.down`] and [`method: Keyboard.up`].

View File

@ -99,7 +99,15 @@ export class Locator implements api.Locator {
}
first(): Locator {
return new Locator(this._frame, this._selector + ' >> _first=true');
return new Locator(this._frame, this._selector + ' >> _nth=first');
}
last(): Locator {
return new Locator(this._frame, this._selector + ` >> _nth=last`);
}
nth(index: number): Locator {
return new Locator(this._frame, this._selector + ` >> _nth=${index}`);
}
async focus(options?: TimeoutOptions): Promise<void> {

View File

@ -75,6 +75,7 @@ export class InjectedScript {
this._engines.set('css', this._createCSSEngine());
this._engines.set('_first', { queryAll: () => [] });
this._engines.set('_visible', { queryAll: () => [] });
this._engines.set('_nth', { queryAll: () => [] });
for (const { name, engine } of customEngines)
this._engines.set(name, engine);
@ -110,11 +111,30 @@ export class InjectedScript {
if (index === selector.parts.length)
return roots;
if (selector.parts[index].name === '_first')
return roots.slice(0, 1);
const part = selector.parts[index];
if (part.name === '_nth') {
let filtered: ElementMatch[] = [];
if (part.body === 'first') {
filtered = roots.slice(0, 1);
} else if (part.body === 'last') {
if (roots.length)
filtered = roots.slice(roots.length - 1);
} else {
if (typeof selector.capture === 'number')
throw new Error(`Can't query n-th element in a request with the capture.`);
const nth = +part.body;
const set = new Set<Element>();
for (const root of roots) {
set.add(root.element);
if (nth + 1 === set.size)
filtered = [root];
}
}
return this._querySelectorRecursively(filtered, selector, index + 1, queryCache);
}
if (selector.parts[index].name === '_visible') {
const visible = Boolean(selector.parts[index].body);
if (part.name === '_visible') {
const visible = Boolean(part.body);
return roots.filter(match => visible === isVisible(match.element));
}

View File

@ -43,7 +43,7 @@ export class Selectors {
'data-testid', 'data-testid:light',
'data-test-id', 'data-test-id:light',
'data-test', 'data-test:light',
'_visible', '_first'
'_visible', '_nth'
]);
this._engines = new Map();
}

View File

@ -17,7 +17,7 @@
import { test as it, expect } from './pageTest';
it('should respect first()', async ({page}) => {
it('should respect first() and last()', async ({page}) => {
await page.setContent(`
<section>
<div><p>A</p></div>
@ -27,4 +27,23 @@ it('should respect first()', async ({page}) => {
expect(await page.locator('div >> p').count()).toBe(6);
expect(await page.locator('div').locator('p').count()).toBe(6);
expect(await page.locator('div').first().locator('p').count()).toBe(1);
expect(await page.locator('div').last().locator('p').count()).toBe(3);
});
it('should respect nth()', async ({page}) => {
await page.setContent(`
<section>
<div><p>A</p></div>
<div><p>A</p><p>A</p></div>
<div><p>A</p><p>A</p><p>A</p></div>
</section>`);
expect(await page.locator('div >> p').nth(0).count()).toBe(1);
expect(await page.locator('div').nth(1).locator('p').count()).toBe(2);
expect(await page.locator('div').nth(2).locator('p').count()).toBe(3);
});
it('should throw on capture w/ nth()', async ({page}) => {
await page.setContent(`<section><div><p>A</p></div></section>`);
const e = await page.locator('*css=div >> p').nth(0).click().catch(e => e);
expect(e.message).toContain(`Can't query n-th element`);
});

11
types/types.d.ts vendored
View File

@ -7592,6 +7592,11 @@ export interface Locator {
timeout?: number;
}): Promise<boolean>;
/**
* Returns locator to the last matching element.
*/
last(): Locator;
/**
* The method finds an element matching the specified selector in the `Locator`'s subtree. See
* [Working with selectors](https://playwright.dev/docs/selectors) for more details.
@ -7599,6 +7604,12 @@ export interface Locator {
*/
locator(selector: string): Locator;
/**
* Returns locator to the n-th matching element.
* @param index
*/
nth(index: number): Locator;
/**
* Focuses the element, and then uses [keyboard.down(key)](https://playwright.dev/docs/api/class-keyboard#keyboard-down)
* and [keyboard.up(key)](https://playwright.dev/docs/api/class-keyboard#keyboard-up).