` is not a direct child of `article`
- `":light(article > .in-the-shadow)"` does not match anything.
- `"article li#target"` matches the `
Deep in the shadow`, piercing two shadow roots.
## Selecting elements based on layout
Playwright can select elements based on the page layout. These can be combined with regular CSS for
better results, for example `input:right-of(:text("Password"))` matches an input field that is to the
right of text "Password".
:::note
Layout selectors depend on the page layout and may produce unexpected results. For example, a different
element could be matched when layout changes by one pixel.
:::
Layout selectors use [bounding client rect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect)
to compute distance and relative position of the elements.
* `:right-of(inner > selector)` - Matches elements that are to the right of any element matching the inner selector.
* `:left-of(inner > selector)` - Matches elements that are to the left of any element matching the inner selector.
* `:above(inner > selector)` - Matches elements that are above any of the elements matching the inner selector.
* `:below(inner > selector)` - Matches elements that are below any of the elements matching the inner selector.
* `:near(inner > selector)` - Matches elements that are near (within 50 CSS pixels) any of the elements matching the inner selector.
```js
// Fill an input to the right of "Username".
await page.locator('input:right-of(:text("Username"))').fill('value');
// Click a button near the promo card.
await page.locator('button:near(.promo-card)').click();
```
```java
// Fill an input to the right of "Username".
page.locator("input:right-of(:text(\"Username\"))").fill("value");
// Click a button near the promo card.
page.locator("button:near(.promo-card)").click();
```
```python async
# Fill an input to the right of "Username".
await page.locator('input:right-of(:text("Username"))').fill('value')
# Click a button near the promo card.
await page.locator('button:near(.promo-card)').click()
```
```python sync
# Fill an input to the right of "Username".
page.locator('input:right-of(:text("Username"))').fill('value')
# Click a button near the promo card.
page.locator('button:near(.promo-card)').click()
```
```csharp
// Fill an input to the right of "Username".
await page.Locator("input:right-of(:text(\"Username\"))").FillAsync("value");
// Click a button near the promo card.
await page.Locator("button:near(.promo-card)").ClickAsync();
```
All layout selectors support optional maximum pixel distance as the last argument. For example
`button:near(:text("Username"), 120)` matches a button that is at most 120 pixels away from the element with the text "Username".
## XPath selectors
XPath selectors are equivalent to calling [`Document.evaluate`](https://developer.mozilla.org/en/docs/Web/API/Document/evaluate).
Example: `xpath=//html/body`.
Selector starting with `//` or `..` is assumed to be an xpath selector. For example, Playwright
converts `'//html/body'` to `'xpath=//html/body'`.
:::note
`xpath` does not pierce shadow roots
:::
## N-th element selector
You can narrow down query to the n-th match using the `nth=` selector. Unlike CSS's nth-match, provided index is 0-based.
```js
// Click first button
await page.locator('button >> nth=0').click();
// Click last button
await page.locator('button >> nth=-1').click();
```
```java
// Click first button
page.locator("button >> nth=0").click();
// Click last button
page.locator("button >> nth=-1").click();
```
```python async
# Click first button
await page.locator("button >> nth=0").click()
# Click last button
await page.locator("button >> nth=-1").click()
```
```python sync
# Click first button
page.locator("button >> nth=0").click()
# Click last button
page.locator("button >> nth=-1").click()
```
```csharp
// Click first button
await page.Locator("button >> nth=0").ClickAsync();
// Click last button
await page.Locator("button >> nth=-1").ClickAsync();
```
## React selectors
:::note
React selectors are experimental and prefixed with `_`. The functionality might change in future.
:::
React selectors allow selecting elements by their component name and property values. The syntax is very similar to [attribute selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) and supports all attribute selector operators.
In react selectors, component names are transcribed with **CamelCase**.
Selector examples:
- match by **component**: `_react=BookItem`
- match by component and **exact property value**, case-sensitive: `_react=BookItem[author = "Steven King"]`
- match by property value only, **case-insensitive**: `_react=[author = "steven king" i]`
- match by component and **truthy property value**: `_react=MyButton[enabled]`
- match by component and **boolean value**: `_react=MyButton[enabled = false]`
- match by property **value substring**: `_react=[author *= "King"]`
- match by component and **multiple properties**: `_react=BookItem[author *= "king" i][year = 1990]`
- match by **nested** property value: `_react=[some.nested.value = 12]`
- match by component and property value **prefix**: `_react=BookItem[author ^= "Steven"]`
- match by component and property value **suffix**: `_react=BookItem[author $= "Steven"]`
- match by component and **key**: `_react=BookItem[key = '2']`
To find React element names in a tree use [React DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi).
:::note
React selectors support React 15 and above.
:::
:::note
React selectors, as well as [React DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi), only work against **unminified** application builds.
:::
## Vue selectors
:::note
Vue selectors are experimental and prefixed with `_`. The functionality might change in future.
:::
Vue selectors allow selecting elements by their component name and property values. The syntax is very similar to [attribute selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) and supports all attribute selector operators.
In Vue selectors, component names are transcribed with **kebab-case**.
Selector examples:
- match by **component**: `_vue=book-item`
- match by component and **exact property value**, case-sensitive: `_vue=book-item[author = "Steven King"]`
- match by property value only, **case-insensitive**: `_vue=[author = "steven king" i]`
- match by component and **truthy property value**: `_vue=my-button[enabled]`
- match by component and **boolean value**: `_vue=my-button[enabled = false]`
- match by property **value substring**: `_vue=[author *= "King"]`
- match by component and **multiple properties**: `_vue=book-item[author *= "king" i][year = 1990]`
- match by **nested** property value: `_vue=[some.nested.value = 12]`
- match by component and property value **prefix**: `_vue=book-item[author ^= "Steven"]`
- match by component and property value **suffix**: `_vue=book-item[author $= "Steven"]`
To find Vue element names in a tree use [Vue DevTools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=en).
:::note
Vue selectors support Vue2 and above.
:::
:::note
Vue selectors, as well as [Vue DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi), only work against **unminified** application builds.
:::
## id, data-testid, data-test-id, data-test selectors
Playwright supports shorthand for selecting elements using certain attributes. Currently, only
the following attributes are supported:
- `id`
- `data-testid`
- `data-test-id`
- `data-test`
```js
// Fill an input with the id "username"
await page.locator('id=username').fill('value');
// Click an element with data-test-id "submit"
await page.locator('data-test-id=submit').click();
```
```java
// Fill an input with the id "username"
page.locator("id=username").fill("value");
// Click an element with data-test-id "submit"
page.locator("data-test-id=submit").click();
```
```python async
# Fill an input with the id "username"
await page.locator('id=username').fill('value')
# Click an element with data-test-id "submit"
await page.locator('data-test-id=submit').click()
```
```python sync
# Fill an input with the id "username"
page.locator('id=username').fill('value')
# Click an element with data-test-id "submit"
page.locator('data-test-id=submit').click()
```
```csharp
// Fill an input with the id "username"
await page.Locator("id=username").FillAsync("value");
// Click an element with data-test-id "submit"
await page.Locator("data-test-id=submit").ClickAsync();
```
:::note
Attribute selectors are not CSS selectors, so anything CSS-specific like `:enabled` is not supported. For more features, use a proper [css] selector, e.g. `css=[data-test="login"]:enabled`.
:::
:::note
Attribute selectors pierce shadow DOM. To opt-out from this behavior, use `:light` suffix after attribute, for example `page.locator('data-test-id:light=submit').click()`
:::
## Pick n-th match from the query result
Sometimes page contains a number of similar elements, and it is hard to select a particular one. For example:
```html
```
In this case, `:nth-match(:text("Buy"), 3)` will select the third button from the snippet above. Note that index is one-based.
```js
// Click the third "Buy" button
await page.locator(':nth-match(:text("Buy"), 3)').click();
```
```java
// Click the third "Buy" button
page.locator(":nth-match(:text('Buy'), 3)").click();
```
```python async
# Click the third "Buy" button
await page.locator(":nth-match(:text('Buy'), 3).click()"
```
```python sync
# Click the third "Buy" button
page.locator(":nth-match(:text('Buy'), 3).click()"
```
```csharp
// Click the third "Buy" button
await page.Locator(":nth-match(:text('Buy'), 3)").ClickAsync();
```
`:nth-match()` is also useful to wait until a specified number of elements appear, using [`method: Locator.waitFor`].
```js
// Wait until all three buttons are visible
await page.locator(':nth-match(:text("Buy"), 3)').waitFor();
```
```java
// Wait until all three buttons are visible
page.locator(":nth-match(:text('Buy'), 3)").waitFor();
```
```python async
# Wait until all three buttons are visible
await page.locator(":nth-match(:text('Buy'), 3)").wait_for()
```
```python sync
# Wait until all three buttons are visible
page.locator(":nth-match(:text('Buy'), 3)").wait_for()
```
```csharp
// Wait until all three buttons are visible
await page.Locator(":nth-match(:text('Buy'), 3)").WaitForAsync();
```
:::note
Unlike [`:nth-child()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child), elements do not have to be siblings, they could be anywhere on the page. In the snippet above, all three buttons match `:text("Buy")` selector, and `:nth-match()` selects the third button.
:::
:::note
It is usually possible to distinguish elements by some attribute or text content. In this case,
prefer using [text] or [css] selectors over the `:nth-match()`.
:::
## Chaining selectors
Selectors defined as `engine=body` or in short-form can be combined with the `>>` token, e.g. `selector1 >> selector2 >> selectors3`. When selectors are chained, the next one is queried relative to the previous one's result.
For example,
```
css=article >> css=.bar > .baz >> css=span[attr=value]
```
is equivalent to
```js browser
document
.querySelector('article')
.querySelector('.bar > .baz')
.querySelector('span[attr=value]')
```
If a selector needs to include `>>` in the body, it should be escaped inside a string to not be confused with chaining separator, e.g. `text="some >> text"`.
### Intermediate matches
By default, chained selectors resolve to an element queried by the last selector. A selector can be prefixed with `*` to capture elements that are queried by an intermediate 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`.
## 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
```
```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.
```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
[css]: #css-selector
[xpath]: #xpath-selectors
[react]: #react-selectors
[vue]: #vue-selectors
[id]: #id-data-testid-data-test-id-data-test-selectors