diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md
index ea1c0065da..7244592f6c 100644
--- a/docs/src/api/class-frame.md
+++ b/docs/src/api/class-frame.md
@@ -920,6 +920,16 @@ Attribute name to get the value for.
* since: v1.8
+## method: Frame.getByLabelText
+* since: v1.27
+- returns: <[Locator]>
+
+%%-template-locator-get-by-label-text-%%
+
+### param: Frame.getByLabelText.text = %%-locator-get-by-text-text-%%
+### option: Frame.getByLabelText.exact = %%-locator-get-by-text-exact-%%
+
+
## method: Frame.getByRole
* since: v1.27
- returns: <[Locator]>
diff --git a/docs/src/api/class-framelocator.md b/docs/src/api/class-framelocator.md
index d5d2f6dc3c..0cc0de2a0e 100644
--- a/docs/src/api/class-framelocator.md
+++ b/docs/src/api/class-framelocator.md
@@ -126,6 +126,16 @@ in that iframe.
* since: v1.27
+## method: FrameLocator.getByLabelText
+* since: v1.27
+- returns: <[Locator]>
+
+%%-template-locator-get-by-label-text-%%
+
+### param: FrameLocator.getByLabelText.text = %%-locator-get-by-text-text-%%
+### option: FrameLocator.getByLabelText.exact = %%-locator-get-by-text-exact-%%
+
+
## method: FrameLocator.getByRole
* since: v1.27
- returns: <[Locator]>
diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md
index b09da96468..0f15c9fbc9 100644
--- a/docs/src/api/class-locator.md
+++ b/docs/src/api/class-locator.md
@@ -646,6 +646,16 @@ Attribute name to get the value for.
* since: v1.14
+## method: Locator.getByLabelText
+* since: v1.27
+- returns: <[Locator]>
+
+%%-template-locator-get-by-label-text-%%
+
+### param: Locator.getByLabelText.text = %%-locator-get-by-text-text-%%
+### option: Locator.getByLabelText.exact = %%-locator-get-by-text-exact-%%
+
+
## method: Locator.getByRole
* since: v1.27
- returns: <[Locator]>
diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md
index 33f32587d0..fa08796f06 100644
--- a/docs/src/api/class-page.md
+++ b/docs/src/api/class-page.md
@@ -2195,6 +2195,16 @@ Attribute name to get the value for.
* since: v1.8
+## method: Page.getByLabelText
+* since: v1.27
+- returns: <[Locator]>
+
+%%-template-locator-get-by-label-text-%%
+
+### param: Page.getByLabelText.text = %%-locator-get-by-text-text-%%
+### option: Page.getByLabelText.exact = %%-locator-get-by-text-exact-%%
+
+
## method: Page.getByRole
* since: v1.27
- returns: <[Locator]>
diff --git a/docs/src/api/params.md b/docs/src/api/params.md
index e4ec9849aa..29d2087e71 100644
--- a/docs/src/api/params.md
+++ b/docs/src/api/params.md
@@ -1180,6 +1180,15 @@ Locate element by the test id. By default, the `data-testid` attribute is used a
Allows locating elements that contain given text.
+## template-locator-get-by-label-text
+
+Allows locating input elements by the text of the associated label. For example, this method will find the input by label text Password in the following DOM:
+
+```html
+
+
+```
+
## template-locator-get-by-role
Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). Note that role selector **does not replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
diff --git a/docs/src/testing-library-js.md b/docs/src/testing-library-js.md
index 33fc2f01a8..20c75b2a05 100644
--- a/docs/src/testing-library-js.md
+++ b/docs/src/testing-library-js.md
@@ -21,14 +21,14 @@ If you use DOM Testing Library in the browser (for example, you bundle end-to-en
| [queries](https://testing-library.com/docs/queries/about) | [locators](./locators) |
| [async helpers](https://testing-library.com/docs/dom-testing-library/api-async) | [assertions](./test-assertions) |
| [user events](https://testing-library.com/docs/user-event/intro) | [actions](./api/class-locator) |
-| `await user.click(screen.getByText('Click me'))` | `await component.locator('text=Click me').click()` |
-| `await user.click(await screen.findByText('Click me'))` | `await component.locator('text=Click me').click()` |
-| `await user.type(screen.getByLabelText('Password'), 'secret')` | `await component.locator('text=Password').fill('secret')` |
-| `expect(screen.getByLabelText('Password')).toHaveValue('secret')` | `await expect(component.locator('text=Password')).toHaveValue('secret')` |
-| `screen.findByText('...')` | `component.locator('text=...')` |
-| `screen.getByTestId('...')` | `component.locator('data-testid=...')` |
-| `screen.queryByPlaceholderText('...')` | `component.locator('[placeholder="..."]')` |
-| `screen.getAllByRole('button', { pressed: true })` | `component.locator('role=button[pressed]')` |
+| `await user.click(screen.getByText('Click me'))` | `await component.getByText('Click me').click()` |
+| `await user.click(await screen.findByText('Click me'))` | `await component.getByText('Click me').click()` |
+| `await user.type(screen.getByLabelText('Password'), 'secret')` | `await component.getByLabelText('Password').fill('secret')` |
+| `expect(screen.getByLabelText('Password')).toHaveValue('secret')` | `await expect(component.getByLabelText('Password')).toHaveValue('secret')` |
+| `screen.findByText('...')` | `component.getByText('...')` |
+| `screen.getByTestId('...')` | `component.getByTestId('...')` |
+| `screen.queryByPlaceholderText('...')` | `component.get('[placeholder="..."]')` |
+| `screen.getByRole('button', { pressed: true })` | `component.getByRole('button', { pressed: true })`|
## Example
diff --git a/packages/playwright-core/src/client/frame.ts b/packages/playwright-core/src/client/frame.ts
index f39d290a41..b06910f478 100644
--- a/packages/playwright-core/src/client/frame.ts
+++ b/packages/playwright-core/src/client/frame.ts
@@ -307,6 +307,10 @@ export class Frame extends ChannelOwner implements api.Fr
return this.locator(Locator.getByTestIdSelector(testId));
}
+ getByLabelText(text: string | RegExp, options?: { exact?: boolean }): Locator {
+ return this.locator(Locator.getByLabelTextSelector(text, options));
+ }
+
getByText(text: string | RegExp, options?: { exact?: boolean }): Locator {
return this.locator(Locator.getByTextSelector(text, options));
}
diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts
index e51804a17d..e50f0af6e4 100644
--- a/packages/playwright-core/src/client/locator.ts
+++ b/packages/playwright-core/src/client/locator.ts
@@ -55,6 +55,14 @@ export class Locator implements api.Locator {
return `css=[${Locator._testIdAttributeName}=${JSON.stringify(testId)}]`;
}
+ static getByLabelTextSelector(text: string | RegExp, options?: { exact?: boolean }): string {
+ if (!isString(text))
+ return `text=${text}`;
+ const escaped = JSON.stringify(text);
+ const selector = options?.exact ? `text=${escaped}` : `text=${escaped.substring(1, escaped.length - 1)}`;
+ return selector + ' >> control=resolve-label';
+ }
+
static getByTextSelector(text: string | RegExp, options?: { exact?: boolean }): string {
if (!isString(text))
return `text=${text}`;
@@ -189,6 +197,10 @@ export class Locator implements api.Locator {
return this.locator(Locator.getByTestIdSelector(testId));
}
+ getByLabelText(text: string | RegExp, options?: { exact?: boolean }): Locator {
+ return this.locator(Locator.getByLabelTextSelector(text, options));
+ }
+
getByText(text: string | RegExp, options?: { exact?: boolean }): Locator {
return this.locator(Locator.getByTextSelector(text, options));
}
@@ -379,6 +391,9 @@ export class FrameLocator implements api.FrameLocator {
return this.locator(Locator.getByTestIdSelector(testId));
}
+ getByLabelText(text: string | RegExp, options?: { exact?: boolean }): Locator {
+ return this.locator(Locator.getByLabelTextSelector(text, options));
+ }
getByText(text: string | RegExp, options?: { exact?: boolean }): Locator {
return this.locator(Locator.getByTextSelector(text, options));
}
diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts
index 9fd0d8132f..ac27a28a50 100644
--- a/packages/playwright-core/src/client/page.ts
+++ b/packages/playwright-core/src/client/page.ts
@@ -572,6 +572,10 @@ export class Page extends ChannelOwner implements api.Page
return this.mainFrame().getByTestId(testId);
}
+ getByLabelText(text: string | RegExp, options?: { exact?: boolean }): Locator {
+ return this.mainFrame().getByLabelText(text, options);
+ }
+
getByText(text: string | RegExp, options?: { exact?: boolean }): Locator {
return this.mainFrame().getByText(text, options);
}
diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts
index dcab9afe04..1aae3c635c 100644
--- a/packages/playwright-core/src/server/injected/injectedScript.ts
+++ b/packages/playwright-core/src/server/injected/injectedScript.ts
@@ -278,6 +278,10 @@ export class InjectedScript {
return [];
if (body === 'return-empty')
return [];
+ if (body === 'resolve-label') {
+ const control = (root as HTMLLabelElement).control;
+ return control ? [control] : [];
+ }
if (body === 'component') {
if (root.nodeType !== 1 /* Node.ELEMENT_NODE */)
return [];
diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts
index 2ed03b59dc..b8b6892791 100644
--- a/packages/playwright-core/types/types.d.ts
+++ b/packages/playwright-core/types/types.d.ts
@@ -2482,6 +2482,25 @@ export interface Page {
timeout?: number;
}): Promise;
+ /**
+ * Allows locating input elements by the text of the associated label. For example, this method will find the input by
+ * label text Password in the following DOM:
+ *
+ * ```html
+ *
+ *
+ * ```
+ *
+ * @param text Text to locate the element for.
+ * @param options
+ */
+ getByLabelText(text: string|RegExp, options?: {
+ /**
+ * Whether to find an exact match: case-sensitive and whole-string. Default to false.
+ */
+ exact?: boolean;
+ }): Locator;
+
/**
* Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
* [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and
@@ -5523,6 +5542,25 @@ export interface Frame {
timeout?: number;
}): Promise;
+ /**
+ * Allows locating input elements by the text of the associated label. For example, this method will find the input by
+ * label text Password in the following DOM:
+ *
+ * ```html
+ *
+ *
+ * ```
+ *
+ * @param text Text to locate the element for.
+ * @param options
+ */
+ getByLabelText(text: string|RegExp, options?: {
+ /**
+ * Whether to find an exact match: case-sensitive and whole-string. Default to false.
+ */
+ exact?: boolean;
+ }): Locator;
+
/**
* Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
* [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and
@@ -9911,6 +9949,25 @@ export interface Locator {
timeout?: number;
}): Promise;
+ /**
+ * Allows locating input elements by the text of the associated label. For example, this method will find the input by
+ * label text Password in the following DOM:
+ *
+ * ```html
+ *
+ *
+ * ```
+ *
+ * @param text Text to locate the element for.
+ * @param options
+ */
+ getByLabelText(text: string|RegExp, options?: {
+ /**
+ * Whether to find an exact match: case-sensitive and whole-string. Default to false.
+ */
+ exact?: boolean;
+ }): Locator;
+
/**
* Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
* [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and
@@ -15120,6 +15177,25 @@ export interface FrameLocator {
hasText?: string|RegExp;
}): Locator;
+ /**
+ * Allows locating input elements by the text of the associated label. For example, this method will find the input by
+ * label text Password in the following DOM:
+ *
+ * ```html
+ *
+ *
+ * ```
+ *
+ * @param text Text to locate the element for.
+ * @param options
+ */
+ getByLabelText(text: string|RegExp, options?: {
+ /**
+ * Whether to find an exact match: case-sensitive and whole-string. Default to false.
+ */
+ exact?: boolean;
+ }): Locator;
+
/**
* Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
* [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and
diff --git a/tests/page/locator-frame.spec.ts b/tests/page/locator-frame.spec.ts
index de028e25dc..e2cf76902b 100644
--- a/tests/page/locator-frame.spec.ts
+++ b/tests/page/locator-frame.spec.ts
@@ -35,6 +35,7 @@ async function routeIframe(page: Page) {
1
2
+