docs: combine text sections in selectors doc (#5528)
This commit is contained in:
parent
f154a827c5
commit
058ce60511
|
|
@ -63,7 +63,7 @@ methods accept [`param: selector`] as their first argument.
|
||||||
page.click("article:has-text('Playwright')")
|
page.click("article:has-text('Playwright')")
|
||||||
page.click("#nav-bar :text('Contact us')")
|
page.click("#nav-bar :text('Contact us')")
|
||||||
```
|
```
|
||||||
Learn more about [`:has-text()` and `:text()` pseudo classes](#selecting-elements-by-text).
|
Learn more about [`:has-text()` and `:text()` pseudo classes][text].
|
||||||
- Element that contains another, with css selector
|
- Element that contains another, with css selector
|
||||||
```js
|
```js
|
||||||
await page.click('.item-description:has(.item-promo-banner)');
|
await page.click('.item-description:has(.item-promo-banner)');
|
||||||
|
|
@ -120,41 +120,123 @@ methods accept [`param: selector`] as their first argument.
|
||||||
```
|
```
|
||||||
Learn more about [XPath selector][xpath].
|
Learn more about [XPath selector][xpath].
|
||||||
|
|
||||||
## Basic text selectors
|
## Text selector
|
||||||
|
|
||||||
Text selectors locate elements that contain text nodes with the passed text.
|
Text selector locates elements that contain passed text.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
await page.click('text=Log in');
|
await page.click('text=Log in');
|
||||||
```
|
```
|
||||||
|
|
||||||
```python async
|
```python async
|
||||||
await page.click("text=Log in")
|
await page.click("text=Log in")
|
||||||
```
|
```
|
||||||
|
|
||||||
```python sync
|
```python sync
|
||||||
page.click("text=Log in")
|
page.click("text=Log in")
|
||||||
```
|
```
|
||||||
|
|
||||||
Matching is case-insensitive and searches for a substring. This means `text=Login` matches `<button>Button loGIN (click me)</button>`. Matching also normalizes whitespace, for example it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace.
|
Text selector has a few variations:
|
||||||
|
|
||||||
Text body can be escaped with single or double quotes for full-string case-sensitive match instead. This means `text="Login"` will match `<button>Login</button>`, but not `<button>Login (click me)</button>` or `<button>login</button>`. Quoted text follows the usual escaping
|
- `text=Log in` - default matching is case-insensitive and searches for a substring. For example `text=Log` matches `<button>Log in</button>`.
|
||||||
rules, e.g. use `\"` to escape double quote in a double-quoted string: `text="foo\"bar"`. Note that quoted match still normalizes whitespace.
|
|
||||||
|
|
||||||
Text body can also be a JavaScript-like regex wrapped in `/` symbols. This means `text=/^\\s*Login$/i`
|
```js
|
||||||
will match `<button> loGIN</button>` with any number of spaces before "Login" and no spaces after.
|
await page.click('text=Log in');
|
||||||
|
```
|
||||||
|
```python async
|
||||||
|
await page.click("text=Log in")
|
||||||
|
```
|
||||||
|
```python sync
|
||||||
|
page.click("text=Log in")
|
||||||
|
```
|
||||||
|
|
||||||
Input elements of the type `button` and `submit` are rendered with their value as text, and text
|
- `text="Log in"` - text body can be escaped with single or double quotes for full-string case-sensitive match. For example `text="Log"` does not match `<button>Log in</button>` but instead matches `<span>Log</span>`.
|
||||||
engine finds them. For example, `text=Login` matches `<input type=button value="Login">`.
|
|
||||||
|
|
||||||
Selector string starting and ending with a quote (either `"` or `'`) is assumed to be a text selector.
|
Quoted body follows the usual escaping rules, e.g. use `\"` to escape double quote in a double-quoted string: `text="foo\"bar"`.
|
||||||
For example, Playwright converts `'"Login"'` to `'text="Login"'` internally.
|
|
||||||
|
|
||||||
## Basic CSS selectors
|
```js
|
||||||
|
await page.click('text="Log in"');
|
||||||
|
```
|
||||||
|
```python async
|
||||||
|
await page.click("text='Log in'")
|
||||||
|
```
|
||||||
|
```python sync
|
||||||
|
page.click("text='Log in'")
|
||||||
|
```
|
||||||
|
|
||||||
|
- `"Log in"` - selector starting and ending with a quote (either `"` or `'`) is assumed to be a text selector. For example, `"Log in"` is converted to `text="Log in"` internally.
|
||||||
|
|
||||||
|
```js
|
||||||
|
await page.click('"Log in"');
|
||||||
|
```
|
||||||
|
```python async
|
||||||
|
await page.click("'Log in'")
|
||||||
|
```
|
||||||
|
```python sync
|
||||||
|
page.click("'Log in'")
|
||||||
|
```
|
||||||
|
|
||||||
|
- `/Log\s*in/i` - body can be a [JavaScript-like regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) wrapped in `/` symbols. For example, `text=/Log\s*in/i` matches `<button>Login</button>` and `<button>log IN</button>`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
await page.click('text=/Log\\s*in/i');
|
||||||
|
```
|
||||||
|
```python async
|
||||||
|
await page.click("text=/Log\s*in/i")
|
||||||
|
```
|
||||||
|
```python sync
|
||||||
|
page.click("text=/Log\s*in/i")
|
||||||
|
```
|
||||||
|
|
||||||
|
- `article:has-text("Playwright")` - the `:has-text()` pseudo-class can be used inside a [css] selector. It matches any element containing specified text somewhere inside, possibly in a child or a descendant element. For example, `article:has-text("Playwright")` matches `<article><div>Playwright</div></article>`.
|
||||||
|
|
||||||
|
Note that `:has-text()` should be used together with other `css` specifiers, otherwise it will match all the elements containing specified text, including the `<body>`.
|
||||||
|
```js
|
||||||
|
// Wrong, will match many elements including <body>
|
||||||
|
await page.click(':has-text("Playwright")');
|
||||||
|
// Correct, only matches the <article> element
|
||||||
|
await page.click('article:has-text("Playwright")');
|
||||||
|
```
|
||||||
|
```python async
|
||||||
|
# Wrong, will match many elements including <body>
|
||||||
|
await page.click(':has-text("Playwright")')
|
||||||
|
# Correct, only matches the <article> element
|
||||||
|
await page.click('article:has-text("Playwright")')
|
||||||
|
```
|
||||||
|
```python sync
|
||||||
|
# Wrong, will match many elements including <body>
|
||||||
|
page.click(':has-text("Playwright")')
|
||||||
|
# Correct, only matches the <article> element
|
||||||
|
page.click('article:has-text("All products")')
|
||||||
|
```
|
||||||
|
|
||||||
|
- `#nav-bar :text("Home")` - the `:text()` pseudo-class can be used inside a [css] selector. It matches the smallest element containing specified text. This example is equivalent to `text=Home`, but inside the `#nav-bar` element.
|
||||||
|
|
||||||
|
```js
|
||||||
|
await page.click('#nav-bar :text("Home")');
|
||||||
|
```
|
||||||
|
```python async
|
||||||
|
await page.click("#nav-bar :text('Home')")
|
||||||
|
```
|
||||||
|
```python sync
|
||||||
|
page.click("#nav-bar :text('Home')")
|
||||||
|
```
|
||||||
|
|
||||||
|
- `#nav-bar :text-is("Home")` - the `:text-is()` pseudo-class can be used inside a [css] selector, for case-sensitive match. This example is equivalent to `text="Home"` (note quotes), but inside the `#nav-bar` element.
|
||||||
|
|
||||||
|
* `#nav-bar :text-matches("reg?ex", "i")` - the `:text-matches()` pseudo-class can be used inside a [css] selector, for regex-based match. This example is equivalent to `text=/reg?ex/i`, but inside the `#nav-bar` element.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Matching always normalizes whitespace, for example it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Input elements of the type `button` and `submit` are matched by their `value` instead of text content. For example, `text=Log in` matches `<input type=button value="Log in">`.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## CSS selector
|
||||||
|
|
||||||
Playwright augments standard CSS selectors in two ways:
|
Playwright augments standard CSS selectors in two ways:
|
||||||
* `css` engine pierces open shadow DOM by default.
|
* `css` engine pierces open shadow DOM by default.
|
||||||
* Playwright adds a few custom pseudo-classes like `:visible`.
|
* Playwright adds custom pseudo-classes like `:visible`, `:text` and more.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
await page.click('button');
|
await page.click('button');
|
||||||
|
|
@ -258,83 +340,13 @@ await page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))')
|
||||||
page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))')
|
page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))')
|
||||||
```
|
```
|
||||||
|
|
||||||
## Selecting elements by text
|
|
||||||
|
|
||||||
The `:has-text` pseudo-class matches elements that have specific text somewhere inside, possibly in a child or a descendant element. It is approximately equivalent to `element.textContent.includes(textToSearchFor)`.
|
|
||||||
|
|
||||||
The `:text` pseudo-class matches elements that have a text node child with specific text. It is similar to the [text] engine.
|
|
||||||
|
|
||||||
`:has-text` and `:text` should be used differently. Consider the following page:
|
|
||||||
```html
|
|
||||||
<div class=nav-item>Home</div>
|
|
||||||
<div class=nav-item>
|
|
||||||
<span class=bold>New</span> products
|
|
||||||
</div>
|
|
||||||
<div class=nav-item>
|
|
||||||
<span class=bold>All</span> products
|
|
||||||
</div>
|
|
||||||
<div class=nav-item>Contact us</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `:has-text()` to click a navigation item that contains text "All products".
|
|
||||||
```js
|
|
||||||
await page.click('.nav-item:has-text("All products")');
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.click('.nav-item:has-text("All products")')
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.click('.nav-item:has-text("All products")')
|
|
||||||
```
|
|
||||||
`:has-text()` will match even though "All products" text is split between multiple elements. However, it will also match any parent element of this navigation item, including `<body>` and `<html>`, because each of them contains "All products" somewhere inside. Therefore, `:has-text()` must be used together with other `css` specifiers, like a tag name or a class name.
|
|
||||||
```js
|
|
||||||
// Wrong, will match many elements including <body>
|
|
||||||
await page.click(':has-text("All products")');
|
|
||||||
// Correct, only matches the navigation item
|
|
||||||
await page.click('.nav-item:has-text("All products")');
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
# Wrong, will match many elements including <body>
|
|
||||||
await page.click(':has-text("All products")')
|
|
||||||
# Correct, only matches the navigation item
|
|
||||||
await page.click('.nav-item:has-text("All products")')
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
# Wrong, will match many elements including <body>
|
|
||||||
page.click(':has-text("All products")')
|
|
||||||
# Correct, only matches the navigation item
|
|
||||||
page.click('.nav-item:has-text("All products")')
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `:text()` to click an element that directly contains text "Home".
|
|
||||||
```js
|
|
||||||
await page.click(':text("Home")');
|
|
||||||
```
|
|
||||||
```python async
|
|
||||||
await page.click(':text("Home")')
|
|
||||||
```
|
|
||||||
```python sync
|
|
||||||
page.click(':text("Home")')
|
|
||||||
```
|
|
||||||
`:text()` only matches the element that contains the text directly inside, but not any parent elements. It is suitable to use without other `css` specifiers. However, it does not match text across elements. For example, `:text("All products")` will not match anything, because "All" and "products" belong to the different elements.
|
|
||||||
|
|
||||||
:::note
|
|
||||||
Both `:has-text()` and `:text()` perform case-insensitive match. They also normalize whitespace, for example turn multiple spaces into one, turn line breaks into spaces and ignore leading and trailing whitespace.
|
|
||||||
:::
|
|
||||||
|
|
||||||
There are a few `:text()` variations that support different arguments:
|
|
||||||
* `:text("substring")` - Matches when a text node inside the element contains "substring". Matching is case-insensitive and normalizes whitespace.
|
|
||||||
* `:text-is("string")` - Matches when all text nodes inside the element combined have the text value equal to "string". Matching is case-insensitive and normalizes whitespace.
|
|
||||||
* `:text-matches("[+-]?\\d+")` - Matches text nodes against a regular expression. Note that special characters like back-slash `\`, quotes `"`, square brackets `[]` and more should be escaped. Learn more about [regular expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp).
|
|
||||||
* `:text-matches("value", "i")` - Matches text nodes against a regular expression with specified flags.
|
|
||||||
|
|
||||||
## Selecting elements in Shadow DOM
|
## Selecting elements in Shadow DOM
|
||||||
|
|
||||||
Our `css` and `text` engines pierce the [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) by default:
|
Our `css` and `text` engines pierce the [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) by default:
|
||||||
- First it searches for the elements in the light DOM in the iteration order, and
|
- First they search for the elements in the light DOM in the iteration order, and
|
||||||
- Then it searches recursively inside open shadow roots in the iteration order.
|
- Then they search recursively inside open shadow roots in the iteration order.
|
||||||
|
|
||||||
In particular, in `css` engines, any [Descendant combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator)
|
In particular, in `css` engine, any [Descendant combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator)
|
||||||
or [Child combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator) pierces an
|
or [Child combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator) pierces an
|
||||||
arbitrary number of open shadow roots, including the implicit descendant combinator at the start of the
|
arbitrary number of open shadow roots, including the implicit descendant combinator at the start of the
|
||||||
selector. It does not search inside closed shadow roots or iframes.
|
selector. It does not search inside closed shadow roots or iframes.
|
||||||
|
|
@ -634,7 +646,7 @@ page.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc
|
||||||
page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
|
page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
|
||||||
```
|
```
|
||||||
|
|
||||||
[text]: #basic-text-selectors
|
[text]: #text-selector
|
||||||
[css]: #basic-css-selectors
|
[css]: #css-selector
|
||||||
[xpath]: #xpath-selectors
|
[xpath]: #xpath-selectors
|
||||||
[id]: #id-data-testid-data-test-id-data-test-selectors
|
[id]: #id-data-testid-data-test-id-data-test-selectors
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue