+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 `
Log in `.
+- `text=Log in` - default matching is case-insensitive, trims whitespace and searches for a substring. For example, `text=Log` matches `
Log in `.
```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 `
Log in ` because `
` contains a single text node `"Log in"` that is not equal to `"Log"`. However, `text="Log"` matches `Login `, because `` contains a text node `"Log"`. This exact mode implies case-sensitive matching, so `text="Download"` will not match `download `.
+- `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 `Log in ` because `` contains a single text node `"Log in"` that is not equal to `"Log"`. However, `text="Log"` matches ` Log in `, because `` contains a text node `" Log "`. This exact mode implies case-sensitive matching, so `text="Download"` will not match `download `.
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;