chore: use objects for string aria template notes (#33371)

This commit is contained in:
Pavel Feldman 2024-10-30 17:25:51 -07:00 коммит произвёл GitHub
Родитель a43b99368e
Коммит 676f014b5f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 93 добавлений и 56 удалений

Просмотреть файл

@ -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"
}
}