` 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.fill('input:right-of(:text("Username"))', 'value');
// Click a button near the promo card.
await page.click('button:near(.promo-card)');
```
```java
// Fill an input to the right of "Username".
page.fill("input:right-of(:text(\"Username\"))", "value");
// Click a button near the promo card.
page.click("button:near(.promo-card)");
```
```python async
# Fill an input to the right of "Username".
await page.fill('input:right-of(:text("Username"))', 'value')
# Click a button near the promo card.
await page.click('button:near(.promo-card)')
```
```python sync
# Fill an input to the right of "Username".
page.fill('input:right-of(:text("Username"))', 'value')
# Click a button near the promo card.
page.click('button:near(.promo-card)')
```
```csharp
// Fill an input to the right of "Username".
await page.FillAsync("input:right-of(:text(\"Username\"))", "value");
// Click a button near the promo card.
await page.ClickAsync("button:near(.promo-card)");
```
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.click('button >> nth=0');
// Click last button
await page.click('button >> nth=-1');
```
```java
// Click first button
page.click("button >> nth=0");
// Click last button
page.click("button >> nth=-1");
```
```python async
# Click first button
await page.click("button >> nth=0")
# Click last button
await page.click("button >> nth=-1")
```
```python sync
# Click first button
page.click("button >> nth=0")
# Click last button
page.click("button >> nth=-1")
```
```csharp
// Click first button
await page.ClickAsync("button >> nth=0");
// Click last button
await page.ClickAsync("button >> nth=-1");
```
## React selectors
:::note
React selectors are experimental and prefixed with `_`. The functionality might change in future.
:::
React selectors allow selecting elements by its 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-sensetive: `_react=BookItem[author = "Steven King"]`
- match by property value only, **case-insensetive**: `_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"]`
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 its 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-sensetive: `_vue=book-item[author = "Steven King"]`
- match by property value only, **case-insensetive**: `_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 a 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.fill('id=username', 'value');
// Click an element with data-test-id "submit"
await page.click('data-test-id=submit');
```
```java
// Fill an input with the id "username"
page.fill("id=username", "value");
// Click an element with data-test-id "submit"
page.click("data-test-id=submit");
```
```python async
# Fill an input with the id "username"
await page.fill('id=username', 'value')
# Click an element with data-test-id "submit"
await page.click('data-test-id=submit')
```
```python sync
# Fill an input with the id "username"
page.fill('id=username', 'value')
# Click an element with data-test-id "submit"
page.click('data-test-id=submit')
```
```csharp
// Fill an input with the id "username"
await page.FillAsync("id=username", "value");
// Click an element with data-test-id "submit"
await page.ClickAsync("data-test-id=submit");
```
:::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.click('data-test-id:light=submit')
:::
## 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.click(':nth-match(:text("Buy"), 3)');
```
```java
// Click the third "Buy" button
page.click(":nth-match(:text('Buy'), 3)");
```
```python async
# Click the third "Buy" button
await page.click(":nth-match(:text('Buy'), 3)"
```
```python sync
# Click the third "Buy" button
page.click(":nth-match(:text('Buy'), 3)"
```
```csharp
// Click the third "Buy" button
await page.ClickAsync(":nth-match(:text('Buy'), 3)");
```
`:nth-match()` is also useful to wait until a specified number of elements appear, using [`method: Page.waitForSelector`].
```js
// Wait until all three buttons are visible
await page.waitForSelector(':nth-match(:text("Buy"), 3)');
```
```java
// Wait until all three buttons are visible
page.waitForSelector(":nth-match(:text('Buy'), 3)");
```
```python async
# Wait until all three buttons are visible
await page.wait_for_selector(":nth-match(:text('Buy'), 3)")
```
```python sync
# Wait until all three buttons are visible
page.wait_for_selector(":nth-match(:text('Buy'), 3)")
```
```csharp
// Wait until all three buttons are visible
await page.WaitForSelectorAsync(":nth-match(:text('Buy'), 3)");
```
:::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, 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
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.click('text="Login"');
await page.click('"Login"'); // short-form
// queries "Search GitHub" placeholder attribute
await page.fill('css=[placeholder="Search GitHub"]', 'query');
await page.fill('[placeholder="Search GitHub"]', 'query'); // short-form
// queries "Close" accessibility label
await page.click('css=[aria-label="Close"]');
await page.click('[aria-label="Close"]'); // short-form
// combine role and text queries
await page.click('css=nav >> text=Login');
```
```java
// queries "Login" text selector
page.click("text=\"Login\"");
page.click("\"Login\""); // short-form
// queries "Search GitHub" placeholder attribute
page.fill("css=[placeholder='Search GitHub']", "query");
page.fill("[placeholder='Search GitHub']", "query"); // short-form
// queries "Close" accessibility label
page.click("css=[aria-label='Close']");
page.click("[aria-label='Close']"); // short-form
// combine role and text queries
page.click("css=nav >> text=Login");
```
```python async
# queries "Login" text selector
await page.click('text="Login"')
await page.click('"Login"') # short-form
# queries "Search GitHub" placeholder attribute
await page.fill('css=[placeholder="Search GitHub"]', 'query')
await page.fill('[placeholder="Search GitHub"]', 'query') # short-form
# queries "Close" accessibility label
await page.click('css=[aria-label="Close"]')
await page.click('[aria-label="Close"]') # short-form
# combine role and text queries
await page.click('css=nav >> text=Login')
```
```python sync
# queries "Login" text selector
page.click('text="Login"')
page.click('"Login"') # short-form
# queries "Search GitHub" placeholder attribute
page.fill('css=[placeholder="Search GitHub"]')
page.fill('[placeholder="Search GitHub"]') # short-form
# queries "Close" accessibility label
page.click('css=[aria-label="Close"]')
page.click('[aria-label="Close"]') # short-form
# combine role and text queries
page.click('css=nav >> text=Login')
```
```csharp
// queries "Login" text selector
await page.ClickAsync("text=\"Login\"");
await page.ClickAsync("\"Login\""); // short-form
// queries "Search GitHub" placeholder attribute
await page.FillAsync("css=[placeholder='Search GitHub']", "query");
await page.FillAsync("[placeholder='Search GitHub']", "query"); // short-form
// queries "Close" accessibility label
await page.ClickAsync("css=[aria-label='Close']");
await page.ClickAsync("[aria-label='Close']"); // short-form
// combine role and text queries
await page.ClickAsync("css=nav >> text=Login");
```
### 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.click('css=[data-test-id=directions]');
await page.click('[data-test-id=directions]'); // short-form
// queries data-test-id with id
await page.click('data-test-id=directions');
```
```java
// queries data-test-id attribute with css
page.click("css=[data-test-id=directions]");
page.click("[data-test-id=directions]"); // short-form
// queries data-test-id with id
page.click("data-test-id=directions");
```
```python async
# queries data-test-id attribute with css
await page.click('css=[data-test-id=directions]')
await page.click('[data-test-id=directions]') # short-form
# queries data-test-id with id
await page.click('data-test-id=directions')
```
```python sync
# queries data-test-id attribute with css
page.click('css=[data-test-id=directions]')
page.click('[data-test-id=directions]') # short-form
# queries data-test-id with id
page.click('data-test-id=directions')
```
```csharp
// queries data-test-id attribute with css
await page.ClickAsync("css=[data-test-id=directions]");
await page.ClickAsync("[data-test-id=directions]"); // short-form
// queries data-test-id with id
await page.ClickAsync("data-test-id=directions");
```
### 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.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input');
await page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input');
```
```java
// avoid long css or xpath chains
page.click("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input");
page.click("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input");
```
```python async
# avoid long css or xpath chains
await page.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input')
await page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
```
```python sync
# avoid long css or xpath chains
page.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input')
page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
```
```csharp
// avoid long css or xpath chains
await page.ClickAsync("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input");
await page.ClickAsync("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input");
```
[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