Add heading-hierarchy a11y check to docs E2E tests (#48422)

Closes DOCS-1232

## Problem

We do not have any tests to verify that we are following a proper
heading hierarchy. For a documentation site that deals in mostly static
content, this test is important.

Single h1 + logical heading hierarchy (h1→h2→h3, no skips) matters
because screen reader users navigate by jumping between headings —
broken structure breaks that navigation.

Relevant: WCAG 1.3.1 Info and Relationships (Level A) —
https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html

## Solution

Add Playwright axe-core, which we plan to expand later, to test only the
h1 and header-hierarchy rule.
This is added to our current suite that dynamically checks only pages
that are edited.

## Manual testing

1. Find a docs guide and intentionally break the header hierarchy.
2. Run `pnpm e2e:docs:a11y` and see your errors.
3. Resolve the issue and run again to see errors resolved. Ensure there
is at least a line changed to see the page tested.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
  * Added automated accessibility checks for documentation pages.
* Verified heading order and the presence of a level-one heading on each
page.
  * Added a dedicated command to run documentation accessibility tests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Miranda LimonczenkoandClaude Sonnet 5 authored and GitHub committed 2026-07-31 12:31:39 -07:00
1 parent ec64135f9d
commit 8dda0c3910
3 files changed
+40 -4

No files matched your search

+19 -4
View File
@@ -1,3 +1,4 @@
import { AxeBuilder } from '@axe-core/playwright'
import { expect, test } from '@playwright/test'
import {
@@ -39,10 +40,6 @@ test.describe('Docs owned pages', () => {
const article = page.locator(articleSelector)
await expect(article, 'Page article should be present').toBeVisible()
await expect(
article.getByRole('heading', { level: 1 }),
'Page article should include an h1'
).toBeVisible()
const links = await collectDocsOwnedLinks(page, baseURL!, articleSelector)
const userAgent = await browserLikeUserAgent(page)
@@ -64,4 +61,22 @@ test.describe('Docs owned pages', () => {
}
})
}
for (const pagePath of pagePaths) {
test(`${pagePath} has a valid heading hierarchy @a11y`, async ({ page }) => {
const articleSelector = articleSelectorForPagePath(pagePath)
const response = await page.goto(pagePath)
expect(response?.ok(), `Expected a successful response for ${pagePath}`).toBeTruthy()
const axeResults = await new AxeBuilder({ page })
.include(articleSelector)
.withRules(['heading-order', 'page-has-heading-one'])
.analyze()
expect(
axeResults.violations,
`Heading hierarchy issues in ${articleSelector}:\n${JSON.stringify(axeResults.violations, null, 2)}`
).toEqual([])
})
}
})
+2
View File
@@ -6,6 +6,7 @@
"scripts": {
"e2e:docs": "node --experimental-strip-types scripts/run-e2e-docs.ts",
"e2e:docs:all": "node --experimental-strip-types scripts/run-e2e-docs.ts --all",
"e2e:docs:a11y": "node --experimental-strip-types scripts/run-e2e-docs.ts --grep @a11y",
"e2e:ui": "node --experimental-strip-types scripts/run-e2e-docs.ts --ui",
"e2e:docs:local-smoke": "playwright test --config=playwright.local-smoke.config.ts",
"resolve-docs-scope": "node --experimental-strip-types scripts/resolve-docs-scope.ts"
@@ -14,6 +15,7 @@
"@playwright/test": "^1.59.1"
},
"devDependencies": {
"@axe-core/playwright": "^4.12.1",
"@types/node": "catalog:"
}
}