Back to blog
Guide8 min read

Accessibility testing in CI with axe-core, Playwright and GitHub Actions

The cheapest accessibility bug is the one that never reaches production. This tutorial adds an automated accessibility check to your pull requests with axe-core (the open-source rules engine from Deque), Playwright and GitHub Actions. It catches issues that can be detected automatically. That is only part of all accessibility issues, so treat it as a safety net next to manual checks, not as a certificate.

1. Install

npm install --save-dev @playwright/test @axe-core/playwright
npx playwright install chromium

2. Write the test

Create tests/a11y.spec.ts. The tags select the WCAG 2.1 Level A and AA rules, which is the baseline behind EN 301 549 v3.2.1:

import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

const pages = ["/", "/products", "/cart", "/checkout", "/contact"];

for (const path of pages) {
  test("no detectable WCAG 2.1 AA issues on " + path, async ({ page }) => {
    await page.goto("http://localhost:3000" + path);

    const results = await new AxeBuilder({ page })
      .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
      .analyze();

    expect(results.violations).toEqual([]);
  });
}

Test the states users actually meet, not only the first paint: open the menu, the cookie banner and the modal before you call analyze(), and test the form error state after a failed submit.

3. Run it on every pull request

Start your app in the workflow, wait for it, then run the tests. .github/workflows/a11y.yml:

name: Accessibility
on: [pull_request]

jobs:
  axe:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npm run build
      - run: npm run start &
      - run: npx wait-on http://localhost:3000
      - run: npx playwright test tests/a11y.spec.ts

4. Roll it out without drowning the team

If the site already has dozens of issues, a test that fails on everything gets switched off within a week. Start narrower:

  • Fail only on serious and critical issues first. Filter the results: results.violations.filter(v => v.impact === "critical" || v.impact === "serious").
  • Record known issues in a list with a ticket and an owner, exclude exactly those, and delete the exclusion when the ticket closes. Never exclude a whole rule silently.
  • Scope per component. Use .include("#checkout") on a page you are actively fixing, then widen.
  • Print readable failures. Log violation.id, help and the failing target selectors so a developer can act without opening a report.

5. Know the limits

  • Automated rules cannot judge whether alternative text is meaningful, whether the reading order makes sense or whether a flow works with a screen reader. Add manual checks for key journeys.
  • A passing test means "no automatically detectable issues on these states on this commit", not "accessible" and not "EAA compliant".
  • The EAA reference is moving from WCAG 2.1 to 2.2 (details). Axe covers only part of the new criteria, so add manual checks for focus visibility, dragging alternatives and authentication.

When you outgrow a single repository

A pipeline test guards one codebase. Agencies also need to watch live client sites on a schedule, keep the history and send reports. That is what WCAGAlert does, and it also offers an API and a CI action (Agency plan and above) so the two can work together. Or scan a live URL for free to see what a first report looks like.

Monitor your sites for free

Scan your website for WCAG violations in under 60 seconds. No credit card required.

Accessibility testing in CI with axe-core, Playwright and GitHub Actions | WCAGAlert