Closes DOCS-1233 ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Test coverage. The docs accessibility check now covers the full WCAG 2.1 A/AA rule set instead of two rules. **Note:** This PR tests _only_ the main article of changed pages (meaning, the content itself). A follow-up Linear issue is to address scanning the pieces outside of that: header, navigation, and interactive elements. ## What is the current behavior? The `@a11y` test in `e2e/docs` runs two axe rules against each in-scope page, `heading-order` and `page-has-heading-one`. Both already pass everywhere, so the check only guards a result we have. Nothing else in WCAG A/AA is checked. ## What is the new behavior? The same test runs the full WCAG 2.1 A/AA rule set. - **Existing debt does not block PRs.** Only the two heading rules fail. Everything else reports. - **The check stays fast.** It scans the article only and skips nine rules that cannot fire there. Scan time drops from 2405ms to 981ms. - **Findings belong to us.** Legacy mode excludes cross-origin frames. YouTube embeds were counting against us, 11 of 15 violations on one page. - **A pass carries meaning.** A 404 reports as a load failure, not an a11y bug. A page scanned before it hydrates warns instead of quietly reporting clean. ## How the findings appear The test is named `has no blocking accessibility violations`, so a failure listed by CI is always something to fix. It is not named for the full rule set, because a green check would then claim more than the check verifies. | | Rules | Where you see it | | --- | --- | --- | | Blocking | `heading-order`, `page-has-heading-one` | Test failure, so the runner reports it on the PR | | Reported | Everything else in WCAG A/AA | `::warning` annotation on the run | An annotation looks like this, on a run that still passes: ``` ::warning title=Accessibility::/docs/guides/database/functions has 1 non-blocking accessibility finding(s): frame-title (4) ``` The full axe result for each page is attached to the report as `axe-results.json`. ## Matching the Studio ratchet This follows the ESLint ratchet in `apps/studio`. That pattern warns on pre-existing debt rather than blocking on it, surfaces findings as annotations rather than PR comments, and promotes a rule to an error once its violations reach zero. The mechanism here is `ENFORCED_RULES` in `utils/axe-helpers.ts`. The two heading rules are on it because the heading-hierarchy work drove them to zero site-wide. The intent is to migrate rules into that list one at a time. Pick a rule, fix its violations, then move it into `ENFORCED_RULES` so it cannot come back. An exhaustive scan of the site groups the current backlog by root cause to sequence that work, and two fixes cover 99.1% of it. Studio keeps per-file baseline counts, which this does not. A whole-rule list is coarser, and it works here because docs violations reach zero across the site rather than per file. ## Manual testing Install the browser once, then run each step from the repo root. Every command scans production, so you do not need a local docs server. ```bash pnpm -C e2e/docs exec playwright install chromium ``` 1. Confirm a reported finding does not fail the check. ```bash DOCS_E2E_PAGE_PATHS=/docs/guides/database/functions PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs:a11y ``` Expect `1 passed`, and the `::warning` annotation above in the output. 2. Confirm the scan finds that violation. Same page, now failing on every rule. ```bash A11Y_ENFORCE_ALL=1 DOCS_E2E_PAGE_PATHS=/docs/guides/database/functions PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs:a11y ``` Expect `1 failed`, reporting `frame-title (serious, 4 node(s))`. Steps 1 and 2 together are the point of this PR. 3. Confirm the skipped rules stay skipped. ```bash A11Y_ENFORCE_ALL=1 DOCS_E2E_PAGE_PATHS=/docs/guides/getting-started/quickstarts/nextjs PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs:a11y ``` Expect `button-name (critical, 2 node(s))` and `label (critical, 2 node(s))`, and no `color-contrast`. 4. Confirm a page that does not load reports a load failure. ```bash DOCS_E2E_PAGE_PATHS=/docs/guides/does-not-exist-xyz PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs:a11y ``` Expect `Expected a successful response for /docs/guides/does-not-exist-xyz, got 404`, and no axe assertion. 5. Confirm the link checker still passes alongside the a11y test. ```bash DOCS_E2E_PAGE_PATHS=/docs/guides/auth/passwords PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs ``` Expect `3 passed`. ## Known gaps - `/docs/reference/*` is not scanned. Those routes render client-side into tens of thousands of elements, where axe exceeds its timeout and results depend on whether the scan caught the page mid-render. - Shared chrome is outside the article scope, so nav, sidebar, footer, menus, and drawers are not covered. - axe catches roughly 30-40% of WCAG issues. Keyboard navigation, focus management, and screen reader behavior still need manual testing. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
7.9 KiB
Docs E2E tests
This guide explains how to run Playwright end-to-end checks against docs pages this repo owns.
Use this suite when you change guides, troubleshooting entries, or shared
partials under apps/docs/content. It loads each in-scope page, checks that the
article renders, and verifies that docs-owned links in the article resolve.
This page covers:
- Set up — install the browser once
- Run the tests — the usual local command
- Choose a target URL — production, preview, or local docs
- Override which pages run — when the default git scope is wrong
- What the suite covers — in-scope paths and limits
- Accessibility scans — WCAG coverage and skipped rules
- Debug failures — reports and traces
- How CI uses this suite — pull request behavior
Set up
-
From this directory, install the Playwright Chromium browser once:
cd e2e/docs pnpm exec playwright install chromium
Run the tests
By default, pnpm e2e:docs tests pages affected by your current changes:
commits since origin/master, plus staged and unstaged working-tree files. If
nothing in scope changed, the command exits successfully without starting
Playwright.
-
From the repository root, point the suite at a deployed docs site and run it:
PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs -
Optional: open Playwright UI mode for the same scoped run:
PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs:ui
You can also run from e2e/docs with pnpm run e2e:docs.
Choose a target URL
Tests use PLAYWRIGHT_BASE_URL. When unset, they default to the local docs
dev server at http://localhost:3001.
Prefer a deployed site for day-to-day checks. Use the local server only when you need unpublished content that production does not serve yet.
Deployed site
PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs
For a protected Vercel preview, also set VERCEL_AUTOMATION_BYPASS_SECRET.
Local docs server
-
From the repository root, start docs in a separate terminal:
pnpm dev:docs -
Run the suite without
PLAYWRIGHT_BASE_URL, or set it tohttp://localhost:3001.
The local server needs a full monorepo install and credentials for some content.
Local runs are unreliable for pages whose docs-owned links point into
/docs/reference/* or /docs/guides/auth/server-side/*: reference pages can
take over a minute to compile on first request in dev mode, which exceeds the
suite's per-test timeout, and server-side auth guides have a known local-only
routing issue that 404s even though the page serves correctly in production.
Prefer a deployed site for pages that link into either of those sections.
Override which pages run
Leave DOCS_E2E_PAGE_PATHS unset to keep the default changed-files scope.
To test specific pages instead of the git diff:
DOCS_E2E_PAGE_PATHS=/docs/guides/getting-started/quickstarts/nextjs \
PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs
To compare against a different base ref:
DOCS_E2E_BASE_REF=origin/develop \
PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs
DOCS_E2E_PAGE_PATHS accepts a comma- or newline-separated list of /docs/...
paths.
Run every in-scope page
To test every guide and troubleshooting entry instead of a changed-files scope — for example, a periodic full-site check — run:
PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs:all
This ignores DOCS_E2E_PAGE_PATHS and the 20-page cap described in
Limits, and tests every page listed by
pnpm -C e2e/docs resolve-docs-scope across the whole guides and
troubleshooting trees — several hundred pages as of this writing. --all
runs also default to --max-failures=0, so a full run isn't cut short by
playwright.config.ts's global maxFailures: 3. Expect a long run: the suite
runs one worker by default, so pass --workers to parallelize it, for
example:
PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs:all -- --workers=4
Run this against a deployed site, not the local dev server — see Local docs server for why local runs are unreliable for pages linking into reference docs or server-side auth guides.
What the suite covers
In scope
| Changed path | Behavior |
|---|---|
apps/docs/content/guides/**/*.mdx |
Test /docs/guides/<slug>, excluding federated sections |
apps/docs/content/troubleshooting/**/*.mdx |
Test /docs/guides/troubleshooting/<slug> |
apps/docs/content/_partials/** |
Test owned pages that include that partial |
Out of scope
- Federated guide sections:
graphql,database/extensions/wrappers,ai/python,deployment/terraform,deployment/ci - Reference docs under
/docs/reference - Non-docs routes such as
/dashboardand/ui, which the link checker skips
Limits
Resolved scope is capped at 20 pages so a widely shared partial cannot explode
runtime. If a change resolves to more pages than that, only the first 20 in
sorted order are tested and the rest are silently dropped from that run. To
test beyond the cap, use pnpm e2e:docs:all instead of raising it. See
Run every in-scope page.
To inspect the resolved list without running Playwright, replicate the same
scope pnpm e2e:docs uses by default: commits since origin/master, plus
staged and unstaged working-tree changes.
{
git diff --name-only --diff-filter=ACMR origin/master...HEAD
git diff --name-only --diff-filter=ACMR
git diff --name-only --diff-filter=ACMR --cached
} | pnpm -C e2e/docs resolve-docs-scope
Accessibility scans
The @a11y-tagged test scans each in-scope page for WCAG 2.1 A/AA violations using
@axe-core/playwright, limited to the main article.
PLAYWRIGHT_BASE_URL=https://supabase.com pnpm e2e:docs:a11y
Which pages get scanned comes from your branch, but the content comes from
whatever you point PLAYWRIGHT_BASE_URL at. Production won't have your edits and
will 404 on a page you just added, so use your pull request's preview to scan your
own content.
EXCLUDED_RULES in utils/axe-helpers.ts lists the rules the scan skips.
color-contrast is most of the scan time and finds nothing inside an article, since
docs contrast comes from shared tokens and chrome. The rest target <html>, <head>,
and <body>, which an article-scoped scan can't reach.
Cross-origin frames are skipped, so a third-party embed isn't reported as ours.
Not covered: /docs/reference/*, shared chrome, and most of WCAG. Keyboard
navigation, focus management, and screen reader behavior need manual testing.
Debug failures
-
Open the HTML report after a run:
pnpm -C e2e/docs exec playwright show-report -
Inspect traces and screenshots under
test-results/for failed runs.
How CI uses this suite
The workflow at .github/workflows/docs-e2e.yml runs on pull requests that touch
owned docs content, partials, or e2e/docs.
- Diff the pull request against its base branch and resolve in-scope page paths.
- Skip Playwright when nothing in scope changed.
- When
apps/docschanged, wait for the Vercel docs preview and setPLAYWRIGHT_BASE_URLto that preview. When no preview resolves, skip rather than test against production. - Run the suite with
DOCS_E2E_PAGE_PATHSset to the resolved list.
Draft pull requests stay skipped until you mark them ready for review. Manual
workflow_dispatch runs require a page_paths input and accept an optional
base_url, which defaults to production.