Salta al contenuto principale

Playwright a11y testing: @axe-core integration

Articolo della serie Playwright Component Testing Avanzato. Pubblicazione prevista 26 November 2027.

Cosa ne pensi di questo articolo?

Playwright a11y testing: @axe-core integration
Playwright ComponentsFoto:panumas nikhomkhaiPexels

In Breve

Articolo della serie Playwright Component Testing Avanzato. Pubblicazione prevista 26 November 2027.

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.

Bash - Installazione dipendenze
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à:

TypeScript - Test base con axe-core
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:

TypeScript - Test con scope limitato
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:

TypeScript - Gestione granulare dei 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:

TypeScript - Esclusione di violazioni
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:

TypeScript - Test di componente modale
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:

TypeScript - Helper riusabile
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à:

YAML - GitHub Actions
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 role e aria-label correttamente
  • Test flaky: Aggiungi waitForSelector prima 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

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti