diff --git a/docs/src/api/class-framelocator.md b/docs/src/api/class-framelocator.md
index d3cef417f6..48829a775d 100644
--- a/docs/src/api/class-framelocator.md
+++ b/docs/src/api/class-framelocator.md
@@ -196,7 +196,7 @@ Returns locator to the last matching frame.
%%-template-locator-locator-%%
-### param: FrameLocator.locator.selector = %%-find-selector-%%
+### param: FrameLocator.locator.selectorOrLocator = %%-find-selector-or-locator-%%
* since: v1.17
### option: FrameLocator.locator.-inline- = %%-locator-options-list-v1.14-%%
diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md
index 176011a1b5..f2f2acc619 100644
--- a/docs/src/api/class-locator.md
+++ b/docs/src/api/class-locator.md
@@ -959,7 +959,7 @@ var locator = page.FrameLocator("iframe").GetByText("Submit");
await locator.ClickAsync();
```
-### param: Locator.frameLocator.selector = %%-find-selector-%%
+### param: Locator.frameLocator.selectorOrLocator = %%-find-selector-%%
* since: v1.17
## async method: Locator.getAttribute
@@ -1389,7 +1389,7 @@ var banana = await page.GetByRole(AriaRole.Listitem).Last(1);
%%-template-locator-locator-%%
-### param: Locator.locator.selector = %%-find-selector-%%
+### param: Locator.locator.selectorOrLocator = %%-find-selector-or-locator-%%
* since: v1.14
### option: Locator.locator.-inline- = %%-locator-options-list-v1.14-%%
diff --git a/docs/src/api/params.md b/docs/src/api/params.md
index 48571a4b62..702a6cbed9 100644
--- a/docs/src/api/params.md
+++ b/docs/src/api/params.md
@@ -134,6 +134,11 @@ A selector to query for.
A selector to use when resolving DOM element.
+## find-selector-or-locator
+- `selectorOrLocator` <[string]|[Locator]>
+
+A selector or locator to use when resolving DOM element.
+
## wait-for-selector-state
- `state` <[WaitForSelectorState]<"attached"|"detached"|"visible"|"hidden">>
diff --git a/docs/src/locators.md b/docs/src/locators.md
index 40a36d2ae9..6fa7aa7ec7 100644
--- a/docs/src/locators.md
+++ b/docs/src/locators.md
@@ -1044,6 +1044,44 @@ await product
.ClickAsync();
```
+You can also chain two locators together, for example to find a "Save" button inside a particular dialog:
+
+```js
+const saveButton = page.getByRole('button', { name: 'Save' });
+// ...
+const dialog = page.getByTestId('settings-dialog');
+await dialog.locator(saveButton).click();
+```
+
+```python async
+save_button = page.get_by_role("button", name="Save")
+# ...
+dialog = page.get_by_test_id("settings-dialog")
+await dialog.locator(save_button).click()
+```
+
+```python sync
+save_button = page.get_by_role("button", name="Save")
+# ...
+dialog = page.get_by_test_id("settings-dialog")
+dialog.locator(save_button).click()
+```
+
+```java
+Locator saveButton = page.getByRole(AriaRole.BUTTON,
+ new Page.GetByRoleOptions().setName("Save"));
+// ...
+Locator dialog = page.getByTestId("settings-dialog");
+dialog.locator(saveButton).click();
+```
+
+```csharp
+var saveButton = page.GetByRole(AriaRole.Button, new() { Name = "Save" });
+// ...
+var dialog = page.GetByTestId("settings-dialog");
+await dialog.Locator(saveButton).ClickAsync();
+```
+
## Lists
### Count items in a list
diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts
index 295fed4ed0..1c6530d28c 100644
--- a/packages/playwright-core/src/client/locator.ts
+++ b/packages/playwright-core/src/client/locator.ts
@@ -18,7 +18,7 @@ import type * as structs from '../../types/structs';
import type * as api from '../../types/types';
import type * as channels from '@protocol/channels';
import * as util from 'util';
-import { monotonicTime } from '../utils';
+import { isString, monotonicTime } from '../utils';
import { ElementHandle } from './elementHandle';
import type { Frame } from './frame';
import type { FilePayload, FrameExpectOptions, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types';
@@ -128,8 +128,12 @@ export class Locator implements api.Locator {
return this._frame._highlight(this._selector);
}
- locator(selector: string, options?: LocatorOptions): Locator {
- return new Locator(this._frame, this._selector + ' >> ' + selector, options);
+ locator(selectorOrLocator: string | Locator, options?: LocatorOptions): Locator {
+ if (isString(selectorOrLocator))
+ return new Locator(this._frame, this._selector + ' >> ' + selectorOrLocator, options);
+ if (selectorOrLocator._frame !== this._frame)
+ throw new Error(`Locators must belong to the same frame.`);
+ return new Locator(this._frame, this._selector + ' >> ' + selectorOrLocator._selector, options);
}
getByTestId(testId: string | RegExp): Locator {
@@ -336,8 +340,12 @@ export class FrameLocator implements api.FrameLocator {
this._frameSelector = selector;
}
- locator(selector: string, options?: { hasText?: string | RegExp }): Locator {
- return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selector, options);
+ locator(selectorOrLocator: string | Locator, options?: LocatorOptions): Locator {
+ if (isString(selectorOrLocator))
+ return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selectorOrLocator, options);
+ if (selectorOrLocator._frame !== this._frame)
+ throw new Error(`Locators must belong to the same frame.`);
+ return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selectorOrLocator._selector, options);
}
getByTestId(testId: string | RegExp): Locator {
diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts
index 7cd013cdf7..906225bd7b 100644
--- a/packages/playwright-core/types/types.d.ts
+++ b/packages/playwright-core/types/types.d.ts
@@ -11392,10 +11392,10 @@ export interface Locator {
* method.
*
* [Learn more about locators](https://playwright.dev/docs/locators).
- * @param selector A selector to use when resolving DOM element.
+ * @param selectorOrLocator A selector or locator to use when resolving DOM element.
* @param options
*/
- locator(selector: string, options?: {
+ locator(selectorOrLocator: string|Locator, options?: {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer
* one. For example, `article` that has `text=Playwright` matches `