test(#118): add axe-core wcag2a/wcag2aa accessibility checks to E2E suite

Installs @axe-core/playwright and adds e2e/accessibility.spec.ts covering:
- home, persons, admin (authenticated via stored admin session)
- login (unauthenticated context)

Uses wcag2a + wcag2aa tags. Violations are logged with impact level and
node count before the assertion fails, so the first run against the live
stack will produce a clear inventory of any issues to fix or exclude.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-28 16:37:52 +01:00
parent e27af75e21
commit f9236cc575
3 changed files with 83 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
import AxeBuilder from '@axe-core/playwright';
import { test, expect } from '@playwright/test';
/**
* Automated accessibility checks using axe-core (wcag2a + wcag2aa).
* Authenticated pages use the stored admin session from playwright.config.ts.
* The login page test overrides to an unauthenticated context.
*
* On first run: if violations are found they are logged with full details so
* that they can be either fixed or explicitly excluded here with a comment
* explaining the reason.
*/
const AUTHENTICATED_PAGES = [
{ name: 'home', path: '/' },
{ name: 'persons', path: '/persons' },
{ name: 'admin', path: '/admin' }
];
test.describe('Accessibility — authenticated pages', () => {
for (const { name, path } of AUTHENTICATED_PAGES) {
test(`${name} page has no critical wcag2a/wcag2aa violations`, async ({ page }) => {
await page.goto(path);
await page.waitForSelector('[data-hydrated]');
const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze();
if (results.violations.length > 0) {
const summary = results.violations
.map((v) => `[${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} node(s))`)
.join('\n');
console.log(`\nAccessibility violations on ${name}:\n${summary}`);
}
expect(results.violations).toEqual([]);
});
}
});
test.describe('Accessibility — login page', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('login page has no critical wcag2a/wcag2aa violations', async ({ page }) => {
await page.goto('/login');
await expect(page.getByLabel('Benutzername')).toBeVisible();
const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze();
if (results.violations.length > 0) {
const summary = results.violations
.map((v) => `[${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} node(s))`)
.join('\n');
console.log(`\nAccessibility violations on login:\n${summary}`);
}
expect(results.violations).toEqual([]);
});
});

View File

@@ -13,6 +13,7 @@
"pdfjs-dist": "^5.5.207"
},
"devDependencies": {
"@axe-core/playwright": "^4.11.1",
"@eslint/compat": "^1.4.0",
"@eslint/js": "^9.39.1",
"@inlang/paraglide-js": "^2.5.0",
@@ -47,6 +48,19 @@
"vitest-browser-svelte": "^2.0.1"
}
},
"node_modules/@axe-core/playwright": {
"version": "4.11.1",
"resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.1.tgz",
"integrity": "sha512-mKEfoUIB1MkVTht0BGZFXtSAEKXMJoDkyV5YZ9jbBmZCcWDz71tegNsdTkIN8zc/yMi5Gm2kx7Z5YQ9PfWNAWw==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"axe-core": "~4.11.1"
},
"peerDependencies": {
"playwright-core": ">= 1.0.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
@@ -2866,6 +2880,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/axe-core": {
"version": "4.11.1",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz",
"integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==",
"dev": true,
"license": "MPL-2.0",
"engines": {
"node": ">=4"
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",

View File

@@ -26,6 +26,7 @@
"pdfjs-dist": "^5.5.207"
},
"devDependencies": {
"@axe-core/playwright": "^4.11.1",
"@eslint/compat": "^1.4.0",
"@eslint/js": "^9.39.1",
"@inlang/paraglide-js": "^2.5.0",