feat: allow chaining locators with Locator.locator(anotherLocator) (#21391)
This commit is contained in:
parent
d904a6129f
commit
0c5d46bb94
|
|
@ -196,7 +196,7 @@ Returns locator to the last matching frame.
|
||||||
|
|
||||||
%%-template-locator-locator-%%
|
%%-template-locator-locator-%%
|
||||||
|
|
||||||
### param: FrameLocator.locator.selector = %%-find-selector-%%
|
### param: FrameLocator.locator.selectorOrLocator = %%-find-selector-or-locator-%%
|
||||||
* since: v1.17
|
* since: v1.17
|
||||||
|
|
||||||
### option: FrameLocator.locator.-inline- = %%-locator-options-list-v1.14-%%
|
### option: FrameLocator.locator.-inline- = %%-locator-options-list-v1.14-%%
|
||||||
|
|
|
||||||
|
|
@ -959,7 +959,7 @@ var locator = page.FrameLocator("iframe").GetByText("Submit");
|
||||||
await locator.ClickAsync();
|
await locator.ClickAsync();
|
||||||
```
|
```
|
||||||
|
|
||||||
### param: Locator.frameLocator.selector = %%-find-selector-%%
|
### param: Locator.frameLocator.selectorOrLocator = %%-find-selector-%%
|
||||||
* since: v1.17
|
* since: v1.17
|
||||||
|
|
||||||
## async method: Locator.getAttribute
|
## async method: Locator.getAttribute
|
||||||
|
|
@ -1389,7 +1389,7 @@ var banana = await page.GetByRole(AriaRole.Listitem).Last(1);
|
||||||
|
|
||||||
%%-template-locator-locator-%%
|
%%-template-locator-locator-%%
|
||||||
|
|
||||||
### param: Locator.locator.selector = %%-find-selector-%%
|
### param: Locator.locator.selectorOrLocator = %%-find-selector-or-locator-%%
|
||||||
* since: v1.14
|
* since: v1.14
|
||||||
|
|
||||||
### option: Locator.locator.-inline- = %%-locator-options-list-v1.14-%%
|
### option: Locator.locator.-inline- = %%-locator-options-list-v1.14-%%
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,11 @@ A selector to query for.
|
||||||
|
|
||||||
A selector to use when resolving DOM element.
|
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
|
## wait-for-selector-state
|
||||||
- `state` <[WaitForSelectorState]<"attached"|"detached"|"visible"|"hidden">>
|
- `state` <[WaitForSelectorState]<"attached"|"detached"|"visible"|"hidden">>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1044,6 +1044,44 @@ await product
|
||||||
.ClickAsync();
|
.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
|
## Lists
|
||||||
|
|
||||||
### Count items in a list
|
### Count items in a list
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import type * as structs from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import { monotonicTime } from '../utils';
|
import { isString, monotonicTime } from '../utils';
|
||||||
import { ElementHandle } from './elementHandle';
|
import { ElementHandle } from './elementHandle';
|
||||||
import type { Frame } from './frame';
|
import type { Frame } from './frame';
|
||||||
import type { FilePayload, FrameExpectOptions, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types';
|
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);
|
return this._frame._highlight(this._selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
locator(selector: string, options?: LocatorOptions): Locator {
|
locator(selectorOrLocator: string | Locator, options?: LocatorOptions): Locator {
|
||||||
return new Locator(this._frame, this._selector + ' >> ' + selector, options);
|
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 {
|
getByTestId(testId: string | RegExp): Locator {
|
||||||
|
|
@ -336,8 +340,12 @@ export class FrameLocator implements api.FrameLocator {
|
||||||
this._frameSelector = selector;
|
this._frameSelector = selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
locator(selector: string, options?: { hasText?: string | RegExp }): Locator {
|
locator(selectorOrLocator: string | Locator, options?: LocatorOptions): Locator {
|
||||||
return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selector, options);
|
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 {
|
getByTestId(testId: string | RegExp): Locator {
|
||||||
|
|
|
||||||
8
packages/playwright-core/types/types.d.ts
vendored
8
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -11392,10 +11392,10 @@ export interface Locator {
|
||||||
* method.
|
* method.
|
||||||
*
|
*
|
||||||
* [Learn more about locators](https://playwright.dev/docs/locators).
|
* [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
|
* @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
|
* 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 `<article><div>Playwright</div></article>`.
|
* one. For example, `article` that has `text=Playwright` matches `<article><div>Playwright</div></article>`.
|
||||||
|
|
@ -16969,10 +16969,10 @@ export interface FrameLocator {
|
||||||
* method.
|
* method.
|
||||||
*
|
*
|
||||||
* [Learn more about locators](https://playwright.dev/docs/locators).
|
* [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
|
* @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
|
* 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 `<article><div>Playwright</div></article>`.
|
* one. For example, `article` that has `text=Playwright` matches `<article><div>Playwright</div></article>`.
|
||||||
|
|
|
||||||
|
|
@ -154,3 +154,20 @@ it('locator.count should work with deleted Map in main world', async ({ page })
|
||||||
await expect(page.locator('#searchResultTableDiv .x-grid3-row')).toHaveCount(0);
|
await expect(page.locator('#searchResultTableDiv .x-grid3-row')).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Locator.locator() and FrameLocator.locator() should accept locator', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div><input value=outer></div>
|
||||||
|
<iframe srcdoc="<div><input value=inner></div>"></iframe>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const inputLocator = page.locator('input');
|
||||||
|
expect(await inputLocator.inputValue()).toBe('outer');
|
||||||
|
expect(await page.locator('div').locator(inputLocator).inputValue()).toBe('outer');
|
||||||
|
expect(await page.frameLocator('iframe').locator(inputLocator).inputValue()).toBe('inner');
|
||||||
|
expect(await page.frameLocator('iframe').locator('div').locator(inputLocator).inputValue()).toBe('inner');
|
||||||
|
|
||||||
|
const divLocator = page.locator('div');
|
||||||
|
expect(await divLocator.locator('input').inputValue()).toBe('outer');
|
||||||
|
expect(await page.frameLocator('iframe').locator(divLocator).locator('input').inputValue()).toBe('inner');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue