feat: locator.that (#13731)
Filters existing locator by options, currently `has` and `hasText`.
This commit is contained in:
parent
d71060321d
commit
97750ccf9a
|
|
@ -770,6 +770,13 @@ Returns the `node.textContent`.
|
|||
|
||||
### option: Locator.textContent.timeout = %%-input-timeout-%%
|
||||
|
||||
## method: Locator.that
|
||||
- returns: <[Locator]>
|
||||
|
||||
This method narrows existing locator according to the options, for example filters by text.
|
||||
|
||||
### option: Locator.that.-inline- = %%-locator-options-list-%%
|
||||
|
||||
## async method: Locator.type
|
||||
|
||||
Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text.
|
||||
|
|
|
|||
|
|
@ -67,10 +67,69 @@ await locator.HoverAsync();
|
|||
await locator.ClickAsync();
|
||||
```
|
||||
|
||||
## Creating Locators
|
||||
|
||||
Use [`method: Page.locator`] method to create a locator. This method takes a selector that describes how to find an element in the page. Playwright supports many different selectors like [Text](./selectors.md#text-selector), [CSS](./selectors.md#css-selector), [XPath](./selectors.md#xpath-selectors) and many more. Learn more about available selectors and how to pick one in this [in-depth guide](./selectors.md).
|
||||
|
||||
```js
|
||||
// Find by text.
|
||||
await page.locator('text=Sign up').click();
|
||||
|
||||
// Find by CSS.
|
||||
await page.locator('button.sign-up').click();
|
||||
|
||||
// Find by test id.
|
||||
await page.locator('data-testid=sign-up').click();
|
||||
```
|
||||
|
||||
```python async
|
||||
# Find by text.
|
||||
await page.locator("text=Sign up").click()
|
||||
|
||||
# Find by CSS.
|
||||
await page.locator("button.sign-up").click()
|
||||
|
||||
# Find by test id.
|
||||
await page.locator("data-testid=sign-up").click()
|
||||
```
|
||||
|
||||
```python sync
|
||||
# Find by text.
|
||||
page.locator("text=Sign up").click()
|
||||
|
||||
# Find by CSS.
|
||||
page.locator("button.sign-up").click()
|
||||
|
||||
# Find by test id.
|
||||
page.locator("data-testid=sign-up").click()
|
||||
```
|
||||
|
||||
```java
|
||||
// Find by text.
|
||||
page.locator("text=Sign up").click();
|
||||
|
||||
// Find by CSS.
|
||||
page.locator("button.sign-up").click();
|
||||
|
||||
// Find by test id.
|
||||
page.locator("data-testid=sign-up").click();
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Find by text.
|
||||
await page.Locator("text=Sign up").ClickAsync();
|
||||
|
||||
// Find by CSS.
|
||||
await page.Locator("button.sign-up").ClickAsync();
|
||||
|
||||
// Find by test id.
|
||||
await page.Locator("data-testid=sign-up").ClickAsync();
|
||||
```
|
||||
|
||||
## Strictness
|
||||
|
||||
Locators are strict. This means that all operations on locators that imply
|
||||
some target DOM element will throw an exception if more than one element matches
|
||||
some target DOM element will throw an exception if more than one element matches
|
||||
given selector.
|
||||
|
||||
```js
|
||||
|
|
@ -217,6 +276,74 @@ for (let i = 0; i < count; ++i)
|
|||
var texts = await rows.EvaluateAllAsync("list => list.map(element => element.textContent)");
|
||||
```
|
||||
|
||||
## Filtering Locators
|
||||
|
||||
When creating a locator, you can pass additional options to filter it.
|
||||
|
||||
Filtering by text will search for a particular string somewhere inside the element, possibly in a descendant element, case-insensitively. You can also pass a regular expression.
|
||||
|
||||
```js
|
||||
await page.locator('button', { hasText: 'Sign up' }).click();
|
||||
```
|
||||
```java
|
||||
page.locator("button", new Page.LocatorOptions().setHasText("Sign up")).click();
|
||||
```
|
||||
```python async
|
||||
await page.locator("button", has_text="Sign up").click()
|
||||
```
|
||||
```python sync
|
||||
page.locator("button", has_text="Sign up").click()
|
||||
```
|
||||
```csharp
|
||||
await page.Locator("button", new PageLocatorOptions { HasText = "Sign up" }).ClickAsync();
|
||||
```
|
||||
|
||||
Locators also support an option to only select elements that have a descendant matching another locator. Note that inner locator is matched starting from the outer one, not from the document root.
|
||||
|
||||
```js
|
||||
page.locator('article', { has: page.locator('button.subscribe') })
|
||||
```
|
||||
```java
|
||||
page.locator("article", new Page.LocatorOptions().setHas(page.locator("button.subscribe")))
|
||||
```
|
||||
```python async
|
||||
page.locator("article", has=page.locator("button.subscribe"))
|
||||
```
|
||||
```python sync
|
||||
page.locator("article", has=page.locator("button.subscribe"))
|
||||
```
|
||||
```csharp
|
||||
page.Locator("article", new PageLocatorOptions { Has = page.Locator("button.subscribe") })
|
||||
```
|
||||
|
||||
You can also filter an existing locator with [`method: Locator.that`] method.
|
||||
|
||||
```js
|
||||
const buttonLocator = page.locator('button');
|
||||
// ...
|
||||
await buttonLocator.that({ hasText: 'Sign up' }).click();
|
||||
```
|
||||
```java
|
||||
Locator buttonLocator = page.locator("button");
|
||||
// ...
|
||||
buttonLocator.that(new Locator.ThatOptions().setHasText("Sign up")).click();
|
||||
```
|
||||
```python async
|
||||
button_locator = page.locator("button")
|
||||
# ...
|
||||
await button_locator.that(has_text="Sign up").click()
|
||||
```
|
||||
```python sync
|
||||
button_locator = page.locator("button")
|
||||
# ...
|
||||
button_locator.that(has_text="Sign up").click()
|
||||
```
|
||||
```csharp
|
||||
var buttonLocator = page.Locator("button");
|
||||
// ...
|
||||
await buttonLocator.That(new LocatorThatOptions { HasText = "Sign up" }).ClickAsync();
|
||||
```
|
||||
|
||||
## Locator vs ElementHandle
|
||||
|
||||
:::caution
|
||||
|
|
|
|||
|
|
@ -130,6 +130,10 @@ export class Locator implements api.Locator {
|
|||
return new FrameLocator(this._frame, this._selector + ' >> ' + selector);
|
||||
}
|
||||
|
||||
that(options?: { hasText?: string | RegExp, has?: Locator }): Locator {
|
||||
return new Locator(this._frame, this._selector, options);
|
||||
}
|
||||
|
||||
async elementHandle(options?: TimeoutOptions): Promise<ElementHandle<SVGElement | HTMLElement>> {
|
||||
return await this._frame.waitForSelector(this._selector, { strict: true, state: 'attached', ...options })!;
|
||||
}
|
||||
|
|
|
|||
21
packages/playwright-core/types/types.d.ts
vendored
21
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -9799,6 +9799,27 @@ export interface Locator {
|
|||
timeout?: number;
|
||||
}): Promise<null|string>;
|
||||
|
||||
/**
|
||||
* This method narrows existing locator according to the options, for example filters by text.
|
||||
* @param options
|
||||
*/
|
||||
that(options?: {
|
||||
/**
|
||||
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
|
||||
* For example, `article` that has `text=Playwright` matches `<article><div>Playwright</div></article>`.
|
||||
*
|
||||
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain [FrameLocator]s.
|
||||
*/
|
||||
has?: Locator;
|
||||
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches
|
||||
* `<article><div>Playwright</div></article>`.
|
||||
*/
|
||||
hasText?: string|RegExp;
|
||||
}): Locator;
|
||||
|
||||
/**
|
||||
* Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text.
|
||||
*
|
||||
|
|
|
|||
21
tests/config/experimental.d.ts
vendored
21
tests/config/experimental.d.ts
vendored
|
|
@ -9808,6 +9808,27 @@ export interface Locator {
|
|||
timeout?: number;
|
||||
}): Promise<null|string>;
|
||||
|
||||
/**
|
||||
* This method narrows existing locator according to the options, for example filters by text.
|
||||
* @param options
|
||||
*/
|
||||
that(options?: {
|
||||
/**
|
||||
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
|
||||
* For example, `article` that has `text=Playwright` matches `<article><div>Playwright</div></article>`.
|
||||
*
|
||||
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain [FrameLocator]s.
|
||||
*/
|
||||
has?: Locator;
|
||||
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches
|
||||
* `<article><div>Playwright</div></article>`.
|
||||
*/
|
||||
hasText?: string|RegExp;
|
||||
}): Locator;
|
||||
|
||||
/**
|
||||
* Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -124,6 +124,23 @@ it('should support has:locator', async ({ page, trace }) => {
|
|||
})).toHaveCount(1);
|
||||
});
|
||||
|
||||
it('should support locator.that', async ({ page, trace }) => {
|
||||
it.skip(trace === 'on');
|
||||
|
||||
await page.setContent(`<section><div><span>hello</span></div><div><span>world</span></div></section>`);
|
||||
await expect(page.locator(`div`).that({ hasText: 'hello' })).toHaveCount(1);
|
||||
await expect(page.locator(`div`, { hasText: 'hello' }).that({ hasText: 'hello' })).toHaveCount(1);
|
||||
await expect(page.locator(`div`, { hasText: 'hello' }).that({ hasText: 'world' })).toHaveCount(0);
|
||||
await expect(page.locator(`section`, { hasText: 'hello' }).that({ hasText: 'world' })).toHaveCount(1);
|
||||
await expect(page.locator(`div`).that({ hasText: 'hello' }).locator('span')).toHaveCount(1);
|
||||
await expect(page.locator(`div`).that({ has: page.locator('span', { hasText: 'world' }) })).toHaveCount(1);
|
||||
await expect(page.locator(`div`).that({ has: page.locator('span') })).toHaveCount(2);
|
||||
await expect(page.locator(`div`).that({
|
||||
has: page.locator('span'),
|
||||
hasText: 'world',
|
||||
})).toHaveCount(1);
|
||||
});
|
||||
|
||||
it('should enforce same frame for has:locator', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/frames/two-frames.html');
|
||||
const child = page.frames()[1];
|
||||
|
|
|
|||
Loading…
Reference in a new issue