Skip to content

Commit

Permalink
Add heading levels to getAccessibilityTree (#445)
Browse files Browse the repository at this point in the history
  • Loading branch information
calebeby committed Mar 31, 2022
1 parent 1eaa648 commit 5fa4103
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 41 deletions.
13 changes: 13 additions & 0 deletions .changeset/silent-candles-sleep.md
@@ -0,0 +1,13 @@
---
'pleasantest': major
---

Add heading levels to `getAccessibilityTree`. The heading levels are computed from the corresponding element number in `<h1>` - `<h6>`, or from the `aria-level` role.

In the accessibility tree snapshot, it looks like this:

```
heading "Name of Heading" (level=2)
```

This is a breaking change because it will cause existing accessibility tree snapshots to fail which contain headings. Update the snapshots to make them pass again.
4 changes: 2 additions & 2 deletions examples/menu/index.test.ts
Expand Up @@ -79,7 +79,7 @@ test(
expect(await getAccessibilityTree(await screen.getByRole('navigation')))
.toMatchInlineSnapshot(`
navigation
heading "Company"
heading "Company" (level=1)
link "Company"
text "Company"
list
Expand All @@ -104,7 +104,7 @@ test(
expect(await getAccessibilityTree(await screen.getByRole('navigation')))
.toMatchInlineSnapshot(`
navigation
heading "Company"
heading "Company" (level=1)
link "Company"
text "Company"
list
Expand Down
16 changes: 16 additions & 0 deletions src/accessibility/browser.ts
Expand Up @@ -124,6 +124,22 @@ export const getAccessibilityTree = (
)
text += ` (expanded=false)`;
if (document.activeElement === element) text += ` (focused)`;
if (role === 'heading') {
const level =
element.ariaLevel ||
(element.tagName.length === 2 &&
element.tagName.startsWith('H') &&
element.tagName[1]);
if (level) {
text +=
Number.parseInt(level, 10).toString() === level &&
Number.parseInt(level, 10) > 0
? ` (level=${level})`
: ` (INVALID HEADING LEVEL: ${JSON.stringify(level)})`;
} else {
text += ` (MISSING HEADING LEVEL)`;
}
}
if (includeDescriptions) {
const description = computeAccessibleDescription(element);
if (description) text += `\n ↳ description: "${description}"`;
Expand Down
128 changes: 89 additions & 39 deletions tests/accessibility/getAccessibilityTree.test.ts
Expand Up @@ -42,7 +42,7 @@ test(
document "pleasantest"
main
button "Add to cart"
heading "hiiii"
heading "hiiii" (level=1)
text "hiiii"
button "foo > bar"
`);
Expand All @@ -62,13 +62,13 @@ test(
`);
expect(await getAccessibilityTree(page, { includeText: true }))
.toMatchInlineSnapshot(`
document "pleasantest"
list
listitem
text "something"
listitem
text "something else"
`);
document "pleasantest"
list
listitem
text "something"
listitem
text "something else"
`);
await utils.injectHTML(`
<button aria-describedby="click-me-description">click me</button>
<button aria-describedby="click-me-description"><div>click me</div></button>
Expand All @@ -87,24 +87,24 @@ test(
`);
expect(await getAccessibilityTree(page, { includeText: true }))
.toMatchInlineSnapshot(`
document "pleasantest"
button "click me"
↳ description: "extended description"
button "click me"
↳ description: "extended description"
button "click me"
↳ description: "extended description"
text "extended description"
`);
document "pleasantest"
button "click me"
↳ description: "extended description"
button "click me"
↳ description: "extended description"
button "click me"
↳ description: "extended description"
text "extended description"
`);

expect(await getAccessibilityTree(page, { includeDescriptions: false }))
.toMatchInlineSnapshot(`
document "pleasantest"
button "click me"
button "click me"
button "click me"
text "extended description"
`);
document "pleasantest"
button "click me"
button "click me"
button "click me"
text "extended description"
`);

await utils.injectHTML(`
<label>
Expand All @@ -118,12 +118,12 @@ test(

expect(await getAccessibilityTree(page, { includeText: true }))
.toMatchInlineSnapshot(`
document "pleasantest"
text "Label Text"
textbox "Label Text"
text "Label Text"
textbox "Label Text"
`);
document "pleasantest"
text "Label Text"
textbox "Label Text"
text "Label Text"
textbox "Label Text"
`);
}),
);

Expand Down Expand Up @@ -200,7 +200,7 @@ test(
document "pleasantest"
text "Sample Content"
text "More Sample Content"
heading "Hi"
heading "Hi" (MISSING HEADING LEVEL)
text "Hi"
`);
// Now the third list item has an explicit role which is the same as its implicit role.
Expand Down Expand Up @@ -235,7 +235,7 @@ test(
document "pleasantest"
text "Sample Content"
text "More Sample Content"
heading "Hi"
heading "Hi" (MISSING HEADING LEVEL)
text "Hi"
`);
// The required owned elements search should _not_ pass through elements with roles
Expand All @@ -250,11 +250,11 @@ test(
`);
expect(await getAccessibilityTree(page)).toMatchInlineSnapshot(`
document "pleasantest"
heading "Sample Content"
heading "Sample Content" (level=1)
listitem
text "Sample Content"
text "More Sample Content"
heading "Hi"
heading "Hi" (MISSING HEADING LEVEL)
text "Hi"
`);
}),
Expand All @@ -281,6 +281,56 @@ test(
}),
);

test(
'heading level labels',
withBrowser(async ({ utils, page }) => {
await utils.injectHTML(`
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>
<div>Not a heading</div>
<div aria-level="3">Not a heading</div>
<div role="heading" aria-level="3">Heading 3 div</div>
<div role="heading">Heading missing level</div>
<div role="heading" aria-level="-2">Invalid heading level</div>
<div role="heading" aria-level="asdf">Invalid heading level</div>
<h2 aria-level="3">Heading 3 h2</h2>
`);

expect(await getAccessibilityTree(page)).toMatchInlineSnapshot(`
document "pleasantest"
heading "Heading 1" (level=1)
text "Heading 1"
heading "Heading 2" (level=2)
text "Heading 2"
heading "Heading 3" (level=3)
text "Heading 3"
heading "Heading 4" (level=4)
text "Heading 4"
heading "Heading 5" (level=5)
text "Heading 5"
heading "Heading 6" (level=6)
text "Heading 6"
text "Not a heading"
text "Not a heading"
heading "Heading 3 div" (level=3)
text "Heading 3 div"
heading "Heading missing level" (MISSING HEADING LEVEL)
text "Heading missing level"
heading "Invalid heading level" (INVALID HEADING LEVEL: "-2")
text "Invalid heading level"
heading "Invalid heading level" (INVALID HEADING LEVEL: "asdf")
text "Invalid heading level"
heading "Heading 3 h2" (level=3)
text "Heading 3 h2"
`);
}),
);

test(
'<details>/<summary>',
withBrowser(async ({ utils, page, screen, user }) => {
Expand All @@ -297,7 +347,7 @@ test(

// Starts collapsed
expect(await getAccessibilityTree(page)).toMatchInlineSnapshot(`
document
document "pleasantest"
group
button "Click me! Tags in summary do not preserve their semantic meaning" (expanded=false)
`);
Expand All @@ -307,11 +357,11 @@ test(

// After toggling it should be expanded
expect(await getAccessibilityTree(page)).toMatchInlineSnapshot(`
document
document "pleasantest"
group
button "Click me! Tags in summary do not preserve their semantic meaning" (expanded=true) (focused)
text "Some content"
heading "Tags in details do preserve their semantic meaning"
heading "Tags in details do preserve their semantic meaning" (level=2)
text "Tags in details do preserve their semantic meaning"
`);
}),
Expand All @@ -324,23 +374,23 @@ test(
<button aria-expanded="false">Click me!</button>
`);
expect(await getAccessibilityTree(page)).toMatchInlineSnapshot(`
document
document "pleasantest"
button "Click me!" (expanded=false)
`);

await utils.injectHTML(`
<button aria-expanded="true">Click me!</button>
`);
expect(await getAccessibilityTree(page)).toMatchInlineSnapshot(`
document
document "pleasantest"
button "Click me!" (expanded=true)
`);

await utils.injectHTML(`
<button>Click me!</button>
`);
expect(await getAccessibilityTree(page)).toMatchInlineSnapshot(`
document
document "pleasantest"
button "Click me!"
`);
}),
Expand Down

0 comments on commit 5fa4103

Please sign in to comment.