mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-12 11:50:22 +03:00
feat(locators): implement last,nth (#7870)
This commit is contained in:
parent
b9aad5eb86
commit
cc43f9339f
@ -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`].
|
||||
|
@ -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> {
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
11
types/types.d.ts
vendored
@ -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).
|
||||
|
Loading…
Reference in New Issue
Block a user