chore: implement locator.ariaSnapshot (#33125)
This commit is contained in:
parent
b92b855638
commit
94321fef1c
|
|
@ -150,6 +150,63 @@ var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
|
||||||
|
|
||||||
Additional locator to match.
|
Additional locator to match.
|
||||||
|
|
||||||
|
## async method: Locator.ariaSnapshot
|
||||||
|
* since: v1.49
|
||||||
|
- returns: <[string]>
|
||||||
|
|
||||||
|
Captures the aria snapshot of the given element. See [`method: LocatorAssertions.toMatchAriaSnapshot`] for the corresponding assertion.
|
||||||
|
|
||||||
|
**Usage**
|
||||||
|
|
||||||
|
```js
|
||||||
|
await page.getByRole('link').ariaSnapshot();
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
page.getByRole(AriaRole.LINK).ariaSnapshot();
|
||||||
|
```
|
||||||
|
|
||||||
|
```python async
|
||||||
|
await page.get_by_role("link").aria_snapshot()
|
||||||
|
```
|
||||||
|
|
||||||
|
```python sync
|
||||||
|
page.get_by_role("link").aria_snapshot()
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await page.GetByRole(AriaRole.Link).AriaSnapshotAsync();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Details**
|
||||||
|
|
||||||
|
This method captures the aria snapshot of the given element. The snapshot is a string that represents the state of the element and its children.
|
||||||
|
The snapshot can be used to assert the state of the element in the test, or to compare it to state in the future.
|
||||||
|
|
||||||
|
The ARIA snapshot is represented using [YAML](https://yaml.org/spec/1.2.2/) markup language:
|
||||||
|
* The keys of the objects are the roles and optional accessible names of the elements.
|
||||||
|
* The values are either text content or an array of child elements.
|
||||||
|
* Generic static text can be represented with the `text` key.
|
||||||
|
|
||||||
|
Below is the HTML markup and the respective ARIA snapshot:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ul aria-label="Links">
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="/about">About</a></li>
|
||||||
|
<ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
```yml
|
||||||
|
- list "Links":
|
||||||
|
- listitem:
|
||||||
|
- link "Home"
|
||||||
|
- listitem:
|
||||||
|
- link "About"
|
||||||
|
```
|
||||||
|
|
||||||
|
### option: Locator.ariaSnapshot.timeout = %%-input-timeout-js-%%
|
||||||
|
* since: v1.49
|
||||||
|
|
||||||
## async method: Locator.blur
|
## async method: Locator.blur
|
||||||
* since: v1.28
|
* since: v1.28
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,11 @@ export class Locator implements api.Locator {
|
||||||
return await this._withElement((h, timeout) => h.screenshot({ ...options, timeout }), options.timeout);
|
return await this._withElement((h, timeout) => h.screenshot({ ...options, timeout }), options.timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ariaSnapshot(options?: TimeoutOptions): Promise<string> {
|
||||||
|
const result = await this._frame._channel.ariaSnapshot({ ...options, selector: this._selector });
|
||||||
|
return result.snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
async scrollIntoViewIfNeeded(options: channels.ElementHandleScrollIntoViewIfNeededOptions = {}) {
|
async scrollIntoViewIfNeeded(options: channels.ElementHandleScrollIntoViewIfNeededOptions = {}) {
|
||||||
return await this._withElement((h, timeout) => h.scrollIntoViewIfNeeded({ ...options, timeout }), options.timeout);
|
return await this._withElement((h, timeout) => h.scrollIntoViewIfNeeded({ ...options, timeout }), options.timeout);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1424,6 +1424,13 @@ scheme.FrameAddStyleTagParams = tObject({
|
||||||
scheme.FrameAddStyleTagResult = tObject({
|
scheme.FrameAddStyleTagResult = tObject({
|
||||||
element: tChannel(['ElementHandle']),
|
element: tChannel(['ElementHandle']),
|
||||||
});
|
});
|
||||||
|
scheme.FrameAriaSnapshotParams = tObject({
|
||||||
|
selector: tString,
|
||||||
|
timeout: tOptional(tNumber),
|
||||||
|
});
|
||||||
|
scheme.FrameAriaSnapshotResult = tObject({
|
||||||
|
snapshot: tString,
|
||||||
|
});
|
||||||
scheme.FrameBlurParams = tObject({
|
scheme.FrameBlurParams = tObject({
|
||||||
selector: tString,
|
selector: tString,
|
||||||
strict: tOptional(tBoolean),
|
strict: tOptional(tBoolean),
|
||||||
|
|
|
||||||
|
|
@ -267,4 +267,8 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Br
|
||||||
result.received = serializeResult(result.received);
|
result.received = serializeResult(result.received);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ariaSnapshot(params: channels.FrameAriaSnapshotParams, metadata: CallMetadata): Promise<channels.FrameAriaSnapshotResult> {
|
||||||
|
return { snapshot: await this._frame.ariaSnapshot(metadata, params.selector, params) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -789,6 +789,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
return this._page._delegate.getBoundingBox(this);
|
return this._page._delegate.getBoundingBox(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ariaSnapshot(): Promise<string> {
|
||||||
|
return await this.evaluateInUtility(([injected, element]) => injected.ariaSnapshot(element), {});
|
||||||
|
}
|
||||||
|
|
||||||
async screenshot(metadata: CallMetadata, options: ScreenshotOptions & TimeoutOptions = {}): Promise<Buffer> {
|
async screenshot(metadata: CallMetadata, options: ScreenshotOptions & TimeoutOptions = {}): Promise<Buffer> {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
return controller.run(
|
return controller.run(
|
||||||
|
|
|
||||||
|
|
@ -1405,6 +1405,13 @@ export class Frame extends SdkObject {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ariaSnapshot(metadata: CallMetadata, selector: string, options: types.TimeoutOptions = {}): Promise<string> {
|
||||||
|
const controller = new ProgressController(metadata, this);
|
||||||
|
return controller.run(async progress => {
|
||||||
|
return await this._retryWithProgressIfNotConnected(progress, selector, true /* strict */, true /* performActionPreChecks */, handle => handle.ariaSnapshot());
|
||||||
|
}, this._page._timeoutSettings.timeout(options));
|
||||||
|
}
|
||||||
|
|
||||||
async expect(metadata: CallMetadata, selector: string, options: FrameExpectParams): Promise<{ matches: boolean, received?: any, log?: string[], timedOut?: boolean }> {
|
async expect(metadata: CallMetadata, selector: string, options: FrameExpectParams): Promise<{ matches: boolean, received?: any, log?: string[], timedOut?: boolean }> {
|
||||||
const result = await this._expectImpl(metadata, selector, options);
|
const result = await this._expectImpl(metadata, selector, options);
|
||||||
// Library mode special case for the expect errors which are return values, not exceptions.
|
// Library mode special case for the expect errors which are return values, not exceptions.
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import { escapeForTextSelector } from '../../utils/isomorphic/stringUtils';
|
||||||
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
|
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
|
||||||
import type { Language } from '../../utils/isomorphic/locatorGenerators';
|
import type { Language } from '../../utils/isomorphic/locatorGenerators';
|
||||||
import type { InjectedScript } from './injectedScript';
|
import type { InjectedScript } from './injectedScript';
|
||||||
import { renderedAriaTree } from './ariaSnapshot';
|
|
||||||
|
|
||||||
const selectorSymbol = Symbol('selector');
|
const selectorSymbol = Symbol('selector');
|
||||||
|
|
||||||
|
|
@ -86,7 +85,7 @@ class ConsoleAPI {
|
||||||
inspect: (selector: string) => this._inspect(selector),
|
inspect: (selector: string) => this._inspect(selector),
|
||||||
selector: (element: Element) => this._selector(element),
|
selector: (element: Element) => this._selector(element),
|
||||||
generateLocator: (element: Element, language?: Language) => this._generateLocator(element, language),
|
generateLocator: (element: Element, language?: Language) => this._generateLocator(element, language),
|
||||||
ariaSnapshot: (element?: Element) => renderedAriaTree(element || this._injectedScript.document.body),
|
ariaSnapshot: (element?: Element) => this._injectedScript.ariaSnapshot(element || this._injectedScript.document.body),
|
||||||
resume: () => this._resume(),
|
resume: () => this._resume(),
|
||||||
...new Locator(injectedScript, ''),
|
...new Locator(injectedScript, ''),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -206,8 +206,10 @@ export class InjectedScript {
|
||||||
return new Set<Element>(result.map(r => r.element));
|
return new Set<Element>(result.map(r => r.element));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderedAriaTree(target: Element): string {
|
ariaSnapshot(node: Node): string {
|
||||||
return renderedAriaTree(target);
|
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||||
|
throw this.createStacklessError('Can only capture aria snapshot of Element nodes.');
|
||||||
|
return renderedAriaTree(node as Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
querySelectorAll(selector: ParsedSelector, root: Node): Element[] {
|
querySelectorAll(selector: ParsedSelector, root: Node): Element[] {
|
||||||
|
|
|
||||||
|
|
@ -714,7 +714,7 @@ class TextAssertionTool implements RecorderTool {
|
||||||
name: 'assertSnapshot',
|
name: 'assertSnapshot',
|
||||||
selector: this._hoverHighlight.selector,
|
selector: this._hoverHighlight.selector,
|
||||||
signals: [],
|
signals: [],
|
||||||
snapshot: this._recorder.injectedScript.renderedAriaTree(target),
|
snapshot: this._recorder.injectedScript.ariaSnapshot(target),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
this._hoverHighlight = this._recorder.injectedScript.generateSelector(target, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true });
|
this._hoverHighlight = this._recorder.injectedScript.generateSelector(target, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true });
|
||||||
|
|
|
||||||
51
packages/playwright-core/types/types.d.ts
vendored
51
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -12424,6 +12424,57 @@ export interface Locator {
|
||||||
*/
|
*/
|
||||||
and(locator: Locator): Locator;
|
and(locator: Locator): Locator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures the aria snapshot of the given element. See
|
||||||
|
* [expect(locator).toMatchAriaSnapshot(expected[, options])](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-match-aria-snapshot)
|
||||||
|
* for the corresponding assertion.
|
||||||
|
*
|
||||||
|
* **Usage**
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* await page.getByRole('link').ariaSnapshot();
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* **Details**
|
||||||
|
*
|
||||||
|
* This method captures the aria snapshot of the given element. The snapshot is a string that represents the state of
|
||||||
|
* the element and its children. The snapshot can be used to assert the state of the element in the test, or to
|
||||||
|
* compare it to state in the future.
|
||||||
|
*
|
||||||
|
* The ARIA snapshot is represented using [YAML](https://yaml.org/spec/1.2.2/) markup language:
|
||||||
|
* - The keys of the objects are the roles and optional accessible names of the elements.
|
||||||
|
* - The values are either text content or an array of child elements.
|
||||||
|
* - Generic static text can be represented with the `text` key.
|
||||||
|
*
|
||||||
|
* Below is the HTML markup and the respective ARIA snapshot:
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <ul aria-label="Links">
|
||||||
|
* <li><a href="/">Home</a></li>
|
||||||
|
* <li><a href="/about">About</a></li>
|
||||||
|
* <ul>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```yml
|
||||||
|
* - list "Links":
|
||||||
|
* - listitem:
|
||||||
|
* - link "Home"
|
||||||
|
* - listitem:
|
||||||
|
* - link "About"
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
ariaSnapshot(options?: {
|
||||||
|
/**
|
||||||
|
* Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`
|
||||||
|
* option in the config, or by using the
|
||||||
|
* [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout)
|
||||||
|
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.
|
||||||
|
*/
|
||||||
|
timeout?: number;
|
||||||
|
}): Promise<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur) on the element.
|
* Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur) on the element.
|
||||||
* @param options
|
* @param options
|
||||||
|
|
|
||||||
|
|
@ -2509,6 +2509,7 @@ export interface FrameChannel extends FrameEventTarget, Channel {
|
||||||
evalOnSelectorAll(params: FrameEvalOnSelectorAllParams, metadata?: CallMetadata): Promise<FrameEvalOnSelectorAllResult>;
|
evalOnSelectorAll(params: FrameEvalOnSelectorAllParams, metadata?: CallMetadata): Promise<FrameEvalOnSelectorAllResult>;
|
||||||
addScriptTag(params: FrameAddScriptTagParams, metadata?: CallMetadata): Promise<FrameAddScriptTagResult>;
|
addScriptTag(params: FrameAddScriptTagParams, metadata?: CallMetadata): Promise<FrameAddScriptTagResult>;
|
||||||
addStyleTag(params: FrameAddStyleTagParams, metadata?: CallMetadata): Promise<FrameAddStyleTagResult>;
|
addStyleTag(params: FrameAddStyleTagParams, metadata?: CallMetadata): Promise<FrameAddStyleTagResult>;
|
||||||
|
ariaSnapshot(params: FrameAriaSnapshotParams, metadata?: CallMetadata): Promise<FrameAriaSnapshotResult>;
|
||||||
blur(params: FrameBlurParams, metadata?: CallMetadata): Promise<FrameBlurResult>;
|
blur(params: FrameBlurParams, metadata?: CallMetadata): Promise<FrameBlurResult>;
|
||||||
check(params: FrameCheckParams, metadata?: CallMetadata): Promise<FrameCheckResult>;
|
check(params: FrameCheckParams, metadata?: CallMetadata): Promise<FrameCheckResult>;
|
||||||
click(params: FrameClickParams, metadata?: CallMetadata): Promise<FrameClickResult>;
|
click(params: FrameClickParams, metadata?: CallMetadata): Promise<FrameClickResult>;
|
||||||
|
|
@ -2613,6 +2614,16 @@ export type FrameAddStyleTagOptions = {
|
||||||
export type FrameAddStyleTagResult = {
|
export type FrameAddStyleTagResult = {
|
||||||
element: ElementHandleChannel,
|
element: ElementHandleChannel,
|
||||||
};
|
};
|
||||||
|
export type FrameAriaSnapshotParams = {
|
||||||
|
selector: string,
|
||||||
|
timeout?: number,
|
||||||
|
};
|
||||||
|
export type FrameAriaSnapshotOptions = {
|
||||||
|
timeout?: number,
|
||||||
|
};
|
||||||
|
export type FrameAriaSnapshotResult = {
|
||||||
|
snapshot: string,
|
||||||
|
};
|
||||||
export type FrameBlurParams = {
|
export type FrameBlurParams = {
|
||||||
selector: string,
|
selector: string,
|
||||||
strict?: boolean,
|
strict?: boolean,
|
||||||
|
|
|
||||||
|
|
@ -1875,6 +1875,13 @@ Frame:
|
||||||
flags:
|
flags:
|
||||||
snapshot: true
|
snapshot: true
|
||||||
|
|
||||||
|
ariaSnapshot:
|
||||||
|
parameters:
|
||||||
|
selector: string
|
||||||
|
timeout: number?
|
||||||
|
returns:
|
||||||
|
snapshot: string
|
||||||
|
|
||||||
blur:
|
blur:
|
||||||
parameters:
|
parameters:
|
||||||
selector: string
|
selector: string
|
||||||
|
|
|
||||||
40
tests/page/page-aria-snapshot.spec.ts
Normal file
40
tests/page/page-aria-snapshot.spec.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
* Modifications copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test as it, expect } from './pageTest';
|
||||||
|
|
||||||
|
it('should snapshot the check box @smoke', async ({ page }) => {
|
||||||
|
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
|
||||||
|
expect(await page.locator('body').ariaSnapshot()).toBe('- checkbox');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should snapshot nested element', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div>
|
||||||
|
<input id='checkbox' type='checkbox'></input>
|
||||||
|
</div>`);
|
||||||
|
expect(await page.locator('body').ariaSnapshot()).toBe('- checkbox');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should snapshot fragment', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<div>
|
||||||
|
<a href="about:blank">Link</a>
|
||||||
|
<a href="about:blank">Link</a>
|
||||||
|
</div>`);
|
||||||
|
expect(await page.locator('body').ariaSnapshot()).toBe(`- link "Link"\n- link "Link"`);
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue