feat: locator.visible (#34905)
This commit is contained in:
parent
9b633ddd2f
commit
ed0bf35435
|
@ -2478,6 +2478,18 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Locator.uncheck.trial = %%-input-trial-%%
|
||||
* since: v1.14
|
||||
|
||||
## method: Locator.visible
|
||||
* since: v1.51
|
||||
- returns: <[Locator]>
|
||||
|
||||
Returns a locator that only matches [visible](../actionability.md#visible) elements.
|
||||
|
||||
### option: Locator.visible.visible
|
||||
* since: v1.51
|
||||
- `visible` <[boolean]>
|
||||
|
||||
Whether to match visible or invisible elements.
|
||||
|
||||
## async method: Locator.waitFor
|
||||
* since: v1.16
|
||||
|
||||
|
|
|
@ -1310,19 +1310,19 @@ Consider a page with two buttons, the first invisible and the second [visible](.
|
|||
* This will only find a second button, because it is visible, and then click it.
|
||||
|
||||
```js
|
||||
await page.locator('button').locator('visible=true').click();
|
||||
await page.locator('button').visible().click();
|
||||
```
|
||||
```java
|
||||
page.locator("button").locator("visible=true").click();
|
||||
page.locator("button").visible().click();
|
||||
```
|
||||
```python async
|
||||
await page.locator("button").locator("visible=true").click()
|
||||
await page.locator("button").visible().click()
|
||||
```
|
||||
```python sync
|
||||
page.locator("button").locator("visible=true").click()
|
||||
page.locator("button").visible().click()
|
||||
```
|
||||
```csharp
|
||||
await page.Locator("button").Locator("visible=true").ClickAsync();
|
||||
await page.Locator("button").Visible().ClickAsync();
|
||||
```
|
||||
|
||||
## Lists
|
||||
|
|
|
@ -14615,6 +14615,17 @@ export interface Locator {
|
|||
trial?: boolean;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns a locator that only matches [visible](https://playwright.dev/docs/actionability#visible) elements.
|
||||
* @param options
|
||||
*/
|
||||
visible(options?: {
|
||||
/**
|
||||
* Whether to match visible or invisible elements.
|
||||
*/
|
||||
visible?: boolean;
|
||||
}): Locator;
|
||||
|
||||
/**
|
||||
* Returns when element specified by locator satisfies the
|
||||
* [`state`](https://playwright.dev/docs/api/class-locator#locator-wait-for-option-state) option.
|
||||
|
|
|
@ -218,6 +218,11 @@ export class Locator implements api.Locator {
|
|||
return new Locator(this._frame, this._selector + ` >> nth=${index}`);
|
||||
}
|
||||
|
||||
visible(options: { visible?: boolean } = {}): Locator {
|
||||
const { visible = true } = options;
|
||||
return new Locator(this._frame, this._selector + ` >> visible=${visible ? 'true' : 'false'}`);
|
||||
}
|
||||
|
||||
and(locator: Locator): Locator {
|
||||
if (locator._frame !== this._frame)
|
||||
throw new Error(`Locators must belong to the same frame.`);
|
||||
|
|
|
@ -21,7 +21,7 @@ import type { NestedSelectorBody } from './selectorParser';
|
|||
import type { ParsedSelector } from './selectorParser';
|
||||
|
||||
export type Language = 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl';
|
||||
export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'has-text' | 'has-not-text' | 'has' | 'hasNot' | 'frame' | 'frame-locator' | 'and' | 'or' | 'chain';
|
||||
export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'visible' | 'has-text' | 'has-not-text' | 'has' | 'hasNot' | 'frame' | 'frame-locator' | 'and' | 'or' | 'chain';
|
||||
export type LocatorBase = 'page' | 'locator' | 'frame-locator';
|
||||
export type Quote = '\'' | '"' | '`';
|
||||
|
||||
|
@ -68,6 +68,10 @@ function innerAsLocators(factory: LocatorFactory, parsed: ParsedSelector, isFram
|
|||
tokens.push([factory.generateLocator(base, 'nth', part.body as string)]);
|
||||
continue;
|
||||
}
|
||||
if (part.name === 'visible') {
|
||||
tokens.push([factory.generateLocator(base, 'visible', part.body as string), factory.generateLocator(base, 'default', `visible=${part.body}`)]);
|
||||
continue;
|
||||
}
|
||||
if (part.name === 'internal:text') {
|
||||
const { exact, text } = detectExact(part.body as string);
|
||||
tokens.push([factory.generateLocator(base, 'text', text, { exact })]);
|
||||
|
@ -275,6 +279,8 @@ export class JavaScriptLocatorFactory implements LocatorFactory {
|
|||
return `first()`;
|
||||
case 'last':
|
||||
return `last()`;
|
||||
case 'visible':
|
||||
return `visible(${body === 'true' ? '' : '{ visible: false }'})`;
|
||||
case 'role':
|
||||
const attrs: string[] = [];
|
||||
if (isRegExp(options.name)) {
|
||||
|
@ -369,6 +375,8 @@ export class PythonLocatorFactory implements LocatorFactory {
|
|||
return `first`;
|
||||
case 'last':
|
||||
return `last`;
|
||||
case 'visible':
|
||||
return `visible(${body === 'true' ? '' : 'visible=False'})`;
|
||||
case 'role':
|
||||
const attrs: string[] = [];
|
||||
if (isRegExp(options.name)) {
|
||||
|
@ -476,6 +484,8 @@ export class JavaLocatorFactory implements LocatorFactory {
|
|||
return `first()`;
|
||||
case 'last':
|
||||
return `last()`;
|
||||
case 'visible':
|
||||
return `visible(${body === 'true' ? '' : `new ${clazz}.VisibleOptions().setVisible(false)`})`;
|
||||
case 'role':
|
||||
const attrs: string[] = [];
|
||||
if (isRegExp(options.name)) {
|
||||
|
@ -573,6 +583,8 @@ export class CSharpLocatorFactory implements LocatorFactory {
|
|||
return `First`;
|
||||
case 'last':
|
||||
return `Last`;
|
||||
case 'visible':
|
||||
return `Visible(${body === 'true' ? '' : 'new() { Visible = false }'})`;
|
||||
case 'role':
|
||||
const attrs: string[] = [];
|
||||
if (isRegExp(options.name)) {
|
||||
|
|
|
@ -170,6 +170,9 @@ function transform(template: string, params: TemplateParams, testIdAttributeName
|
|||
.replace(/first(\(\))?/g, 'nth=0')
|
||||
.replace(/last(\(\))?/g, 'nth=-1')
|
||||
.replace(/nth\(([^)]+)\)/g, 'nth=$1')
|
||||
.replace(/visible\(,?visible=true\)/g, 'visible=true')
|
||||
.replace(/visible\(,?visible=false\)/g, 'visible=false')
|
||||
.replace(/visible\(\)/g, 'visible=true')
|
||||
.replace(/filter\(,?hastext=([^)]+)\)/g, 'internal:has-text=$1')
|
||||
.replace(/filter\(,?hasnottext=([^)]+)\)/g, 'internal:has-not-text=$1')
|
||||
.replace(/filter\(,?has2=([^)]+)\)/g, 'internal:has=$1')
|
||||
|
|
|
@ -14615,6 +14615,17 @@ export interface Locator {
|
|||
trial?: boolean;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns a locator that only matches [visible](https://playwright.dev/docs/actionability#visible) elements.
|
||||
* @param options
|
||||
*/
|
||||
visible(options?: {
|
||||
/**
|
||||
* Whether to match visible or invisible elements.
|
||||
*/
|
||||
visible?: boolean;
|
||||
}): Locator;
|
||||
|
||||
/**
|
||||
* Returns when element specified by locator satisfies the
|
||||
* [`state`](https://playwright.dev/docs/api/class-locator#locator-wait-for-option-state) option.
|
||||
|
|
|
@ -320,6 +320,27 @@ it('reverse engineer hasNotText', async ({ page }) => {
|
|||
});
|
||||
});
|
||||
|
||||
it('reverse engineer visible', async ({ page }) => {
|
||||
expect.soft(generate(page.getByText('Hello').visible().locator('div'))).toEqual({
|
||||
csharp: `GetByText("Hello").Visible().Locator("div")`,
|
||||
java: `getByText("Hello").visible().locator("div")`,
|
||||
javascript: `getByText('Hello').visible().locator('div')`,
|
||||
python: `get_by_text("Hello").visible().locator("div")`,
|
||||
});
|
||||
expect.soft(generate(page.getByText('Hello').visible({ visible: true }).locator('div'))).toEqual({
|
||||
csharp: `GetByText("Hello").Visible().Locator("div")`,
|
||||
java: `getByText("Hello").visible().locator("div")`,
|
||||
javascript: `getByText('Hello').visible().locator('div')`,
|
||||
python: `get_by_text("Hello").visible().locator("div")`,
|
||||
});
|
||||
expect.soft(generate(page.getByText('Hello').visible({ visible: false }).locator('div'))).toEqual({
|
||||
csharp: `GetByText("Hello").Visible(new() { Visible = false }).Locator("div")`,
|
||||
java: `getByText("Hello").visible(new Locator.VisibleOptions().setVisible(false)).locator("div")`,
|
||||
javascript: `getByText('Hello').visible({ visible: false }).locator('div')`,
|
||||
python: `get_by_text("Hello").visible(visible=False).locator("div")`,
|
||||
});
|
||||
});
|
||||
|
||||
it('reverse engineer has', async ({ page }) => {
|
||||
expect.soft(generate(page.getByText('Hello').filter({ has: page.locator('div').getByText('bye') }))).toEqual({
|
||||
csharp: `GetByText("Hello").Filter(new() { Has = Locator("div").GetByText("bye") })`,
|
||||
|
|
|
@ -150,6 +150,23 @@ it('should combine visible with other selectors', async ({ page }) => {
|
|||
await expect(page.locator('.item >> visible=true >> text=data3')).toHaveText('visible data3');
|
||||
});
|
||||
|
||||
it('should support .visible()', async ({ page }) => {
|
||||
await page.setContent(`<div>
|
||||
<div class="item" style="display: none">Hidden data0</div>
|
||||
<div class="item">visible data1</div>
|
||||
<div class="item" style="display: none">Hidden data1</div>
|
||||
<div class="item">visible data2</div>
|
||||
<div class="item" style="display: none">Hidden data2</div>
|
||||
<div class="item">visible data3</div>
|
||||
</div>
|
||||
`);
|
||||
const locator = page.locator('.item').visible().nth(1);
|
||||
await expect(locator).toHaveText('visible data2');
|
||||
await expect(page.locator('.item').visible().getByText('data3')).toHaveText('visible data3');
|
||||
await expect(page.locator('.item').visible({ visible: true }).getByText('data2')).toHaveText('visible data2');
|
||||
await expect(page.locator('.item').visible({ visible: false }).getByText('data1')).toHaveText('Hidden data1');
|
||||
});
|
||||
|
||||
it('locator.count should work with deleted Map in main world', async ({ page }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/11254' });
|
||||
await page.evaluate('Map = 1');
|
||||
|
|
Loading…
Reference in New Issue