The simulated screen reader in CI
voiceover only exists on macOS and nvda only on Windows, and
neither runs unattended on a CI runner. Pass --sr virtual
on every sr start or sr walk call in CI. Without
--sr, sr start and sr walk start the platform's
own screen reader, which on a macOS runner means VoiceOver.
a1 sr walk http://localhost:3000/checkout --sr virtual --json Install a browser
axe, tree, and audit load the page in a headless
browser. A CI image with no browser installed needs one added before the job runs them.
npx playwright install chromium The simulated screen reader also needs this browser when a session opens a URL, because it runs inside a headless Chromium page, which is what makes page scripts, live regions, and dialogs work. With no Chromium-family browser, the simulated screen reader falls back to jsdom and page scripts do not run, which changes what a walk or an axe run can see.
a1 wcag reads data bundled with the package. It starts no browser and
makes no network request, so it needs no setup step in CI.
Check the runner with doctor
a1 doctor --strict exits 3 while a required step is missing, and prints
the exact command for each one under Action items. Run it as an early step so a missing
browser fails that step with the command to fix it, instead of a timeout in a later step.
a1 doctor --strict GitHub Actions example
jobs:
accessibility:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm ci
- run: npx playwright install chromium
- name: Start the app
run: npm run start &
- run: npx a1 doctor --strict
- run: npx a1 axe http://localhost:3000/checkout --level AA --fail-on serious
- run: npx a1 sr walk http://localhost:3000/checkout --sr virtual --out walk.md
- uses: actions/upload-artifact@v4
if: always()
with:
name: a11y-walk
path: walk.md axe exits 4 on a violation at or above --fail-on's
impact level, which fails the step and the
job. sr walk --out writes the transcript either way, and
if: always() uploads it, so a failed job includes every announcement in
order as well as the axe result.
Baselines for existing sites
Adding a1 axe to CI on a site with existing violations means the first
run fails on all of them. Commit a baseline
once, so CI only fails on a new violation, and fix the accepted ones over time.
a1 axe http://localhost:3000/checkout --level AA --baseline a11y-baseline.json Screen reader checks
sr goto, sr find, and sr expect all exit 4 when
nothing matches, so a step that checks one announcement fails the job the same way a
failed axe run does.
a1 sr start http://localhost:3000/checkout --sr virtual --json
a1 sr goto --role button --name "Pay now"
a1 sr expect "button, Pay now"
a1 sr stop --json
For a test suite instead of shell steps, write screen reader
tests with a11ied/test or a11ied/vitest and run them
through the same npm test step as the rest of the suite.
