- Data & AI Solutions
A IA já atravessa quase todo o nosso fluxo: escreve o componente, sugere o teste, abre o PR. Com isso, surge uma pergunta que quase ninguém faz: se o agente entrega exatamente o que foi pedido, o que acontece com aquilo que ninguém pediu?
Esse é o caso da acessibilidade, que quase nunca é pedida. Não por má vontade: ela simplesmente não está nos critérios de aceite. O agente gera um clicável com uma fidelidade impressionante, porque foi isso que estava escrito.
A boa notícia é que a mesma automação que ajudou a criar o problema também pode ajudar a resolvê-lo. É possível ampliar a cobertura de testes automatizados com acessibilidade de forma prática e simples: bastam quatro linhas de código para executar o primeiro scan.
Este artigo mostra como aplicar essa abordagem em dois contextos bem diferentes: um projeto novo, usando Spec-Driven Development (SDD), e um projeto que já está em andamento.
Ninguém sobe doze andares e depois pergunta onde vai ficar a rampa. Em software, a gente faz isso toda semana. O custo de adicionar acessibilidade depois não é apenas maior, ele é de outra natureza.
Corrigir contraste na UI é trocar um hexadecimal. Corrigir contraste em produção é reabrir o Design System, retestar quarenta telas e negociar prazo com três times. Um vira uma tarefa de vinte minutos, o outro vira uma iniciativa com nome próprio e apresentação em comitê.
Tem também o problema de dono. Acessibilidade adicionada depois não pertence a nenhuma feature e, por isso, vira aquele ticket que atravessa quatro releases intacto, sempre importante e nunca prioritário.
Por fim, tem o pior dos três: acessibilidade remendada raramente é a mesma coisa. Um nativo carrega um contrato inteiro que o navegador implementa de graça.
Ele é anunciado como botão pelo leitor de tela, entra na ordem de tabulação, recebe contorno de foco, responde ao Enter e à barra de espaço, e ainda recebe estilo específico no modo de alto contraste do Windows.
Um com role="button" não recebe nada disso, e você precisa reimplementar cada item na mão, em JavaScript e CSS. O <div role="button"> é o falso amigo da acessibilidade: no diff parece igual, no teclado é outro produto.
O lado bom é que esse custo é uma inclinação, não um muro. A segunda metade deste artigo é sobre como subir essa ladeira sem parar o time.
Quase todo material sobre testes automatizados de acessibilidade assume um dos dois extremos. Ou fala com quem está começando do zero, e aí é muito fácil dizer “faça certo desde o início”.
Ou fala com quem tem quatrocentas telas e nenhuma delas navegável por teclado, e aí a recomendação vem no formato de relatório de auditoria com noventa páginas que ninguém executa.
Este texto assume os dois. E a parte interessante é que a ferramenta é a mesma: o mesmo pacote @axe-core/playwright, a mesma fixture, a mesma estrutura de contrato. O que muda são três parâmetros: o que bloqueia o build, o que apenas reporta, e o que é supressão autorizada.
A diferença entre os dois projetos não está no código do teste. Está em quantas violações você consegue olhar facilmente.
Se você acompanha o nosso blog, já viu o texto sobre Spec-Driven Development, a abordagem em que especificações estruturadas se tornam a fonte de verdade e guiam a geração de código com apoio de IA.
Vale reler aquele texto com uma lente específica: se a spec é a fonte de verdade e o agente deriva o código dela, então o que não está na spec não existe no produto. Não é falha de atenção do desenvolvedor nem alucinação do modelo. É ausência de requisito, cumprida com precisão.
Ou seja, spec sem acessibilidade não gera código inacessível por acidente. Gera por obediência. Isso muda a economia do problema.
No modelo tradicional, adicionar acessibilidade significa caçar componente, entender quem construiu, descobrir se ainda tem dono. Em Spec-Driven Development, significa editar a spec. Uma linha nos critérios de aceite, antes de existir código.
O contraponto também é verdadeiro, e é o motivo pelo qual este artigo existe: depois que 80% da implementação já foi gerada em cima de uma spec omissa, o custo volta a ser o antigo.
A janela em que acessibilidade é barata é estreita, e ela abre exatamente quando o time acha que ainda não é hora de pensar nisso.
O motor de verificação usado pela maior parte do mercado é o axe, da Deque, e a documentação de acessibilidade do Playwright dá três exemplos que resumem bem o que ele encontra:
A própria documentação do Playwright abre a seção com um aviso honesto: testes automatizados detectam alguns problemas comuns, como propriedades ausentes ou inválidas, mas muitos problemas só aparecem em teste manual.
A recomendação é combinar automação, avaliação manual e teste com usuários reais.
Na prática, a automação cobre algo em torno da metade dos critérios de sucesso do WCAG, dependendo bastante de como você conta. E essa divisão é a coisa mais importante deste artigo, porque ela organiza todo o resto:
A instalação é uma linha:
npm i -D @axe-core/playwright
O pacote é versionado em conjunto com o axe-core e injeta o motor em todos os frames automaticamente. Não existe passo de injeção manual.
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('a home não tem violações detectáveis automaticamente', async ({ page }) => {
await page.goto('https://seu-site.com/');
const resultados = await new AxeBuilder({ page }).analyze();
expect(resultados.violations).toEqual([]);
});
Em base nova, isso passa. Em base legada, esse toEqual([]) é a linha mais otimista do repositório.
Por padrão, o axe roda um conjunto amplo de regras, e parte delas é de boa prática, não de critério WCAG. Para verificar apenas o que corresponde a critério de sucesso nível A e AA, use as tags:
const resultados = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
Um detalhe que derruba muita gente: analyze() escaneia a tela no estado em que ela está no momento da chamada. Modal fechado não é escaneado. Se o que você quer verificar aparece depois de um clique, abra, espere o elemento estar presente e só então chame analyze().
Repetir configuração em trinta arquivos de teste é como repetir qualquer outra coisa em trinta arquivos: funciona por dois meses.
A fixture do Playwright resolve isso, entregando um AxeBuilder já configurado para todos os testes do projeto. É também o lugar onde a política de acessibilidade do time passa a morar de verdade, e é sobre isso que fala o restante do conteúdo.
Esta seção é curta de propósito, e a brevidade já é parte do argumento.
Em projeto novo com SDD, acessibilidade não é tarefa de implementação. É requisito. Ela entra como uma linha nos critérios de aceite da spec, e o agente gera o com nome acessível porque foi isso que foi pedido. Custo total: uma linha.
A configuração padrão também é diferente, e mais simples: bloqueia todos os impactos. Sem supressões. Sem exclusões. Não existe dívida herdada para negociar, então a primeira violação do projeto será sempre de código escrito ontem, e código de ontem é barato de corrigir.
O momento de fazer isso é antes de qualquer código existir, ainda na análise da spec. É quando definir que “o erro é anunciado como alerta” custa apenas uma frase, e não uma refatoração do Design System.
No modelo de maturidade do texto sobre SDD, isso corresponde ao nível spec-first aplicado a um requisito não funcional. Só que esse preço de uma linha não volta. Todo mês que a feature roda sem cenário de acessibilidade, ele sobe.
Você instala o @axe-core/playwright, roda o primeiro scan na home e o terminal devolve 84 violações. 31 são o mesmo componente de card, repetido trinta e uma vezes.
É um exame de sangue depois das festas de fim de ano. Você já suspeitava, mas ver escrito é diferente.
O erro mais comum a partir daqui é tentar rodar a configuração de projeto novo. A suíte fica vermelha no primeiro dia, permanece vermelha na primeira semana, e na primeira sprint apertada alguém comenta a verificação. Ninguém nunca descomenta.
Existe um caminho melhor, em cinco movimentos.
Rode o scan e não falhe nada. Só registre. O objetivo do primeiro dia é medir o problema, não resolver.
Junto disso, faça o inventário do que já existe: qual o nome acessível de cada controle dos fluxos que importam, se o projeto tem alguma convenção de acessibilidade, se o Design System já resolve parte da questão.
Botão sem nome acessível, aqui, não é “seletor a confirmar”. É divergência, e ela vai virar bug.
Este é o mecanismo central da trilha, e a documentação do Playwright já traz a técnica na seção sobre lidar com problemas conhecidos.
Em vez de exigir zero violação, você registra o que já existe hoje: o identificador da regra e os seletores dos nós afetados. A partir daí, o teste só falha quando esse conjunto aumenta.
A documentação alerta para não guardar o array de violações inteiro, porque ele carrega o HTML renderizado dos elementos e quebra o teste a cada mudança sem relação nenhuma com acessibilidade.
A suíte não é tribunal, é catraca. Não julga o passado, só impede o retrocesso.
Cada violação que o axe reporta tem um impacto atribuído. Em base existente, a política que funciona é bloquear apenas critical e serious, e registrar moderate e minor como relatório anexo ao teste.
Isso não é frouxidão, é engenharia de adoção. Uma suíte que reprova por 84 violações herdadas não produz rigor, produz ruído. E toda regra impossível de cumprir tem data de validade: sexta-feira, 18h.
Vai existir exceção. O que não pode existir é exceção sem prazo.
Toda supressão exige duas informações obrigatórias: o ticket que vai resolver e a data em que ela expira. E a fixture falha de propósito quando o prazo vence, com mensagem explícita pedindo para corrigir a violação ou renovar o acordo. É a diferença entre dívida registrada e dívida esquecida.
Supressão sem prazo é um // TODO de terno.
Vale um cuidado técnico aqui, e ele está explícito na documentação do Playwright. A saída mais óbvia para calar uma violação é excluir o elemento do scan, mas exclude() remove o elemento e todos os seus descendentes, e desliga todas as regras naquele nó, não só a que está falhando.
Uma linha para silenciar um card silencia o card inteiro, para sempre. Filtrar o resultado por regra mais seletor dá o mesmo alívio sem apagar a cobertura.
A documentação do Cypress sobre acessibilidade tem a orientação mais sensata que já li sobre onde parar: garanta que a acessibilidade seja testada explicitamente ao menos uma vez por componente, área ou fluxo.
Depois disso, repetir asserção sobre o mesmo componente em outros testes não adiciona cobertura nenhuma. Em legado, isso é libertador. Você não precisa de quatrocentos scans. Precisa de um por fluxo crítico. Login, busca, checkout, nessa ordem.
O mesmo artigo do Cypress traz um alerta de performance que vale para o Playwright igualmente: cada verificação avalia o DOM elemento por elemento para descobrir quais regras se aplicam, roda as verificações e computa o resultado.
Com poucos scans isso é irrelevante. Com centenas, vira uma parcela considerável do tempo de pipeline, e ela cresce devagar o suficiente para ninguém notar.
Projeto legado normalmente não tem especificação nenhuma, e a conclusão natural é que SDD não se aplica. Porém, não é o caso.
Ninguém precisa especificar o sistema inteiro. Especifica-se o trecho que está sendo mexido. Toda feature nova, todo bug corrigido, toda tela refatorada vira uma spec pequena, com a linha de acessibilidade dentro.
É o nível spec-anchored do modelo de maturidade, chegando por baixo em vez de por cima. Em dois trimestres, os fluxos que importam têm spec. O resto do legado continua legado, e está tudo bem.
O que descrevemos até aqui virou um pacote de skills de QA para agente, não um script nem um plugin. A esteira recebe uma spec, investiga a implementação real, gera cenários, produz um contrato de automação, gera o código Playwright, valida na UI real e emite um parecer final.
O objetivo declarado dela é específico e vale repetir: pegar as divergências entre o que foi pedido e o que a IA entregou. Cada requisito da spec é cruzado com o código e classificado como implementado, divergente, ausente, extra ou não verificável.
Quando o código diverge da spec, o teste é escrito conforme a spec, e a falha esperada é o bug sendo pego. Nunca se ajusta o teste para passar sobre comportamento errado.
A decisão de desenho mais importante para este artigo é o que não fizemos: acessibilidade não ganhou fase própria nem skill própria. Ela entrou como dimensão transversal, do mesmo jeito que segurança.
O motivo? Fase nova significa abrir o navegador duas vezes e criar um gate a mais para o time contornar quando o prazo aperta.
Ela atravessa as fases abaixo:
| Fase | O que entra |
| Contexto | Informações relevantes do produto, padrões adotados e requisitos já estabelecidos |
| Análise da especificação | Identificação dos cenários e comportamentos esperados sob a ótica do negócio |
| Contrato | Critérios de qualidade, conformidade, regras aplicáveis e pontos de verificação |
| Implementação | Estruturação dos testes e definição dos pontos de validação necessários |
| Validação | Execução das verificações previstas e análise dos resultados obtidos |
| Revisão | Conferência da cobertura dos critérios definidos e aderência ao escopo estabelecido |
A primeira é a separação entre comportamento de negócio e validações técnicas. Os cenários devem permanecer claros e compreensíveis para todos os envolvidos no projeto, enquanto regras técnicas, critérios de conformidade e detalhes de implementação ficam registrados nos artefatos apropriados de apoio.
A segunda é que desvios identificados durante a validação devem ser tratados como oportunidades de correção do produto ou do processo. O objetivo não é ajustar os mecanismos de verificação para eliminar o apontamento, mas sim analisar sua causa e garantir que o comportamento esperado esteja sendo atendido.
Há também uma premissa importante: o processo depende de informações mínimas de entrada, como requisitos, especificações ou critérios definidos para o escopo avaliado.
Quando esses insumos não existem, a atividade deixa de ser uma validação orientada por requisitos e passa a exigir uma análise exploratória ou uma avaliação específica do produto. Dessa forma, trata-se de abordagens distintas, com objetivos e entregáveis diferentes.
Vale ver como fica na prática a metade que o axe não pega. Estes são cenários reais do exemplo que acompanha o pacote, um fluxo de cupom de desconto no checkout:
@acessibilidade @p0-critico
Cenário: Erro de cupom expirado é anunciado como alerta
Quando aplico o cupom "EXPRD01"
Então a mensagem de erro é anunciada como alerta
E consigo alcançar a mensagem sem depender da cor do texto
@acessibilidade @p1-importante
Cenário: Aplicar cupom usando apenas o teclado
Quando preencho e aplico o cupom usando apenas Tab e Enter
Então a linha "Desconto (PRCT10)" deve aparecer no resumo do pedido
E o foco permanece visível em cada elemento percorrido
Nenhuma regra de axe verifica qualquer uma dessas linhas. E note que qualquer pessoa de produto consegue ler e discordar, que é justamente o ponto.
No código, esses cenários viram testes normais de Playwright, e existe um detalhe elegante: getByRole('alert') só encontra o elemento se ele realmente tiver role="alert" ou viver numa região com aria-live.
Ou seja, o locator recomendado do Playwright já é meia asserção de acessibilidade de graça.
Com uma ressalva honesta, que a documentação do Cypress faz bem: localizar um elemento pelo seu papel não prova que ele é acessível. Prova apenas que dá para encontrá-lo.
Um <div role="button"> é encontrado por getByRole('button') exatamente como um <button> de verdade, e continua sem o contrato do navegador. Localizar é uma coisa, verificar comportamento é outra.
Um lugar para você se reconhecer, e para decidir qual é o próximo passo em vez de tentar todos.
| Nível | Onde você está |
| 0 | Ninguém nunca rodou um scan. Talvez esteja tudo bem. Provavelmente não. |
| 1 | Um scan, um fluxo crítico, bloqueando apenas critical e serious |
| 2 | Linha de base registrada e catraca ligada: não piora mais |
| 3 | Checkpoints declarados no contrato e cobrados na revisão de código |
| 4 | Cenários de teclado, foco e anúncio no .feature, em linguagem de negócio |
| 5 | Acessibilidade nos critérios de aceite da spec, antes de existir código |
Projeto novo com SDD começa no nível 5. Legado sobe um nível por trimestre, e isso é rápido o suficiente.
O motor é o mesmo. O plugin comunitário cypress-axe integra o axe aos testes e adiciona um comando de verificação que roda o scan no estado atual da página, com configuração para escopo e regras.
Existem plugins construídos em cima dele, como wick-a11y e cypress-a11y-report, além de alternativas com outro motor, como o verificador da IBM.
Para teclado há o comando de tecla nativo, e o Cypress Cloud oferece uma solução paga que move os scans para fora do contexto do teste, resolvendo boa parte da questão de performance.
Ou seja: nada do que está neste texto depende de Playwright. A linha de base, a catraca, a política de impactos, a supressão com prazo e a divisão entre regra e comportamento funcionam igual nos dois.
A Inteligência Artificial acelerou o que sabemos especificar. Acessibilidade não ficou de fora porque é difícil. Ficou de fora porque nunca foi especificada, e agora um agente muito competente cumpre essa omissão em segundos, com uma consistência nunca vista antes.
Escrever a linha na spec é a parte simples. Lembrar de escrever é a parte que precisa de processo.
É para isso que serve colocar acessibilidade dentro do fluxo de QA em vez de em um backlog paralelo: para que a lembrança não dependa de ninguém em particular estar tendo um dia bom.
Em projetos novos, isso custa uma linha. Em projetos que já existem, custa uma catraca e um trimestre. Nos dois casos, custa muito menos do que a versão em que ninguém faz nada.
Não. Ele verifica um conjunto conhecido de regras e cobre algo em torno da metade dos critérios do WCAG, dependendo da metodologia de contagem. A recomendação da própria documentação do Playwright é combinar automação, avaliação manual e teste com usuários reais.
O scan verifica regra: contraste, rótulo, ID duplicado. Por outro lado, o cenário verifica comportamento: dá para usar só o teclado, o foco fica visível, o erro é anunciado. Um não substitui o outro e, por isso, eles vivem em lugares diferentes: o scan na configuração técnica e o cenário no arquivo de feature.
Vale, desde que a meta do primeiro mês não seja ficar verde. Seja parar de piorar. Um scan num fluxo crítico, linha de base registrada, bloqueio apenas dos impactos altos.
Três decisões: registre a linha de base das violações existentes e falhe só quando o conjunto crescer; bloqueie apenas critical e serious; e, por fim, exija ticket mais data de expiração em toda supressão, com a fixture falhando quando o prazo vence.
Não para começar. O axe é aberto e a integração com Playwright é um pacote de desenvolvimento. Soluções pagas resolvem principalmente escala e performance, o que é um problema de quem já tem centenas de verificações rodando.
Não, e essa é a parte que nenhuma automação vai cobrir. O axe encontra o rótulo ausente. Só uma pessoa usando o produto encontra o fluxo que tecnicamente funciona e na prática é inutilizável.