mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore(aria): only emit actionable generic nodes (#35838)
This commit is contained in:
parent
71eb3b9f0f
commit
79cbb15a4a
@ -186,31 +186,25 @@ function toAriaNode(element: Element, options?: { emitGeneric?: boolean }): Aria
|
||||
return result;
|
||||
}
|
||||
|
||||
function normalizeGenericRoles(rootA11yNode: AriaNode) {
|
||||
const visit = (ariaNode: AriaNode) => {
|
||||
const newChildren: (AriaNode | string)[] = [];
|
||||
for (const child of ariaNode.children) {
|
||||
function normalizeGenericRoles(node: AriaNode) {
|
||||
const normalizeChildren = (node: AriaNode) => {
|
||||
const result: (AriaNode | string)[] = [];
|
||||
for (const child of node.children || []) {
|
||||
if (typeof child === 'string') {
|
||||
newChildren.push(child);
|
||||
result.push(child);
|
||||
continue;
|
||||
}
|
||||
const isEmptyGeneric = child.role === 'generic' && child.children.length === 0;
|
||||
const isSingleGenericChild = child.role === 'generic' && child.children.length === 1;
|
||||
if (isSingleGenericChild) {
|
||||
// Inline single child chains.
|
||||
const newChild = child.children[0];
|
||||
newChildren.push(newChild);
|
||||
if (typeof newChild !== 'string')
|
||||
visit(newChild);
|
||||
} else if (!isEmptyGeneric) {
|
||||
// Empty div
|
||||
newChildren.push(child);
|
||||
visit(child);
|
||||
}
|
||||
const normalized = normalizeChildren(child);
|
||||
result.push(...normalized);
|
||||
}
|
||||
ariaNode.children = newChildren;
|
||||
const removeSelf = node.role === 'generic' && result.every(c => typeof c !== 'string' && canRef(c));
|
||||
if (removeSelf)
|
||||
return result;
|
||||
node.children = result;
|
||||
return [node];
|
||||
};
|
||||
visit(rootA11yNode);
|
||||
|
||||
normalizeChildren(node);
|
||||
}
|
||||
|
||||
function normalizeStringChildren(rootA11yNode: AriaNode) {
|
||||
@ -408,7 +402,7 @@ export function renderAriaTree(ariaSnapshot: AriaSnapshot, options?: { mode?: 'r
|
||||
key += ` [pressed]`;
|
||||
if (ariaNode.selected === true)
|
||||
key += ` [selected]`;
|
||||
if (options?.ref && ariaNode.box.visible && ariaNode.receivesPointerEvents) {
|
||||
if (options?.ref && canRef(ariaNode)) {
|
||||
const id = ariaSnapshot.ids.get(ariaNode.element);
|
||||
if (id)
|
||||
key += ` [ref=s${ariaSnapshot.generation}e${id}]`;
|
||||
@ -501,3 +495,7 @@ function textContributesInfo(node: AriaNode, text: string): boolean {
|
||||
filtered = filtered.replace(substr, '');
|
||||
return filtered.trim().length / text.length > 0.1;
|
||||
}
|
||||
|
||||
function canRef(ariaNode: AriaNode): boolean {
|
||||
return ariaNode.box.visible && ariaNode.receivesPointerEvents;
|
||||
}
|
||||
|
||||
@ -16,7 +16,30 @@
|
||||
*/
|
||||
|
||||
import type { Locator, FrameLocator, Page } from '@playwright/test';
|
||||
import { test as it, expect } from './pageTest';
|
||||
import { test as it, expect as baseExpect } from './pageTest';
|
||||
|
||||
const expect = baseExpect.extend({
|
||||
toContainYaml(received: string, expected: string) {
|
||||
const trimmed = expected.split('\n').filter(a => !!a.trim());
|
||||
const maxPrefixLength = Math.min(...trimmed.map(line => line.match(/^\s*/)[0].length));
|
||||
const trimmedExpected = trimmed.map(line => line.substring(maxPrefixLength)).join('\n');
|
||||
try {
|
||||
if (this.isNot)
|
||||
expect(received).not.toContain(trimmedExpected);
|
||||
else
|
||||
expect(received).toContain(trimmedExpected);
|
||||
return {
|
||||
pass: !this.isNot,
|
||||
message: () => '',
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
pass: this.isNot,
|
||||
message: () => e.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function unshift(snapshot: string): string {
|
||||
const lines = snapshot.split('\n');
|
||||
@ -720,15 +743,15 @@ it('ref mode can be used to stitch all frame snapshots', async ({ page, server }
|
||||
return result.join('\n');
|
||||
}
|
||||
|
||||
expect(await allFrameSnapshot(page)).toEqual(`
|
||||
- iframe [ref=s1e3]:
|
||||
- iframe [ref=s1e3]:
|
||||
- text: Hi, I'm frame
|
||||
- iframe [ref=s1e4]:
|
||||
- text: Hi, I'm frame
|
||||
- iframe [ref=s1e4]:
|
||||
- text: Hi, I'm frame
|
||||
`.trim());
|
||||
expect(await allFrameSnapshot(page)).toContainYaml(`
|
||||
- iframe [ref=s1e3]:
|
||||
- iframe [ref=s1e3]:
|
||||
- text: Hi, I'm frame
|
||||
- iframe [ref=s1e4]:
|
||||
- text: Hi, I'm frame
|
||||
- iframe [ref=s1e4]:
|
||||
- text: Hi, I'm frame
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not generate refs for hidden elements', async ({ page }) => {
|
||||
@ -739,9 +762,11 @@ it('should not generate refs for hidden elements', async ({ page }) => {
|
||||
`);
|
||||
|
||||
const snapshot = await page.locator('body').ariaSnapshot({ ref: true });
|
||||
expect(snapshot).toContain(`- button "One" [ref=s1e3]
|
||||
- button "Two"
|
||||
- button "Three" [ref=s1e5]`);
|
||||
expect(snapshot).toContainYaml(`
|
||||
- button "One" [ref=s1e3]
|
||||
- button "Two"
|
||||
- button "Three" [ref=s1e5]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not generate refs for elements with pointer-events:none', async ({ page }) => {
|
||||
@ -768,11 +793,13 @@ it('should not generate refs for elements with pointer-events:none', async ({ pa
|
||||
`);
|
||||
|
||||
const snapshot = await page.locator('body').ariaSnapshot({ ref: true });
|
||||
expect(snapshot).toContain(`- button "no-ref"
|
||||
- button "with-ref" [ref=s1e5]
|
||||
- button "with-ref" [ref=s1e8]
|
||||
- button "with-ref" [ref=s1e11]
|
||||
- button "no-ref"`);
|
||||
expect(snapshot).toContainYaml(`
|
||||
- button "no-ref"
|
||||
- button "with-ref" [ref=s1e5]
|
||||
- button "with-ref" [ref=s1e8]
|
||||
- button "with-ref" [ref=s1e11]
|
||||
- button "no-ref"
|
||||
`);
|
||||
});
|
||||
|
||||
it('emit generic roles for nodes w/o roles', async ({ page }) => {
|
||||
@ -808,14 +835,32 @@ it('emit generic roles for nodes w/o roles', async ({ page }) => {
|
||||
|
||||
const snapshot = await page.locator('body').ariaSnapshot({ ref: true, emitGeneric: true });
|
||||
|
||||
expect(snapshot).toContain(`- generic [ref=s1e3]:
|
||||
- generic [ref=s1e4]:
|
||||
- radio "Apple" [checked]
|
||||
- text: Apple
|
||||
- generic [ref=s1e8]:
|
||||
- radio "Pear"
|
||||
- text: Pear
|
||||
- generic [ref=s1e12]:
|
||||
- radio "Orange"
|
||||
- text: Orange`);
|
||||
expect(snapshot).toContainYaml(`
|
||||
- generic [ref=s1e5]:
|
||||
- radio "Apple" [checked]
|
||||
- generic [ref=s1e7]: Apple
|
||||
- generic [ref=s1e9]:
|
||||
- radio "Pear"
|
||||
- generic [ref=s1e11]: Pear
|
||||
- generic [ref=s1e13]:
|
||||
- radio "Orange"
|
||||
- generic [ref=s1e15]: Orange
|
||||
`);
|
||||
});
|
||||
|
||||
it('should collapse generic nodes', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<button>Button</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const snapshot = await page.locator('body').ariaSnapshot({ ref: true, emitGeneric: true });
|
||||
expect(snapshot).toContainYaml(`
|
||||
- button \"Button\" [ref=s1e6]
|
||||
`);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user