mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 19:05:06 +03:00
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>
83 lines
3.0 KiB
TypeScript
83 lines
3.0 KiB
TypeScript
import { AxeBuilder } from '@axe-core/playwright'
|
|
import { expect, test } from '@playwright/test'
|
|
|
|
import {
|
|
articleSelectorForPagePath,
|
|
browserLikeUserAgent,
|
|
collectDocsOwnedLinks,
|
|
parseDocsE2EPagePaths,
|
|
} from '../utils/docs-links.js'
|
|
|
|
const pagePaths = parseDocsE2EPagePaths(process.env.DOCS_E2E_PAGE_PATHS)
|
|
|
|
test.describe('Docs owned pages', () => {
|
|
// playwright.config.ts sets fullyParallel: false, and Playwright shards
|
|
// work by file rather than by test in that mode — without this, every test
|
|
// in this single spec file runs on one worker no matter what --workers is
|
|
// passed. Opt this describe block into parallel scheduling explicitly.
|
|
test.describe.configure({ mode: 'parallel' })
|
|
|
|
test('resolved page list must not be empty', () => {
|
|
expect(
|
|
pagePaths.length,
|
|
'No pages to test. `pnpm e2e:docs` resolves pages from git changes by default, ' +
|
|
'or set DOCS_E2E_PAGE_PATHS explicitly.'
|
|
).toBeGreaterThan(0)
|
|
})
|
|
|
|
for (const pagePath of pagePaths) {
|
|
test(`${pagePath} loads and docs-owned article links resolve`, async ({ page }, testInfo) => {
|
|
const baseURL = testInfo.project.use.baseURL
|
|
expect(baseURL, 'A Playwright base URL should be configured').toBeTruthy()
|
|
|
|
const articleSelector = articleSelectorForPagePath(pagePath)
|
|
const response = await page.goto(pagePath)
|
|
expect(response, `Expected a response for ${pagePath}`).not.toBeNull()
|
|
expect(
|
|
response!.ok(),
|
|
`Page should return a successful status, got ${response!.status()}`
|
|
).toBeTruthy()
|
|
|
|
const article = page.locator(articleSelector)
|
|
await expect(article, 'Page article should be present').toBeVisible()
|
|
|
|
const links = await collectDocsOwnedLinks(page, baseURL!, articleSelector)
|
|
const userAgent = await browserLikeUserAgent(page)
|
|
|
|
for (const url of links) {
|
|
try {
|
|
const linkResponse = await page.request.get(url, { headers: { 'user-agent': userAgent } })
|
|
expect
|
|
.soft(linkResponse.ok(), `${url} should resolve (status ${linkResponse.status()})`)
|
|
.toBeTruthy()
|
|
} catch (error) {
|
|
expect
|
|
.soft(
|
|
null,
|
|
`${url} should be reachable (${error instanceof Error ? error.message : error})`
|
|
)
|
|
.toBeTruthy()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
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([])
|
|
})
|
|
}
|
|
})
|