Modern QA2026Playwright + axe-core — tiles
Log inJoin
25 / 74 · 10 Visual & Accessibility Testing · axe-core Integration← prev⊞ allnext →☰ Read as one page

5.2Playwright + axe-core

// tests/accessibility/axe-audit.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('Accessibility Audit', () => {
    test('homepage has no critical accessibility violations', async ({ page }) => {
        await page.goto('/');

        const results = await new AxeBuilder({ page })
            .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])  // WCAG 2.1 AA
            .exclude('#third-party-widget')  // Exclude content you don't control
            .analyze();

        // Report all violations with details
        if (results.violations.length > 0) {
            const report = results.violations.map(v => ({
                rule: v.id,
                impact: v.impact,
                description: v.description,
                helpUrl: v.helpUrl,
                nodes: v.nodes.length,
                elements: v.nodes.map(n => n.html).slice(0, 3),
            }));
            console.log('Accessibility violations:', JSON.stringify(report, null, 2));
        }

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

    test('form page meets WCAG 2.1 AA', async ({ page }) => {
        await page.goto('/checkout');

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

        // Separate critical from minor violations
        const critical = results.violations.filter(v =>
            v.impact === 'critical' || v.impact === 'serious'
        );
        const minor = results.violations.filter(v =>
            v.impact === 'moderate' || v.impact === 'minor'
        );

        // Critical violations fail the build
        expect(critical).toEqual([]);

        // Minor violations are logged as warnings
        if (minor.length > 0) {
            console.warn(`${minor.length} minor accessibility issues found`);
            minor.forEach(v => console.warn(`  - ${v.id}: ${v.description}`));
        }
    });

    test('all pages pass accessibility audit', async ({ page }) => {
        const pages = [
            '/', '/login', '/register', '/products',
            '/products/1', '/cart', '/checkout', '/account',
            '/help', '/about', '/privacy', '/terms'
        ];

        const allViolations: Record<string, any[]> = {};

        for (const pagePath of pages) {
            await page.goto(pagePath);
            const results = await new AxeBuilder({ page })
                .withTags(['wcag2aa'])
                .analyze();

            if (results.violations.length > 0) {
                allViolations[pagePath] = results.violations;
            }
        }

        const totalViolations = Object.values(allViolations)
            .reduce((sum, v) => sum + v.length, 0);

        if (totalViolations > 0) {
            console.error('Accessibility violations by page:');
            for (const [pagePath, violations] of Object.entries(allViolations)) {
                console.error(`\n${pagePath}:`);
                violations.forEach(v =>
                    console.error(`  [${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} elements)`)
                );
            }
        }

        expect(totalViolations).toBe(0);
    });
});