Testes de acessibilidade automatizados com axe-core e Playwright
1. Introdução à acessibilidade web e automação de testes
A acessibilidade web não é mais uma opção — é uma necessidade. Com mais de 1 bilhão de pessoas vivendo com alguma forma de deficiência, garantir que sites e aplicações sejam utilizáveis por todos é uma questão de inclusão digital. Além disso, requisitos legais como as Diretrizes de Acessibilidade para Conteúdo Web (WCAG) e leis como a ADA (Americans with Disabilities Act) tornam a conformidade obrigatória em muitos países. A acessibilidade também impacta positivamente o SEO, já que mecanismos de busca favorecem sites com estrutura semântica clara e navegação intuitiva.
Testar acessibilidade manualmente é um processo demorado e sujeito a erros humanos. Ferramentas automatizadas como o axe-core surgem como uma solução eficiente, aplicando centenas de regras de verificação em segundos. O axe-core, desenvolvido pela Deque Systems, é um motor de regras de acessibilidade que pode ser integrado a frameworks de automação de testes. Quando combinado com o Playwright — uma ferramenta moderna de automação de navegadores —, obtemos uma dupla poderosa para garantir que cada página seja acessível desde o início do desenvolvimento.
2. Configuração do ambiente de teste com Playwright e axe-core
Para começar, precisamos instalar as dependências necessárias. O Playwright oferece suporte nativo ao axe-core através do pacote @axe-core/playwright.
npm init -y
npm install @playwright/test @axe-core/playwright
npx playwright install
Após a instalação, configuramos o Playwright no arquivo playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
use: {
browserName: 'chromium',
viewport: { width: 1280, height: 720 },
},
});
Agora, em cada arquivo de teste, importamos o AxeBuilder para realizar as análises:
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
3. Escrevendo o primeiro teste de acessibilidade automatizado
Vamos criar um teste simples que navega para uma página e executa a análise de acessibilidade:
test('Página inicial deve ser acessível', async ({ page }) => {
await page.goto('https://exemplo.com');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
O método analyze() retorna um objeto contendo:
- violations: violações de regras de acessibilidade
- passes: verificações que passaram
- incomplete: verificações que não puderam ser concluídas automaticamente
- inapplicable: regras que não se aplicam à página
Para um relatório mais detalhado, podemos percorrer as violações:
test('Verificar violações de acessibilidade', async ({ page }) => {
await page.goto('https://exemplo.com');
const results = await new AxeBuilder({ page }).analyze();
if (results.violations.length > 0) {
console.log(`Encontradas ${results.violations.length} violações:`);
results.violations.forEach(violation => {
console.log(`- ${violation.id}: ${violation.description}`);
violation.nodes.forEach(node => {
console.log(` Elemento: ${node.html}`);
});
});
}
expect(results.violations.length).toBe(0);
});
4. Personalizando regras e configurando a análise
Nem todas as regras do axe-core são relevantes para todos os projetos. Podemos filtrar regras específicas:
test('Testar apenas regras WCAG 2.1 AA', async ({ page }) => {
await page.goto('https://exemplo.com');
const results = await new AxeBuilder({ page })
.withTags(['wcag21aa', 'wcag2aa'])
.analyze();
expect(results.violations).toEqual([]);
});
Para excluir elementos específicos da análise (como banners de terceiros ou modais temporários):
test('Excluir banner de cookies', async ({ page }) => {
await page.goto('https://exemplo.com');
const results = await new AxeBuilder({ page })
.exclude('#cookie-banner')
.analyze();
expect(results.violations).toEqual([]);
});
Também podemos definir limites de severidade:
test('Permitir apenas violações menores', async ({ page }) => {
await page.goto('https://exemplo.com');
const results = await new AxeBuilder({ page })
.withRules(['color-contrast', 'label'])
.analyze();
const violacoesCriticas = results.violations.filter(
v => v.impact === 'critical' || v.impact === 'serious'
);
expect(violacoesCriticas).toEqual([]);
});
5. Integração com relatórios e CI/CD
Para gerar relatórios detalhados, podemos salvar os resultados em formato JSON:
import fs from 'fs';
test('Gerar relatório de acessibilidade', async ({ page }) => {
await page.goto('https://exemplo.com');
const results = await new AxeBuilder({ page }).analyze();
fs.writeFileSync(
'relatorio-acessibilidade.json',
JSON.stringify(results, null, 2)
);
expect(results.violations).toEqual([]);
});
Em pipelines de CI/CD (como GitHub Actions), podemos falhar o build automaticamente:
test('Falhar build se houver violações críticas', async ({ page }) => {
await page.goto('https://exemplo.com');
const results = await new AxeBuilder({ page }).analyze();
const violacoesCriticas = results.violations.filter(
v => v.impact === 'critical'
);
if (violacoesCriticas.length > 0) {
console.error('Violações críticas encontradas:');
violacoesCriticas.forEach(v => {
console.error(`- ${v.id}: ${v.help}`);
});
}
expect(violacoesCriticas).toEqual([]);
});
6. Boas práticas e armadilhas comuns
Para testar múltiplos estados da UI, é essencial interagir com a página antes da análise:
test('Testar menu aberto', async ({ page }) => {
await page.goto('https://exemplo.com');
await page.click('#menu-toggle');
await page.waitForSelector('#menu.open');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
Para evitar falsos positivos com componentes dinâmicos, use exclude() ou configure regras específicas:
test('Testar formulário com erro de validação', async ({ page }) => {
await page.goto('https://exemplo.com/contato');
await page.click('#submit');
await page.waitForSelector('.error-message');
const results = await new AxeBuilder({ page })
.exclude('#recaptcha') // Excluir reCAPTCHA
.analyze();
expect(results.violations).toEqual([]);
});
7. Ampliando a cobertura: testes visuais e de teclado
A acessibilidade vai além do axe-core. Podemos combinar com testes de navegação por teclado:
test('Navegação por teclado deve ser funcional', async ({ page }) => {
await page.goto('https://exemplo.com');
// Simular navegação por teclado
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Enter');
// Verificar se o foco está no elemento correto
const focusedElement = await page.evaluate(() => document.activeElement?.tagName);
expect(focusedElement).toBe('A');
// Executar análise de acessibilidade
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
Para verificar contraste de cores, podemos usar ferramentas complementares:
test('Verificar contraste de cores', async ({ page }) => {
await page.goto('https://exemplo.com');
// Tirar screenshot para análise visual
await page.screenshot({ path: 'screenshot.png' });
const results = await new AxeBuilder({ page })
.withRules(['color-contrast'])
.analyze();
expect(results.violations).toEqual([]);
});
Referências
- Documentação oficial do axe-core — Guia completo do motor de regras de acessibilidade, incluindo todas as regras suportadas e exemplos de implementação
- Playwright: Acessibilidade — Documentação oficial do Playwright sobre testes de acessibilidade, com exemplos de integração com axe-core
- WCAG 2.1 - Diretrizes de Acessibilidade para Conteúdo Web — Especificação oficial das diretrizes WCAG 2.1, referência para regras de acessibilidade
- axe-core no GitHub — Repositório oficial do axe-core com código-fonte, issues e exemplos de uso
- Deque University: Curso de Acessibilidade Web — Plataforma de treinamento com cursos práticos sobre acessibilidade web e uso do axe-core