mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 21:53:35 +03:00
Selectors quick fix (#18742)
This commit is contained in:
parent
8e882fdd58
commit
25605c616c
@ -5,215 +5,9 @@ title: "Selectors"
|
|||||||
|
|
||||||
Selectors are strings that are used to create [Locator]s. Locators are used to perform actions on the elements by means of methods such as [`method: Locator.click`], [`method: Locator.fill`] and alike. For debugging selectors, see [here](./debug-selectors).
|
Selectors are strings that are used to create [Locator]s. Locators are used to perform actions on the elements by means of methods such as [`method: Locator.click`], [`method: Locator.fill`] and alike. For debugging selectors, see [here](./debug-selectors).
|
||||||
|
|
||||||
Writing good selectors is part art, part science so be sure to checkout the [Best Practices](#best-practices) section.
|
:::info
|
||||||
|
Check out our [locators guide](./locators) for more information on our new locators API.
|
||||||
## Quick guide
|
:::
|
||||||
|
|
||||||
- Text selector
|
|
||||||
```js
|
|
||||||
await page.locator('text=Log in').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator("text=Log in").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator("text=Log in").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator("text=Log in").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator("text=Log in").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [text selector][text].
|
|
||||||
- CSS selector
|
|
||||||
```js
|
|
||||||
await page.locator('button').click();
|
|
||||||
await page.locator('#nav-bar .contact-us-item').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator("button").click();
|
|
||||||
page.locator("#nav-bar .contact-us-item").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator("button").click()
|
|
||||||
await page.locator("#nav-bar .contact-us-item").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator("button").click()
|
|
||||||
page.locator("#nav-bar .contact-us-item").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator("button").ClickAsync();
|
|
||||||
await page.Locator("#nav-bar .contact-us-item").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [css selector][css].
|
|
||||||
- Select by attribute, with css selector
|
|
||||||
```js
|
|
||||||
await page.locator('[data-test=login-button]').click();
|
|
||||||
await page.locator('[aria-label="Sign in"]').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator("[data-test=login-button]").click();
|
|
||||||
page.locator("[aria-label='Sign in']").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator("[data-test=login-button]").click()
|
|
||||||
await page.locator("[aria-label='Sign in']").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator("[data-test=login-button]").click()
|
|
||||||
page.locator("[aria-label='Sign in']").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator("[data-test=login-button]").ClickAsync();
|
|
||||||
await page.Locator("[aria-label='Sign in']").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [css selector][css].
|
|
||||||
- Combine css and text selectors
|
|
||||||
```js
|
|
||||||
await page.locator('article:has-text("Playwright")').click();
|
|
||||||
await page.locator('#nav-bar >> text=Contact Us').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator("article:has-text(\"Playwright\")").click();
|
|
||||||
page.locator("#nav-bar :text(\"Contact us\")").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator("article:has-text('Playwright')").click()
|
|
||||||
await page.locator("#nav-bar :text('Contact us')").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator("article:has-text('Playwright')").click()
|
|
||||||
page.locator("#nav-bar :text('Contact us')").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator("article:has-text(\"Playwright\")").ClickAsync();
|
|
||||||
await page.Locator("#nav-bar :text(\"Contact us\")").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [`:has-text()` and `:text()` pseudo classes][text].
|
|
||||||
- Element that contains another, with css selector
|
|
||||||
```js
|
|
||||||
await page.locator('.item-description:has(.item-promo-banner)').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator(".item-description:has(.item-promo-banner)").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator(".item-description:has(.item-promo-banner)").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator(".item-description:has(.item-promo-banner)").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator(".item-description:has(.item-promo-banner)").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [`:has()` pseudo class](#selecting-elements-that-contain-other-elements).
|
|
||||||
- Selecting based on layout, with css selector
|
|
||||||
```js
|
|
||||||
await page.locator('input:right-of(:text("Username"))').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator("input:right-of(:text(\"Username\"))").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator("input:right-of(:text('Username'))").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator("input:right-of(:text('Username'))").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator("input:right-of(:text(\"Username\"))").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [layout selectors](#selecting-elements-based-on-layout).
|
|
||||||
- Only visible elements, with css selector
|
|
||||||
```js
|
|
||||||
await page.locator('.login-button:visible').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator(".login-button:visible").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator(".login-button:visible").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator(".login-button:visible").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator(".login-button:visible").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [selecting visible elements](#selecting-visible-elements).
|
|
||||||
- Pick n-th match
|
|
||||||
```js
|
|
||||||
await page.locator(':nth-match(:text("Buy"), 3)').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator(":nth-match(:text('Buy'), 3)").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator(":nth-match(:text('Buy'), 3)").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator(":nth-match(:text('Buy'), 3)").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator(":nth-match(:text('Buy'), 3)").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [`:nth-match()` pseudo-class](#pick-n-th-match-from-the-query-result).
|
|
||||||
- XPath selector
|
|
||||||
```js
|
|
||||||
await page.locator('xpath=//button').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator("xpath=//button").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator("xpath=//button").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator("xpath=//button").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator("xpath=//button").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [XPath selector][xpath].
|
|
||||||
- React selector (experimental)
|
|
||||||
```js
|
|
||||||
await page.locator('_react=ListItem[text *= "milk" i]').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator("_react=ListItem[text *= 'milk' i]").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator("_react=ListItem[text *= 'milk' i]").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator("_react=ListItem[text *= 'milk' i]").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator("_react=ListItem[text *= 'milk' i]").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [React selectors][react].
|
|
||||||
- Vue selector (experimental)
|
|
||||||
```js
|
|
||||||
await page.locator('_vue=list-item[text *= "milk" i]').click();
|
|
||||||
```
|
|
||||||
```java
|
|
||||||
page.locator("_vue=list-item[text *= 'milk' i]").click();
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.locator("_vue=list-item[text *= 'milk' i]").click()
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.locator("_vue=list-item[text *= 'milk' i]").click()
|
|
||||||
```
|
|
||||||
```csharp
|
|
||||||
await page.Locator("_vue=list-item[text *= 'milk' i]").ClickAsync();
|
|
||||||
```
|
|
||||||
Learn more about [Vue selectors][vue].
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Text selector
|
## Text selector
|
||||||
|
|
||||||
Text selector locates elements that contain passed text.
|
Text selector locates elements that contain passed text.
|
||||||
@ -1197,187 +991,6 @@ By default, chained selectors resolve to an element queried by the last selector
|
|||||||
|
|
||||||
For example, `css=article >> text=Hello` captures the element with the text `Hello`, and `*css=article >> text=Hello` (note the `*`) captures the `article` element that contains some element with the text `Hello`.
|
For example, `css=article >> text=Hello` captures the element with the text `Hello`, and `*css=article >> text=Hello` (note the `*`) captures the `article` element that contains some element with the text `Hello`.
|
||||||
|
|
||||||
## Best practices
|
|
||||||
|
|
||||||
The choice of selectors determines the resiliency of automation scripts. To reduce the maintenance burden, we recommend prioritizing user-facing attributes and explicit contracts.
|
|
||||||
|
|
||||||
### Prioritize user-facing attributes
|
|
||||||
Attributes like text content, input placeholder, accessibility roles and labels are user-facing attributes that change rarely. These attributes are not impacted by DOM structure changes.
|
|
||||||
|
|
||||||
The following examples use the built-in [text] and [css] selector engines.
|
|
||||||
|
|
||||||
```js
|
|
||||||
// queries "Login" text selector
|
|
||||||
await page.locator('text="Login"').click();
|
|
||||||
await page.locator('"Login"').click(); // short-form
|
|
||||||
|
|
||||||
// queries "Search GitHub" placeholder attribute
|
|
||||||
await page.locator('css=[placeholder="Search GitHub"]').fill('query');
|
|
||||||
await page.locator('[placeholder="Search GitHub"]').fill('query'); // short-form
|
|
||||||
|
|
||||||
// queries "Close" accessibility label
|
|
||||||
await page.locator('css=[aria-label="Close"]').click();
|
|
||||||
await page.locator('[aria-label="Close"]').click(); // short-form
|
|
||||||
|
|
||||||
// combine role and text queries
|
|
||||||
await page.locator('css=nav >> text=Login').click();
|
|
||||||
```
|
|
||||||
|
|
||||||
```java
|
|
||||||
// queries "Login" text selector
|
|
||||||
page.locator("text=\"Login\"").click();
|
|
||||||
page.locator("\"Login\"").click(); // short-form
|
|
||||||
|
|
||||||
// queries "Search GitHub" placeholder attribute
|
|
||||||
page.locator("css=[placeholder='Search GitHub']").fill("query");
|
|
||||||
page.locator("[placeholder='Search GitHub']").fill("query"); // short-form
|
|
||||||
|
|
||||||
// queries "Close" accessibility label
|
|
||||||
page.locator("css=[aria-label='Close']").click();
|
|
||||||
page.locator("[aria-label='Close']").click(); // short-form
|
|
||||||
|
|
||||||
// combine role and text queries
|
|
||||||
page.locator("css=nav >> text=Login").click();
|
|
||||||
```
|
|
||||||
|
|
||||||
```python async
|
|
||||||
# queries "Login" text selector
|
|
||||||
await page.locator('text="Login"').click()
|
|
||||||
await page.locator('"Login"').click() # short-form
|
|
||||||
|
|
||||||
# queries "Search GitHub" placeholder attribute
|
|
||||||
await page.locator('css=[placeholder="Search GitHub"]').fill('query')
|
|
||||||
await page.locator('[placeholder="Search GitHub"]').fill('query') # short-form
|
|
||||||
|
|
||||||
# queries "Close" accessibility label
|
|
||||||
await page.locator('css=[aria-label="Close"]').click()
|
|
||||||
await page.locator('[aria-label="Close"]').click() # short-form
|
|
||||||
|
|
||||||
# combine role and text queries
|
|
||||||
await page.locator('css=nav >> text=Login').click()
|
|
||||||
```
|
|
||||||
|
|
||||||
```python sync
|
|
||||||
# queries "Login" text selector
|
|
||||||
page.locator('text="Login"').click()
|
|
||||||
page.locator('"Login"').click() # short-form
|
|
||||||
|
|
||||||
# queries "Search GitHub" placeholder attribute
|
|
||||||
page.locator('css=[placeholder="Search GitHub"]').fill('query')
|
|
||||||
page.locator('[placeholder="Search GitHub"]').fill('query') # short-form
|
|
||||||
|
|
||||||
# queries "Close" accessibility label
|
|
||||||
page.locator('css=[aria-label="Close"]').click()
|
|
||||||
page.locator('[aria-label="Close"]').click() # short-form
|
|
||||||
|
|
||||||
# combine role and text queries
|
|
||||||
page.locator('css=nav >> text=Login').click()
|
|
||||||
```
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// queries "Login" text selector
|
|
||||||
await page.Locator("text=\"Login\"").ClickAsync();
|
|
||||||
await page.Locator("\"Login\"").ClickAsync(); // short-form
|
|
||||||
|
|
||||||
// queries "Search GitHub" placeholder attribute
|
|
||||||
await page.Locator("css=[placeholder='Search GitHub']").FillAsync("query");
|
|
||||||
await page.Locator("[placeholder='Search GitHub']").FillAsync("query"); // short-form
|
|
||||||
|
|
||||||
// queries "Close" accessibility label
|
|
||||||
await page.Locator("css=[aria-label='Close']").ClickAsync();
|
|
||||||
await page.Locator("[aria-label='Close']").ClickAsync(); // short-form
|
|
||||||
|
|
||||||
// combine role and text queries
|
|
||||||
await page.Locator("css=nav >> text=Login").ClickAsync();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Define explicit contract
|
|
||||||
|
|
||||||
When user-facing attributes change frequently, it is recommended to use explicit test ids, like `data-test-id`. These `data-*` attributes are supported by the [css] and [id selectors][id].
|
|
||||||
|
|
||||||
```html
|
|
||||||
<button data-test-id="directions">Itinéraire</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
// queries data-test-id attribute with css
|
|
||||||
await page.locator('css=[data-test-id=directions]').click();
|
|
||||||
await page.locator('[data-test-id=directions]').click(); // short-form
|
|
||||||
|
|
||||||
// queries data-test-id with id
|
|
||||||
await page.locator('data-test-id=directions').click();
|
|
||||||
```
|
|
||||||
|
|
||||||
```java
|
|
||||||
// queries data-test-id attribute with css
|
|
||||||
page.locator("css=[data-test-id=directions]").click();
|
|
||||||
page.locator("[data-test-id=directions]").click(); // short-form
|
|
||||||
|
|
||||||
// queries data-test-id with id
|
|
||||||
page.locator("data-test-id=directions").click();
|
|
||||||
```
|
|
||||||
|
|
||||||
```python async
|
|
||||||
# queries data-test-id attribute with css
|
|
||||||
await page.locator('css=[data-test-id=directions]').click()
|
|
||||||
await page.locator('[data-test-id=directions]').click() # short-form
|
|
||||||
|
|
||||||
# queries data-test-id with id
|
|
||||||
await page.locator('data-test-id=directions').click()
|
|
||||||
```
|
|
||||||
|
|
||||||
```python sync
|
|
||||||
# queries data-test-id attribute with css
|
|
||||||
page.locator('css=[data-test-id=directions]').click()
|
|
||||||
page.locator('[data-test-id=directions]').click() # short-form
|
|
||||||
|
|
||||||
# queries data-test-id with id
|
|
||||||
page.locator('data-test-id=directions').click()
|
|
||||||
```
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// queries data-test-id attribute with css
|
|
||||||
await page.Locator("css=[data-test-id=directions]").ClickAsync();
|
|
||||||
await page.Locator("[data-test-id=directions]").ClickAsync(); // short-form
|
|
||||||
|
|
||||||
// queries data-test-id with id
|
|
||||||
await page.Locator("data-test-id=directions").ClickAsync();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Avoid selectors tied to implementation
|
|
||||||
|
|
||||||
[xpath] and [css] can be tied to the DOM structure or implementation. These selectors can break when
|
|
||||||
the DOM structure changes. Similarly, [`method: Locator.nth`], [`method: Locator.first`], and [`method: Locator.last`] are tied to implementation and the structure of the DOM, and will target the incorrect element if the DOM changes.
|
|
||||||
|
|
||||||
```js
|
|
||||||
// avoid long css or xpath chains
|
|
||||||
await page.locator('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input').click();
|
|
||||||
await page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click();
|
|
||||||
```
|
|
||||||
|
|
||||||
```java
|
|
||||||
// avoid long css or xpath chains
|
|
||||||
page.locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").click();
|
|
||||||
page.locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").click();
|
|
||||||
```
|
|
||||||
|
|
||||||
```python async
|
|
||||||
# avoid long css or xpath chains
|
|
||||||
await page.locator('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input').click()
|
|
||||||
await page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click()
|
|
||||||
```
|
|
||||||
|
|
||||||
```python sync
|
|
||||||
# avoid long css or xpath chains
|
|
||||||
page.locator('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input').click()
|
|
||||||
page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click()
|
|
||||||
```
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// avoid long css or xpath chains
|
|
||||||
await page.Locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").ClickAsync();
|
|
||||||
await page.Locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").ClickAsync();
|
|
||||||
```
|
|
||||||
|
|
||||||
[text]: #text-selector
|
[text]: #text-selector
|
||||||
[css]: #css-selector
|
[css]: #css-selector
|
||||||
|
Loading…
Reference in New Issue
Block a user