diff --git a/packages/playwright-core/src/server/injected/ariaSnapshot.ts b/packages/playwright-core/src/server/injected/ariaSnapshot.ts index 7156d0cce7..d544c0a82c 100644 --- a/packages/playwright-core/src/server/injected/ariaSnapshot.ts +++ b/packages/playwright-core/src/server/injected/ariaSnapshot.ts @@ -16,7 +16,7 @@ import { escapeWithQuotes } from '@isomorphic/stringUtils'; import * as roleUtils from './roleUtils'; -import { isElementVisible, isElementStyleVisibilityVisible, getElementComputedStyle } from './domUtils'; +import { getElementComputedStyle } from './domUtils'; import type { AriaRole } from './roleUtils'; type AriaProps = { @@ -29,7 +29,7 @@ type AriaProps = { }; type AriaNode = AriaProps & { - role: AriaRole | 'fragment' | 'text'; + role: AriaRole | 'fragment'; name: string; children: (AriaNode | string)[]; }; @@ -56,22 +56,10 @@ export function generateAriaTree(rootElement: Element): AriaNode { if (roleUtils.isElementHiddenForAria(element)) return; - const visible = isElementVisible(element); - const hasVisibleChildren = isElementStyleVisibilityVisible(element); - - if (!hasVisibleChildren) - return; - - if (visible) { - const childAriaNode = toAriaNode(element); - const isHiddenContainer = childAriaNode && hiddenContainerRoles.has(childAriaNode.ariaNode.role); - if (childAriaNode && !isHiddenContainer) - ariaNode.children.push(childAriaNode.ariaNode); - if (isHiddenContainer || !childAriaNode?.isLeaf) - processChildNodes(childAriaNode?.ariaNode || ariaNode, element); - } else { - processChildNodes(ariaNode, element); - } + const childAriaNode = toAriaNode(element); + if (childAriaNode) + ariaNode.children.push(childAriaNode); + processChildNodes(childAriaNode || ariaNode, element); }; function processChildNodes(ariaNode: AriaNode, element: Element) { @@ -101,6 +89,9 @@ export function generateAriaTree(rootElement: Element): AriaNode { if (treatAsBlock) ariaNode.children.push(treatAsBlock); + + if (ariaNode.children.length === 1 && ariaNode.name === ariaNode.children[0]) + ariaNode.children = []; } roleUtils.beginAriaCaches(); @@ -115,19 +106,13 @@ export function generateAriaTree(rootElement: Element): AriaNode { return ariaRoot; } -function toAriaNode(element: Element): { ariaNode: AriaNode, isLeaf: boolean } | null { +function toAriaNode(element: Element): AriaNode | null { const role = roleUtils.getAriaRole(element); if (!role) return null; const name = roleUtils.getElementAccessibleName(element, false) || ''; - const isLeaf = leafRoles.has(role); const result: AriaNode = { role, name, children: [] }; - if (isLeaf && !name) { - const text = roleUtils.accumulatedElementText(element); - if (text) - result.children = [text]; - } if (roleUtils.kAriaCheckedRoles.includes(role)) result.checked = roleUtils.getAriaChecked(element); @@ -147,7 +132,7 @@ function toAriaNode(element: Element): { ariaNode: AriaNode, isLeaf: boolean } | if (roleUtils.kAriaSelectedRoles.includes(role)) result.selected = roleUtils.getAriaSelected(element); - return { isLeaf, ariaNode: result }; + return result; } export function renderedAriaTree(rootElement: Element): string { @@ -178,21 +163,12 @@ function normalizeStringChildren(rootA11yNode: AriaNode) { } flushChildren(buffer, normalizedChildren); ariaNode.children = normalizedChildren.length ? normalizedChildren : []; + if (ariaNode.children.length === 1 && ariaNode.children[0] === ariaNode.name) + ariaNode.children = []; }; visit(rootA11yNode); } -const hiddenContainerRoles = new Set(['none', 'presentation']); - -const leafRoles = new Set([ - 'alert', 'blockquote', 'button', 'caption', 'checkbox', 'code', 'columnheader', - 'definition', 'deletion', 'emphasis', 'generic', 'heading', 'img', 'insertion', - 'link', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'meter', 'option', - 'progressbar', 'radio', 'rowheader', 'scrollbar', 'searchbox', 'separator', - 'slider', 'spinbutton', 'strong', 'subscript', 'superscript', 'switch', 'tab', 'term', - 'textbox', 'time', 'tooltip' -]); - const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\t\r\n]+/g, ' '); function matchesText(text: string | undefined, template: RegExp | string | undefined) { @@ -305,15 +281,7 @@ export function renderAriaTree(ariaNode: AriaNode, options?: { noText?: boolean if (ariaNode.selected === true) line += ` [selected]`; - const stringValue = !ariaNode.children.length || (ariaNode.children?.length === 1 && typeof ariaNode.children[0] === 'string'); - if (stringValue) { - if (!options?.noText && ariaNode.children.length) - line += ': ' + quoteYamlString(ariaNode.children?.[0] as string); - lines.push(line); - return; - } - - lines.push(line + ':'); + lines.push(line + (ariaNode.children.length ? ':' : '')); for (const child of ariaNode.children || []) visit(child, indent + ' '); }; diff --git a/tests/page/page-aria-snapshot.spec.ts b/tests/page/page-aria-snapshot.spec.ts index 188808a178..2b8790589a 100644 --- a/tests/page/page-aria-snapshot.spec.ts +++ b/tests/page/page-aria-snapshot.spec.ts @@ -64,8 +64,10 @@ it('should snapshot list with accessible name', async ({ page }) => { `); await checkAndMatchSnapshot(page.locator('body'), ` - list "my list": - - listitem: "one" - - listitem: "two" + - listitem: + - text: "one" + - listitem: + - text: "two" `); }); @@ -105,7 +107,8 @@ it('should snapshot details visibility', async ({ page }) => { `); await checkAndMatchSnapshot(page.locator('body'), ` - - group: "Summary" + - group: + - text: "Summary" `); }); @@ -148,7 +151,8 @@ it('should snapshot integration', async ({ page }) => { - text: "Open source projects and samples from Microsoft" - list: - listitem: - - group: "Verified" + - group: + - text: "Verified" - listitem: - link "Sponsor" `); @@ -164,13 +168,15 @@ it('should support multiline text', async ({ page }) => { `); await checkAndMatchSnapshot(page.locator('body'), ` - - paragraph: "Line 1 Line 2 Line 3" + - paragraph: + - text: "Line 1 Line 2 Line 3" `); await expect(page.locator('body')).toMatchAriaSnapshot(` - - paragraph: | - Line 1 - Line 2 - Line 3 + - paragraph: + - text: | + Line 1 + Line 2 + Line 3 `); }); @@ -382,6 +388,22 @@ it('should include pseudo codepoints', async ({ page, server }) => { `); await checkAndMatchSnapshot(page.locator('body'), ` - - paragraph: "\ueab2hello" + - paragraph: + - text: "\ueab2hello" + `); +}); + +it('check aria-hidden text', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(` +

+ hello + +

+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - paragraph: + - text: "hello" `); });