Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions packages/playwright-core/src/server/injected/ariaSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type AriaNode = AriaProps & {
name: string;
children: (AriaNode | string)[];
element: Element;
props: Record<string, string>;
};

export type AriaSnapshot = {
Expand All @@ -40,7 +41,7 @@ export function generateAriaTree(rootElement: Element, generation: number): Aria
const visited = new Set<Node>();

const snapshot: AriaSnapshot = {
root: { role: 'fragment', name: '', children: [], element: rootElement },
root: { role: 'fragment', name: '', children: [], element: rootElement, props: {} },
elements: new Map<number, Element>(),
generation,
ids: new Map<Element, number>(),
Expand Down Expand Up @@ -124,6 +125,11 @@ export function generateAriaTree(rootElement: Element, generation: number): Aria

if (ariaNode.children.length === 1 && ariaNode.name === ariaNode.children[0])
ariaNode.children = [];

if (ariaNode.role === 'link' && element.hasAttribute('href')) {
const href = element.getAttribute('href')!;
ariaNode.props['url'] = href;
}
}

roleUtils.beginAriaCaches();
Expand All @@ -143,7 +149,7 @@ function toAriaNode(element: Element): AriaNode | null {
return null;

const name = normalizeWhiteSpace(roleUtils.getElementAccessibleName(element, false) || '');
const result: AriaNode = { role, name, children: [], element };
const result: AriaNode = { role, name, children: [], props: {}, element };

if (roleUtils.kAriaCheckedRoles.includes(role))
result.checked = roleUtils.getAriaChecked(element);
Expand Down Expand Up @@ -263,6 +269,8 @@ function matchesNode(node: AriaNode | string, template: AriaTemplateNode, depth:
return false;
if (!matchesName(node.name, template))
return false;
if (!matchesText(node.props.url, template.props?.url))
return false;
if (!containsList(node.children || [], template.children || [], depth))
return false;
return true;
Expand Down Expand Up @@ -355,16 +363,19 @@ export function renderAriaTree(ariaSnapshot: AriaSnapshot, options?: { mode?: 'r
}

const escapedKey = indent + '- ' + yamlEscapeKeyIfNeeded(key);
if (!ariaNode.children.length) {
const hasProps = !!Object.keys(ariaNode.props).length;
if (!ariaNode.children.length && !hasProps) {
lines.push(escapedKey);
} else if (ariaNode.children.length === 1 && typeof ariaNode.children[0] === 'string') {
} else if (ariaNode.children.length === 1 && typeof ariaNode.children[0] === 'string' && !hasProps) {
const text = includeText(ariaNode, ariaNode.children[0]) ? renderString(ariaNode.children[0] as string) : null;
if (text)
lines.push(escapedKey + ': ' + yamlEscapeValueIfNeeded(text));
else
lines.push(escapedKey);
} else {
lines.push(escapedKey + ':');
for (const [name, value] of Object.entries(ariaNode.props))
lines.push(indent + ' - /' + name + ': ' + yamlEscapeValueIfNeeded(value));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe place properties under special child properties: and not use the special symbol prefix /?

for (const child of ariaNode.children || [])
visit(child, ariaNode, indent + ' ');
}
Expand Down
16 changes: 16 additions & 0 deletions packages/playwright-core/src/utils/isomorphic/ariaSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export type AriaTemplateRoleNode = AriaProps & {
role: AriaRole | 'fragment';
name?: AriaRegex | string;
children?: AriaTemplateNode[];
props?: Record<string, string | AriaRegex>;
};

export type AriaTemplateNode = AriaTemplateRoleNode | AriaTemplateTextNode;
Expand Down Expand Up @@ -151,6 +152,21 @@ export function parseAriaSnapshot(yaml: YamlLibrary, text: string, options: yaml
continue;
}

// - /url: "about:blank"
if (key.value.startsWith('/')) {
const valueIsString = value instanceof yaml.Scalar && typeof value.value === 'string';
if (!valueIsString) {
errors.push({
message: 'Property value should be a string',
range: convertRange(((entry.value as any).range || map.range)),
});
continue;
}
container.props = container.props ?? {};
container.props[key.value.slice(1)] = valueOrRegex(value.value);
continue;
}

// role "name": ...
const childNode = KeyParser.parse(key, parseOptions, errors);
if (!childNode)
Expand Down
88 changes: 60 additions & 28 deletions tests/page/page-aria-snapshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ it('should snapshot complex', async ({ page }) => {
await checkAndMatchSnapshot(page.locator('body'), `
- list:
- listitem:
- link "link"
- link "link":
- /url: about:blank
`);
});

Expand Down Expand Up @@ -149,7 +150,8 @@ it('should snapshot integration', async ({ page }) => {
- listitem:
- group: Verified
- listitem:
- link "Sponsor"
- link "Sponsor":
- /url: about:blank
`);
});

Expand Down Expand Up @@ -220,7 +222,8 @@ it('should include pseudo in text', async ({ page }) => {
`);

await checkAndMatchSnapshot(page.locator('body'), `
- link "worldhello hellobye"
- link "worldhello hellobye":
- /url: about:blank
`);
});

Expand All @@ -243,7 +246,8 @@ it('should not include hidden pseudo in text', async ({ page }) => {
`);

await checkAndMatchSnapshot(page.locator('body'), `
- link "hello hello"
- link "hello hello":
- /url: about:blank
`);
});

Expand All @@ -266,7 +270,8 @@ it('should include new line for block pseudo', async ({ page }) => {
`);

await checkAndMatchSnapshot(page.locator('body'), `
- link "world hello hello bye"
- link "world hello hello bye":
- /url: about:blank
`);
});

Expand Down Expand Up @@ -450,10 +455,12 @@ it('should respect aria-owns', async ({ page }) => {
// - Disregarding these as aria-owns can't suggest multiple parts by spec.
await checkAndMatchSnapshot(page.locator('body'), `
- link "Link 1 Value Paragraph":
- /url: about:blank
- region: Link 1
- textbox: Value
- paragraph: Paragraph
- link "Link 2 Value Paragraph":
- /url: about:blank
- region: Link 2
`);
});
Expand All @@ -467,6 +474,7 @@ it('should be ok with circular ownership', async ({ page }) => {

await checkAndMatchSnapshot(page.locator('body'), `
- link "Hello":
- /url: about:blank
- region: Hello
`);
});
Expand All @@ -488,22 +496,30 @@ it('should escape yaml text in text nodes', async ({ page }) => {
await checkAndMatchSnapshot(page.locator('body'), `
- group:
- text: "one:"
- link "link1"
- link "link1":
- /url: "#"
- text: "\\\"two"
- link "link2"
- link "link2":
- /url: "#"
- text: "'three"
- link "link3"
- link "link3":
- /url: "#"
- text: "\`four"
- list:
- link "one"
- link "one":
- /url: "#"
- text: ","
- link "two"
- link "two":
- /url: "#"
- text: (
- link "three"
- link "three":
- /url: "#"
- text: ") {"
- link "four"
- link "four":
- /url: "#"
- text: "} ["
- link "five"
- link "five":
- /url: "#"
- text: "]"
- text: "[Select all]"
`);
Expand All @@ -521,7 +537,8 @@ it('should normalize whitespace', async ({ page }) => {
await checkAndMatchSnapshot(page.locator('body'), `
- group:
- text: one two
- link "link 1"
- link "link 1":
- /url: "#"
- textbox: hello world
- button "helloworld"
`);
Expand All @@ -532,7 +549,8 @@ it('should normalize whitespace', async ({ page }) => {
- text: |
one
two
- link " link 1 "
- link " link 1 ":
- /url: "#"
- textbox: hello world
- button "he\u00adlloworld\u200b"
`);
Expand All @@ -548,6 +566,7 @@ it('should handle long strings', async ({ page }) => {

await checkAndMatchSnapshot(page.locator('body'), `
- link:
- /url: about:blank
- region: ${s}
`);
});
Expand All @@ -562,15 +581,20 @@ it('should escape special yaml characters', async ({ page }) => {
`);

await checkAndMatchSnapshot(page.locator('body'), `
- link "@hello"
- link "@hello":
- /url: "#"
- text: "@hello"
- link "]hello"
- link "]hello":
- /url: "#"
- text: "]hello"
- link "hello"
- link "hello":
- /url: "#"
- text: hello
- link "hello"
- link "hello":
- /url: "#"
- text: hello
- link "#hello"
- link "#hello":
- /url: "#"
- text: "#hello"
`);
});
Expand All @@ -589,21 +613,29 @@ it('should escape special yaml values', async ({ page }) => {
`);

await checkAndMatchSnapshot(page.locator('body'), `
- link "true"
- link "true":
- /url: "#"
- text: "False"
- link "NO"
- link "NO":
- /url: "#"
- text: "yes"
- link "y"
- link "y":
- /url: "#"
- text: "N"
- link "on"
- link "on":
- /url: "#"
- text: "Off"
- link "null"
- link "null":
- /url: "#"
- text: "NULL"
- link "123"
- link "123":
- /url: "#"
- text: "123"
- link "-1.2"
- link "-1.2":
- /url: "#"
- text: "-1.2"
- link "-"
- link "-":
- /url: "#"
- text: "-"
- textbox: "555"
`);
Expand Down
11 changes: 11 additions & 0 deletions tests/page/to-match-aria-snapshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -682,3 +682,14 @@ test('should not match what is not matched', async ({ page }) => {
- - button "bogus"
+ - paragraph: Text`);
});

test('should match url', async ({ page }) => {
await page.setContent(`
<a href='https://example.com'>Link</a>
`);

await expect(page.locator('body')).toMatchAriaSnapshot(`
- link:
- /url: /.*example.com/
`);
});
11 changes: 7 additions & 4 deletions tests/playwright-test/update-aria-snapshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ test('should generate baseline with special characters', async ({ runInlineTest
expect(trimPatch(data)).toBe(`diff --git a/a.spec.ts b/a.spec.ts
--- a/a.spec.ts
+++ b/a.spec.ts
@@ -17,6 +17,27 @@
@@ -17,6 +17,30 @@
<li>Item: 1</li>
<li>Item {a: b}</li>
</ul>\`);
Expand All @@ -265,11 +265,14 @@ test('should generate baseline with special characters', async ({ runInlineTest
+ - list:
+ - group:
+ - text: "one:"
+ - link "link1"
+ - link "link1":
+ - /url: "#"
+ - text: "\\\\\"two"
+ - link "link2"
+ - link "link2":
+ - /url: "#"
+ - text: "'three"
+ - link "link3"
+ - link "link3":
+ - /url: "#"
+ - text: "\\\`four"
+ - heading "heading \\\\"name\\\\" [level=1]" [level=1]
+ - 'button "Click: me"'
Expand Down
Loading