devops(testing): add axe-core accessibility checks to Playwright E2E suite #118

Closed
opened 2026-03-27 17:54:01 +01:00 by marcel · 1 comment
Owner

Problem

Accessibility regressions are currently invisible in CI. A change that removes an aria-label or breaks keyboard focus order will pass all tests and be merged silently.

Fix

Add @axe-core/playwright to the E2E suite and run automated accessibility checks on key pages:

Install:

npm install --save-dev @axe-core/playwright

Add a dedicated a11y spec (e2e/accessibility.spec.ts):

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

const PAGES = [
  { name: 'home', path: '/' },
  { name: 'document-detail', path: '/documents/<seed-id>' },
  { name: 'edit-document', path: '/documents/<seed-id>/edit' },
  { name: 'persons', path: '/persons' },
  { name: 'admin', path: '/admin' },
];

for (const { name, path } of PAGES) {
  test(`${name} has no critical a11y violations`, async ({ page }) => {
    await page.goto(path);
    await page.waitForSelector('[data-hydrated]');

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

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

Notes

  • Run with wcag2a + wcag2aa tags — covers the most impactful rules
  • Use seed document IDs from the E2E test data initializer
  • Violations will fail the build, forcing accessibility to be considered on every PR

Acceptance Criteria

  • @axe-core/playwright installed as dev dependency
  • e2e/accessibility.spec.ts runs against at least 5 key pages
  • All current violations documented (or fixed) so the suite starts green
  • Spec is included in the CI Playwright run
## Problem Accessibility regressions are currently invisible in CI. A change that removes an `aria-label` or breaks keyboard focus order will pass all tests and be merged silently. ## Fix Add `@axe-core/playwright` to the E2E suite and run automated accessibility checks on key pages: **Install:** ```bash npm install --save-dev @axe-core/playwright ``` **Add a dedicated a11y spec (`e2e/accessibility.spec.ts`):** ```typescript import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; const PAGES = [ { name: 'home', path: '/' }, { name: 'document-detail', path: '/documents/<seed-id>' }, { name: 'edit-document', path: '/documents/<seed-id>/edit' }, { name: 'persons', path: '/persons' }, { name: 'admin', path: '/admin' }, ]; for (const { name, path } of PAGES) { test(`${name} has no critical a11y violations`, async ({ page }) => { await page.goto(path); await page.waitForSelector('[data-hydrated]'); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa']) .analyze(); expect(results.violations).toEqual([]); }); } ``` ## Notes - Run with `wcag2a` + `wcag2aa` tags — covers the most impactful rules - Use seed document IDs from the E2E test data initializer - Violations will fail the build, forcing accessibility to be considered on every PR ## Acceptance Criteria - `@axe-core/playwright` installed as dev dependency - `e2e/accessibility.spec.ts` runs against at least 5 key pages - All current violations documented (or fixed) so the suite starts green - Spec is included in the CI Playwright run
marcel added the test label 2026-03-27 18:45:05 +01:00
Author
Owner

Architect review (@mkeller): Approach is correct — @axe-core/playwright is the official Deque integration, the right package choice.

One implementation prerequisite: the spec uses waitForSelector('[data-hydrated]') before running axe. That sentinel attribute needs to actually exist in the layout (e.g. set on <body> or the root element after onMount). Verify this is in place before writing the spec, otherwise the wait will time out or resolve too early.

Close #122 — it covers the same goal but uses axe-playwright, a community wrapper that @axe-core/playwright supersedes. This ticket is the one to implement.

**Architect review (@mkeller):** ✅ Approach is correct — `@axe-core/playwright` is the official Deque integration, the right package choice. One implementation prerequisite: the spec uses `waitForSelector('[data-hydrated]')` before running axe. That sentinel attribute needs to actually exist in the layout (e.g. set on `<body>` or the root element after `onMount`). Verify this is in place before writing the spec, otherwise the wait will time out or resolve too early. Close #122 — it covers the same goal but uses `axe-playwright`, a community wrapper that `@axe-core/playwright` supersedes. This ticket is the one to implement.
Sign in to join this conversation.
No Label test
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#118