Skill: Diff Visual
Compara duas URLs visualmente — uma linha de base (ex.: site de produção) e uma atual (ex.: servidor de desenvolvimento local ou história do Storybook) — para encontrar diferenças no nível de pixel e orientar correções.
Quando Usar
- Replicar uma página ou seção existente no Storybook ou no site Astro
- Verificar a precisão visual de um componente em relação a um design de referência
- Checar a fidelidade responsiva em diferentes breakpoints após alterações de estilo
- Iterar no CSS/HTML até que um componente corresponda à sua referência
Quando NÃO Usar
- Alterações apenas de conteúdo (atualizações de texto) onde o layout não muda
- Comparar designs do Figma — use a skill Figma MCP
- Auditorias de desempenho ou acessibilidade — use Lighthouse ou o addon de a11y
Pré-requisitos
Antes de começar, confirme:
- Playwright MCP está configurado em
.vscode/mcp.jsoncom--caps=vision - pixelmatch e pngjs estão instalados: verifique as devDependencies no
package.json - A URL de linha de base está acessível (site externo ou servidor rodando)
- A URL atual está acessível (servidor de desenvolvimento na porta
:4321, Storybook na porta:6006, etc.) - O diretório
temp/existe na raiz do workspace (está no .gitignore)
Se o servidor de desenvolvimento ou o Storybook não estiver rodando, inicie-os usando as tarefas do VS Code:
- Servidor de desenvolvimento: use a tarefa Dev Server
- Storybook: use a tarefa Storybook
Fluxo de Trabalho
Passo 1: Receber entradas
O agente precisa de:
| Entrada | Obrigatório | Exemplo |
|---|---|---|
| URL de linha de base | Sim | https://www.bitovi.com/ |
| URL atual | Sim | http://localhost:4321/ ou http://localhost:6006/?path=/story/card--default |
| Seletor CSS | Não | #hero-section ou .card-grid (para comparação escopada a elemento) |
| Breakpoints | Não | Padrão: mobile (375×667), tablet (768×1024), desktop (1280×720) |
Passo 2: Screenshot da linha de base
Comparação escopada a componente (preferida ao comparar um único componente)
Ao comparar um componente específico — especialmente quando a história do Storybook renderiza apenas aquele componente, mas a página de linha de base tem a página completa ao redor — corte para a bounding box do componente para que o diff não seja poluído por seções não relacionadas da página (barras de navegação, overlays fixos, outros módulos acima/abaixo).
Como cortar na página de linha de base:
- Navegue até a URL de linha de base
- Redimensione a viewport para
{ width: 1280, height: 720 } - Role o elemento alvo para a visualização:
() => document.querySelector('<selector>').scrollIntoView({ behavior: 'instant', block: 'start' }) - Meça a altura renderizada do componente:
() => document.querySelector('<selector>').getBoundingClientRect().height - Redimensione a altura da viewport para corresponder exatamente a essa altura (mantendo a largura constante):
mcp_playwright_browser_resize → { width: 1280, height: <altura medida> } - Capture o screenshot — agora ele captura apenas a área do componente.
Aviso de sangria de fundo: O que está visível atrás do componente será diferente entre o Storybook (fundo em branco) e a página de linha de base (outros módulos). Casos comuns: barras de navegação sticky/locais sobrepondo o topo, modais/overlays com conteúdo de página visível atrás e componentes parcialmente transparentes. Esses diffs residuais se agrupam fora do conteúdo do próprio componente e não são acionáveis — ignore-os.
Como cortar na história do Storybook:
O iframe.html do Storybook renderiza apenas o componente sem nenhum chrome ao redor — não precisa redimensionar. Apenas navegue e capture o screenshot:
mcp_playwright_browser_navigate → { url: "http://localhost:6006/iframe.html?id=<story-id>&viewMode=story" }
mcp_playwright_browser_resize → { width: 1280, height: <mesma altura do corte da linha de base> }
mcp_playwright_browser_take_screenshot → { filename: "temp/vdiff-current-desktop.png" }
Screenshot de viewport completa (ao comparar páginas inteiras)
1. mcp_playwright_browser_navigate → { url: "<baseline-url>" }
2. mcp_playwright_browser_resize → { width: 1280, height: 720 }
3. mcp_playwright_browser_wait_for → { time: 2 } # deixe a página renderizar completamente
4. mcp_playwright_browser_take_screenshot → {
type: "png",
filename: "temp/vdiff-baseline-desktop.png"
}
Repita para cada breakpoint:
| Breakpoint | Largura | Altura | Sufixo do arquivo |
|---|---|---|---|
| Mobile | 375 | 667 | -mobile |
| Tablet | 768 | 1024 | -tablet |
| Desktop | 1280 | 720 | -desktop |
Passo 3: Screenshot da URL atual
Repita exatamente o mesmo processo para a URL atual, usando nomes de arquivo temp/vdiff-current-{breakpoint}.png.
Importante: Use as mesmas dimensões de viewport e a mesma estratégia de corte (escopada a componente ou página completa) em ambos os lados para que a comparação seja justa.
Passo 4: Executar o script de diff
Para cada breakpoint, execute o script de diff visual no terminal:
node scripts/visual-diff.mjs \
--baseline temp/vdiff-baseline-desktop.png \
--current temp/vdiff-current-desktop.png \
--output temp/vdiff-diff-desktop.png
O script gera JSON na saída padrão:
{
"totalPixels": 921600,
"diffPixels": 4521,
"diffPercent": 0.49,
"width": 1280,
"height": 720,
"baselineDimensions": { "width": 1280, "height": 720 },
"currentDimensions": { "width": 1280, "height": 720 },
"dimensionsMismatch": false,
"diffImagePath": "/absolute/path/to/temp/vdiff-diff-desktop.png"
}
Se dimensionsMismatch for true, as imagens tinham tamanhos diferentes. O script preenche a imagem menor com pixels transparentes antes de comparar. Isso geralmente indica um problema de layout que merece investigação.
Passo 5: Visualizar a imagem de diff
Inicie um servidor HTTP temporário para servir o diretório temp/ e, em seguida, navegue com o Playwright até a imagem de diff:
# Inicie o servidor em segundo plano (execute uma vez por sessão)
npx -y http-server temp/ -p 8787 --cors -c-1 &
Depois, navegue com o Playwright até a imagem de diff:
mcp_playwright_browser_navigate → { url: "http://localhost:8787/vdiff-diff-desktop.png" }
mcp_playwright_browser_take_screenshot → {} # aciona a visão — o agente agora pode ver o diff
A flag --caps=vision no Playwright MCP significa que o agente pode ver a imagem diretamente na saída do screenshot.
Nota: URLs file:// são bloqueadas pelo Playwright MCP, então o servidor HTTP é necessário. Inicie-o uma vez e reutilize-o para todas as imagens de diff na sessão.
Interpretando a imagem de diff:
- Pixels vermelhos = diferenças entre linha de base e atual
- Pixels amarelos = diferenças de anti-aliasing (geralmente ignoráveis)
- Original escurecido = áreas que correspondem (mostradas com 30% de opacidade para contexto)
- Grandes aglomerados vermelhos = problemas estruturais (elementos faltando, mudanças de layout, tamanhos errados)
- Pontos vermelhos espalhados = renderização sub-pixel, suavização de fonte ou anti-aliasing (geralmente aceitável)
- Bordas vermelhas = incompatibilidade de dimensão ou diferenças de padding/margem
Passo 6: Interpretar e agir
Combine os dados quantitativos do JSON com a inspeção visual do diff:
| diffPercent | Interpretação | Ação |
|---|---|---|
| < 1% | Visualmente idêntico | Apenas diferenças sub-pixel menores. Nenhuma ação necessária. |
| 1–5% | Correspondência próxima | Inspecione a imagem de diff em busca de diferenças de espaçamento, peso da fonte ou borda. Pequenos ajustes de CSS provavelmente necessários. |
| 5–15% | Diferenças notáveis | Mudanças de layout, incompatibilidades de cor ou elementos faltando. Revise os aglomerados vermelhos no diff para identificar quais seções precisam de trabalho. |
| > 15% | Lacuna significativa | Grandes diferenças estruturais ou de estilo. Foco nas maiores regiões vermelhas primeiro — elas indicam as maiores discrepâncias de layout. |
Ao interpretar a imagem de diff, observe:
- Onde na página estão os aglomerados vermelhos? (topo, meio, fundo, esquerda, direita)
- Eles estão associados a elementos específicos? (navegação, hero, cards, rodapé)
- Os aglomerados sugerem problemas de espaçamento, diferenças de cor ou conteúdo faltando?
Passo 7: Corrigir e re-diff
- Faça correções de CSS/HTML com base na análise do diff
- Repita os Passos 3–6 (apenas re-faça o screenshot da URL atual)
- Continue até que
diffPercentatinja um nível aceitável
Alvos típicos:
- Replicação exata: < 2%
- Correspondência razoável: < 5%
- Correspondência estrutural (conteúdo diferente): < 15%
Screenshots de página completa
Por padrão, os screenshots capturam apenas a viewport visível. Para páginas longas, use a opção fullPage:
mcp_playwright_browser_take_screenshot → {
type: "png",
fullPage: true,
filename: "temp/vdiff-baseline-desktop-full.png"
}
Aviso: Screenshots de página completa produzem imagens grandes e diffs mais lentos. Use apenas ao comparar layouts de página inteira.
Dicas específicas para Storybook
Ao capturar screenshots de histórias do Storybook:
-
Use a URL iframe para screenshots mais limpos (sem chrome do Storybook):
http://localhost:6006/iframe.html?id=components-card--default&viewMode=story -
Aguarde a renderização — as histórias do Storybook podem levar um momento para hidratar:
mcp_playwright_browser_wait_for → { time: 3 } -
Screenshots de elemento funcionam bem para isolar o conteúdo da história de qualquer padding do Storybook.
Ajuste de limite
A flag --threshold (0 a 1) controla a sensibilidade do pixelmatch:
| Valor | Sensibilidade | Caso de uso |
|---|---|---|
| 0.05 | Muito rigoroso | Correspondência exata de pixels, captura tudo |
| 0.1 | Padrão | Bom equilíbrio, ignora a maioria do anti-aliasing |
| 0.2 | Permissivo | Tolerante a diferenças de renderização de fonte entre sistemas |
| 0.3 | Muito permissivo | Apenas captura grandes diferenças de cor/layout |
node scripts/visual-diff.mjs \
--baseline temp/vdiff-baseline-desktop.png \
--current temp/vdiff-current-desktop.png \
--output temp/vdiff-diff-desktop.png \
--threshold 0.2
Exemplo: Comparando uma seção hero
# 1. Screenshot da linha de base (hero do site de produção)
mcp_playwright_browser_navigate → { url: "https://www.bitovi.com/" }
mcp_playwright_browser_resize → { width: 1280, height: 720 }
mcp_playwright_browser_wait_for → { time: 2 }
mcp_playwright_browser_snapshot → {}
# Encontre a referência para a seção hero, ex.: ref="hero-[1]"
mcp_playwright_browser_take_screenshot → {
type: "png",
element: "hero section",
ref: "hero-[1]",
filename: "temp/vdiff-baseline-hero-desktop.png"
}
# 2. Screenshot da atual (história do Storybook)
mcp_playwright_browser_navigate → { url: "http://localhost:6006/iframe.html?id=components-hero--default" }
mcp_playwright_browser_resize → { width: 1280, height: 720 }
mcp_playwright_browser_wait_for → { time: 3 }
mcp_playwright_browser_take_screenshot → {
type: "png",
filename: "temp/vdiff-current-hero-desktop.png"
}
# 3. Execute o diff (no terminal)
node scripts/visual-diff.mjs \
--baseline temp/vdiff-baseline-hero-desktop.png \
--current temp/vdiff-current-hero-desktop.png \
--output temp/vdiff-diff-hero-desktop.png
# 4. Inicie o servidor temporário (uma vez por sessão, em terminal em segundo plano)
npx -y http-server temp/ -p 8787 --cors -c-1 &
# 5. Visualize a imagem de diff via Playwright
mcp_playwright_browser_navigate → { url: "http://localhost:8787/vdiff-diff-hero-desktop.png" }
mcp_playwright_browser_take_screenshot → {} # aciona a visão
# 6. Interprete: leia a saída JSON + inspecione visualmente o diff
# 7. Corrija os estilos, re-faça o screenshot da atual, re-diff
Limpeza
Todos os arquivos de saída vão para temp/, que está no .gitignore. Para limpar após uma sessão:
rm -f temp/vdiff-*.png