Skills.

Um teste visual que não distingue “a página mudou” de “a captura não é reprodutível” é silenciado em uma semana. A diferença é essa, e ela é determinística por construção: mesmas entradas, mesmo veredicto.

Os dois motivos, e as respostas opostas

Um gate que não separa os dois vira ruído e alguém desliga. O trabalho todo é manter os dois separados — sem IA no meio: mesma entrada, mesmo veredicto.

Os quatro veredictos (e o que fazer com cada um)

STALE existe porque o modo clássico de falhar do teste visual não é flakiness: é a referência re-gerada em silêncio até o teste ficar verde. Amarrar a referência ao git HEAD e a um hash da config de máscara transforma “refazer a referência” num ato deliberado e revisável.

Os códigos de saída são o contrato

0 PASS   1 FAIL   2 STALE   3 ERROR

python scripts/visual_gate.py fingerprint baseline/home.png \
    --url https://app.exemplo/home --viewport 1280x720 \
    --config baseline/home.masks.json

python scripts/visual_gate.py compare baseline/home.png actual/home.png \
    --meta baseline/home.png.meta.json --masks baseline/home.masks.json \
    --max-diff 0.001 --max-regions 1 --bundle artifacts/home

Com --json sai veredicto, percentual de diferença, número de regiões e a caixa de cada uma — o suficiente para o CI publicar o que se moveu, em vez de “o teste falhou”.

Orçamentos que sobrevivem à realidade

Checklist de determinismo (antes de culpar o gate)

  1. Viewport fixo e DPR 1DPR escala o layout e invalida todas as referências.
  2. Desligue animaçãoprefers-reduced-motion: reduce e *{animation:none!important;transition:none!important}.
  3. Espere as fontes e a rededocument.fonts.ready e network idle — uma fonte de fallback difere a página inteira.
  4. Esconda cursor de texto, barra de rolagem e overlay de devNada disso é conteúdo da página, e todos mudam pixels.
  5. Congele tempo e aleatoriedadeSe a interface renderiza data/hora ou Math.random, congele antes de capturar.
  6. Masque só o que é genuinamente dinâmicoSlot de anúncio, avatar do dia, contador ao vivo, posição de A/B.
  7. Capture no mesmo contêiner do CIStack de fontes diferente = pixels diferentes em todo lugar.

Evidência: o gate é testado nos dois sentidos

15 testes passando — pytest -q  (28,9s)
  capturas idênticas               -> PASS, diff 0.00000%, 0 regiões
  um bloco 60x40 alterado          -> FAIL, 1 região nas coordenadas exatas
  mesma mudança dentro de máscara  -> PASS, masked_pixels >= a área mascarada
  mudança fora da máscara          -> FAIL (máscara não esconde o resto da página)
  duas mudanças com --max-regions 1-> FAIL; com --max-regions 2 -> PASS
  referência velha (git HEAD muda) -> STALE, saída 2
  config de máscara alterada       -> STALE
  640x480 contra 1280x720          -> ERROR, "size mismatch" (nunca PASS silencioso)
  decodificador contra PIL e ImageMagick -> pixels idênticos em 6 codificações
  16-bit                           -> byte alto igual ao render de 8 bits
  bundle de falha                  -> diff.png re-decodifica, área vermelha >= a região

O teste nos dois sentidos é o ponto: um gate testado só contra “nada mudou” é decoração. Aqui os dois lados são verificados — inclusive o caso que morde em produção: referência otimizada (optipng, pngquant, ImageMagick) que um decodificador ingênuo recusa.

E o decodificador de PNG é stdlib (zlib + struct), sem PIL, OpenCV ou ImageMagick no runner: gate de CI que exige biblioteca C em cada máquina é gate que quebra justamente na máquina que importa.

Os erros que mais custam

Comece agora

A skill Visual Regression Gates traz o gate em stdlib, 15 testes, o template de captura Playwright, o formato das máscaras e os dois guias (determinismo da captura e estratégia de máscara/fingerprint).

Ver a skill Visual Regression Gates Ver todas as skills

Quer isso ligado no seu pipeline? Setup guiado a partir de R$ 1.500, com o primeiro ciclo rodado junto.

← todas as skills

Pagamento processado pelo Stripe — nenhum dado de cartão passa por este servidor. Entrega imediata após a confirmação. Termos, entrega e reembolso.