chore: add a basic snapshot generator test (#33123)
This commit is contained in:
parent
4b1fbde2ad
commit
b421bd8b0d
|
|
@ -2106,15 +2106,14 @@ Expected options currently selected.
|
|||
|
||||
## async method: LocatorAssertions.toMatchAriaSnapshot
|
||||
* since: v1.49
|
||||
* langs: js
|
||||
* langs:
|
||||
- alias-java: matchesAriaSnapshot
|
||||
|
||||
Asserts that the target element matches the given accessibility snapshot.
|
||||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
import { role as x } from '@playwright/test';
|
||||
// ...
|
||||
await page.goto('https://demo.playwright.dev/todomvc/');
|
||||
await expect(page.locator('body')).toMatchAriaSnapshot(`
|
||||
- heading "todos"
|
||||
|
|
@ -2122,11 +2121,41 @@ await expect(page.locator('body')).toMatchAriaSnapshot(`
|
|||
`);
|
||||
```
|
||||
|
||||
```python async
|
||||
await page.goto('https://demo.playwright.dev/todomvc/')
|
||||
await expect(page.locator('body')).to_match_aria_snapshot('''
|
||||
- heading "todos"
|
||||
- textbox "What needs to be done?"
|
||||
''')
|
||||
```
|
||||
|
||||
```python sync
|
||||
page.goto('https://demo.playwright.dev/todomvc/')
|
||||
expect(page.locator('body')).to_match_aria_snapshot('''
|
||||
- heading "todos"
|
||||
- textbox "What needs to be done?"
|
||||
''')
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.GotoAsync("https://demo.playwright.dev/todomvc/");
|
||||
await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(@"
|
||||
- heading ""todos""
|
||||
- textbox ""What needs to be done?""
|
||||
");
|
||||
```
|
||||
|
||||
```java
|
||||
page.navigate("https://demo.playwright.dev/todomvc/");
|
||||
assertThat(page.locator("body")).matchesAriaSnapshot("""
|
||||
- heading "todos"
|
||||
- textbox "What needs to be done?"
|
||||
""");
|
||||
```
|
||||
|
||||
### param: LocatorAssertions.toMatchAriaSnapshot.expected
|
||||
* since: v1.49
|
||||
* langs: js
|
||||
- `expected` <string>
|
||||
|
||||
### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-js-assertions-timeout-%%
|
||||
* since: v1.49
|
||||
* langs: js
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ This project incorporates components from the projects listed below. The origina
|
|||
- stack-utils@2.0.5 (https://github.com/tapjs/stack-utils)
|
||||
- wrappy@1.0.2 (https://github.com/npm/wrappy)
|
||||
- ws@8.17.1 (https://github.com/websockets/ws)
|
||||
- yaml@2.6.0 (https://github.com/eemeli/yaml)
|
||||
- yauzl@2.10.0 (https://github.com/thejoshwolfe/yauzl)
|
||||
- yazl@2.5.1 (https://github.com/thejoshwolfe/yazl)
|
||||
|
||||
|
|
@ -1121,6 +1122,24 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
=========================================
|
||||
END OF ws@8.17.1 AND INFORMATION
|
||||
|
||||
%% yaml@2.6.0 NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
Copyright Eemeli Aro <eemeli@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
=========================================
|
||||
END OF yaml@2.6.0 AND INFORMATION
|
||||
|
||||
%% yauzl@2.10.0 NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
The MIT License (MIT)
|
||||
|
|
@ -1175,6 +1194,6 @@ END OF yazl@2.5.1 AND INFORMATION
|
|||
|
||||
SUMMARY BEGIN HERE
|
||||
=========================================
|
||||
Total Packages: 46
|
||||
Total Packages: 47
|
||||
=========================================
|
||||
END OF SUMMARY
|
||||
|
|
@ -25,7 +25,8 @@
|
|||
"signal-exit": "3.0.7",
|
||||
"socks-proxy-agent": "8.0.4",
|
||||
"stack-utils": "2.0.5",
|
||||
"ws": "8.17.1"
|
||||
"ws": "8.17.1",
|
||||
"yaml": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.7",
|
||||
|
|
@ -432,6 +433,17 @@
|
|||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
||||
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -726,6 +738,11 @@
|
|||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
||||
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"signal-exit": "3.0.7",
|
||||
"socks-proxy-agent": "8.0.4",
|
||||
"stack-utils": "2.0.5",
|
||||
"yaml": "^2.5.1",
|
||||
"ws": "8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ export { SocksProxyAgent } from 'socks-proxy-agent';
|
|||
import StackUtilsLibrary from 'stack-utils';
|
||||
export const StackUtils = StackUtilsLibrary;
|
||||
|
||||
import yamlLibrary from 'yaml';
|
||||
export const yaml = yamlLibrary;
|
||||
|
||||
// @ts-ignore
|
||||
import wsLibrary, { WebSocketServer, Receiver, Sender } from 'ws';
|
||||
export const ws = wsLibrary;
|
||||
|
|
|
|||
74
packages/playwright-core/src/server/ariaSnapshot.ts
Normal file
74
packages/playwright-core/src/server/ariaSnapshot.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* 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 type { AriaTemplateNode } from './injected/ariaSnapshot';
|
||||
import { yaml } from '../utilsBundle';
|
||||
|
||||
export function parseAriaSnapshot(text: string): AriaTemplateNode {
|
||||
type YamlNode = Record<string, Array<YamlNode> | string>;
|
||||
|
||||
const parseKey = (key: string): AriaTemplateNode => {
|
||||
if (!key)
|
||||
return { role: '' };
|
||||
|
||||
const match = key.match(/^([a-z]+)(?:\s+(?:"([^"]*)"|\/([^\/]*)\/))?$/);
|
||||
|
||||
if (!match)
|
||||
throw new Error(`Invalid key ${key}`);
|
||||
|
||||
const role = match[1];
|
||||
if (role && role !== 'text' && !allRoles.includes(role))
|
||||
throw new Error(`Invalid role ${role}`);
|
||||
|
||||
if (match[2])
|
||||
return { role, name: match[2] };
|
||||
if (match[3])
|
||||
return { role, name: new RegExp(match[3]) };
|
||||
return { role };
|
||||
};
|
||||
|
||||
const valueOrRegex = (value: string): string | RegExp => {
|
||||
return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : value;
|
||||
};
|
||||
|
||||
const convert = (object: YamlNode | string): AriaTemplateNode | RegExp | string => {
|
||||
const key = typeof object === 'string' ? object : Object.keys(object)[0];
|
||||
const value = typeof object === 'string' ? undefined : object[key];
|
||||
const parsed = parseKey(key);
|
||||
if (parsed.role === 'text') {
|
||||
if (typeof value !== 'string')
|
||||
throw new Error(`Generic role must have a text value`);
|
||||
return valueOrRegex(value as string);
|
||||
}
|
||||
if (Array.isArray(value))
|
||||
parsed.children = value.map(convert);
|
||||
else if (value)
|
||||
parsed.children = [valueOrRegex(value)];
|
||||
return parsed;
|
||||
};
|
||||
const fragment = yaml.parse(text) as YamlNode[];
|
||||
return convert({ '': fragment }) as AriaTemplateNode;
|
||||
}
|
||||
|
||||
const allRoles = [
|
||||
'alert', 'alertdialog', 'application', 'article', 'banner', 'blockquote', 'button', 'caption', 'cell', 'checkbox', 'code', 'columnheader', 'combobox', 'command',
|
||||
'complementary', 'composite', 'contentinfo', 'definition', 'deletion', 'dialog', 'directory', 'document', 'emphasis', 'feed', 'figure', 'form', 'generic', 'grid',
|
||||
'gridcell', 'group', 'heading', 'img', 'input', 'insertion', 'landmark', 'link', 'list', 'listbox', 'listitem', 'log', 'main', 'marquee', 'math', 'meter', 'menu',
|
||||
'menubar', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'navigation', 'none', 'note', 'option', 'paragraph', 'presentation', 'progressbar', 'radio', 'radiogroup',
|
||||
'range', 'region', 'roletype', 'row', 'rowgroup', 'rowheader', 'scrollbar', 'search', 'searchbox', 'section', 'sectionhead', 'select', 'separator', 'slider',
|
||||
'spinbutton', 'status', 'strong', 'structure', 'subscript', 'superscript', 'switch', 'tab', 'table', 'tablist', 'tabpanel', 'term', 'textbox', 'time', 'timer',
|
||||
'toolbar', 'tooltip', 'tree', 'treegrid', 'treeitem', 'widget', 'window'
|
||||
];
|
||||
|
|
@ -26,6 +26,7 @@ import type { CallMetadata } from '../instrumentation';
|
|||
import type { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||
import type { PageDispatcher } from './pageDispatcher';
|
||||
import { debugAssert } from '../../utils';
|
||||
import { parseAriaSnapshot } from '../ariaSnapshot';
|
||||
|
||||
export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, BrowserContextDispatcher | PageDispatcher> implements channels.FrameChannel {
|
||||
_type_Frame = true;
|
||||
|
|
@ -258,7 +259,9 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Br
|
|||
|
||||
async expect(params: channels.FrameExpectParams, metadata: CallMetadata): Promise<channels.FrameExpectResult> {
|
||||
metadata.potentiallyClosesScope = true;
|
||||
const expectedValue = params.expectedValue ? parseArgument(params.expectedValue) : undefined;
|
||||
let expectedValue = params.expectedValue ? parseArgument(params.expectedValue) : undefined;
|
||||
if (params.expression === 'to.match.aria' && expectedValue)
|
||||
expectedValue = parseAriaSnapshot(expectedValue);
|
||||
const result = await this._frame.expect(metadata, params.selector, { ...params, expectedValue });
|
||||
if (result.received !== undefined)
|
||||
result.received = serializeResult(result.received);
|
||||
|
|
|
|||
|
|
@ -704,7 +704,7 @@ class TextAssertionTool implements RecorderTool {
|
|||
value: (target as (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)).value,
|
||||
};
|
||||
}
|
||||
} if (this._kind === 'snapshot') {
|
||||
} else if (this._kind === 'snapshot') {
|
||||
this._hoverHighlight = this._recorder.injectedScript.generateSelector(target, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true });
|
||||
this._hoverHighlight.color = '#8acae480';
|
||||
// forTextExpect can update the target, re-highlight it.
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export const PNG: typeof import('../bundles/utils/node_modules/@types/pngjs').PN
|
|||
export const program: typeof import('../bundles/utils/node_modules/commander').program = require('./utilsBundleImpl').program;
|
||||
export const progress: typeof import('../bundles/utils/node_modules/@types/progress') = require('./utilsBundleImpl').progress;
|
||||
export const SocksProxyAgent: typeof import('../bundles/utils/node_modules/socks-proxy-agent').SocksProxyAgent = require('./utilsBundleImpl').SocksProxyAgent;
|
||||
export const yaml: typeof import('../bundles/utils/node_modules/yaml') = require('./utilsBundleImpl').yaml;
|
||||
export const ws: typeof import('../bundles/utils/node_modules/@types/ws') = require('./utilsBundleImpl').ws;
|
||||
export const wsServer: typeof import('../bundles/utils/node_modules/@types/ws').WebSocketServer = require('./utilsBundleImpl').wsServer;
|
||||
export const wsReceiver = require('./utilsBundleImpl').wsReceiver;
|
||||
|
|
|
|||
|
|
@ -155,7 +155,6 @@ This project incorporates components from the projects listed below. The origina
|
|||
- undici-types@6.19.8 (https://github.com/nodejs/undici)
|
||||
- update-browserslist-db@1.0.13 (https://github.com/browserslist/update-db)
|
||||
- yallist@3.1.1 (https://github.com/isaacs/yallist)
|
||||
- yaml@2.5.1 (https://github.com/eemeli/yaml)
|
||||
|
||||
%% @ampproject/remapping@2.2.1 NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
|
|
@ -4398,26 +4397,8 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|||
=========================================
|
||||
END OF yallist@3.1.1 AND INFORMATION
|
||||
|
||||
%% yaml@2.5.1 NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
Copyright Eemeli Aro <eemeli@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
=========================================
|
||||
END OF yaml@2.5.1 AND INFORMATION
|
||||
|
||||
SUMMARY BEGIN HERE
|
||||
=========================================
|
||||
Total Packages: 152
|
||||
Total Packages: 151
|
||||
=========================================
|
||||
END OF SUMMARY
|
||||
19
packages/playwright/bundles/utils/package-lock.json
generated
19
packages/playwright/bundles/utils/package-lock.json
generated
|
|
@ -13,8 +13,7 @@
|
|||
"json5": "2.2.3",
|
||||
"pirates": "4.0.4",
|
||||
"source-map-support": "0.5.21",
|
||||
"stoppable": "1.1.0",
|
||||
"yaml": "^2.5.1"
|
||||
"stoppable": "1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/source-map-support": "^0.5.4",
|
||||
|
|
@ -281,17 +280,6 @@
|
|||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -476,11 +464,6 @@
|
|||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@
|
|||
"json5": "2.2.3",
|
||||
"pirates": "4.0.4",
|
||||
"source-map-support": "0.5.21",
|
||||
"stoppable": "1.1.0",
|
||||
"yaml": "^2.5.1"
|
||||
"stoppable": "1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/source-map-support": "^0.5.4",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,3 @@ export const enquirer = enquirerLibrary;
|
|||
|
||||
import chokidarLibrary from 'chokidar';
|
||||
export const chokidar = chokidarLibrary;
|
||||
|
||||
import yamlLibrary from 'yaml';
|
||||
export const yaml = yamlLibrary;
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@
|
|||
import type { LocatorEx } from './matchers';
|
||||
import type { ExpectMatcherState } from '../../types/test';
|
||||
import { kNoElementsFoundError, matcherHint, type MatcherResult } from './matcherHint';
|
||||
import type { AriaTemplateNode } from 'playwright-core/lib/server/injected/ariaSnapshot';
|
||||
import { yaml } from '../utilsBundle';
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import { EXPECTED_COLOR } from '../common/expectBundle';
|
||||
import { callLogText } from '../util';
|
||||
|
|
@ -46,9 +44,8 @@ export async function toMatchAriaSnapshot(
|
|||
].join('\n\n'));
|
||||
}
|
||||
|
||||
const ariaTree = toAriaTree(expected) as AriaTemplateNode;
|
||||
const timeout = options.timeout ?? this.timeout;
|
||||
const { matches: pass, received, log, timedOut } = await receiver._expect('to.match.aria', { expectedValue: ariaTree, isNot: this.isNot, timeout });
|
||||
const { matches: pass, received, log, timedOut } = await receiver._expect('to.match.aria', { expectedValue: expected, isNot: this.isNot, timeout });
|
||||
|
||||
const messagePrefix = matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined);
|
||||
const notFound = received === kNoElementsFoundError;
|
||||
|
|
@ -76,59 +73,3 @@ export async function toMatchAriaSnapshot(
|
|||
timeout: timedOut ? timeout : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function parseKey(key: string): AriaTemplateNode {
|
||||
if (!key)
|
||||
return { role: '' };
|
||||
|
||||
const match = key.match(/^([a-z]+)(?:\s+(?:"([^"]*)"|\/([^\/]*)\/))?$/);
|
||||
|
||||
if (!match)
|
||||
throw new Error(`Invalid key ${key}`);
|
||||
|
||||
const role = match[1];
|
||||
if (role && role !== 'text' && !allRoles.includes(role))
|
||||
throw new Error(`Invalid role ${role}`);
|
||||
|
||||
if (match[2])
|
||||
return { role, name: match[2] };
|
||||
if (match[3])
|
||||
return { role, name: new RegExp(match[3]) };
|
||||
return { role };
|
||||
}
|
||||
|
||||
function valueOrRegex(value: string): string | RegExp {
|
||||
return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : value;
|
||||
}
|
||||
|
||||
type YamlNode = Record<string, Array<YamlNode> | string>;
|
||||
|
||||
function toAriaTree(text: string): AriaTemplateNode {
|
||||
const convert = (object: YamlNode | string): AriaTemplateNode | RegExp | string => {
|
||||
const key = typeof object === 'string' ? object : Object.keys(object)[0];
|
||||
const value = typeof object === 'string' ? undefined : object[key];
|
||||
const parsed = parseKey(key);
|
||||
if (parsed.role === 'text') {
|
||||
if (typeof value !== 'string')
|
||||
throw new Error(`Generic role must have a text value`);
|
||||
return valueOrRegex(value as string);
|
||||
}
|
||||
if (Array.isArray(value))
|
||||
parsed.children = value.map(convert);
|
||||
else if (value)
|
||||
parsed.children = [valueOrRegex(value)];
|
||||
return parsed;
|
||||
};
|
||||
const fragment = yaml.parse(text) as YamlNode[];
|
||||
return convert({ '': fragment }) as AriaTemplateNode;
|
||||
}
|
||||
|
||||
const allRoles = [
|
||||
'alert', 'alertdialog', 'application', 'article', 'banner', 'blockquote', 'button', 'caption', 'cell', 'checkbox', 'code', 'columnheader', 'combobox', 'command',
|
||||
'complementary', 'composite', 'contentinfo', 'definition', 'deletion', 'dialog', 'directory', 'document', 'emphasis', 'feed', 'figure', 'form', 'generic', 'grid',
|
||||
'gridcell', 'group', 'heading', 'img', 'input', 'insertion', 'landmark', 'link', 'list', 'listbox', 'listitem', 'log', 'main', 'marquee', 'math', 'meter', 'menu',
|
||||
'menubar', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'navigation', 'none', 'note', 'option', 'paragraph', 'presentation', 'progressbar', 'radio', 'radiogroup',
|
||||
'range', 'region', 'roletype', 'row', 'rowgroup', 'rowheader', 'scrollbar', 'search', 'searchbox', 'section', 'sectionhead', 'select', 'separator', 'slider',
|
||||
'spinbutton', 'status', 'strong', 'structure', 'subscript', 'superscript', 'switch', 'tab', 'table', 'tablist', 'tabpanel', 'term', 'textbox', 'time', 'timer',
|
||||
'toolbar', 'tooltip', 'tree', 'treegrid', 'treeitem', 'widget', 'window'
|
||||
];
|
||||
|
|
|
|||
|
|
@ -20,4 +20,3 @@ export const sourceMapSupport: typeof import('../bundles/utils/node_modules/@typ
|
|||
export const stoppable: typeof import('../bundles/utils/node_modules/@types/stoppable') = require('./utilsBundleImpl').stoppable;
|
||||
export const enquirer: typeof import('../bundles/utils/node_modules/enquirer') = require('./utilsBundleImpl').enquirer;
|
||||
export const chokidar: typeof import('../bundles/utils/node_modules/chokidar') = require('./utilsBundleImpl').chokidar;
|
||||
export const yaml: typeof import('../bundles/utils/node_modules/yaml') = require('./utilsBundleImpl').yaml;
|
||||
|
|
|
|||
2
packages/playwright/types/test.d.ts
vendored
2
packages/playwright/types/test.d.ts
vendored
|
|
@ -7644,8 +7644,6 @@ interface LocatorAssertions {
|
|||
* **Usage**
|
||||
*
|
||||
* ```js
|
||||
* import { role as x } from '@playwright/test';
|
||||
* // ...
|
||||
* await page.goto('https://demo.playwright.dev/todomvc/');
|
||||
* await expect(page.locator('body')).toMatchAriaSnapshot(`
|
||||
* - heading "todos"
|
||||
|
|
|
|||
42
tests/library/inspector/cli-codegen-aria.spec.ts
Normal file
42
tests/library/inspector/cli-codegen-aria.spec.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* 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, expect } from './inspectorTest';
|
||||
|
||||
test.describe(() => {
|
||||
test.skip(({ mode }) => mode !== 'default');
|
||||
test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events');
|
||||
|
||||
test('should generate aria snapshot', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<main><button>Submit</button></main>`);
|
||||
|
||||
await recorder.page.click('x-pw-tool-item.snapshot');
|
||||
await recorder.page.hover('button');
|
||||
await recorder.trustedClick();
|
||||
|
||||
await expect.poll(() =>
|
||||
recorder.text('JavaScript')).toContain(`await expect(page.getByRole('button')).toMatchAriaSnapshot(\`- button "Submit"\`);`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('Python')).toContain(`expect(page.get_by_role("button")).to_match_aria_snapshot("- button \\"Submit\\"")`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('Python Async')).toContain(`await expect(page.get_by_role(\"button\")).to_match_aria_snapshot("- button \\"Submit\\"")`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('Java')).toContain(`assertThat(page.getByRole(AriaRole.BUTTON)).matchesAriaSnapshot("- button \\"Submit\\"");`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('C#')).toContain(`await Expect(page.GetByRole(AriaRole.Button)).ToMatchAriaSnapshotAsync("- button \\"Submit\\"");`);
|
||||
});
|
||||
});
|
||||
|
|
@ -160,6 +160,15 @@ export class Recorder {
|
|||
return this._sources;
|
||||
}
|
||||
|
||||
async text(file: string): Promise<string> {
|
||||
const sources: Source[] = await this.recorderPage.evaluate(() => (window as any).playwrightSourcesEchoForTest || []);
|
||||
for (const source of sources) {
|
||||
if (codegenLangId2lang.get(source.id) === file)
|
||||
return source.text;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async waitForHighlight(action: () => Promise<void>): Promise<string> {
|
||||
await this.page.$$eval('x-pw-highlight', els => els.forEach(e => e.remove()));
|
||||
await this.page.$$eval('x-pw-tooltip', els => els.forEach(e => e.remove()));
|
||||
|
|
|
|||
Loading…
Reference in a new issue