Skip to content

Commit 058ce60

Browse files
authored
docs: combine text sections in selectors doc (#5528)
1 parent f154a82 commit 058ce60

File tree

1 file changed

+103
-91
lines changed

1 file changed

+103
-91
lines changed

docs/src/selectors.md

Lines changed: 103 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ methods accept [`param: selector`] as their first argument.
6363
page.click("article:has-text('Playwright')")
6464
page.click("#nav-bar :text('Contact us')")
6565
```
66-
Learn more about [`:has-text()` and `:text()` pseudo classes](#selecting-elements-by-text).
66+
Learn more about [`:has-text()` and `:text()` pseudo classes][text].
6767
- Element that contains another, with css selector
6868
```js
6969
await page.click('.item-description:has(.item-promo-banner)');
@@ -120,41 +120,123 @@ methods accept [`param: selector`] as their first argument.
120120
```
121121
Learn more about [XPath selector][xpath].
122122

123-
## Basic text selectors
123+
## Text selector
124124

125-
Text selectors locate elements that contain text nodes with the passed text.
125+
Text selector locates elements that contain passed text.
126126

127127
```js
128128
await page.click('text=Log in');
129129
```
130-
131130
```python async
132131
await page.click("text=Log in")
133132
```
134-
135133
```python sync
136134
page.click("text=Log in")
137135
```
138136

139-
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.
137+
Text selector has a few variations:
140138

141-
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
142-
rules, e.g. use `\"` to escape double quote in a double-quoted string: `text="foo\"bar"`. Note that quoted match still normalizes whitespace.
139+
- `text=Log in` - default matching is case-insensitive and searches for a substring. For example `text=Log` matches `<button>Log in</button>`.
143140

144-
Text body can also be a JavaScript-like regex wrapped in `/` symbols. This means `text=/^\\s*Login$/i`
145-
will match `<button> loGIN</button>` with any number of spaces before "Login" and no spaces after.
141+
```js
142+
await page.click('text=Log in');
143+
```
144+
```python async
145+
await page.click("text=Log in")
146+
```
147+
```python sync
148+
page.click("text=Log in")
149+
```
146150

147-
Input elements of the type `button` and `submit` are rendered with their value as text, and text
148-
engine finds them. For example, `text=Login` matches `<input type=button value="Login">`.
151+
- `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>`.
149152

150-
Selector string starting and ending with a quote (either `"` or `'`) is assumed to be a text selector.
151-
For example, Playwright converts `'"Login"'` to `'text="Login"'` internally.
153+
Quoted body follows the usual escaping rules, e.g. use `\"` to escape double quote in a double-quoted string: `text="foo\"bar"`.
152154
153-
## Basic CSS selectors
155+
```js
156+
await page.click('text="Log in"');
157+
```
158+
```python async
159+
await page.click("text='Log in'")
160+
```
161+
```python sync
162+
page.click("text='Log in'")
163+
```
164+
165+
- `"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.
166+
167+
```js
168+
await page.click('"Log in"');
169+
```
170+
```python async
171+
await page.click("'Log in'")
172+
```
173+
```python sync
174+
page.click("'Log in'")
175+
```
176+
177+
- `/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>`.
178+
179+
```js
180+
await page.click('text=/Log\\s*in/i');
181+
```
182+
```python async
183+
await page.click("text=/Log\s*in/i")
184+
```
185+
```python sync
186+
page.click("text=/Log\s*in/i")
187+
```
188+
189+
- `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>`.
190+
191+
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>`.
192+
```js
193+
// Wrong, will match many elements including <body>
194+
await page.click(':has-text("Playwright")');
195+
// Correct, only matches the <article> element
196+
await page.click('article:has-text("Playwright")');
197+
```
198+
```python async
199+
# Wrong, will match many elements including <body>
200+
await page.click(':has-text("Playwright")')
201+
# Correct, only matches the <article> element
202+
await page.click('article:has-text("Playwright")')
203+
```
204+
```python sync
205+
# Wrong, will match many elements including <body>
206+
page.click(':has-text("Playwright")')
207+
# Correct, only matches the <article> element
208+
page.click('article:has-text("All products")')
209+
```
210+
211+
- `#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.
212+
213+
```js
214+
await page.click('#nav-bar :text("Home")');
215+
```
216+
```python async
217+
await page.click("#nav-bar :text('Home')")
218+
```
219+
```python sync
220+
page.click("#nav-bar :text('Home')")
221+
```
222+
223+
- `#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.
224+
225+
* `#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.
226+
227+
:::note
228+
Matching always normalizes whitespace, for example it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace.
229+
:::
230+
231+
:::note
232+
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">`.
233+
:::
234+
235+
## CSS selector
154236

155237
Playwright augments standard CSS selectors in two ways:
156238
* `css` engine pierces open shadow DOM by default.
157-
* Playwright adds a few custom pseudo-classes like `:visible`.
239+
* Playwright adds custom pseudo-classes like `:visible`, `:text` and more.
158240

159241
```js
160242
await page.click('button');
@@ -258,83 +340,13 @@ await page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))')
258340
page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))')
259341
```
260342

261-
## Selecting elements by text
262-
263-
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)`.
264-
265-
The `:text` pseudo-class matches elements that have a text node child with specific text. It is similar to the [text] engine.
266-
267-
`:has-text` and `:text` should be used differently. Consider the following page:
268-
```html
269-
<div class=nav-item>Home</div>
270-
<div class=nav-item>
271-
<span class=bold>New</span> products
272-
</div>
273-
<div class=nav-item>
274-
<span class=bold>All</span> products
275-
</div>
276-
<div class=nav-item>Contact us</div>
277-
```
278-
279-
Use `:has-text()` to click a navigation item that contains text "All products".
280-
```js
281-
await page.click('.nav-item:has-text("All products")');
282-
```
283-
```python async
284-
await page.click('.nav-item:has-text("All products")')
285-
```
286-
```python sync
287-
page.click('.nav-item:has-text("All products")')
288-
```
289-
`: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.
290-
```js
291-
// Wrong, will match many elements including <body>
292-
await page.click(':has-text("All products")');
293-
// Correct, only matches the navigation item
294-
await page.click('.nav-item:has-text("All products")');
295-
```
296-
```python async
297-
# Wrong, will match many elements including <body>
298-
await page.click(':has-text("All products")')
299-
# Correct, only matches the navigation item
300-
await page.click('.nav-item:has-text("All products")')
301-
```
302-
```python sync
303-
# Wrong, will match many elements including <body>
304-
page.click(':has-text("All products")')
305-
# Correct, only matches the navigation item
306-
page.click('.nav-item:has-text("All products")')
307-
```
308-
309-
Use `:text()` to click an element that directly contains text "Home".
310-
```js
311-
await page.click(':text("Home")');
312-
```
313-
```python async
314-
await page.click(':text("Home")')
315-
```
316-
```python sync
317-
page.click(':text("Home")')
318-
```
319-
`: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.
320-
321-
:::note
322-
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.
323-
:::
324-
325-
There are a few `:text()` variations that support different arguments:
326-
* `:text("substring")` - Matches when a text node inside the element contains "substring". Matching is case-insensitive and normalizes whitespace.
327-
* `: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.
328-
* `: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).
329-
* `:text-matches("value", "i")` - Matches text nodes against a regular expression with specified flags.
330-
331343
## Selecting elements in Shadow DOM
332344

333345
Our `css` and `text` engines pierce the [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) by default:
334-
- First it searches for the elements in the light DOM in the iteration order, and
335-
- Then it searches recursively inside open shadow roots in the iteration order.
346+
- First they search for the elements in the light DOM in the iteration order, and
347+
- Then they search recursively inside open shadow roots in the iteration order.
336348

337-
In particular, in `css` engines, any [Descendant combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator)
349+
In particular, in `css` engine, any [Descendant combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator)
338350
or [Child combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator) pierces an
339351
arbitrary number of open shadow roots, including the implicit descendant combinator at the start of the
340352
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
634646
page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
635647
```
636648

637-
[text]: #basic-text-selectors
638-
[css]: #basic-css-selectors
649+
[text]: #text-selector
650+
[css]: #css-selector
639651
[xpath]: #xpath-selectors
640652
[id]: #id-data-testid-data-test-id-data-test-selectors

0 commit comments

Comments
 (0)