From 058ce605111b065dd9b7bc459c0a7e9f262beffa Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 19 Feb 2021 16:28:35 -0800 Subject: [PATCH] docs: combine text sections in selectors doc (#5528) --- docs/src/selectors.md | 194 ++++++++++++++++++++++-------------------- 1 file changed, 103 insertions(+), 91 deletions(-) diff --git a/docs/src/selectors.md b/docs/src/selectors.md index 6fb1b22e63..0004a261a1 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -63,7 +63,7 @@ methods accept [`param: selector`] as their first argument. page.click("article:has-text('Playwright')") 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 ```js 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]. -## 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 await page.click('text=Log in'); ``` - ```python async await page.click("text=Log in") ``` - ```python sync page.click("text=Log in") ``` -Matching is case-insensitive and searches for a substring. This means `text=Login` matches ``. 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 ``, but not `` or ``. Quoted text follows the usual escaping -rules, e.g. use `\"` to escape double quote in a double-quoted string: `text="foo\"bar"`. Note that quoted match still normalizes whitespace. +- `text=Log in` - default matching is case-insensitive and searches for a substring. For example `text=Log` matches ``. -Text body can also be a JavaScript-like regex wrapped in `/` symbols. This means `text=/^\\s*Login$/i` -will match `` with any number of spaces before "Login" and no spaces after. + ```js + 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 -engine finds them. For example, `text=Login` matches ``. +- `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 `` but instead matches `Log`. -Selector string starting and ending with a quote (either `"` or `'`) is assumed to be a text selector. -For example, Playwright converts `'"Login"'` to `'text="Login"'` internally. + Quoted body follows the usual escaping rules, e.g. use `\"` to escape double quote in a double-quoted string: `text="foo\"bar"`. -## 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 `` and ``. + + ```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 `
Playwright
`. + + Note that `:has-text()` should be used together with other `css` specifiers, otherwise it will match all the elements containing specified text, including the ``. + ```js + // Wrong, will match many elements including + await page.click(':has-text("Playwright")'); + // Correct, only matches the
element + await page.click('article:has-text("Playwright")'); + ``` + ```python async + # Wrong, will match many elements including + await page.click(':has-text("Playwright")') + # Correct, only matches the
element + await page.click('article:has-text("Playwright")') + ``` + ```python sync + # Wrong, will match many elements including + page.click(':has-text("Playwright")') + # Correct, only matches the
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 ``. +::: + +## CSS selector Playwright augments standard CSS selectors in two ways: * `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 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"))') ``` -## 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 - - - - -``` - -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 `` and ``, 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 -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 -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 -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 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 -- Then it searches recursively inside open shadow roots in the iteration order. +- First they search for the elements in the light DOM in the iteration order, and +- 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 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. @@ -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') ``` -[text]: #basic-text-selectors -[css]: #basic-css-selectors +[text]: #text-selector +[css]: #css-selector [xpath]: #xpath-selectors [id]: #id-data-testid-data-test-id-data-test-selectors