diff --git a/docs/src/api/params.md b/docs/src/api/params.md index b934e5a932..7066b53dd6 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1074,7 +1074,7 @@ Text to locate the element for. * since: v1.27 - `exact` <[boolean]> -Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular expression. +Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular expression. Note that exact match still trims whitespace. ## locator-get-by-role-role * since: v1.27 @@ -1185,7 +1185,109 @@ use: { ## template-locator-get-by-text -Allows locating elements that contain given text. +Allows locating elements that contain given text. Consider the following DOM structure: + +```html +
Hello world
+
Hello
+``` + +You can locate by text substring, exact string, or a regular expression: + +```js +// Matches +page.getByText('world') + +// Matches first
+page.getByText('Hello world') + +// Matches second
+page.getByText('Hello', { exact: true }) + +// Matches both
s +page.getByText(/Hello/) + +// Matches second
+page.getByText(/^hello$/i) +``` + +```python async +# Matches +page.get_by_text("world") + +# Matches first
+page.get_by_text("Hello world") + +# Matches second
+page.get_by_text("Hello", exact=True) + +# Matches both
s +page.get_by_text(re.compile("Hello")) + +# Matches second
+page.get_by_text(re.compile("^hello$", re.IGNORECASE)) +``` + +```python sync +# Matches +page.get_by_text("world") + +# Matches first
+page.get_by_text("Hello world") + +# Matches second
+page.get_by_text("Hello", exact=True) + +# Matches both
s +page.get_by_text(re.compile("Hello")) + +# Matches second
+page.get_by_text(re.compile("^hello$", re.IGNORECASE)) +``` + +```java +// Matches +page.getByText("world") + +// Matches first
+page.getByText("Hello world") + +// Matches second
+page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) + +// Matches both
s +page.getByText(Pattern.compile("Hello")) + +// Matches second
+page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) +``` + +```csharp +// Matches +page.GetByText("world") + +// Matches first
+page.GetByText("Hello world") + +// Matches second
+page.GetByText("Hello", new() { Exact: true }) + +// Matches both
s +page.GetByText(new Regex("Hello")) + +// Matches second
+page.GetByText(new Regex("^hello$", RegexOptions.IgnoreCase)) +``` + +See also [`method: Locator.filter`] that allows to match by another criteria, like an accessible role, and then filter by the text content. + +:::note +Matching by text always normalizes whitespace, even with exact match. 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 the text content. For example, locating by text `"Log in"` matches ``. +::: ## template-locator-get-by-alt-text diff --git a/docs/src/locators.md b/docs/src/locators.md index 6a15cbbac0..48e13c93db 100644 --- a/docs/src/locators.md +++ b/docs/src/locators.md @@ -217,6 +217,10 @@ page.get_by_test_id("product-item").filter(has_text="Playwright Book").click() await page.GetByTestId("product-item").Filter(new() { HasText = "Playwright Book" }).ClickAsync(); ``` +:::note +Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace. +::: + ### Locate based on accessible attributes with [`method: Page.getByRole`] The [`method: Page.getByRole`] locator reflects how users and assistive technology percieve the page, for example whether some element is a button or a checkbox. When locating by role, you should usually pass the accessible name as well, so that locator pinpoints the exact element. diff --git a/docs/src/selectors.md b/docs/src/selectors.md index 6615f6e5e7..3a5a96e81e 100644 --- a/docs/src/selectors.md +++ b/docs/src/selectors.md @@ -236,7 +236,7 @@ await page.Locator("text=Log in").ClickAsync(); Text selector has a few variations: -- `text=Log in` - default matching is case-insensitive and searches for a substring. For example, `text=Log` matches ``. +- `text=Log in` - default matching is case-insensitive, trims whitespace and searches for a substring. For example, `text=Log` matches ``. ```js await page.locator('text=Log in').click(); @@ -254,7 +254,7 @@ Text selector has a few variations: await page.Locator("text=Log in").ClickAsync(); ``` -- `text="Log in"` - text body can be escaped with single or double quotes to search for a text node with exact content. For example, `text="Log"` does not match `` because ``, because ``. +- `text="Log in"` - text body can be escaped with single or double quotes to search for a text node with exact content after trimming whitespace. For example, `text="Log"` does not match `` because ``, because ``. Quoted body follows the usual escaping rules, e.g. use `\"` to escape double quote in a double-quoted string: `text="foo\"bar"`. @@ -310,7 +310,7 @@ Text selector has a few variations: await page.Locator("text=/Log\\s*in/i").ClickAsync(); ``` -- `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. Matching is case-insensitive and searches for a substring. For example, `article:has-text("Playwright")` matches `
Playwright
`. +- `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. Matching is case-insensitive, trims whitestapce and searches for a substring. 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 diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index bfd3918c52..17f32c8b7a 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -2498,7 +2498,7 @@ export interface Page { getByAltText(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -2518,7 +2518,7 @@ export interface Page { getByLabel(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -2537,7 +2537,7 @@ export interface Page { getByPlaceholder(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -2634,14 +2634,46 @@ export interface Page { getByTestId(testId: string): Locator; /** - * Allows locating elements that contain given text. + * Allows locating elements that contain given text. Consider the following DOM structure: + * + * ```html + *
Hello world
+ *
Hello
+ * ``` + * + * You can locate by text substring, exact string, or a regular expression: + * + * ```js + * // Matches + * page.getByText('world') + * + * // Matches first
+ * page.getByText('Hello world') + * + * // Matches second
+ * page.getByText('Hello', { exact: true }) + * + * // Matches both
s + * page.getByText(/Hello/) + * + * // Matches second
+ * page.getByText(/^hello$/i) + * ``` + * + * See also [locator.filter([options])](https://playwright.dev/docs/api/class-locator#locator-filter) that allows to match + * by another criteria, like an accessible role, and then filter by the text content. + * + * > NOTE: Matching by text always normalizes whitespace, even with exact match. 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 the text content. For + * example, locating by text `"Log in"` matches ``. * @param text Text to locate the element for. * @param options */ getByText(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -2659,7 +2691,7 @@ export interface Page { getByTitle(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -5645,7 +5677,7 @@ export interface Frame { getByAltText(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -5665,7 +5697,7 @@ export interface Frame { getByLabel(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -5684,7 +5716,7 @@ export interface Frame { getByPlaceholder(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -5781,14 +5813,46 @@ export interface Frame { getByTestId(testId: string): Locator; /** - * Allows locating elements that contain given text. + * Allows locating elements that contain given text. Consider the following DOM structure: + * + * ```html + *
Hello world
+ *
Hello
+ * ``` + * + * You can locate by text substring, exact string, or a regular expression: + * + * ```js + * // Matches + * page.getByText('world') + * + * // Matches first
+ * page.getByText('Hello world') + * + * // Matches second
+ * page.getByText('Hello', { exact: true }) + * + * // Matches both
s + * page.getByText(/Hello/) + * + * // Matches second
+ * page.getByText(/^hello$/i) + * ``` + * + * See also [locator.filter([options])](https://playwright.dev/docs/api/class-locator#locator-filter) that allows to match + * by another criteria, like an accessible role, and then filter by the text content. + * + * > NOTE: Matching by text always normalizes whitespace, even with exact match. 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 the text content. For + * example, locating by text `"Log in"` matches ``. * @param text Text to locate the element for. * @param options */ getByText(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -5806,7 +5870,7 @@ export interface Frame { getByTitle(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -10175,7 +10239,7 @@ export interface Locator { getByAltText(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -10195,7 +10259,7 @@ export interface Locator { getByLabel(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -10214,7 +10278,7 @@ export interface Locator { getByPlaceholder(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -10311,14 +10375,46 @@ export interface Locator { getByTestId(testId: string): Locator; /** - * Allows locating elements that contain given text. + * Allows locating elements that contain given text. Consider the following DOM structure: + * + * ```html + *
Hello world
+ *
Hello
+ * ``` + * + * You can locate by text substring, exact string, or a regular expression: + * + * ```js + * // Matches + * page.getByText('world') + * + * // Matches first
+ * page.getByText('Hello world') + * + * // Matches second
+ * page.getByText('Hello', { exact: true }) + * + * // Matches both
s + * page.getByText(/Hello/) + * + * // Matches second
+ * page.getByText(/^hello$/i) + * ``` + * + * See also [locator.filter([options])](https://playwright.dev/docs/api/class-locator#locator-filter) that allows to match + * by another criteria, like an accessible role, and then filter by the text content. + * + * > NOTE: Matching by text always normalizes whitespace, even with exact match. 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 the text content. For + * example, locating by text `"Log in"` matches ``. * @param text Text to locate the element for. * @param options */ getByText(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -10336,7 +10432,7 @@ export interface Locator { getByTitle(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -15604,7 +15700,7 @@ export interface FrameLocator { getByAltText(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -15624,7 +15720,7 @@ export interface FrameLocator { getByLabel(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -15643,7 +15739,7 @@ export interface FrameLocator { getByPlaceholder(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -15740,14 +15836,46 @@ export interface FrameLocator { getByTestId(testId: string): Locator; /** - * Allows locating elements that contain given text. + * Allows locating elements that contain given text. Consider the following DOM structure: + * + * ```html + *
Hello world
+ *
Hello
+ * ``` + * + * You can locate by text substring, exact string, or a regular expression: + * + * ```js + * // Matches + * page.getByText('world') + * + * // Matches first
+ * page.getByText('Hello world') + * + * // Matches second
+ * page.getByText('Hello', { exact: true }) + * + * // Matches both
s + * page.getByText(/Hello/) + * + * // Matches second
+ * page.getByText(/^hello$/i) + * ``` + * + * See also [locator.filter([options])](https://playwright.dev/docs/api/class-locator#locator-filter) that allows to match + * by another criteria, like an accessible role, and then filter by the text content. + * + * > NOTE: Matching by text always normalizes whitespace, even with exact match. 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 the text content. For + * example, locating by text `"Log in"` matches ``. * @param text Text to locate the element for. * @param options */ getByText(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator; @@ -15765,7 +15893,7 @@ export interface FrameLocator { getByTitle(text: string|RegExp, options?: { /** * Whether to find an exact match: case-sensitive and whole-string. Default to false. Ignored when locating by a regular - * expression. + * expression. Note that exact match still trims whitespace. */ exact?: boolean; }): Locator;