Skip to content

Commit

Permalink
feat: rename Locator.filter(locator) to Locator.and (#22101)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgozman committed Mar 30, 2023
1 parent ebcb37f commit 539d987
Show file tree
Hide file tree
Showing 16 changed files with 109 additions and 110 deletions.
80 changes: 41 additions & 39 deletions docs/src/api/class-locator.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,45 @@ String[] texts = page.getByRole(AriaRole.LINK).allTextContents();
var texts = await page.GetByRole(AriaRole.Link).AllTextContentsAsync();
```

## method: Locator.and
* since: v1.33
* langs:
- alias-python: and_
- returns: <[Locator]>

Creates a locator that matches both this locator and the argument locator.

**Usage**

The following example finds a button with a specific title.

```js
const button = page.getByRole('button').and(page.getByTitle('Subscribe'));
```

```java
Locator button = page.getByRole(AriaRole.BUTTON).and(page.getByTitle("Subscribe"));
```

```python async
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
```

```python sync
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
```

```csharp
var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
```

### param: Locator.and.locator
* since: v1.33
- `locator` <[Locator]>

Additional locator to match.


## async method: Locator.blur
* since: v1.28

Expand Down Expand Up @@ -888,7 +927,7 @@ Value to set for the `<input>`, `<textarea>` or `[contenteditable]` element.
### option: Locator.fill.timeout = %%-input-timeout-js-%%
* since: v1.14

## method: Locator.filter#1
## method: Locator.filter
* since: v1.22
- returns: <[Locator]>

Expand Down Expand Up @@ -946,47 +985,10 @@ await rowLocator
.ScreenshotAsync();
```

### option: Locator.filter#1.-inline- = %%-locator-options-list-v1.14-%%
### option: Locator.filter.-inline- = %%-locator-options-list-v1.14-%%
* since: v1.22


## method: Locator.filter#2
* since: v1.33
- returns: <[Locator]>

Creates a locator that matches both this locator and the argument locator.

**Usage**

The following example finds a button with a specific title.

```js
const button = page.getByRole('button').filter(page.getByTitle('Subscribe'));
```

```java
Locator button = page.getByRole(AriaRole.BUTTON).filter(page.getByTitle("Subscribe"));
```

```python async
button = page.get_by_role("button").filter(page.getByTitle("Subscribe"))
```

```python sync
button = page.get_by_role("button").filter(page.getByTitle("Subscribe"))
```

```csharp
var button = page.GetByRole(AriaRole.Button).Filter(page.GetByTitle("Subscribe"));
```

### param: Locator.filter#2.locator
* since: v1.33
- `locator` <[Locator]>

Additional locator to match.


## method: Locator.first
* since: v1.14
- returns: <[Locator]>
Expand Down
4 changes: 2 additions & 2 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -1233,7 +1233,7 @@ Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-sele

## template-locator-locator

The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options, similar to [`method: Locator.filter#1`] method.
The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options, similar to [`method: Locator.filter`] method.

[Learn more about locators](../locators.md).

Expand Down Expand Up @@ -1293,7 +1293,7 @@ use: {

Allows locating elements that contain given text.

See also [`method: Locator.filter#1`] that allows to match by another criteria, like an accessible role, and then filter by the text content.
See also [`method: Locator.filter`] that allows to match by another criteria, like an accessible role, and then filter by the text content.


**Usage**
Expand Down
18 changes: 9 additions & 9 deletions docs/src/locators.md
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,7 @@ Consider the following DOM structure where we want to click on the buy button of

### Filter by text

Locators can be filtered by text with the [`method: Locator.filter#1`] method. It will search for a particular string somewhere inside the element, possibly in a descendant element, case-insensitively. You can also pass a regular expression.
Locators can be filtered by text with the [`method: Locator.filter`] method. It 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
Expand Down Expand Up @@ -987,26 +987,26 @@ Note that the inner locator is matched starting from the outer one, not from the

### Filter by matching an additional locator

Method [`method: Locator.filter#2`] narrows down an existing locator by matching an additional locator. For example, you can combine [`method: Page.getByRole`] and [`method: Page.getByTitle`] to match by both role and title.
Method [`method: Locator.and`] narrows down an existing locator by matching an additional locator. For example, you can combine [`method: Page.getByRole`] and [`method: Page.getByTitle`] to match by both role and title.

```js
const button = page.getByRole('button').filter(page.getByTitle('Subscribe'));
const button = page.getByRole('button').and(page.getByTitle('Subscribe'));
```

```java
Locator button = page.getByRole(AriaRole.BUTTON).filter(page.getByTitle("Subscribe"));
Locator button = page.getByRole(AriaRole.BUTTON).and(page.getByTitle("Subscribe"));
```

```python async
button = page.get_by_role("button").filter(page.getByTitle("Subscribe"))
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
```

```python sync
button = page.get_by_role("button").filter(page.getByTitle("Subscribe"))
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
```

```csharp
var button = page.GetByRole(AriaRole.Button).Filter(page.GetByTitle("Subscribe"));
var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
```

### Filter by **not** matching an additional locator
Expand Down Expand Up @@ -1246,7 +1246,7 @@ await page.GetByText("orange").ClickAsync();
```

#### Filter by text
Use the [`method: Locator.filter#1`] to locate a specific item in a list.
Use the [`method: Locator.filter`] to locate a specific item in a list.

For example, consider the following DOM structure:

Expand Down Expand Up @@ -1351,7 +1351,7 @@ However, use this method with caution. Often times, the page might change, and t

### Chaining filters

When you have elements with various similarities, you can use the [`method: Locator.filter#1`] method to select the right one. You can also chain multiple filters to narrow down the selection.
When you have elements with various similarities, you can use the [`method: Locator.filter`] method to select the right one. You can also chain multiple filters to narrow down the selection.

For example, consider the following DOM structure:

Expand Down
6 changes: 3 additions & 3 deletions docs/src/other-locators.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,14 +432,14 @@ await page.Locator("button").Locator("nth=-1").ClickAsync();

## Parent element locator

When you need to target a parent element of some other element, most of the time you should [`method: Locator.filter#1`] by the child locator. For example, consider the following DOM structure:
When you need to target a parent element of some other element, most of the time you should [`method: Locator.filter`] by the child locator. For example, consider the following DOM structure:

```html
<li><label>Hello</label></li>
<li><label>World</label></li>
```

If you'd like to target the parent `<li>` of a label with text `"Hello"`, using [`method: Locator.filter#1`] works best:
If you'd like to target the parent `<li>` of a label with text `"Hello"`, using [`method: Locator.filter`] works best:

```js
const child = page.getByText('Hello');
Expand All @@ -466,7 +466,7 @@ var child = page.GetByText("Hello");
var parent = page.GetByRole(AriaRole.Listitem).Filter(new () { Has = child });
```

Alternatively, if you cannot find a suitable locator for the parent element, use `xpath=..`. Note that this method is not as reliable, because any changes to the DOM structure will break your tests. Prefer [`method: Locator.filter#1`] when possible.
Alternatively, if you cannot find a suitable locator for the parent element, use `xpath=..`. Note that this method is not as reliable, because any changes to the DOM structure will break your tests. Prefer [`method: Locator.filter`] when possible.

```js
const parent = page.getByText('Hello').locator('xpath=..');
Expand Down
2 changes: 1 addition & 1 deletion docs/src/release-notes-csharp.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ Note that the new methods [`method: Page.routeFromHAR`] and [`method: BrowserCon

Read more in [our documentation](./locators.md#locate-by-role).

- New [`method: Locator.filter#1`] API to filter an existing locator
- New [`method: Locator.filter`] API to filter an existing locator

```csharp
var buttons = page.Locator("role=button");
Expand Down
2 changes: 1 addition & 1 deletion docs/src/release-notes-java.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ Note that the new methods [`method: Page.routeFromHAR`] and [`method: BrowserCon

Read more in [our documentation](./locators.md#locate-by-role).

- New [`method: Locator.filter#1`] API to filter an existing locator
- New [`method: Locator.filter`] API to filter an existing locator

```java
Locator buttonsLocator = page.locator("role=button");
Expand Down
2 changes: 1 addition & 1 deletion docs/src/release-notes-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ WebServer is now considered "ready" if request to the specified url has any of t

Read more in [our documentation](./locators.md#locate-by-role).

- New [`method: Locator.filter#1`] API to filter an existing locator
- New [`method: Locator.filter`] API to filter an existing locator

```js
const buttons = page.locator('role=button');
Expand Down
2 changes: 1 addition & 1 deletion docs/src/release-notes-python.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ Note that the new methods [`method: Page.routeFromHAR`] and [`method: BrowserCon

Read more in [our documentation](./locators.md#locate-by-role).

- New [`method: Locator.filter#1`] API to filter an existing locator
- New [`method: Locator.filter`] API to filter an existing locator

```py
buttons = page.locator("role=button")
Expand Down
17 changes: 8 additions & 9 deletions packages/playwright-core/src/client/locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,8 @@ export class Locator implements api.Locator {
return new FrameLocator(this._frame, this._selector + ' >> ' + selector);
}

filter(options?: LocatorOptions): Locator;
filter(locator: Locator): Locator;
filter(optionsOrLocator?: LocatorOptions | Locator): Locator {
if (optionsOrLocator instanceof Locator) {
if (optionsOrLocator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ` >> internal:and=` + JSON.stringify(optionsOrLocator._selector));
}
return new Locator(this._frame, this._selector, optionsOrLocator);
filter(options?: LocatorOptions): Locator {
return new Locator(this._frame, this._selector, options);
}

async elementHandle(options?: TimeoutOptions): Promise<ElementHandle<SVGElement | HTMLElement>> {
Expand All @@ -187,6 +180,12 @@ export class Locator implements api.Locator {
return this._frame.$$(this._selector);
}

and(locator: Locator): Locator {
if (locator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ` >> internal:and=` + JSON.stringify(locator._selector));
}

first(): Locator {
return new Locator(this._frame, this._selector + ' >> nth=0');
}
Expand Down
8 changes: 3 additions & 5 deletions packages/playwright-core/src/server/injected/consoleApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,11 @@ class Locator {
self.getByText = (text: string | RegExp, options?: { exact?: boolean }): Locator => self.locator(getByTextSelector(text, options));
self.getByTitle = (text: string | RegExp, options?: { exact?: boolean }): Locator => self.locator(getByTitleSelector(text, options));
self.getByRole = (role: string, options: ByRoleOptions = {}): Locator => self.locator(getByRoleSelector(role, options));
self.filter = (optionsOrLocator?: { hasText?: string | RegExp, has?: Locator } | Locator): Locator => {
if (optionsOrLocator instanceof Locator)
return new Locator(injectedScript, selectorBase + ` >> internal:and=` + JSON.stringify((optionsOrLocator as any)[selectorSymbol]));
return new Locator(injectedScript, selector, optionsOrLocator);
};
self.filter = (options?: { hasText?: string | RegExp, has?: Locator }): Locator => new Locator(injectedScript, selector, options);
self.first = (): Locator => self.locator('nth=0');
self.last = (): Locator => self.locator('nth=-1');
self.nth = (index: number): Locator => self.locator(`nth=${index}`);
self.and = (locator: Locator): Locator => new Locator(injectedScript, selectorBase + ` >> internal:and=` + JSON.stringify((locator as any)[selectorSymbol]));
self.or = (locator: Locator): Locator => new Locator(injectedScript, selectorBase + ` >> internal:or=` + JSON.stringify((locator as any)[selectorSymbol]));
self.not = (locator: Locator): Locator => new Locator(injectedScript, selectorBase + ` >> internal:not=` + JSON.stringify((locator as any)[selectorSymbol]));
}
Expand Down Expand Up @@ -94,6 +91,7 @@ class ConsoleAPI {
delete this._injectedScript.window.playwright.first;
delete this._injectedScript.window.playwright.last;
delete this._injectedScript.window.playwright.nth;
delete this._injectedScript.window.playwright.and;
delete this._injectedScript.window.playwright.or;
delete this._injectedScript.window.playwright.not;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export class JavaScriptLocatorFactory implements LocatorFactory {
case 'or':
return `or(${body})`;
case 'and':
return `filter(${body})`;
return `and(${body})`;
case 'not':
return `not(${body})`;
case 'test-id':
Expand Down Expand Up @@ -287,7 +287,7 @@ export class PythonLocatorFactory implements LocatorFactory {
case 'or':
return `or_(${body})`;
case 'and':
return `filter(${body})`;
return `and_(${body})`;
case 'not':
return `not_(${body})`;
case 'test-id':
Expand Down Expand Up @@ -370,7 +370,7 @@ export class JavaLocatorFactory implements LocatorFactory {
case 'or':
return `or(${body})`;
case 'and':
return `filter(${body})`;
return `and(${body})`;
case 'not':
return `not(${body})`;
case 'test-id':
Expand Down Expand Up @@ -447,7 +447,7 @@ export class CSharpLocatorFactory implements LocatorFactory {
case 'or':
return `Or(${body})`;
case 'and':
return `Filter(${body})`;
return `And(${body})`;
case 'not':
return `Not(${body})`;
case 'test-id':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function shiftParams(template: string, sub: number) {

function transform(template: string, params: TemplateParams, testIdAttributeName: string): string {
// Recursively handle filter(has=).
// TODO: handle or(locator), not(locator) and filter(locator).
// TODO: handle or(locator), not(locator), and(locator).
while (true) {
const hasMatch = template.match(/filter\(,?has=/);
if (!hasMatch)
Expand Down

0 comments on commit 539d987

Please sign in to comment.