Skill: Diferencia Visual (Visual Diff)
Compara dos URLs visualmente — una línea base (ej. sitio en producción) y una actual (ej. servidor de desarrollo local o historia de Storybook) — para encontrar diferencias a nivel de píxel y guiar correcciones.
Cuándo usarlo
- Replicar una página o sección existente en Storybook o el sitio Astro
- Verificar la precisión visual de un componente contra un diseño de referencia
- Comprobar fidelidad responsiva en distintos puntos de quiebre después de cambios de estilo
- Iterar sobre CSS/HTML hasta que un componente coincida con su referencia
Cuándo NO usarlo
- Cambios solo de contenido (actualizaciones de texto, ediciones de copia) donde el diseño no cambia
- Comparar diseños de Figma — usa el skill de Figma MCP en su lugar
- Auditorías de rendimiento o accesibilidad — usa Lighthouse o el addon de accesibilidad
Prerrequisitos
Antes de comenzar, confirma:
- Playwright MCP está configurado en
.vscode/mcp.jsoncon--caps=vision - pixelmatch y pngjs están instalados: revisa las devDependencias en
package.json - La URL de línea base es accesible (sitio externo o servidor en ejecución)
- La URL actual es accesible (servidor de desarrollo en
:4321, Storybook en:6006, etc.) - El directorio
temp/existe en la raíz del espacio de trabajo (está en gitignore)
Si el servidor de desarrollo o Storybook no está en ejecución, inícialos usando las tareas de VS Code:
- Servidor de desarrollo: usa la tarea Dev Server
- Storybook: usa la tarea Storybook
Flujo de trabajo
Paso 1: Recibir entradas
El agente necesita:
| Entrada | Requerido | Ejemplo |
|---|---|---|
| URL de línea base | Sí | https://www.bitovi.com/ |
| URL actual | Sí | http://localhost:4321/ o http://localhost:6006/?path=/story/card--default |
| Selector CSS | No | #hero-section o .card-grid (para comparación a nivel de elemento) |
| Puntos de quiebre | No | Por defecto: móvil (375×667), tableta (768×1024), escritorio (1280×720) |
Paso 2: Capturar la línea base
Comparación a nivel de componente (preferida al comparar un solo componente)
Al comparar un componente específico, especialmente cuando la historia de Storybook renderiza solo ese componente pero la página de línea base tiene una página completa alrededor, recorta al cuadro delimitador del componente para que la diferencia no se contamine con secciones de página no relacionadas (barras de navegación, superposiciones fijas, otros módulos arriba/abajo).
Cómo recortar en la página de línea base:
- Navega a la URL de línea base
- Cambia el tamaño de la ventana gráfica a
{ width: 1280, height: 720 } - Desplázate hasta que el elemento objetivo esté visible:
() => document.querySelector('<selector>').scrollIntoView({ behavior: 'instant', block: 'start' }) - Mide la altura renderizada del componente:
() => document.querySelector('<selector>').getBoundingClientRect().height - Cambia la altura de la ventana gráfica para que coincida exactamente con esa altura (mantén el ancho constante):
mcp_playwright_browser_resize → { width: 1280, height: <altura medida> } - Toma la captura de pantalla — ahora captura solo el área del componente.
Advertencia de sangrado de fondo: Lo que es visible detrás del componente diferirá entre Storybook (vacío) y la página de línea base (otros módulos). Casos comunes: barras de navegación fijas que se superponen en la parte superior, modales/superposiciones con otro contenido de página visible detrás, y componentes parcialmente transparentes. Estas diferencias residuales se agrupan fuera del contenido propio del componente y no son procesables — ignóralas.
Cómo recortar en la historia de Storybook:
Storybook iframe.html renderiza solo el componente sin ningún marco alrededor — no se necesita cambiar tamaño. Solo navega y captura:
mcp_playwright_browser_navigate → { url: "http://localhost:6006/iframe.html?id=<story-id>&viewMode=story" }
mcp_playwright_browser_resize → { width: 1280, height: <misma altura que el recorte de línea base> }
mcp_playwright_browser_take_screenshot → { filename: "temp/vdiff-current-desktop.png" }
Captura de pantalla de página completa (viewport) (al comparar páginas completas)
1. mcp_playwright_browser_navigate → { url: "<url-linea-base>" }
2. mcp_playwright_browser_resize → { width: 1280, height: 720 }
3. mcp_playwright_browser_wait_for → { time: 2 } # deja que la página se renderice completamente
4. mcp_playwright_browser_take_screenshot → {
type: "png",
filename: "temp/vdiff-baseline-desktop.png"
}
Repite para cada punto de quiebre:
| Punto de quiebre | Ancho | Alto | Sufijo de nombre de archivo |
|---|---|---|---|
| Móvil | 375 | 667 | -mobile |
| Tableta | 768 | 1024 | -tablet |
| Escritorio | 1280 | 720 | -desktop |
Paso 3: Capturar la versión actual
Repite exactamente el mismo proceso para la URL actual, usando nombres de archivo temp/vdiff-current-{breakpoint}.png.
Importante: Usa las mismas dimensiones de ventana gráfica y la misma estrategia de recorte (a nivel de componente o página completa) en ambos lados para que la comparación sea de manzanas con manzanas.
Paso 4: Ejecutar el script de diferencia
Para cada punto de quiebre, ejecuta el script de diferencia visual en la terminal:
node scripts/visual-diff.mjs \
--baseline temp/vdiff-baseline-desktop.png \
--current temp/vdiff-current-desktop.png \
--output temp/vdiff-diff-desktop.png
El script produce JSON en la salida estándar:
{
"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"
}
Si dimensionsMismatch es true, las imágenes tenían tamaños diferentes. El script rellena la imagen más pequeña con píxeles transparentes antes de comparar. Esto a menudo indica un problema de diseño que vale la pena investigar.
Paso 5: Ver la imagen de diferencias
Inicia un servidor HTTP temporal para servir el directorio temp/, luego navega con Playwright a la imagen de diferencias:
# Inicia el servidor en segundo plano (ejecuta una vez por sesión)
npx -y http-server temp/ -p 8787 --cors -c-1 &
Luego navega con Playwright a la imagen de diferencias:
mcp_playwright_browser_navigate → { url: "http://localhost:8787/vdiff-diff-desktop.png" }
mcp_playwright_browser_take_screenshot → {} # activa visión — el agente ahora puede ver la diferencia
La bandera --caps=vision en Playwright MCP significa que el agente puede ver la imagen directamente en la salida de la captura de pantalla.
Nota: Las URL file:// están bloqueadas por Playwright MCP, por lo que el servidor HTTP es necesario. Inícialo una vez y reutilízalo para todas las imágenes de diferencias en la sesión.
Lectura de la imagen de diferencias:
- Píxeles rojos = diferencias entre línea base y actual
- Píxeles amarillos = diferencias de suavizado (generalmente ignorables)
- Original atenuado = áreas que coinciden (mostradas al 30% de opacidad para contexto)
- Grandes grupos rojos = problemas estructurales (elementos faltantes, cambios de diseño, tamaño incorrecto)
- Puntos rojos dispersos = renderizado de subpíxeles, suavizado de fuentes o anti-aliasing (generalmente aceptable)
- Bordes rojos = discrepancia de dimensiones o diferencias de relleno/margen
Paso 6: Interpretar y actuar
Combina los datos cuantitativos del JSON con la inspección visual de la diferencia:
| diffPercent | Interpretación | Acción |
|---|---|---|
| < 1% | Visualmente idéntico | Solo diferencias menores de subpíxeles. No se necesita acción. |
| 1–5% | Coincidencia cercana | Inspecciona la imagen de diferencias en busca de diferencias de espaciado, peso de fuente o bordes. Probablemente se necesiten pequeños ajustes CSS. |
| 5–15% | Diferencias notables | Cambios de diseño, discrepancias de color o elementos faltantes. Revisa los grupos rojos en la diferencia para identificar qué secciones necesitan trabajo. |
| > 15% | Brecha significativa | Grandes diferencias estructurales o de estilo. Concéntrate primero en las regiones rojas más grandes — estas indican las mayores discrepancias de diseño. |
Al interpretar la imagen de diferencias, nota:
- ¿Dónde en la página están los grupos rojos? (arriba, medio, abajo, izquierda, derecha)
- ¿Están asociados con elementos específicos? (navegación, héroe, tarjetas, pie de página)
- ¿Los grupos sugieren problemas de espaciado, diferencias de color o contenido faltante?
Paso 7: Corregir y volver a comparar
- Realiza correcciones CSS/HTML basadas en el análisis de diferencias
- Repite los Pasos 3–6 (solo vuelve a capturar la URL actual)
- Continúa hasta que
diffPercentalcance un nivel aceptable
Objetivos típicos:
- Replicación exacta: < 2%
- Coincidencia razonable: < 5%
- Coincidencia estructural (contenido diferente): < 15%
Capturas de pantalla de página completa
Por defecto, las capturas de pantalla solo capturan la ventana gráfica visible. Para páginas largas, usa la opción fullPage:
mcp_playwright_browser_take_screenshot → {
type: "png",
fullPage: true,
filename: "temp/vdiff-baseline-desktop-full.png"
}
Advertencia: Las capturas de pantalla de página completa producen imágenes grandes y diferencias más lentas. Úsalas solo al comparar diseños de página completos.
Consejos específicos de Storybook
Al capturar historias de Storybook:
-
Usa la URL de iframe para capturas más limpias (sin el marco de Storybook):
http://localhost:6006/iframe.html?id=components-card--default&viewMode=story -
Espera a que se renderice — las historias de Storybook pueden tardar un momento en hidratarse:
mcp_playwright_browser_wait_for → { time: 3 } -
Las capturas de elementos funcionan bien para aislar el contenido de la historia de cualquier relleno de Storybook.
Ajuste del umbral
La bandera --threshold (0 a 1) controla la sensibilidad de pixelmatch:
| Valor | Sensibilidad | Caso de uso |
|---|---|---|
| 0.05 | Muy estricto | Coincidencia exacta de píxeles, captura todo |
| 0.1 | Predeterminado | Buen equilibrio, ignora la mayoría del suavizado |
| 0.2 | Tolerante | Tolerante a diferencias de renderizado de fuentes entre sistemas |
| 0.3 | Muy tolerante | Solo captura grandes diferencias de color/diseño |
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
Ejemplo: Comparar una sección héroe
# 1. Capturar línea base (héroe del sitio de producción)
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 → {}
# Encontrar ref para la sección héroe, ej. ref="hero-[1]"
mcp_playwright_browser_take_screenshot → {
type: "png",
element: "hero section",
ref: "hero-[1]",
filename: "temp/vdiff-baseline-hero-desktop.png"
}
# 2. Capturar actual (historia de 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. Ejecutar diferencia (en 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. Iniciar servidor temporal (una vez por sesión, en terminal en segundo plano)
npx -y http-server temp/ -p 8787 --cors -c-1 &
# 5. Ver imagen de diferencias a través de Playwright
mcp_playwright_browser_navigate → { url: "http://localhost:8787/vdiff-diff-hero-desktop.png" }
mcp_playwright_browser_take_screenshot → {} # activa visión
# 6. Interpretar: leer salida JSON + inspeccionar visualmente la diferencia
# 7. Corregir estilos, volver a capturar actual, volver a diferenciar
Limpieza
Todos los archivos de salida van a temp/, que está en gitignore. Para limpiar después de una sesión:
rm -f temp/vdiff-*.png