Skip to content

Dá cobertura de navegador ao CSP e ao JS da aplicação - #94

Merged
Hirley merged 1 commit into
mainfrom
claude-hirley/system-specs
Sep 1, 2026
Merged

Hirley merged 1 commit into
mainfrom
claude-hirley/system-specs

Conversation

@Hirley

@Hirley Hirley commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Fecha a #85.

Nada que dependesse de JavaScript tinha teste automatizado. O custo não era teórico: a CSP da v2.0.0 precisou ser corrigida duas vezes por comportamento que só aparece num navegador de verdade — o widget do VLibras carregando imagem de outro CDN, e o nonce por requisição quebrando a navegação do Turbo Drive. Nenhuma das duas quebraria um spec de request.

Cinco system specs (Capybara + Chrome headless), deliberadamente poucos. Não replicam o que os request specs cobrem: cobrem os lugares onde a falha é silenciosa — a página renderiza, o servidor responde 200, e só o navegador sabe que algo foi bloqueado ou deixou de rodar.

Exemplo O que quebraria sem ele
página pública sem violação de CSP diretiva nova bloqueando recurso legítimo, sem sintoma no servidor
página autenticada sem violação de CSP idem, nas telas com importmap e VLibras
sem violação depois de navegação do Turbo Drive o bug que já aconteceu
alto contraste sobrevive ao Turbo application.js não executando — nonce do importmap recusado deixa a página bonita e inerte
confirmação do Turbo ao excluir data-turbo-confirm parando de funcionar, e a exclusão acontecendo sem perguntar

Eles discriminam — e isso foi medido

Reintroduzi o nonce por requisição no initializer e rodei. O exemplo da navegação do Turbo falha com a mensagem real do navegador:

Executing inline script violates the following Content Security Policy
directive... a nonce ('nonce-...') is required to enable inline
execution. The action has been blocked.

Com o nonce de sessão, passa. Um spec de invariante que nunca falha não vale nada, e este repositório já me mostrou isso uma vez (a factory que quase fez os specs de papel passarem por vacuidade, na #79).

Dois achados que só apareceram por rodar um navegador

O app não declarava favicon. Todo navegador pede /favicon.ico por conta própria e, sem rota para isso, cada primeira visita virava um ActionController::RoutingError — ruído de log em produção, e falha imediata nos system specs, já que a Capybara repropaga erro de servidor. Corrigido com um <link rel="icon"> e um ícone SVG na identidade visual do projeto (o gradiente verde da navbar), que é o que faz o navegador parar de pedir o .ico.

O tour guiado intercepta cliques. Ele dispara sozinho no painel de quem tem tour_completed_at nulo. Num request spec é só markup; com navegador, é um overlay que recebe o clique no lugar do botão. Os specs usam um usuário que já passou pelo tour — que é quem eles representam.

Decisões

selenium-webdriver em vez de cuprite/ferrum: é o driver padrão dos system tests do Rails, e o Chrome já vem na imagem do runner — nenhum pacote de sistema a instalar no CI. É a única dependência pesada do projeto e entra só no grupo de teste; o BUNDLE_WITHOUT do Dockerfile mantém a imagem de produção intacta.

Rodam no bundle exec rspec normal, não num job à parte, para que "rodar os testes" signifique a mesma coisa aqui e no CI. O preço é que o container de verificação local precisa de Chromium — documentado na skill verificar-local e no CLAUDE.md, porque sem ele a suíte inteira falha, não só os cinco.

O que continua sem cobertura

Tour guiado, dropdown de busca, busca por voz e o widget do VLibras. São falhas visíveis, não silenciosas — e a issue pedia poucos specs nos fluxos onde a falha passa despercebida, não uma segunda suíte.

Verificação

No container (com Chromium instalado): RuboCop 112 arquivos / 0 ofensas, zeitwerk:check All is good!, RSpec 399 exemplos / 0 falhas em 13s — eram 394 em 7,5s, então os cinco novos custam ~5,5s. Bem dentro do critério de "tempo de CI continua aceitável".

Nada que dependesse de JavaScript tinha teste automatizado. O custo disso
não era teórico: a CSP da v2.0.0 precisou ser corrigida duas vezes por
comportamento que só aparece num navegador de verdade — o widget do
VLibras carregando imagem de outro CDN, e o nonce por requisição
quebrando a navegação do Turbo Drive. Nenhuma das duas quebraria um spec
de request.

Cinco system specs (Capybara + Chrome headless), deliberadamente poucos.
Não replicam o que os request specs cobrem: cobrem os lugares onde a
falha é SILENCIOSA — a página renderiza, o servidor responde 200, e só o
navegador sabe que algo foi bloqueado ou deixou de rodar.

Violação de CSP não levanta erro nem quebra a renderização: o navegador
recusa o recurso e escreve no console. Por isso o driver liga
goog:loggingPrefs e os exemplos leem o log. O filtro é pelo texto
"Content Security Policy", e não "console limpo": o layout carrega
Bootstrap, fontes do Google e o VLibras de CDNs externos, e uma falha de
rede vira "Failed to load resource" — ruído que não é o que se verifica
aqui.

Os specs discriminam, e isso foi medido, não suposto: reintroduzindo o
nonce por requisição no initializer, o exemplo da navegação do Turbo
falha com a mensagem real do navegador ("Executing inline script violates
the following Content Security Policy directive... The action has been
blocked"). Com o nonce de sessão, passa.

Dois achados que só apareceram por rodar um navegador de verdade:

O app não declarava favicon, então todo navegador pedia /favicon.ico por
conta própria — e sem rota para isso, cada primeira visita virava um
ActionController::RoutingError. Em produção é ruído de log; nos system
specs, falha imediata, porque a Capybara repropaga erro de servidor.
Corrigido com um <link rel="icon"> e um ícone SVG na identidade visual do
projeto, que é o que faz o navegador parar de pedir o .ico.

O tour guiado dispara sozinho no painel de quem tem tour_completed_at
nulo, e o overlay dele intercepta todo clique. Num request spec ele é só
markup; com navegador, é um elemento que existe e recebe o clique no
lugar do botão. Os specs usam um usuário que já passou pelo tour, que é
quem eles representam.

Decisões:

selenium-webdriver em vez de cuprite/ferrum: é o driver padrão dos system
tests do Rails, e o Chrome já vem na imagem do runner — nenhum pacote de
sistema a instalar no CI. É a única dependência pesada do projeto, e
entra só no grupo de teste (BUNDLE_WITHOUT no Dockerfile mantém a imagem
de produção intacta).

Os system specs rodam no `bundle exec rspec` normal, não num job à parte,
para que "rodar os testes" signifique a mesma coisa aqui e no CI. O preço
é que o container de verificação local precisa de Chromium — está na
skill verificar-local e no CLAUDE.md.

Verificado no container: RuboCop 112 arquivos sem ofensas,
zeitwerk:check limpo, RSpec 399 exemplos e 0 falhas em 13s (eram 394 em
7,5s — os cinco novos custam ~5,5s).

Closes #85

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Hirley
Hirley merged commit 4b289e5 into main Sep 1, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in @Hirley's task_keeper_api Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant