feat(api): introduce get/getByText/getByRole (#17577)

This commit is contained in:
Pavel Feldman 2022-09-27 15:13:56 -08:00 committed by GitHub
parent 7be3e5cb82
commit d9a28bd244
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 989 additions and 124 deletions

View File

@ -887,6 +887,18 @@ await locator.ClickAsync();
* since: v1.17
## method: Frame.get
* since: v1.27
- returns: <[Locator]>
%%-template-locator-root-locator-%%
### param: Frame.get.selector = %%-find-selector-%%
* since: v1.27
### option: Frame.get.-inline- = %%-locator-options-list-v1.14-%%
* since: v1.27
## async method: Frame.getAttribute
* since: v1.8
- returns: <[null]|[string]>
@ -907,6 +919,29 @@ Attribute name to get the value for.
### option: Frame.getAttribute.timeout = %%-input-timeout-%%
* since: v1.8
## method: Frame.getByRole
* since: v1.27
- returns: <[Locator]>
%%-template-locator-get-by-role-%%
### param: Frame.getByRole.role = %%-locator-get-by-role-role-%%
### option: Frame.getByRole.-inline- = %%-locator-get-by-role-option-list-v1.27-%%
* since: v1.27
## method: Frame.getByText
* since: v1.27
- returns: <[Locator]>
%%-template-locator-get-by-text-%%
### param: Frame.getByText.text = %%-locator-get-by-text-text-%%
### option: Frame.getByText.exact = %%-locator-get-by-text-exact-%%
## async method: Frame.goto
* since: v1.8
* langs:
@ -1131,8 +1166,7 @@ Returns whether the element is [visible](../actionability.md#visible). [`option:
* since: v1.14
- returns: <[Locator]>
The method returns an element locator that can be used to perform actions in the frame.
Locator is resolved to the element immediately before performing an action, so a series of actions on the same locator can in fact be performed on different DOM elements. That would happen if the DOM structure between those actions has changed.
%%-template-locator-root-locator-%%
[Learn more about locators](../locators.md).

View File

@ -114,6 +114,39 @@ in that iframe.
* since: v1.17
## method: FrameLocator.get
* since: v1.27
- returns: <[Locator]>
%%-template-locator-locator-%%
### param: FrameLocator.get.selector = %%-find-selector-%%
* since: v1.27
### option: FrameLocator.get.-inline- = %%-locator-options-list-v1.14-%%
* since: v1.27
## method: FrameLocator.getByRole
* since: v1.27
- returns: <[Locator]>
%%-template-locator-get-by-role-%%
### param: FrameLocator.getByRole.role = %%-locator-get-by-role-role-%%
### option: FrameLocator.getByRole.-inline- = %%-locator-get-by-role-option-list-v1.27-%%
* since: v1.27
## method: FrameLocator.getByText
* since: v1.27
- returns: <[Locator]>
%%-template-locator-get-by-text-%%
### param: FrameLocator.getByText.text = %%-locator-get-by-text-text-%%
### option: FrameLocator.getByText.exact = %%-locator-get-by-text-exact-%%
## method: FrameLocator.last
* since: v1.17
- returns: <[FrameLocator]>

View File

@ -618,6 +618,18 @@ await locator.ClickAsync();
* since: v1.17
## method: Locator.get
* since: v1.27
- returns: <[Locator]>
%%-template-locator-locator-%%
### param: Locator.get.selector = %%-find-selector-%%
* since: v1.27
### option: Locator.get.-inline- = %%-locator-options-list-v1.14-%%
* since: v1.27
## async method: Locator.getAttribute
* since: v1.14
- returns: <[null]|[string]>
@ -633,6 +645,28 @@ Attribute name to get the value for.
### option: Locator.getAttribute.timeout = %%-input-timeout-%%
* since: v1.14
## method: Locator.getByRole
* since: v1.27
- returns: <[Locator]>
%%-template-locator-get-by-role-%%
### param: Locator.getByRole.role = %%-locator-get-by-role-role-%%
### option: Locator.getByRole.-inline- = %%-locator-get-by-role-option-list-v1.27-%%
* since: v1.27
## method: Locator.getByText
* since: v1.27
- returns: <[Locator]>
%%-template-locator-get-by-text-%%
### param: Locator.getByText.text = %%-locator-get-by-text-text-%%
### option: Locator.getByText.exact = %%-locator-get-by-text-exact-%%
## async method: Locator.highlight
* since: v1.20

View File

@ -2162,6 +2162,18 @@ await locator.ClickAsync();
An array of all frames attached to the page.
## method: Page.get
* since: v1.27
- returns: <[Locator]>
%%-template-locator-root-locator-%%
### param: Page.get.selector = %%-find-selector-%%
* since: v1.27
### option: Page.get.-inline- = %%-locator-options-list-v1.14-%%
* since: v1.27
## async method: Page.getAttribute
* since: v1.8
- returns: <[null]|[string]>
@ -2182,6 +2194,28 @@ Attribute name to get the value for.
### option: Page.getAttribute.timeout = %%-input-timeout-%%
* since: v1.8
## method: Page.getByRole
* since: v1.27
- returns: <[Locator]>
%%-template-locator-get-by-role-%%
### param: Page.getByRole.role = %%-locator-get-by-role-role-%%
### option: Page.getByRole.-inline- = %%-locator-get-by-role-option-list-v1.27-%%
* since: v1.27
## method: Page.getByText
* since: v1.27
- returns: <[Locator]>
%%-template-locator-get-by-text-%%
### param: Page.getByText.text = %%-locator-get-by-text-text-%%
### option: Page.getByText.exact = %%-locator-get-by-text-exact-%%
## async method: Page.goBack
* since: v1.8
- returns: <[null]|[Response]>
@ -2447,12 +2481,7 @@ Returns whether the element is [visible](../actionability.md#visible). [`option:
* since: v1.14
- returns: <[Locator]>
The method returns an element locator that can be used to perform actions on the page.
Locator is resolved to the element immediately before performing an action, so a series of actions on the same locator can in fact be performed on different DOM elements. That would happen if the DOM structure between those actions has changed.
[Learn more about locators](../locators.md).
Shortcut for main frame's [`method: Frame.locator`].
%%-template-locator-root-locator-%%
### param: Page.locator.selector = %%-find-selector-%%
* since: v1.14

View File

@ -1058,6 +1058,115 @@ When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`,
- %%-screenshot-option-mask-%%
- %%-input-timeout-%%
## locator-get-by-text-text
* since: v1.27
- `text` <[string]|[RegExp]>
## locator-get-by-text-exact
* since: v1.27
- `exact` <[boolean]>
## locator-get-by-role-role
* since: v1.27
- `role` <[string]>
## locator-get-by-role-option-checked
* since: v1.27
- `checked` <[boolean]>
An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls. Available values for checked are `true`, `false` and `"mixed"`.
Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked).
## locator-get-by-role-option-disabled
* since: v1.27
- `disabled` <[boolean]>
A boolean attribute that is usually set by `aria-disabled` or `disabled`.
:::note
Unlike most other attributes, `disabled` is inherited through the DOM hierarchy.
Learn more about [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled).
:::
## locator-get-by-role-option-expanded
* since: v1.27
- `expanded` <[boolean]>
A boolean attribute that is usually set by `aria-expanded`.
Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).
## locator-get-by-role-option-includeHidden
* since: v1.27
- `includeHidden` <[boolean]>
A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector.
Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden).
## locator-get-by-role-option-level
* since: v1.27
- `level` <[int]>
A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for `<h1>-<h6>` elements.
Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level).
## locator-get-by-role-option-name
* since: v1.27
- `name` <[string]|[RegExp]>
A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
## locator-get-by-role-option-pressed
* since: v1.27
- `pressed` <[boolean]>
An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`.
Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed).
## locator-get-by-role-option-selected
* since: v1.27
- `selected` <boolean>
A boolean attribute that is usually set by `aria-selected`.
Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected).
## locator-get-by-role-option-list-v1.27
- %%-locator-get-by-role-option-checked-%%
- %%-locator-get-by-role-option-disabled-%%
- %%-locator-get-by-role-option-expanded-%%
- %%-locator-get-by-role-option-includeHidden-%%
- %%-locator-get-by-role-option-level-%%
- %%-locator-get-by-role-option-name-%%
- %%-locator-get-by-role-option-pressed-%%
- %%-locator-get-by-role-option-selected-%%
## template-locator-locator
The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options, similar to [`method: Locator.filter`] method.
[Learn more about locators](../locators.md).
## template-locator-root-locator
The method returns an element locator that can be used to perform actions on this page / frame.
Locator is resolved to the element immediately before performing an action, so a series of actions on the same locator can in fact be performed on different DOM elements. That would happen if the DOM structure between those actions has changed.
[Learn more about locators](../locators.md).
## template-locator-get-by-text
Allows locating elements that contain given text.
## 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.
Note that many html elements have an implicitly [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values.

View File

@ -23,11 +23,11 @@ test('expand collapse', async ({ mount }) => {
const component = await mount(<AutoChip header='title'>
Chip body
</AutoChip>);
await expect(component.locator('text=Chip body')).toBeVisible();
await component.locator('text=Title').click();
await expect(component.locator('text=Chip body')).not.toBeVisible();
await component.locator('text=Title').click();
await expect(component.locator('text=Chip body')).toBeVisible();
await expect(component.getByText('Chip body')).toBeVisible();
await component.getByText('Title').click();
await expect(component.getByText('Chip body')).not.toBeVisible();
await component.getByText('Title').click();
await expect(component.getByText('Chip body')).toBeVisible();
await expect(component).toHaveScreenshot();
});
@ -37,7 +37,7 @@ test('render long title', async ({ mount }) => {
Chip body
</AutoChip>);
await expect(component).toContainText('Extremely long title.');
await expect(component.locator('text=Extremely long title.')).toHaveAttribute('title', title);
await expect(component.getByText('Extremely long title.')).toHaveAttribute('title', title);
await expect(component).toHaveScreenshot();
});
@ -47,6 +47,6 @@ test('setExpanded is called', async ({ mount }) => {
setExpanded={(expanded: boolean) => expandedValues.push(expanded)}>
</LocalChip>);
await component.locator('text=Title').click();
await component.getByText('Title').click();
expect(expandedValues).toEqual([true]);
});

View File

@ -29,11 +29,11 @@ test('should render counters', async ({ mount }) => {
ok: false,
duration: 100000
}} filterText='' setFilterText={() => {}}></HeaderView>);
await expect(component.locator('a', { hasText: 'All' }).locator('.counter')).toHaveText('100');
await expect(component.locator('a', { hasText: 'Passed' }).locator('.counter')).toHaveText('42');
await expect(component.locator('a', { hasText: 'Failed' }).locator('.counter')).toHaveText('31');
await expect(component.locator('a', { hasText: 'Flaky' }).locator('.counter')).toHaveText('17');
await expect(component.locator('a', { hasText: 'Skipped' }).locator('.counter')).toHaveText('10');
await expect(component.get('a', { hasText: 'All' }).get('.counter')).toHaveText('100');
await expect(component.get('a', { hasText: 'Passed' }).get('.counter')).toHaveText('42');
await expect(component.get('a', { hasText: 'Failed' }).get('.counter')).toHaveText('31');
await expect(component.get('a', { hasText: 'Flaky' }).get('.counter')).toHaveText('17');
await expect(component.get('a', { hasText: 'Skipped' }).get('.counter')).toHaveText('10');
});
test('should toggle filters', async ({ page, mount: mount }) => {
@ -51,14 +51,14 @@ test('should toggle filters', async ({ page, mount: mount }) => {
filterText=''
setFilterText={(filterText: string) => filters.push(filterText)}>
</HeaderView>);
await component.locator('a', { hasText: 'All' }).click();
await component.locator('a', { hasText: 'Passed' }).click();
await component.get('a', { hasText: 'All' }).click();
await component.get('a', { hasText: 'Passed' }).click();
await expect(page).toHaveURL(/#\?q=s:passed/);
await component.locator('a', { hasText: 'Failed' }).click();
await component.get('a', { hasText: 'Failed' }).click();
await expect(page).toHaveURL(/#\?q=s:failed/);
await component.locator('a', { hasText: 'Flaky' }).click();
await component.get('a', { hasText: 'Flaky' }).click();
await expect(page).toHaveURL(/#\?q=s:flaky/);
await component.locator('a', { hasText: 'Skipped' }).click();
await component.get('a', { hasText: 'Skipped' }).click();
await expect(page).toHaveURL(/#\?q=s:skipped/);
expect(filters).toEqual(['', 's:passed', 's:failed', 's:flaky', 's:skipped']);
});

View File

@ -37,7 +37,7 @@ const imageDiff: ImageDiff = {
test('should render links', async ({ mount }) => {
const component = await mount(<ImageDiffView key='image-diff' imageDiff={imageDiff}></ImageDiffView>);
await expect(component.locator('a')).toHaveText([
await expect(component.get('a')).toHaveText([
'screenshot-actual.png',
'screenshot-expected.png',
'screenshot-diff.png',
@ -46,11 +46,11 @@ test('should render links', async ({ mount }) => {
test('should show actual by default', async ({ mount }) => {
const component = await mount(<ImageDiffView key='image-diff' imageDiff={imageDiff}></ImageDiffView>);
const sliderElement = component.locator('data-testid=test-result-image-mismatch-grip');
const sliderElement = component.get('data-testid=test-result-image-mismatch-grip');
await expect.poll(() => sliderElement.evaluate(e => e.style.left), 'Actual slider is on the right').toBe('611px');
const images = component.locator('img');
const imageCount = await component.locator('img').count();
const images = component.get('img');
const imageCount = await component.get('img').count();
for (let i = 0; i < imageCount; ++i) {
const image = images.nth(i);
const box = await image.boundingBox();
@ -60,12 +60,12 @@ test('should show actual by default', async ({ mount }) => {
test('should switch to expected', async ({ mount }) => {
const component = await mount(<ImageDiffView key='image-diff' imageDiff={imageDiff}></ImageDiffView>);
await component.locator('text="Expected"').click();
const sliderElement = component.locator('data-testid=test-result-image-mismatch-grip');
await component.getByText('Expected', { exact: true }).click();
const sliderElement = component.get('data-testid=test-result-image-mismatch-grip');
await expect.poll(() => sliderElement.evaluate(e => e.style.left), 'Expected slider is on the left').toBe('371px');
const images = component.locator('img');
const imageCount = await component.locator('img').count();
const images = component.get('img');
const imageCount = await component.get('img').count();
for (let i = 0; i < imageCount; ++i) {
const image = images.nth(i);
const box = await image.boundingBox();
@ -75,9 +75,9 @@ test('should switch to expected', async ({ mount }) => {
test('should switch to diff', async ({ mount }) => {
const component = await mount(<ImageDiffView key='image-diff' imageDiff={imageDiff}></ImageDiffView>);
await component.locator('text="Diff"').click();
await component.getByText('Diff', { exact: true }).click();
const image = component.locator('img');
const image = component.get('img');
const box = await image.boundingBox();
expect(box).toEqual({ x: 400, y: 80, width: 200, height: 200 });
});

View File

@ -63,13 +63,13 @@ const testCase: TestCase = {
test('should render test case', async ({ mount }) => {
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase} run={0} anchor=''></TestCaseView>);
await expect(component.locator('text=Annotation text').first()).toBeVisible();
await component.locator('text=Annotations').click();
await expect(component.locator('text=Annotation text')).not.toBeVisible();
await expect(component.locator('text=Outer step')).toBeVisible();
await expect(component.locator('text=Inner step')).not.toBeVisible();
await component.locator('text=Outer step').click();
await expect(component.locator('text=Inner step')).toBeVisible();
await expect(component.locator('text=test.spec.ts:42')).toBeVisible();
await expect(component.locator('text=My test')).toBeVisible();
await expect(component.getByText('Annotation text', { exact: false }).first()).toBeVisible();
await component.getByText('Annotations').click();
await expect(component.getByText('Annotation text')).not.toBeVisible();
await expect(component.getByText('Outer step')).toBeVisible();
await expect(component.getByText('Inner step')).not.toBeVisible();
await component.getByText('Outer step').click();
await expect(component.getByText('Inner step')).toBeVisible();
await expect(component.getByText('test.spec.ts:42')).toBeVisible();
await expect(component.getByText('My test')).toBeVisible();
});

View File

@ -18,7 +18,8 @@
import { assert } from '../utils';
import type * as channels from '@protocol/channels';
import { ChannelOwner } from './channelOwner';
import { FrameLocator, Locator, type LocatorOptions } from './locator';
import { FrameLocator, Locator } from './locator';
import type { ByRoleOptions, LocatorOptions } from './locator';
import { ElementHandle, convertSelectOptionValues, convertInputFiles } from './elementHandle';
import { assertMaxArguments, JSHandle, serializeArgument, parseResult } from './jsHandle';
import fs from 'fs';
@ -298,6 +299,18 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
return new Locator(this, selector, options);
}
get(selector: string, options?: LocatorOptions): Locator {
return this.locator(selector, options);
}
getByText(text: string | RegExp, options?: { exact?: boolean }): Locator {
return this.locator(Locator.getByTextSelector(text, options));
}
getByRole(role: string, options: ByRoleOptions = {}): Locator {
return this.locator(Locator.getByRoleSelector(role, options));
}
frameLocator(selector: string): FrameLocator {
return new FrameLocator(this, selector);
}

View File

@ -19,7 +19,7 @@ import type * as api from '../../types/types';
import type * as channels from '@protocol/channels';
import type { ParsedStackTrace } from '../utils/stackTrace';
import * as util from 'util';
import { isRegExp, monotonicTime } from '../utils';
import { isRegExp, isString, monotonicTime } from '../utils';
import { ElementHandle } from './elementHandle';
import type { Frame } from './frame';
import type { FilePayload, FrameExpectOptions, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types';
@ -31,10 +31,50 @@ export type LocatorOptions = {
has?: Locator;
};
export type ByRoleOptions = LocatorOptions & {
checked?: boolean;
disabled?: boolean;
expanded?: boolean;
includeHidden?: boolean;
level?: number;
name?: string | RegExp;
pressed?: boolean;
selected?: boolean;
};
export class Locator implements api.Locator {
_frame: Frame;
_selector: string;
static getByTextSelector(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;
}
static getByRoleSelector(role: string, options: ByRoleOptions = {}): string {
const props: string[][] = [];
if (options.checked !== undefined)
props.push(['checked', String(options.checked)]);
if (options.disabled !== undefined)
props.push(['disabled', String(options.disabled)]);
if (options.selected !== undefined)
props.push(['selected', String(options.selected)]);
if (options.expanded !== undefined)
props.push(['expanded', String(options.expanded)]);
if (options.includeHidden !== undefined)
props.push(['include-hidden', String(options.includeHidden)]);
if (options.level !== undefined)
props.push(['level', String(options.level)]);
if (options.name !== undefined)
props.push(['name', isString(options.name) ? escapeWithQuotes(options.name, '"') : String(options.name)]);
if (options.pressed !== undefined)
props.push(['pressed', String(options.pressed)]);
return `role=${role}${props.map(([n, v]) => `[${n}=${v}]`).join('')}`;
}
constructor(frame: Frame, selector: string, options?: LocatorOptions) {
this._frame = frame;
this._selector = selector;
@ -132,6 +172,18 @@ export class Locator implements api.Locator {
return new Locator(this._frame, this._selector + ' >> ' + selector, options);
}
get(selector: string, options?: LocatorOptions): Locator {
return this.locator(selector, options);
}
getByText(text: string | RegExp, options?: { exact?: boolean }): Locator {
return this.locator(Locator.getByTextSelector(text, options));
}
getByRole(role: string, options: ByRoleOptions = {}): Locator {
return this.locator(Locator.getByRoleSelector(role, options));
}
frameLocator(selector: string): FrameLocator {
return new FrameLocator(this._frame, this._selector + ' >> ' + selector);
}
@ -306,6 +358,18 @@ export class FrameLocator implements api.FrameLocator {
return new Locator(this._frame, this._frameSelector + ' >> control=enter-frame >> ' + selector, options);
}
get(selector: string, options?: LocatorOptions): Locator {
return this.locator(selector, options);
}
getByText(text: string | RegExp, options?: { exact?: boolean }): Locator {
return this.locator(Locator.getByTextSelector(text, options));
}
getByRole(role: string, options: ByRoleOptions = {}): Locator {
return this.locator(Locator.getByRoleSelector(role, options));
}
frameLocator(selector: string): FrameLocator {
return new FrameLocator(this._frame, this._frameSelector + ' >> control=enter-frame >> ' + selector);
}

View File

@ -45,7 +45,7 @@ import { Frame, verifyLoadState } from './frame';
import { HarRouter } from './harRouter';
import { Keyboard, Mouse, Touchscreen } from './input';
import { assertMaxArguments, JSHandle, parseResult, serializeArgument } from './jsHandle';
import type { FrameLocator, Locator, LocatorOptions } from './locator';
import type { ByRoleOptions, FrameLocator, Locator, LocatorOptions } from './locator';
import type { RouteHandlerCallback } from './network';
import { Response, Route, RouteHandler, validateHeaders, WebSocket } from './network';
import type { Request } from './network';
@ -564,6 +564,18 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
return this.mainFrame().locator(selector, options);
}
get(selector: string, options?: LocatorOptions): Locator {
return this.mainFrame().locator(selector, options);
}
getByText(text: string | RegExp, options?: { exact?: boolean }): Locator {
return this.mainFrame().getByText(text, options);
}
getByRole(role: string, options: ByRoleOptions = {}): Locator {
return this.mainFrame().getByRole(role, options);
}
frameLocator(selector: string): FrameLocator {
return this.mainFrame().frameLocator(selector);
}

View File

@ -2434,6 +2434,32 @@ export interface Page {
*/
frames(): Array<Frame>;
/**
* The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to
* the element immediately before performing an action, so a series of actions on the same locator can in fact be performed
* on different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* [Learn more about locators](https://playwright.dev/docs/locators).
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
* @param options
*/
get(selector: string, 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 `<article><div>Playwright</div></article>`.
*
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain [FrameLocator]s.
*/
has?: Locator;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches
* `<article><div>Playwright</div></article>`.
*/
hasText?: string|RegExp;
}): Locator;
/**
* Returns element attribute value.
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
@ -2456,6 +2482,90 @@ export interface Page {
timeout?: number;
}): Promise<null|string>;
/**
* 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.
*
* Note that many html elements have an implicitly
* [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You
* can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not
* recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values.
* @param role
* @param options
*/
getByRole(role: string, options?: {
/**
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls. Available values for
* checked are `true`, `false` and `"mixed"`.
*
* Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked).
*/
checked?: boolean;
/**
* A boolean attribute that is usually set by `aria-disabled` or `disabled`.
*
* > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about
* [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled).
*/
disabled?: boolean;
/**
* A boolean attribute that is usually set by `aria-expanded`.
*
* Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).
*/
expanded?: boolean;
/**
* A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as
* [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector.
*
* Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden).
*/
includeHidden?: boolean;
/**
* A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for
* `<h1>-<h6>` elements.
*
* Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level).
*/
level?: number;
/**
* A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
*
* Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
*/
name?: string|RegExp;
/**
* An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`.
*
* Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed).
*/
pressed?: boolean;
/**
* A boolean attribute that is usually set by `aria-selected`.
*
* Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected).
*/
selected?: boolean;
}): Locator;
/**
* Allows locating elements that contain given text.
* @param text
* @param options
*/
getByText(text: string|RegExp, options?: {
exact?: boolean;
}): Locator;
/**
* Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
* last redirect. If can not go back, returns `null`.
@ -2826,14 +2936,11 @@ export interface Page {
keyboard: Keyboard;
/**
* The method returns an element locator that can be used to perform actions on the page. Locator is resolved to the
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
* different DOM elements. That would happen if the DOM structure between those actions has changed.
* The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to
* the element immediately before performing an action, so a series of actions on the same locator can in fact be performed
* on different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* [Learn more about locators](https://playwright.dev/docs/locators).
*
* Shortcut for main frame's
* [frame.locator(selector[, options])](https://playwright.dev/docs/api/class-frame#frame-locator).
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
* @param options
*/
@ -5357,6 +5464,32 @@ export interface Frame {
*/
frameLocator(selector: string): FrameLocator;
/**
* The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to
* the element immediately before performing an action, so a series of actions on the same locator can in fact be performed
* on different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* [Learn more about locators](https://playwright.dev/docs/locators).
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
* @param options
*/
get(selector: string, 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 `<article><div>Playwright</div></article>`.
*
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain [FrameLocator]s.
*/
has?: Locator;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches
* `<article><div>Playwright</div></article>`.
*/
hasText?: string|RegExp;
}): Locator;
/**
* Returns element attribute value.
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
@ -5379,6 +5512,90 @@ export interface Frame {
timeout?: number;
}): Promise<null|string>;
/**
* 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.
*
* Note that many html elements have an implicitly
* [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You
* can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not
* recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values.
* @param role
* @param options
*/
getByRole(role: string, options?: {
/**
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls. Available values for
* checked are `true`, `false` and `"mixed"`.
*
* Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked).
*/
checked?: boolean;
/**
* A boolean attribute that is usually set by `aria-disabled` or `disabled`.
*
* > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about
* [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled).
*/
disabled?: boolean;
/**
* A boolean attribute that is usually set by `aria-expanded`.
*
* Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).
*/
expanded?: boolean;
/**
* A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as
* [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector.
*
* Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden).
*/
includeHidden?: boolean;
/**
* A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for
* `<h1>-<h6>` elements.
*
* Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level).
*/
level?: number;
/**
* A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
*
* Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
*/
name?: string|RegExp;
/**
* An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`.
*
* Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed).
*/
pressed?: boolean;
/**
* A boolean attribute that is usually set by `aria-selected`.
*
* Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected).
*/
selected?: boolean;
}): Locator;
/**
* Allows locating elements that contain given text.
* @param text
* @param options
*/
getByText(text: string|RegExp, options?: {
exact?: boolean;
}): Locator;
/**
* Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
* last redirect.
@ -5686,9 +5903,11 @@ export interface Frame {
}): Promise<boolean>;
/**
* The method returns an element locator that can be used to perform actions in the frame. Locator is resolved to the
* element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
* different DOM elements. That would happen if the DOM structure between those actions has changed.
* The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to
* the element immediately before performing an action, so a series of actions on the same locator can in fact be performed
* on different DOM elements. That would happen if the DOM structure between those actions has changed.
*
* [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. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
@ -9630,6 +9849,31 @@ export interface Locator {
*/
frameLocator(selector: string): FrameLocator;
/**
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to [locator.filter([options])](https://playwright.dev/docs/api/class-locator#locator-filter) method.
*
* [Learn more about locators](https://playwright.dev/docs/locators).
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
* @param options
*/
get(selector: string, 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 `<article><div>Playwright</div></article>`.
*
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain [FrameLocator]s.
*/
has?: Locator;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches
* `<article><div>Playwright</div></article>`.
*/
hasText?: string|RegExp;
}): Locator;
/**
* Returns element attribute value.
* @param name Attribute name to get the value for.
@ -9645,6 +9889,90 @@ export interface Locator {
timeout?: number;
}): Promise<null|string>;
/**
* 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.
*
* Note that many html elements have an implicitly
* [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You
* can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not
* recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values.
* @param role
* @param options
*/
getByRole(role: string, options?: {
/**
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls. Available values for
* checked are `true`, `false` and `"mixed"`.
*
* Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked).
*/
checked?: boolean;
/**
* A boolean attribute that is usually set by `aria-disabled` or `disabled`.
*
* > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about
* [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled).
*/
disabled?: boolean;
/**
* A boolean attribute that is usually set by `aria-expanded`.
*
* Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).
*/
expanded?: boolean;
/**
* A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as
* [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector.
*
* Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden).
*/
includeHidden?: boolean;
/**
* A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for
* `<h1>-<h6>` elements.
*
* Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level).
*/
level?: number;
/**
* A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
*
* Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
*/
name?: string|RegExp;
/**
* An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`.
*
* Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed).
*/
pressed?: boolean;
/**
* A boolean attribute that is usually set by `aria-selected`.
*
* Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected).
*/
selected?: boolean;
}): Locator;
/**
* Allows locating elements that contain given text.
* @param text
* @param options
*/
getByText(text: string|RegExp, options?: {
exact?: boolean;
}): Locator;
/**
* Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses
* [locator.highlight()](https://playwright.dev/docs/api/class-locator#locator-highlight).
@ -9839,6 +10167,8 @@ export interface Locator {
/**
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to [locator.filter([options])](https://playwright.dev/docs/api/class-locator#locator-filter) method.
*
* [Learn more about locators](https://playwright.dev/docs/locators).
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
* @param options
*/
@ -14732,6 +15062,115 @@ export interface FrameLocator {
*/
frameLocator(selector: string): FrameLocator;
/**
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to [locator.filter([options])](https://playwright.dev/docs/api/class-locator#locator-filter) method.
*
* [Learn more about locators](https://playwright.dev/docs/locators).
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
* @param options
*/
get(selector: string, 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 `<article><div>Playwright</div></article>`.
*
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain [FrameLocator]s.
*/
has?: Locator;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches
* `<article><div>Playwright</div></article>`.
*/
hasText?: string|RegExp;
}): 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
* [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.
*
* Note that many html elements have an implicitly
* [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You
* can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not
* recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values.
* @param role
* @param options
*/
getByRole(role: string, options?: {
/**
* An attribute that is usually set by `aria-checked` or native `<input type=checkbox>` controls. Available values for
* checked are `true`, `false` and `"mixed"`.
*
* Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked).
*/
checked?: boolean;
/**
* A boolean attribute that is usually set by `aria-disabled` or `disabled`.
*
* > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about
* [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled).
*/
disabled?: boolean;
/**
* A boolean attribute that is usually set by `aria-expanded`.
*
* Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).
*/
expanded?: boolean;
/**
* A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as
* [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector.
*
* Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden).
*/
includeHidden?: boolean;
/**
* A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for
* `<h1>-<h6>` elements.
*
* Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level).
*/
level?: number;
/**
* A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
*
* Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
*/
name?: string|RegExp;
/**
* An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`.
*
* Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed).
*/
pressed?: boolean;
/**
* A boolean attribute that is usually set by `aria-selected`.
*
* Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected).
*/
selected?: boolean;
}): Locator;
/**
* Allows locating elements that contain given text.
* @param text
* @param options
*/
getByText(text: string|RegExp, options?: {
exact?: boolean;
}): Locator;
/**
* Returns locator to the last matching frame.
*/
@ -14740,6 +15179,8 @@ export interface FrameLocator {
/**
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to [locator.filter([options])](https://playwright.dev/docs/api/class-locator#locator-filter) method.
*
* [Learn more about locators](https://playwright.dev/docs/locators).
* @param selector A selector to use when resolving DOM element. See [working with selectors](https://playwright.dev/docs/selectors) for more details.
* @param options
*/

View File

@ -580,15 +580,15 @@ test('should include metainfo', async ({ showTraceViewer, browserName }) => {
const traceViewer = await showTraceViewer([traceFile]);
await traceViewer.page.locator('text=Metadata').click();
const callLine = traceViewer.page.locator('.call-line');
await expect(callLine.locator('text=start time')).toHaveText(/start time: [\d/,: ]+/);
await expect(callLine.locator('text=duration')).toHaveText(/duration: [\dms]+/);
await expect(callLine.locator('text=engine')).toHaveText(/engine: [\w]+/);
await expect(callLine.locator('text=platform')).toHaveText(/platform: [\w]+/);
await expect(callLine.locator('text=width')).toHaveText(/width: [\d]+/);
await expect(callLine.locator('text=height')).toHaveText(/height: [\d]+/);
await expect(callLine.locator('text=pages')).toHaveText(/pages: 1/);
await expect(callLine.locator('text=actions')).toHaveText(/actions: [\d]+/);
await expect(callLine.locator('text=events')).toHaveText(/events: [\d]+/);
await expect(callLine.getByText('start time')).toHaveText(/start time: [\d/,: ]+/);
await expect(callLine.getByText('duration')).toHaveText(/duration: [\dms]+/);
await expect(callLine.getByText('engine')).toHaveText(/engine: [\w]+/);
await expect(callLine.getByText('platform')).toHaveText(/platform: [\w]+/);
await expect(callLine.getByText('width')).toHaveText(/width: [\d]+/);
await expect(callLine.getByText('height')).toHaveText(/height: [\d]+/);
await expect(callLine.getByText('pages')).toHaveText(/pages: 1/);
await expect(callLine.getByText('actions')).toHaveText(/actions: [\d]+/);
await expect(callLine.getByText('events')).toHaveText(/events: [\d]+/);
});
test('should open two trace files', async ({ context, page, request, server, showTraceViewer }, testInfo) => {
@ -631,16 +631,16 @@ test('should open two trace files', async ({ context, page, request, server, sho
await traceViewer.page.locator('text=Metadata').click();
const callLine = traceViewer.page.locator('.call-line');
// Should get metadata from the context trace
await expect(callLine.locator('text=start time')).toHaveText(/start time: [\d/,: ]+/);
await expect(callLine.getByText('start time')).toHaveText(/start time: [\d/,: ]+/);
// duration in the metatadata section
await expect(callLine.locator('text=duration').first()).toHaveText(/duration: [\dms]+/);
await expect(callLine.locator('text=engine')).toHaveText(/engine: [\w]+/);
await expect(callLine.locator('text=platform')).toHaveText(/platform: [\w]+/);
await expect(callLine.locator('text=width')).toHaveText(/width: [\d]+/);
await expect(callLine.locator('text=height')).toHaveText(/height: [\d]+/);
await expect(callLine.locator('text=pages')).toHaveText(/pages: 1/);
await expect(callLine.locator('text=actions')).toHaveText(/actions: 6/);
await expect(callLine.locator('text=events')).toHaveText(/events: [\d]+/);
await expect(callLine.getByText('duration').first()).toHaveText(/duration: [\dms]+/);
await expect(callLine.getByText('engine')).toHaveText(/engine: [\w]+/);
await expect(callLine.getByText('platform')).toHaveText(/platform: [\w]+/);
await expect(callLine.getByText('width')).toHaveText(/width: [\d]+/);
await expect(callLine.getByText('height')).toHaveText(/height: [\d]+/);
await expect(callLine.getByText('pages')).toHaveText(/pages: 1/);
await expect(callLine.getByText('actions')).toHaveText(/actions: 6/);
await expect(callLine.getByText('events')).toHaveText(/events: [\d]+/);
});
test('should include requestUrl in route.fulfill', async ({ page, runAndTrace, browserName }) => {
@ -661,8 +661,8 @@ test('should include requestUrl in route.fulfill', async ({ page, runAndTrace, b
await traceViewer.selectAction('route.fulfill');
await traceViewer.page.locator('.tab-label', { hasText: 'Call' }).click();
const callLine = traceViewer.page.locator('.call-line');
await expect(callLine.locator('text=status')).toContainText('200');
await expect(callLine.locator('text=requestUrl')).toContainText('http://test.com');
await expect(callLine.getByText('status')).toContainText('200');
await expect(callLine.getByText('requestUrl')).toContainText('http://test.com');
});
test('should include requestUrl in route.continue', async ({ page, runAndTrace, server }) => {
@ -677,8 +677,8 @@ test('should include requestUrl in route.continue', async ({ page, runAndTrace,
await traceViewer.selectAction('route.continue');
await traceViewer.page.locator('.tab-label', { hasText: 'Call' }).click();
const callLine = traceViewer.page.locator('.call-line');
await expect(callLine.locator('text=requestUrl')).toContainText('http://test.com');
await expect(callLine.locator('text=/^url: .*/')).toContainText(server.EMPTY_PAGE);
await expect(callLine.getByText('requestUrl')).toContainText('http://test.com');
await expect(callLine.getByText(/^url: .*/)).toContainText(server.EMPTY_PAGE);
});
test('should include requestUrl in route.abort', async ({ page, runAndTrace, server }) => {
@ -693,7 +693,7 @@ test('should include requestUrl in route.abort', async ({ page, runAndTrace, ser
await traceViewer.selectAction('route.abort');
await traceViewer.page.locator('.tab-label', { hasText: 'Call' }).click();
const callLine = traceViewer.page.locator('.call-line');
await expect(callLine.locator('text=requestUrl')).toContainText('http://test.com');
await expect(callLine.getByText('requestUrl')).toContainText('http://test.com');
});
test('should serve overridden request', async ({ page, runAndTrace, server }) => {

View File

@ -68,7 +68,7 @@ async function routeAmbiguous(page: Page) {
it('should work for iframe @smoke', async ({ page, server }) => {
await routeIframe(page);
await page.goto(server.EMPTY_PAGE);
const button = page.frameLocator('iframe').locator('button');
const button = page.frameLocator('iframe').get('button');
await button.waitFor();
expect(await button.innerText()).toBe('Hello iframe');
await expect(button).toHaveText('Hello iframe');
@ -78,7 +78,7 @@ it('should work for iframe @smoke', async ({ page, server }) => {
it('should work for nested iframe', async ({ page, server }) => {
await routeIframe(page);
await page.goto(server.EMPTY_PAGE);
const button = page.frameLocator('iframe').frameLocator('iframe').locator('button');
const button = page.frameLocator('iframe').frameLocator('iframe').get('button');
await button.waitFor();
expect(await button.innerText()).toBe('Hello nested iframe');
await expect(button).toHaveText('Hello nested iframe');
@ -88,15 +88,15 @@ it('should work for nested iframe', async ({ page, server }) => {
it('should work for $ and $$', async ({ page, server }) => {
await routeIframe(page);
await page.goto(server.EMPTY_PAGE);
const locator = page.frameLocator('iframe').locator('button');
const locator = page.frameLocator('iframe').get('button');
await expect(locator).toHaveText('Hello iframe');
const spans = page.frameLocator('iframe').locator('span');
const spans = page.frameLocator('iframe').get('span');
await expect(spans).toHaveCount(2);
});
it('should wait for frame', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
const error = await page.frameLocator('iframe').locator('span').click({ timeout: 1000 }).catch(e => e);
const error = await page.frameLocator('iframe').get('span').click({ timeout: 1000 }).catch(e => e);
expect(error.message).toContain('waiting for frame "iframe"');
});
@ -237,3 +237,12 @@ it('locator.frameLocator should not throw on first/last/nth', async ({ page, ser
const button3 = page.locator('body').frameLocator('iframe').last().locator('button');
await expect(button3).toHaveText('Hello from iframe-3.html');
});
it('role and text coverage', async ({ page, server }) => {
await routeIframe(page);
await page.goto(server.EMPTY_PAGE);
const button1 = page.frameLocator('iframe').getByRole('button');
const button2 = page.frameLocator('iframe').getByText('Hello');
await expect(button1).toHaveText('Hello iframe');
await expect(button2).toHaveText('Hello iframe');
});

View File

@ -172,3 +172,11 @@ it('should enforce same frame for has/leftOf/rightOf/above/below/near', async ({
expect(error.message).toContain(`Inner "${option}" locator must belong to the same frame.`);
}
});
it('alias methods coverage', async ({ page }) => {
await page.setContent(`<div><button>Submit</button></div>`);
await expect(page.get('button')).toHaveCount(1);
await expect(page.get('div').get('button')).toHaveCount(1);
await expect(page.get('div').getByRole('button')).toHaveCount(1);
await expect(page.mainFrame().get('button')).toHaveCount(1);
});

View File

@ -25,25 +25,27 @@ test('should detect roles', async ({ page }) => {
<details><summary>Hello</summary></details>
<div role="dialog">I am a dialog</div>
`);
expect(await page.$$eval(`role=button`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hello</button>`,
]);
expect(await page.$$eval(`role=listbox`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=listbox`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<select multiple="" size="2"></select>`,
]);
expect(await page.$$eval(`role=combobox`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=combobox`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<select></select>`,
]);
expect(await page.$$eval(`role=heading`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=heading`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<h3>Heading</h3>`,
]);
expect(await page.$$eval(`role=group`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=group`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<details><summary>Hello</summary></details>`,
]);
expect(await page.$$eval(`role=dialog`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=dialog`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="dialog">I am a dialog</div>`,
]);
expect(await page.$$eval(`role=menuitem`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=menuitem`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
]);
expect(await page.getByRole('menuitem').evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
]);
});
@ -58,15 +60,25 @@ test('should support selected', async ({ page }) => {
<div role="option" aria-selected="false">Hello</div>
</div>
`);
expect(await page.$$eval(`role=option[selected]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=option[selected]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<option selected="">Hello</option>`,
`<div role="option" aria-selected="true">Hi</div>`,
]);
expect(await page.$$eval(`role=option[selected=true]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=option[selected=true]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<option selected="">Hello</option>`,
`<div role="option" aria-selected="true">Hi</div>`,
]);
expect(await page.$$eval(`role=option[selected=false]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('option', { selected: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<option selected="">Hello</option>`,
`<div role="option" aria-selected="true">Hi</div>`,
]);
expect(await page.get(`role=option[selected=false]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<option>Hi</option>`,
`<div role="option" aria-selected="false">Hello</div>`,
]);
expect(await page.getByRole('option', { selected: false }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<option>Hi</option>`,
`<div role="option" aria-selected="false">Hello</div>`,
]);
@ -82,23 +94,35 @@ test('should support checked', async ({ page }) => {
<div role=checkbox>Unknown</div>
`);
await page.$eval('[indeterminate]', input => (input as HTMLInputElement).indeterminate = true);
expect(await page.$$eval(`role=checkbox[checked]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=checkbox[checked]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<input type="checkbox" checked="">`,
`<div role="checkbox" aria-checked="true">Hi</div>`,
]);
expect(await page.$$eval(`role=checkbox[checked=true]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=checkbox[checked=true]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<input type="checkbox" checked="">`,
`<div role="checkbox" aria-checked="true">Hi</div>`,
]);
expect(await page.$$eval(`role=checkbox[checked=false]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('checkbox', { checked: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<input type="checkbox" checked="">`,
`<div role="checkbox" aria-checked="true">Hi</div>`,
]);
expect(await page.get(`role=checkbox[checked=false]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<input type="checkbox">`,
`<div role="checkbox" aria-checked="false">Hello</div>`,
`<div role="checkbox">Unknown</div>`,
]);
expect(await page.$$eval(`role=checkbox[checked="mixed"]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('checkbox', { checked: false }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<input type="checkbox">`,
`<div role="checkbox" aria-checked="false">Hello</div>`,
`<div role="checkbox">Unknown</div>`,
]);
expect(await page.get(`role=checkbox[checked="mixed"]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<input type="checkbox" indeterminate="">`,
]);
expect(await page.$$eval(`role=checkbox`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=checkbox`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<input type="checkbox">`,
`<input type="checkbox" checked="">`,
`<input type="checkbox" indeterminate="">`,
@ -115,20 +139,27 @@ test('should support pressed', async ({ page }) => {
<button aria-pressed="false">Bye</button>
<button aria-pressed="mixed">Mixed</button>
`);
expect(await page.$$eval(`role=button[pressed]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[pressed]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-pressed="true">Hello</button>`,
]);
expect(await page.$$eval(`role=button[pressed=true]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[pressed=true]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-pressed="true">Hello</button>`,
]);
expect(await page.$$eval(`role=button[pressed=false]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('button', { pressed: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-pressed="true">Hello</button>`,
]);
expect(await page.get(`role=button[pressed=false]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-pressed="false">Bye</button>`,
]);
expect(await page.$$eval(`role=button[pressed="mixed"]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('button', { pressed: false }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-pressed="false">Bye</button>`,
]);
expect(await page.get(`role=button[pressed="mixed"]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-pressed="mixed">Mixed</button>`,
]);
expect(await page.$$eval(`role=button`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-pressed="true">Hello</button>`,
`<button aria-pressed="false">Bye</button>`,
@ -142,13 +173,20 @@ test('should support expanded', async ({ page }) => {
<button aria-expanded="true">Hello</button>
<button aria-expanded="false">Bye</button>
`);
expect(await page.$$eval(`role=button[expanded]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[expanded]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-expanded="true">Hello</button>`,
]);
expect(await page.$$eval(`role=button[expanded=true]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[expanded=true]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-expanded="true">Hello</button>`,
]);
expect(await page.$$eval(`role=button[expanded=false]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('button', { expanded: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button aria-expanded="true">Hello</button>`,
]);
expect(await page.get(`role=button[expanded=false]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-expanded="false">Bye</button>`,
]);
expect(await page.getByRole('button', { expanded: false }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-expanded="false">Bye</button>`,
]);
@ -164,17 +202,26 @@ test('should support disabled', async ({ page }) => {
<button>Yay</button>
</fieldset>
`);
expect(await page.$$eval(`role=button[disabled]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[disabled]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button disabled="">Bye</button>`,
`<button aria-disabled="true">Hello</button>`,
`<button>Yay</button>`,
]);
expect(await page.$$eval(`role=button[disabled=true]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[disabled=true]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button disabled="">Bye</button>`,
`<button aria-disabled="true">Hello</button>`,
`<button>Yay</button>`,
]);
expect(await page.$$eval(`role=button[disabled=false]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('button', { disabled: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button disabled="">Bye</button>`,
`<button aria-disabled="true">Hello</button>`,
`<button>Yay</button>`,
]);
expect(await page.get(`role=button[disabled=false]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-disabled="false">Oh</button>`,
]);
expect(await page.getByRole('button', { disabled: false }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-disabled="false">Oh</button>`,
]);
@ -186,13 +233,19 @@ test('should support level', async ({ page }) => {
<h3>Hi</h3>
<div role="heading" aria-level="5">Bye</div>
`);
expect(await page.$$eval(`role=heading[level=1]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=heading[level=1]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<h1>Hello</h1>`,
]);
expect(await page.$$eval(`role=heading[level=3]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('heading', { level: 1 }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<h1>Hello</h1>`,
]);
expect(await page.get(`role=heading[level=3]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<h3>Hi</h3>`,
]);
expect(await page.$$eval(`role=heading[level=5]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('heading', { level: 3 }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<h3>Hi</h3>`,
]);
expect(await page.get(`role=heading[level=5]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="heading" aria-level="5">Bye</div>`,
]);
});
@ -224,13 +277,13 @@ test('should filter hidden, unless explicitly asked for', async ({ page }) => {
addButton(document.getElementById('host2'), 'Shadow2');
</script>
`);
expect(await page.$$eval(`role=button`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-hidden="false">Nay</button>`,
`<button style="visibility:visible">Still here</button>`,
`<button>Shadow1</button>`,
]);
expect(await page.$$eval(`role=button[include-hidden]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[include-hidden]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button hidden="">Hello</button>`,
`<button aria-hidden="true">Yay</button>`,
@ -242,7 +295,7 @@ test('should filter hidden, unless explicitly asked for', async ({ page }) => {
`<button>Shadow1</button>`,
`<button>Shadow2</button>`,
]);
expect(await page.$$eval(`role=button[include-hidden=true]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[include-hidden=true]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button hidden="">Hello</button>`,
`<button aria-hidden="true">Yay</button>`,
@ -254,7 +307,7 @@ test('should filter hidden, unless explicitly asked for', async ({ page }) => {
`<button>Shadow1</button>`,
`<button>Shadow2</button>`,
]);
expect(await page.$$eval(`role=button[include-hidden=false]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[include-hidden=false]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<button>Hi</button>`,
`<button aria-hidden="false">Nay</button>`,
`<button style="visibility:visible">Still here</button>`,
@ -268,29 +321,53 @@ test('should support name', async ({ page }) => {
<div role="button" aria-label="Hallo"></div>
<div role="button" aria-label="Hello" aria-hidden="true"></div>
<div role="button" aria-label="123" aria-hidden="true"></div>
<div role="button" aria-label="foo\"bar" aria-hidden="true"></div>
`);
expect(await page.$$eval(`role=button[name="Hello"]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[name="Hello"]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
]);
expect(await page.$$eval(`role=button[name*="all"]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('button', { name: 'Hello' }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
]);
expect(await page.get(`role=button[name*="all"]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hallo"></div>`,
]);
expect(await page.$$eval(`role=button[name=/^H[ae]llo$/]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[name=/^H[ae]llo$/]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
`<div role="button" aria-label="Hallo"></div>`,
]);
expect(await page.$$eval(`role=button[name=/h.*o/i]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('button', { name: /^H[ae]llo$/ }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
`<div role="button" aria-label="Hallo"></div>`,
]);
expect(await page.$$eval(`role=button[name="Hello"][include-hidden]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[name=/h.*o/i]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
`<div role="button" aria-label="Hallo"></div>`,
]);
expect(await page.getByRole('button', { name: /h.*o/i }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
`<div role="button" aria-label="Hallo"></div>`,
]);
expect(await page.get(`role=button[name="Hello"][include-hidden]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
`<div role="button" aria-label="Hello" aria-hidden="true"></div>`,
]);
expect(await page.$$eval(`role=button[name=Hello]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.getByRole('button', { name: 'Hello', includeHidden: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
`<div role="button" aria-label="Hello" aria-hidden="true"></div>`,
]);
expect(await page.get(`role=button[name=Hello]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="Hello"></div>`,
]);
expect(await page.$$eval(`role=button[name=123][include-hidden]`, els => els.map(e => e.outerHTML))).toEqual([
expect(await page.get(`role=button[name=123][include-hidden]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="123" aria-hidden="true"></div>`,
]);
expect(await page.getByRole('button', { name: '123', includeHidden: true }).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
`<div role="button" aria-label="123" aria-hidden="true"></div>`,
]);
});

View File

@ -24,9 +24,11 @@ it('should work @smoke', async ({ page }) => {
expect(await page.$eval(`text=/^[ay]+$/`, e => e.outerHTML)).toBe('<div>ya</div>');
expect(await page.$eval(`text=/Ya/i`, e => e.outerHTML)).toBe('<div>ya</div>');
expect(await page.$eval(`text=ye`, e => e.outerHTML)).toBe('<div>\nye </div>');
expect(await page.getByText('ye').evaluate(e => e.outerHTML)).toBe('<div>\nye </div>');
await page.setContent(`<div> ye </div><div>ye</div>`);
expect(await page.$eval(`text="ye"`, e => e.outerHTML)).toBe('<div> ye </div>');
expect(await page.getByText('ye', { exact: true }).first().evaluate(e => e.outerHTML)).toBe('<div> ye </div>');
await page.setContent(`<div>yo</div><div>"ya</div><div> hello world! </div>`);
expect(await page.$eval(`text="\\"ya"`, e => e.outerHTML)).toBe('<div>"ya</div>');