Introduzione all'Accessibility Testing con Playwright
L'accessibility testing è un aspetto fondamentale dello sviluppo web moderno. Non si tratta solo di un'esigenza legale o morale — è un imperativo tecnico che riguarda l'inclusione e la raggiungibilità dei tuoi prodotti digitali. In questa guida della serie Playwright Component Testing Avanzato, scoprirai come automatizzare i test di accessibilità usando Playwright integrato con @axe-core, la libreria open-source più diffusa per rilevare vulnerabilità di accessibilità nel codice.
Affronteremo l'integrazione passo dopo passo, impareremo a scrivere test significativi, e identificheremo le pratiche migliori per garantire che i tuoi componenti siano accessibili a tutti gli utenti — indipendentemente dalle loro abilità fisiche o cognitive.
Perché l'Accessibility Testing è Critico
Secondo le statistiche globali, circa il 16% della popolazione mondiale convive con una forma di disabilità. Per i siti web, ciò significa:
📊 Impatto dell'Accessibilità
- Inclusione legale: Le normative come WCAG 2.1 e la legge Stanca (Italia) richiedono accessibilità
- Mercato più ampio: Raggiungi utenti con screen reader, tastiere, controller adattivi
- SEO migliore: Le pratiche di accessibilità coincidono con le best practice SEO
- Manutenibilità: Codice accessibile è generalmente codice più pulito e ben strutturato
- Reputazione del brand: Dimostra impegno verso l'inclusione
Setup Iniziale: Playwright + @axe-core
Per iniziare, hai bisogno di Playwright e della libreria ufficiale per l'integrazione di axe-core.
npm install --save-dev @playwright/test @axe-core axe-playwright
Il pacchetto axe-playwright fornisce un'integrazione diretta tra Playwright e axe-core, semplificando l'iniettamento e l'esecuzione dei test.
Struttura Base di un Test di Accessibilità
Ecco la struttura minima per un test di accessibilità:
import { test, expect } from '@playwright/test';
import { injectAxe, checkA11y, getViolations } from 'axe-playwright';
test('home page should pass accessibility checks', async ({ page }) => {
// Navigazione
await page.goto('http://localhost:4200');
// Iniettare axe
await injectAxe(page);
// Eseguire il check
await checkA11y(page, null, {
detailedReport: true,
detailedReportOptions: {
html: true,
},
});
});
Targeting e Scopes Specifici
A volte vuoi controllare solo una parte della pagina. Usa il parametro selector per limitare lo scope:
import { test } from '@playwright/test';
import { injectAxe, checkA11y } from 'axe-playwright';
test('navbar should pass accessibility checks', async ({ page }) => {
await page.goto('http://localhost:4200');
await injectAxe(page);
// Test solo la navbar
await checkA11y(page, 'nav[role="navigation"]', {
detailedReport: true,
});
});
Gestione Avanzata dei Risultati
Non tutti i problemi di accessibilità sono critici. Puoi personalizzare come trattare i risultati:
import { test, expect } from '@playwright/test';
import { injectAxe, getViolations } from 'axe-playwright';
test('component with custom violation handling', async ({ page }) => {
await page.goto('http://localhost:4200/components/button');
await injectAxe(page);
// Ottenere le violazioni senza fallire il test
const violations = await getViolations(page);
// Filtriamo solo le critiche
const criticalViolations = violations.filter(v => v.impact === 'critical');
// Log dettagliato
if (criticalViolations.length > 0) {
console.log('Critical violations found:');
criticalViolations.forEach(v => {
console.log(`- ${v.id}: ${v.description}`);
v.nodes.forEach(n => {
console.log(` Element: ${n.html}`);
console.log(` Message: ${n.failureSummary}`);
});
});
}
// Aspettativa
expect(criticalViolations).toHaveLength(0);
});
Tip: Escludere Violazioni Conosciute
A volte una violazione è falsa-positiva o intenzionale. Puoi escluderla:
await checkA11y(page, null, {
rules: {
'color-contrast': { enabled: false }, // Disabilita il check di contrasto
'aria-required-attr': { enabled: false },
},
});
Test di Componenti Interattivi
Molti componenti richiedono interazione. Testa l'accessibilità dopo ogni cambio di stato:
test('modal dialog should be accessible in all states', async ({ page }) => {
await page.goto('http://localhost:4200/components/modal');
await injectAxe(page);
// Test stato iniziale (modale chiuso)
await checkA11y(page, null, { detailedReport: true });
// Apri il modale
await page.click('button:has-text("Open Modal")');
await page.waitForSelector('[role="dialog"]');
// Test del modale aperto
await checkA11y(page, '[role="dialog"]', { detailedReport: true });
// Focus trap test (il focus non deve uscire dal modale)
await page.press('[role="dialog"]', 'Tab');
const focusedElement = await page.evaluate(() => document.activeElement?.tagName);
expect(['BUTTON', 'INPUT', 'A']).toContain(focusedElement);
// Chiudi con Escape
await page.press('[role="dialog"]', 'Escape');
await page.waitForSelector('[role="dialog"]', { state: 'hidden' });
// Verifica il focus sia tornato al bottone
const returnFocus = await page.evaluate(() => document.activeElement?.textContent);
expect(returnFocus).toContain('Open Modal');
});
Automazione a Livello di Suite di Test
Puoi creare un helper riusabile che applica i test di accessibilità a più componenti:
import { Page } from '@playwright/test';
import { injectAxe, checkA11y, getViolations } from 'axe-playwright';
export async function testComponentAccessibility(
page: Page,
componentUrl: string,
selector?: string
): Promise {
await page.goto(componentUrl);
await injectAxe(page);
const violations = await getViolations(page);
const critical = violations.filter(v => v.impact === 'critical');
if (critical.length > 0) {
throw new Error(
`${critical.length} critical a11y violations found on ${componentUrl}`
);
}
await checkA11y(page, selector || null, { detailedReport: true });
}
// Utilizzo
test('all component pages pass a11y', async ({ page }) => {
const components = [
'/components/button',
'/components/input',
'/components/select',
'/components/dropdown',
];
for (const comp of components) {
await testComponentAccessibility(page, comp);
}
});
Best Practices per l'Accessibility Testing
Ecco le pratiche raccomandate da applicare nei tuoi test:
✅ Best Practices
- Test presto e spesso: Includi a11y test dalla fase di sviluppo
- Integra con CI/CD: Fallisci la build se le violazioni critiche vengono rilevate
- Test senza aspettative false: Non disabilitare regole arbitrariamente; comprendi il perché
- Varia i test per device: Testa sia keyboard navigation che screen reader
- Documenta le esclusioni: Se ignori una violazione, spiega il motivo nel codice
- Mantieni i dati di accesso: Usa i report per tracciare i miglioramenti nel tempo
Configurazione CI/CD per Accessibility
Integra i test nel pipeline di build per garantire che nessun commit degradi l'accessibilità:
name: Accessibility Tests
on: [push, pull_request]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm run build
- run: npm run test:a11y
- name: Upload a11y report
if: failure()
uses: actions/upload-artifact@v3
with:
name: a11y-report
path: ./a11y-reports/
Troubleshooting Comune
Ecco i problemi più frequenti e come risolverli:
🔧 Problemi Frequenti
- axe non si inietta: Verifica che la pagina sia completamente caricata prima di
injectAxe() - False positives su elementi dinamici: Aggiungi
await page.waitForLoadState('networkidle')prima dei test - Screen reader non rileva il contenuto: Usa
roleearia-labelcorrettamente - Test flaky: Aggiungi
waitForSelectorprima di testare componenti dinamici
FAQ - Domande Frequenti
D: Qual è la differenza tra axe-core e WAVE?
axe-core è una libreria open-source gratuita e programmaticamente testabile. WAVE è uno strumento browser principalmente manuale (anche se ha un'API). Per l'automazione, axe-core è la scelta migliore.
D: Posso usare axe-core in test non-Playwright?
Sì! axe-core funziona con Cypress, Selenium, Puppeteer e persino direttamente nel browser via console. Ogni framework ha il suo adapter.
D: Se ho un sito legacy con problemi di a11y, come inizio?
Inizia con i problemi critici (contrast, form labels). Usa `getViolations()` per ordinare per impatto. Affronta i criteri WCAG AA prima, poi AAA. Non è una corsa; è un miglioramento continuo.
D: Devo testare ogni componente singolarmente?
Dipende dalla complessità. Per componenti semplici (button, input), un test integrazione sulla pagina basta. Per componenti complessi (table, tree, modal), testa ogni stato separatamente.
D: axe-core copre tutto della WCAG 2.1?
No. axe-core copre circa il 30-40% dei criteri WCAG 2.1 automaticamente. Il resto richiede test manuali (zoom, keyboard navigation, screen reader) e revisione umana.

Commenti
Caricamento commenti...
Accedi per commentare