chore: use objects for string aria template notes (#33371)
This commit is contained in:
Родитель
a43b99368e
Коммит
676f014b5f
|
@ -14,19 +14,19 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { AriaTemplateNode } from './injected/ariaSnapshot';
|
||||
import type { AriaTemplateNode, AriaTemplateRoleNode } from './injected/ariaSnapshot';
|
||||
import { yaml } from '../utilsBundle';
|
||||
import type { AriaRole } from '@injected/roleUtils';
|
||||
import { assert } from '../utils';
|
||||
|
||||
export function parseAriaSnapshot(text: string): AriaTemplateNode {
|
||||
const fragment = yaml.parse(text) as any[];
|
||||
const result: AriaTemplateNode = { role: 'fragment' };
|
||||
const result: AriaTemplateNode = { kind: 'role', role: 'fragment' };
|
||||
populateNode(result, fragment);
|
||||
return result;
|
||||
}
|
||||
|
||||
function populateNode(node: AriaTemplateNode, container: any[]) {
|
||||
function populateNode(node: AriaTemplateRoleNode, container: any[]) {
|
||||
for (const object of container) {
|
||||
if (typeof object === 'string') {
|
||||
const childNode = parseKey(object);
|
||||
|
@ -36,17 +36,33 @@ function populateNode(node: AriaTemplateNode, container: any[]) {
|
|||
}
|
||||
|
||||
for (const key of Object.keys(object)) {
|
||||
const childNode = parseKey(key);
|
||||
const value = object[key];
|
||||
node.children = node.children || [];
|
||||
const value = object[key];
|
||||
|
||||
if (childNode.role === 'text') {
|
||||
node.children.push(valueOrRegex(value));
|
||||
if (key === 'text') {
|
||||
node.children.push({
|
||||
kind: 'text',
|
||||
text: valueOrRegex(value)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const childNode = parseKey(key);
|
||||
if (childNode.kind === 'text') {
|
||||
node.children.push({
|
||||
kind: 'text',
|
||||
text: valueOrRegex(value)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
node.children.push({ ...childNode, children: [valueOrRegex(value)] });
|
||||
node.children.push({
|
||||
...childNode, children: [{
|
||||
kind: 'text',
|
||||
text: valueOrRegex(value)
|
||||
}]
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -56,7 +72,7 @@ function populateNode(node: AriaTemplateNode, container: any[]) {
|
|||
}
|
||||
}
|
||||
|
||||
function applyAttribute(node: AriaTemplateNode, key: string, value: string) {
|
||||
function applyAttribute(node: AriaTemplateRoleNode, key: string, value: string) {
|
||||
if (key === 'checked') {
|
||||
assert(value === 'true' || value === 'false' || value === 'mixed', 'Value of "disabled" attribute must be a boolean or "mixed"');
|
||||
node.checked = value === 'true' ? true : value === 'false' ? false : 'mixed';
|
||||
|
@ -100,7 +116,7 @@ function parseKey(key: string): AriaTemplateNode {
|
|||
if (tokens.length === 0)
|
||||
throw new Error(`Invalid key ${key}`);
|
||||
|
||||
const role = tokens[0] as AriaRole | 'text';
|
||||
const role = tokens[0] as AriaRole;
|
||||
|
||||
let name: string | RegExp = '';
|
||||
let index = 1;
|
||||
|
@ -115,7 +131,7 @@ function parseKey(key: string): AriaTemplateNode {
|
|||
index = 2;
|
||||
}
|
||||
|
||||
const result: AriaTemplateNode = { role, name };
|
||||
const result: AriaTemplateRoleNode = { kind: 'role', role, name };
|
||||
for (; index < tokens.length; index++) {
|
||||
const attrToken = tokens[index];
|
||||
if (attrToken.startsWith('[') && attrToken.endsWith(']')) {
|
||||
|
|
|
@ -35,12 +35,20 @@ type AriaNode = AriaProps & {
|
|||
children: (AriaNode | string)[];
|
||||
};
|
||||
|
||||
export type AriaTemplateNode = AriaProps & {
|
||||
role: AriaRole | 'fragment' | 'text';
|
||||
name?: RegExp | string;
|
||||
children?: (AriaTemplateNode | string | RegExp)[];
|
||||
export type AriaTemplateTextNode = {
|
||||
kind: 'text';
|
||||
text: RegExp | string;
|
||||
};
|
||||
|
||||
export type AriaTemplateRoleNode = AriaProps & {
|
||||
kind: 'role';
|
||||
role: AriaRole | 'fragment';
|
||||
name?: RegExp | string;
|
||||
children?: AriaTemplateNode[];
|
||||
};
|
||||
|
||||
export type AriaTemplateNode = AriaTemplateRoleNode | AriaTemplateTextNode;
|
||||
|
||||
export function generateAriaTree(rootElement: Element): AriaNode {
|
||||
const visit = (ariaNode: AriaNode, node: Node) => {
|
||||
if (node.nodeType === Node.TEXT_NODE && node.nodeValue) {
|
||||
|
@ -172,7 +180,7 @@ function normalizeStringChildren(rootA11yNode: AriaNode) {
|
|||
|
||||
const normalizeWhitespaceWithin = (text: string) => text.replace(/[\u200b\s\t\r\n]+/g, ' ');
|
||||
|
||||
function matchesText(text: string | undefined, template: RegExp | string | undefined) {
|
||||
function matchesText(text: string, template: RegExp | string | undefined): boolean {
|
||||
if (!template)
|
||||
return true;
|
||||
if (!text)
|
||||
|
@ -182,7 +190,20 @@ function matchesText(text: string | undefined, template: RegExp | string | undef
|
|||
return !!text.match(template);
|
||||
}
|
||||
|
||||
export function matchesAriaTree(rootElement: Element, template: AriaTemplateNode): { matches: boolean, received: { raw: string, regex: string } } {
|
||||
function matchesTextNode(text: string, template: AriaTemplateTextNode) {
|
||||
return matchesText(text, template.text);
|
||||
}
|
||||
|
||||
function matchesName(text: string, template: AriaTemplateRoleNode) {
|
||||
return matchesText(text, template.name);
|
||||
}
|
||||
|
||||
export type MatcherReceived = {
|
||||
raw: string;
|
||||
regex: string;
|
||||
};
|
||||
|
||||
export function matchesAriaTree(rootElement: Element, template: AriaTemplateNode): { matches: boolean, received: MatcherReceived } {
|
||||
const root = generateAriaTree(rootElement);
|
||||
const matches = matchesNodeDeep(root, template);
|
||||
return {
|
||||
|
@ -197,11 +218,11 @@ export function matchesAriaTree(rootElement: Element, template: AriaTemplateNode
|
|||
};
|
||||
}
|
||||
|
||||
function matchesNode(node: AriaNode | string, template: AriaTemplateNode | RegExp | string, depth: number): boolean {
|
||||
if (typeof node === 'string' && (typeof template === 'string' || template instanceof RegExp))
|
||||
return matchesText(node, template);
|
||||
function matchesNode(node: AriaNode | string, template: AriaTemplateNode, depth: number): boolean {
|
||||
if (typeof node === 'string' && template.kind === 'text')
|
||||
return matchesTextNode(node, template);
|
||||
|
||||
if (typeof node === 'object' && typeof template === 'object' && !(template instanceof RegExp)) {
|
||||
if (typeof node === 'object' && template.kind === 'role') {
|
||||
if (template.role !== 'fragment' && template.role !== node.role)
|
||||
return false;
|
||||
if (template.checked !== undefined && template.checked !== node.checked)
|
||||
|
@ -216,7 +237,7 @@ function matchesNode(node: AriaNode | string, template: AriaTemplateNode | RegEx
|
|||
return false;
|
||||
if (template.selected !== undefined && template.selected !== node.selected)
|
||||
return false;
|
||||
if (!matchesText(node.name, template.name))
|
||||
if (!matchesName(node.name, template))
|
||||
return false;
|
||||
if (!containsList(node.children || [], template.children || [], depth))
|
||||
return false;
|
||||
|
@ -225,7 +246,7 @@ function matchesNode(node: AriaNode | string, template: AriaTemplateNode | RegEx
|
|||
return false;
|
||||
}
|
||||
|
||||
function containsList(children: (AriaNode | string)[], template: (AriaTemplateNode | RegExp | string)[], depth: number): boolean {
|
||||
function containsList(children: (AriaNode | string)[], template: AriaTemplateNode[], depth: number): boolean {
|
||||
if (template.length > children.length)
|
||||
return false;
|
||||
const cc = children.slice();
|
||||
|
@ -363,6 +384,9 @@ function includeText(node: AriaNode, text: string): boolean {
|
|||
if (!node.name)
|
||||
return true;
|
||||
|
||||
if (node.name.length > text.length)
|
||||
return false;
|
||||
|
||||
// Figure out if text adds any value.
|
||||
const substr = longestCommonSubstring(text, node.name);
|
||||
let filtered = text;
|
||||
|
|
|
@ -23,6 +23,7 @@ import { EXPECTED_COLOR } from '../common/expectBundle';
|
|||
import { callLogText } from '../util';
|
||||
import { printReceivedStringContainExpectedSubstring } from './expect';
|
||||
import { currentTestInfo } from '../common/globals';
|
||||
import type { MatcherReceived } from '@injected/ariaSnapshot';
|
||||
|
||||
export async function toMatchAriaSnapshot(
|
||||
this: ExpectMatcherState,
|
||||
|
@ -70,11 +71,7 @@ export async function toMatchAriaSnapshot(
|
|||
const timeout = options.timeout ?? this.timeout;
|
||||
expected = unshift(expected);
|
||||
const { matches: pass, received, log, timedOut } = await receiver._expect('to.match.aria', { expectedValue: expected, isNot: this.isNot, timeout });
|
||||
const typedReceived = received as {
|
||||
raw: string;
|
||||
noText: string;
|
||||
regex: string;
|
||||
} | typeof kNoElementsFoundError;
|
||||
const typedReceived = received as MatcherReceived | typeof kNoElementsFoundError;
|
||||
|
||||
const messagePrefix = matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined);
|
||||
const notFound = typedReceived === kNoElementsFoundError;
|
||||
|
@ -94,12 +91,12 @@ export async function toMatchAriaSnapshot(
|
|||
if (notFound)
|
||||
return messagePrefix + `Expected: not ${this.utils.printExpected(escapedExpected)}\nReceived: ${escapedReceived}` + callLogText(log);
|
||||
const printedReceived = printReceivedStringContainExpectedSubstring(escapedReceived, escapedReceived.indexOf(escapedExpected), escapedExpected.length);
|
||||
return messagePrefix + `Expected: not ${this.utils.printExpected(escapedExpected)}\nReceived string: ${printedReceived}` + callLogText(log);
|
||||
return messagePrefix + `Expected: not ${this.utils.printExpected(escapedExpected)}\nReceived: ${printedReceived}` + callLogText(log);
|
||||
} else {
|
||||
const labelExpected = `Expected`;
|
||||
if (notFound)
|
||||
return messagePrefix + `${labelExpected}: ${this.utils.printExpected(escapedExpected)}\nReceived: ${escapedReceived}` + callLogText(log);
|
||||
return messagePrefix + this.utils.printDiffOrStringify(escapedExpected, escapedReceived, labelExpected, 'Received string', false) + callLogText(log);
|
||||
return messagePrefix + this.utils.printDiffOrStringify(escapedExpected, escapedReceived, labelExpected, 'Received', false) + callLogText(log);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -396,8 +396,8 @@ test('expected formatter', async ({ page }) => {
|
|||
|
||||
expect(stripAnsi(error.message)).toContain(`
|
||||
Locator: locator('body')
|
||||
- Expected - 2
|
||||
+ Received string + 3
|
||||
- Expected - 2
|
||||
+ Received + 3
|
||||
|
||||
- - heading "todos"
|
||||
- - textbox "Wrong text"
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@playwright/test": "1.49.0-alpha-2024-10-26"
|
||||
"@playwright/test": "1.49.0-alpha-2024-10-29"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.49.0-alpha-2024-10-26",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0-alpha-2024-10-26.tgz",
|
||||
"integrity": "sha512-EUl8wIsAWVJJlX2ynKdY1KxRs44Yz9MPDmN8AH6HIdwazSRe1ML46kaM3V49gQvMVMo5JZfuXnRzbtYDMFpKYA==",
|
||||
"version": "1.49.0-alpha-2024-10-29",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0-alpha-2024-10-29.tgz",
|
||||
"integrity": "sha512-JyT6BHjuJl5Iv91PvaYa1RXRQfSwHk1Abq/hzYFpebQQuKKNr3pck55qmih39+S/bGsuYW6XdzqAX+CfknR3sA==",
|
||||
"dependencies": {
|
||||
"playwright": "1.49.0-alpha-2024-10-26"
|
||||
"playwright": "1.49.0-alpha-2024-10-29"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
@ -36,11 +36,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.49.0-alpha-2024-10-26",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0-alpha-2024-10-26.tgz",
|
||||
"integrity": "sha512-1qh/6z4UdWv7qMocNQmUMbvZAXzzS93jckUzjGr0mWMn9rs4QavHhuK0s2HIS0hLB+t5T1+NBUpHudWzeasudA==",
|
||||
"version": "1.49.0-alpha-2024-10-29",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0-alpha-2024-10-29.tgz",
|
||||
"integrity": "sha512-ypwaWQwpxAiB5JEz4ACrztZsII4BdD5zOuAnjPtiXZtemSZNwxxY7phKlX8nLUlGwWDpb8aGe9tBcxoyrcFIww==",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.49.0-alpha-2024-10-26"
|
||||
"playwright-core": "1.49.0-alpha-2024-10-29"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
@ -53,9 +53,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.49.0-alpha-2024-10-26",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0-alpha-2024-10-26.tgz",
|
||||
"integrity": "sha512-ELIdRRHkdzkHP7siPcFSE9jBLRnDHE1l3UigIgEzVN9o34yGBgH8TAkC2uK1M8Jrkomc3jKQm5faiBsimu0XEQ==",
|
||||
"version": "1.49.0-alpha-2024-10-29",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0-alpha-2024-10-29.tgz",
|
||||
"integrity": "sha512-pJmBdOnVFzBzA6Jo1q7FtJferyLK0a2cNZGbuOMO0LOPWY7FOT91225TYZ9a1qgaYMav+uudmYw6im/qjEwmIQ==",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
|
@ -66,11 +66,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@playwright/test": {
|
||||
"version": "1.49.0-alpha-2024-10-26",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0-alpha-2024-10-26.tgz",
|
||||
"integrity": "sha512-EUl8wIsAWVJJlX2ynKdY1KxRs44Yz9MPDmN8AH6HIdwazSRe1ML46kaM3V49gQvMVMo5JZfuXnRzbtYDMFpKYA==",
|
||||
"version": "1.49.0-alpha-2024-10-29",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0-alpha-2024-10-29.tgz",
|
||||
"integrity": "sha512-JyT6BHjuJl5Iv91PvaYa1RXRQfSwHk1Abq/hzYFpebQQuKKNr3pck55qmih39+S/bGsuYW6XdzqAX+CfknR3sA==",
|
||||
"requires": {
|
||||
"playwright": "1.49.0-alpha-2024-10-26"
|
||||
"playwright": "1.49.0-alpha-2024-10-29"
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
|
@ -80,18 +80,18 @@
|
|||
"optional": true
|
||||
},
|
||||
"playwright": {
|
||||
"version": "1.49.0-alpha-2024-10-26",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0-alpha-2024-10-26.tgz",
|
||||
"integrity": "sha512-1qh/6z4UdWv7qMocNQmUMbvZAXzzS93jckUzjGr0mWMn9rs4QavHhuK0s2HIS0hLB+t5T1+NBUpHudWzeasudA==",
|
||||
"version": "1.49.0-alpha-2024-10-29",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0-alpha-2024-10-29.tgz",
|
||||
"integrity": "sha512-ypwaWQwpxAiB5JEz4ACrztZsII4BdD5zOuAnjPtiXZtemSZNwxxY7phKlX8nLUlGwWDpb8aGe9tBcxoyrcFIww==",
|
||||
"requires": {
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.49.0-alpha-2024-10-26"
|
||||
"playwright-core": "1.49.0-alpha-2024-10-29"
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.49.0-alpha-2024-10-26",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0-alpha-2024-10-26.tgz",
|
||||
"integrity": "sha512-ELIdRRHkdzkHP7siPcFSE9jBLRnDHE1l3UigIgEzVN9o34yGBgH8TAkC2uK1M8Jrkomc3jKQm5faiBsimu0XEQ=="
|
||||
"version": "1.49.0-alpha-2024-10-29",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0-alpha-2024-10-29.tgz",
|
||||
"integrity": "sha512-pJmBdOnVFzBzA6Jo1q7FtJferyLK0a2cNZGbuOMO0LOPWY7FOT91225TYZ9a1qgaYMav+uudmYw6im/qjEwmIQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@playwright/test": "1.49.0-alpha-2024-10-26"
|
||||
"@playwright/test": "1.49.0-alpha-2024-10-29"
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче