Accessibility test toolkit

Run in CI

A CI runner has no VoiceOver or NVDA, so every a1 command that starts a screen reader needs --sr virtual. axe, tree, and audit need Chrome, Edge, Brave, or Chromium. --fail-on sets which impact level fails the build, and --baseline lets CI ignore existing violations while still failing on new ones.

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.