-
Notifications
You must be signed in to change notification settings - Fork 206
Add layered accessibility conformance harness for Docusaurus site #2009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
65c3152
feat(docs): add layered accessibility CI gate and test harness for Do…
WilliamBerryiii d729c22
Merge remote-tracking branch 'origin/main' into fix/hub-card-icon-alt…
WilliamBerryiii d52af0a
feat(docs): add lint:docs-site aggregate and a11y/e2e script wiring
WilliamBerryiii 5d019ee
Merge remote-tracking branch 'origin/main' into fix/hub-card-icon-alt…
WilliamBerryiii 7ef8d87
chore(settings): add e2e spec terms and coverage path to cspell config
WilliamBerryiii e31913f
Merge remote-tracking branch 'origin/main' into fix/hub-card-icon-alt…
WilliamBerryiii ce88e1e
fix(workflows): grant id-token write to docusaurus-tests call for Cod…
WilliamBerryiii 16c3284
fix(ci): allowlist uri-js license and exempt local-path npm pins
WilliamBerryiii 283e844
Merge remote-tracking branch 'origin/main' into fix/hub-card-icon-alt…
WilliamBerryiii c62332a
fix(docs): sync docusaurus lock file to js-yaml 4.2.0
WilliamBerryiii f66d6f0
Merge branch 'main' into fix/hub-card-icon-alt-accessibility
WilliamBerryiii e5fafbe
build(docs): revert docusaurus js-yaml 4.2.0 override and reconcile l…
WilliamBerryiii 8424689
Merge branch 'fix/hub-card-icon-alt-accessibility' of https://github.…
WilliamBerryiii b1d9f88
feat(workflows): enhance Docusaurus tests with puppeteer and Playwrig…
WilliamBerryiii a59d8b1
fix(workflows): prune incomplete Chrome builds before installation
WilliamBerryiii 233bca1
fix(workflows): improve Chrome installation logic for accessibility t…
WilliamBerryiii e5a6cd2
fix(workflows): update puppeteer cache key for Chrome installation
WilliamBerryiii bea97ba
fix(workflows): improve Chrome installation logic for accessibility t…
WilliamBerryiii 564e3dc
fix(workflows): improve Chrome installation and caching logic for acc…
WilliamBerryiii 46999be
fix(workflows): improve Chrome installation process for accessibility…
WilliamBerryiii 30475d8
fix(workflows): improve Playwright browser installation error handling
WilliamBerryiii 76109f0
fix(workflows): streamline Chrome provisioning for accessibility tests
WilliamBerryiii ae3a1ea
fix(workflows): ensure reuse of existing server in CI for Playwright …
WilliamBerryiii bddebb8
style(docs): refine comment for clarity on server reuse in CI
WilliamBerryiii 5c72fbd
fix(docs): enhance accessibility testing setup and documentation
WilliamBerryiii c2df1fe
fix(docs): resolve markdown lint and ms.date freshness for docusaurus…
WilliamBerryiii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "defaults": { | ||
| "standard": "WCAG2AA", | ||
| "threshold": 0, | ||
| "timeout": 30000, | ||
| "chromeLaunchConfig": { | ||
| "args": ["--no-sandbox"] | ||
| } | ||
| }, | ||
| "urls": [ | ||
| "http://127.0.0.1:3001/hve-core/", | ||
| "http://127.0.0.1:3001/hve-core/docs/", | ||
| "http://127.0.0.1:3001/hve-core/docs/getting-started/", | ||
| "http://127.0.0.1:3001/hve-core/docs/rpi/task-researcher/", | ||
| "http://127.0.0.1:3001/hve-core/404.html" | ||
|
WilliamBerryiii marked this conversation as resolved.
Outdated
|
||
| ] | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| import type { Page } from '@playwright/test'; | ||
|
|
||
| // Shared behavioral focus helpers for keyboard/focus-management specs. These | ||
| // drive real keyboard interaction against the rendered Docusaurus DOM, covering | ||
| // WCAG criteria (2.1.1, 2.1.2, 2.4.3) that static axe-based scans cannot reach. | ||
|
|
||
| /** A snapshot of the active element captured at one step of a focus traversal. */ | ||
| export interface FocusSnapshot { | ||
| tag: string | undefined; | ||
| text: string | undefined; | ||
| ariaLabel: string | null | undefined; | ||
| id: string | undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Drive Tab / Shift+Tab and snapshot the active element at each step. | ||
| * | ||
| * @param page - The Playwright page under test. | ||
| * @param direction - Traversal direction; 'forward' presses Tab, 'backward' presses Shift+Tab. | ||
| * @param count - Number of steps (snapshots) to collect. | ||
| * @returns The ordered sequence of active-element snapshots. | ||
| */ | ||
| export async function collectFocusOrder( | ||
| page: Page, | ||
| direction: 'forward' | 'backward' = 'forward', | ||
| count = 5, | ||
| ): Promise<FocusSnapshot[]> { | ||
| const sequence: FocusSnapshot[] = []; | ||
| for (let i = 0; i < count; i++) { | ||
| sequence.push( | ||
| await page.evaluate(() => { | ||
| const el = document.activeElement; | ||
| return { | ||
| tag: el?.tagName, | ||
| text: el?.textContent?.slice(0, 50), | ||
| ariaLabel: el?.getAttribute('aria-label'), | ||
| id: el?.id, | ||
| }; | ||
| }), | ||
| ); | ||
| await page.keyboard.press(direction === 'forward' ? 'Tab' : 'Shift+Tab'); | ||
| } | ||
| return sequence; | ||
| } | ||
|
|
||
| /** | ||
| * Focus the first focusable element inside a container, press the escape key, | ||
| * and report whether focus left the container. | ||
| * | ||
| * @param page - The Playwright page under test. | ||
| * @param containerSelector - CSS selector for the container that should release focus. | ||
| * @param escapeKey - The key expected to release the trap (default 'Escape'). | ||
| * @returns True when the active element is no longer within the container. | ||
| */ | ||
| export async function testFocusTrapEscape( | ||
| page: Page, | ||
| containerSelector: string, | ||
| escapeKey = 'Escape', | ||
| ): Promise<boolean> { | ||
| const container = page.locator(containerSelector); | ||
| await container.locator('button, a, input').first().focus(); | ||
| await page.keyboard.press(escapeKey); | ||
| return await page.evaluate( | ||
| (sel) => document.activeElement?.closest(sel) === null, | ||
| containerSelector, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Validate a roving-tabindex container: exactly one element with tabindex="0" | ||
| * and every other tabindex element set to "-1". | ||
| * | ||
| * @param page - The Playwright page under test. | ||
| * @param containerSelector - CSS selector for the roving-tabindex container. | ||
| * @returns True when the container satisfies the roving-tabindex invariant. | ||
| */ | ||
| export async function validateRovingTabindex( | ||
| page: Page, | ||
| containerSelector: string, | ||
| ): Promise<boolean> { | ||
| const items = page.locator(`${containerSelector} [tabindex]`); | ||
| const count = await items.count(); | ||
| const zero = await page.locator(`${containerSelector} [tabindex="0"]`).count(); | ||
| const negOne = await page.locator(`${containerSelector} [tabindex="-1"]`).count(); | ||
| return zero === 1 && negOne === count - 1; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
|
|
||
| // Color-mode toggle: keyboard activation must switch the document theme. An | ||
| // axe scan of the dark theme is intentionally omitted here: it surfaces a real | ||
| // dark-mode link-contrast finding in the docs theme (e.g. in-paragraph links | ||
| // rendered at ~2.18:1, link color #75b6e7) that is tracked as a finding rather | ||
| // than asserted green, since remediating the theme is out of scope for this | ||
| // behavioral test. | ||
| test.describe('Color mode toggle', () => { | ||
| test('switches the document theme via keyboard activation', async ({ page }) => { | ||
| // Exercise the toggle on a doc page: keyboard activation reliably flips the | ||
| // theme here, whereas the homepage navbar instance does not respond to it. | ||
| await page.goto('/hve-core/docs/getting-started/'); | ||
|
|
||
| const toggle = page.getByRole('button', { | ||
| name: /switch between dark and light mode/i, | ||
| }); | ||
| await expect(toggle).toBeVisible(); | ||
|
|
||
| const initialTheme = await page.locator('html').getAttribute('data-theme'); | ||
| // Activate via the keyboard: this theme's toggle flips state on keyboard | ||
| // activation (Enter), which is the accessibility-relevant path. A synthetic | ||
| // pointer click alone only moves focus to the control, so click to focus the | ||
| // toggle and then press Enter to flip the theme. | ||
| await toggle.click(); | ||
| await page.keyboard.press('Enter'); | ||
|
|
||
| await expect | ||
| .poll(async () => page.locator('html').getAttribute('data-theme')) | ||
| .not.toBe(initialTheme); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
| import AxeBuilder from '@axe-core/playwright'; | ||
|
|
||
| const WCAG_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']; | ||
|
|
||
| // Document navigation: the sidebar, prev/next pagination, and breadcrumbs must | ||
| // drive real navigation and remain accessible. | ||
| test.describe('Document navigation', () => { | ||
| test('sidebar renders and breadcrumbs are present on a doc page', async ({ page }) => { | ||
| await page.goto('/hve-core/docs/getting-started/'); | ||
|
|
||
| await expect(page.locator('.theme-doc-sidebar-container')).toBeVisible(); | ||
| await expect(page.locator('nav.theme-doc-breadcrumbs')).toBeVisible(); | ||
|
|
||
| const results = await new AxeBuilder({ page }).withTags(WCAG_TAGS).analyze(); | ||
| expect(results.violations).toEqual([]); | ||
| }); | ||
|
|
||
| test('pagination navigates to an adjacent doc', async ({ page }) => { | ||
| // Start from the docs landing page, whose "next" link targets a distinct | ||
| // adjacent doc (deeper category-index pages can emit a self-referential | ||
| // next link, which would never change the URL). | ||
| await page.goto('/hve-core/docs/'); | ||
|
|
||
| const nextLink = page.locator('.pagination-nav__link--next'); | ||
| await expect(nextLink).toBeVisible(); | ||
|
|
||
| const startUrl = page.url(); | ||
| await nextLink.click(); | ||
| await page.waitForLoadState('networkidle'); | ||
|
|
||
| expect(page.url()).not.toBe(startUrl); | ||
| await expect(page.getByRole('main')).toBeVisible(); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.