Detección de Bugs de UI con Pruebas de Regresión Visual
El visual regression testing con Playwright y Vitest detecta bugs de UI que los tests unitarios y E2E no ven, con baselines, flakiness y CI.
Las pruebas de regresión visual detectan cambios no deseados en la interfaz de usuario renderizando el componente o la página en un navegador controlado, capturando una captura de pantalla y comparándola píxel a píxel con una imagen de referencia aprobada. Cualquier diferencia que supere un umbral configurable hace fallar la prueba y expone el cambio para revisión humana. Esto detecta una clase de bug que las pruebas unitarias y las pruebas end-to-end estructuralmente no pueden ver: un botón que dispara su manejador de clic correctamente pero se renderiza con el color incorrecto, se desplaza doce píxeles o desaparece detrás de un overlay modal.
Este es el tipo de fallo que se cuela en una refactorización de CSS y rompe silenciosamente el layout en producción. Las pruebas pasan, el build está en verde, y un usuario descubre que el botón de pago está ahora oculto por un banner de cookies. Este artículo muestra cómo cerrar esa brecha con herramientas nativas del framework — las aserciones de captura de pantalla integradas de Playwright y las pruebas visuales en modo navegador de Vitest — sin necesidad de un servicio de pago, y también cubre el control de inestabilidad y la configuración de CI que separan una suite útil de una ruidosa.
Puntos Clave
- Las pruebas de regresión visual comparan una captura de pantalla renderizada con una referencia aprobada; cualquier diferencia por encima de un umbral configurable hace fallar la prueba, detectando bugs de color, posición y contexto de apilamiento que las pruebas funcionales dejan pasar.
expect(page).toHaveScreenshot()de Playwright está integrado y es gratuito, y por defecto estableceanimationsen'disabled'y reintenta automáticamente hasta que dos capturas de pantalla consecutivas coincidan, por lo que no necesitaswaitForTimeout.- La mayor parte de la inestabilidad se origina en cuatro fuentes: animaciones CSS, fuentes web que cargan tarde, contenido dinámico y el antialiasing subpíxel que varía según la GPU y el sistema operativo — cada una tiene una solución específica, no un umbral más permisivo.
- Fija el CI a una imagen Docker específica de Playwright (
mcr.microsoft.com/playwright:v1.61.0-noble) porque el renderizado de fuentes difiere entre sistemas operativos y, de lo contrario, generaría falsos positivos. - Vitest 4.x añadió regresión visual integrada mediante
toMatchScreenshot()en el modo Browser estable, ofreciendo a los usuarios de Vitest una vía nativa sin abandonar su runner existente.
¿Qué son las pruebas de regresión visual?
Las pruebas de regresión visual son un método de prueba que compara capturas de pantalla de una página web o componente de UI con una imagen de referencia aprobada para detectar cambios visuales no deseados. La mecánica es la misma independientemente de la herramienta: captura una vez un renderizado conocido como correcto, almacénalo como referencia, y en cada ejecución posterior captura una nueva captura de pantalla y compárala con esa referencia. Una diferencia por encima de tu umbral hace fallar la prueba y marca el cambio para que una persona lo apruebe o rechace.
Esto es distinto de las pruebas de snapshot. Una prueba de snapshot serializa el markup renderizado de un componente y compara el texto; una prueba de regresión visual compara los píxeles reales renderizados. Los snapshots a nivel de markup no detectan nada que sea puramente visual — un cambio de z-index, un intercambio de token de color, una fuente de respaldo — porque el DOM serializado es idéntico incluso cuando lo que el usuario ve no lo es.
¿Por qué las pruebas unitarias y E2E no detectan bugs visuales?
Discover how at OpenReplay.com.
Las pruebas unitarias verifican que un botón dispara su manejador onClick; las pruebas E2E verifican que al hacer clic se completa un flujo; ninguna puede decirte si el botón tiene el color incorrecto, se ha desplazado doce píxeles hacia la derecha o está oculto detrás de un overlay modal — esa es la brecha que cubren las pruebas de regresión visual. La matriz de cobertura hace explícito ese límite:
| Escenario | Unitaria (Jest/Vitest) | E2E (Playwright/Cypress) | Visual |
|---|---|---|---|
| El botón se renderiza en el DOM | Sí | Parcial | Sí |
El botón dispara onClick | Sí | Sí | No |
| Al hacer clic se completa el flujo | No | Sí | No |
| El botón tiene el color correcto | No | No | Sí |
| El botón está en la posición correcta | No | No | Sí |
| El botón está oculto por un overlay | No | No | Sí |
La última fila es la que duele en producción. Considera un <CheckoutButton> que funciona bien hasta que alguien añade un <CookieBanner> cuyo contexto de apilamiento lo cubre:
// CheckoutButton.tsx
export function CheckoutButton({ onCheckout }: { onCheckout: () => void }) {
return (
<button data-testid="checkout" onClick={onCheckout}>
Complete purchase
</button>
);
}
Una prueba unitaria que verifica que el manejador de clic se dispara pasa — JSDOM, la implementación del DOM que Jest y Vitest usan por defecto, no calcula el layout ni los contextos de apilamiento, por lo que no puede saber que el botón está cubierto. Una aserción E2E como await expect(page.getByTestId('checkout')).toBeVisible() también pasa, porque Playwright considera que un elemento es visible cuando tiene un bounding box no vacío y no tiene display:none — que otro elemento con mayor z-index lo pinte encima no cambia ninguna de las dos condiciones. Aunque un locator.click() posterior puede detectar la obstrucción mediante las comprobaciones de accionabilidad de Playwright, una aserción de visibilidad por sí sola no lo hará. Una comparación de capturas de pantalla muestra el banner situado sobre el botón.
¿Cómo funcionan las pruebas de regresión visual?
El flujo de trabajo es un ciclo de cinco pasos: capturar una referencia, ejecutar tras un cambio, comparar la nueva captura con la referencia, revisar la diferencia y luego aprobar el cambio (actualizar la referencia) o rechazarlo (corregir el código). Playwright lo hace concreto a través de su flujo --update-snapshots.
La primera vez que ejecutas una prueba que contiene toHaveScreenshot(), no existe ninguna referencia, por lo que Playwright escribe una imagen de referencia. Una vez que esa referencia ha sido revisada y confirmada en el repositorio, las ejecuciones posteriores la comparan contra ella. Cuando un cambio visual es intencional — un botón rediseñado, un nuevo token de color — ejecuta npx playwright test --update-snapshots, revisa la diferencia en el informe HTML generado, confirma los archivos .png actualizados (Git LFS es una práctica habitual para estas referencias binarias) y la nueva captura de pantalla se convierte en la referencia; la siguiente ejecución de CI compara contra ella. El flag --update-snapshots regenera las imágenes de referencia en su lugar.
La disciplina crítica es tratar una actualización de referencia como una revisión de código, no como un trámite automático. Una referencia actualizada para “hacer pasar la prueba” sin inspeccionar la diferencia es la forma en que una regresión real se convierte en la nueva UI aceptada.
Técnicas de comparación y alcance de las pruebas
Las herramientas de regresión visual utilizan una de cuatro técnicas de comparación — píxel a píxel, layout, basada en DOM o asistida por IA — y difieren en qué región capturan. El motor de comparación:
| Técnica | Cómo funciona | Compensación |
|---|---|---|
| Píxel a píxel | Compara cada píxel; marca cualquier diferencia | Precisa pero ruidosa por el antialiasing y el suavizado de fuentes; requiere umbrales |
| Layout | Compara posición, tamaño y espaciado de los elementos | Ignora el ruido cosmético de píxeles; no detecta cambios de color o textura |
| Basada en DOM | Compara el markup serializado, no los píxeles renderizados | Detecta cambios estructurales; ciega a los bugs puramente visuales |
| Asistida por IA | La visión por computadora marca solo los cambios perceptibles por humanos | Reduce los falsos positivos; disponible en herramientas comerciales específicas |
El alcance de la captura es un eje independiente. Las pruebas a nivel de componente aíslan un único botón, tarjeta o modal — menor ruido, revisión más rápida, ideales para sistemas de diseño y Storybook. Las pruebas a nivel de página capturan pantallas completas y detectan problemas de layout en flujos reales, a costa de más contenido dinámico y revisiones más lentas. Una suite práctica usa ambas: a nivel de componente para el sistema de diseño, a nivel de página para los flujos críticos.
Sobre la comparación con IA específicamente, el campo se caracteriza con frecuencia de forma incorrecta. A partir de junio de 2026, la comparación visual basada en IA es la oferta de pago principal de Applitools; Percy añade triaje asistido por IA sobre la comparación de píxeles, mientras que Chromatic usa comparación de píxeles con conciencia de Git en lugar de IA — y las herramientas nativas del framework (Playwright, modo Browser de Vitest) usan comparación de píxeles con umbrales configurables, lo que es suficiente para la mayoría de los equipos si la inestabilidad se controla desde el origen.
¿Cómo configuro las pruebas de regresión visual con Playwright?
Playwright incluye la comparación de capturas de pantalla como una aserción integrada, por lo que no necesitas ninguna dependencia adicional más allá de @playwright/test. La aserción es expect(page).toHaveScreenshot(), documentada en la referencia de la API PageAssertions. Comienza con la configuración:
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
// Organiza las referencias por navegador para que las capturas entre navegadores no colisionen.
snapshotPathTemplate: '{testDir}/__screenshots__/{projectName}/{arg}{ext}',
expect: {
toHaveScreenshot: {
// Permite un pequeño presupuesto absoluto de píxeles para el ruido de renderizado subpíxel.
maxDiffPixels: 100,
// Y un presupuesto relativo — 1% de la imagen — para capturas más grandes.
maxDiffPixelRatio: 0.01,
},
},
use: {
// Viewport fijo: los cambios de layout responsivo invalidan las referencias de otro modo.
viewport: { width: 1280, height: 720 },
},
// Fija un navegador para referencias deterministas en CI.
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});
Dos opciones cargan con el peso. Las opciones maxDiffPixels y maxDiffPixelRatio establecen cuánta diferencia se tolera antes de que la prueba falle. Existen porque el antialiasing subpíxel varía entre máquinas — la documentación de Playwright señala directamente que “diferentes sistemas operativos pueden producir capturas de pantalla distintas.” Un umbral de maxDiffPixelRatio: 0.01 absorbe ese ruido sin enmascarar regresiones reales.
Vale la pena entender un valor por defecto en lugar de configurarlo: para la aserción toHaveScreenshot(), animations tiene por defecto el valor 'disabled', lo que finaliza las animaciones CSS finitas y congela las infinitas antes de capturar. Esto difiere de page.screenshot(), donde animations tiene por defecto 'allow'. Por tanto, los fotogramas intermedios de transición ya están gestionados para la aserción — no añades la opción, te apoyas en ella.
Una prueba tiene este aspecto:
// checkout.spec.ts
import { test, expect } from '@playwright/test';
test('checkout button is not obscured', async ({ page }) => {
await page.goto('/checkout');
// Aserción web-first: espera al elemento en lugar de usar un timeout fijo.
await expect(page.getByTestId('checkout')).toBeVisible();
await expect(page.getByTestId('checkout')).toHaveScreenshot('checkout-button.png');
});
Limitar la captura de pantalla a un locator en lugar de a la página completa mantiene la región capturada pequeña y la comparación enfocada — exactamente lo que detecta la superposición del <CookieBanner> sin necesidad de actualizar la referencia de toda la página ante cada cambio no relacionado.
Pruebas a nivel de componente con el modo Browser de Vitest
Si tu stack ya usa Vitest, la versión 4.0 graduó el modo Browser a estable y añadió regresión visual integrada mediante toMatchScreenshot(). Esta es una característica de la versión 4.x — la línea 2.x no tiene dicha aserción — por lo que apunta a una versión mínima de ^4.0. La configuración usa el proveedor de Playwright:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';
export default defineConfig({
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
},
},
});
// Button.visual.test.ts
import { expect, test } from 'vitest';
import { page } from 'vitest/browser';
import { render } from 'vitest-browser-react';
import { CheckoutButton } from './CheckoutButton';
test('checkout button matches baseline', async () => {
render(<CheckoutButton onCheckout={() => {}} />);
// Captura un locator de elemento específico — la documentación de Vitest señala
// que capturar la página completa es un antipatrón.
await expect(page.getByTestId('checkout')).toMatchScreenshot();
});
El proveedor es la función playwright() de @vitest/browser-playwright, pasada como objeto — no la cadena de texto 'playwright' usada en versiones anteriores. La importación de contexto es vitest/browser. Consulta la guía de migración de Vitest antes de actualizar una configuración de modo Browser existente, ya que estas rutas de importación cambiaron en la v4.
Pruebas a nivel de componente con stories de Storybook
Si tus componentes viven en Storybook (actualmente v10.4), no tienes que reescribir nada para añadir cobertura visual — reutiliza las stories que ya mantienes. El patrón antiguo de jest-image-snapshot con postVisit en el test-runner es ahora una vía Node legada: no funciona en el modo Browser basado en Vitest de Storybook, porque jest-image-snapshot depende de Node.js.
El equivalente gratuito y local actual es importar una story en una prueba de modo Browser de Vitest usando la API de portable stories de Storybook (composeStories) y hacer la aserción con el mismo toMatchScreenshot() de la configuración de Vitest anterior:
// CheckoutButton.visual.test.tsx
import { expect, test } from 'vitest';
import { page } from 'vitest/browser';
import { render } from 'vitest-browser-react';
import { composeStories } from '@storybook/react-vite';
import * as stories from './CheckoutButton.stories';
// Compone la story con sus args, decoradores y anotaciones del proyecto,
// de modo que la prueba renderice exactamente el mismo estado del componente que describe la story.
const { Primary } = composeStories(stories);
test('CheckoutButton matches its story baseline', async () => {
render(<Primary />);
// Limita al elemento, no a la página completa.
await expect(page.getByTestId('checkout')).toMatchScreenshot();
});
Esto mantiene una única fuente de verdad — la story — tanto para los estados renderizados del componente como para su referencia visual. Dos advertencias que vale la pena conocer. La aserción se ejecuta en una prueba de modo Browser de Vitest independiente, no a través del addon Vitest de Storybook, que no admite toMatchScreenshot (lanza Invalid Chai property: toMatchScreenshot); el addon gestiona pruebas de interacción y accesibilidad, no comparaciones de píxeles. Y el producto de pruebas visuales propio de Storybook es Chromatic, un servicio cloud de pago, que es la ruta a seguir si quieres referencias alojadas entre navegadores y un flujo de revisión gestionado en lugar de almacenar las referencias en tu repositorio.
¿Por qué mis pruebas visuales siguen fallando?
La mayor parte de la inestabilidad en las pruebas visuales se origina en cuatro fuentes: animaciones CSS que producen fotogramas intermedios de transición, fuentes web que cargan después de que se dispara la captura de pantalla, contenido dinámico (marcas de tiempo, avatares, contadores) que legítimamente difiere entre ejecuciones, y el antialiasing subpíxel que varía según la GPU y el sistema operativo — cada una requiere una solución específica, no un umbral más permisivo. Recurrir a un maxDiffPixels más alto para silenciar el ruido también ciega la suite ante regresiones reales, lo que derrota el propósito.
Un helper de estabilización aborda las fuentes controlables antes de que se dispare la captura de pantalla:
// prepare-page.ts
import { Page } from '@playwright/test';
export async function preparePageForScreenshot(page: Page) {
// 1. Fuerza las animaciones y transiciones a duración cero como medida adicional de seguridad
// (toHaveScreenshot ya las deshabilita, pero page.screenshot
// y los estados intermedios visibles también se benefician).
await page.addStyleTag({
content: `*, *::before, *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}`,
});
// 2. Espera a las fuentes web. document.fonts.ready se resuelve cuando la carga
// de fuentes se estabiliza, evitando el reflow de fuente de respaldo que desplaza el texto.
await page.waitForFunction(() => document.fonts.ready.then(() => true));
// 3. Espera a que todas las imágenes terminen de cargar.
await page.waitForFunction(() =>
Array.from(document.images).every((img) => img.complete)
);
// 4. Neutraliza el contenido dinámico en lugar de mockearlo en todas partes.
await page.locator('[data-dynamic]').evaluateAll((els) =>
els.forEach((el) => ((el as HTMLElement).style.visibility = 'hidden'))
);
}
El paso 2 se apoya en document.fonts.ready, que devuelve una Promise que se resuelve una vez que las operaciones de carga de fuentes y layout del documento se completan — la señal correcta para “las fuentes están pintadas”, mucho más fiable que una espera fija.
Observa lo que este helper deliberadamente no hace: nunca llama a page.waitForTimeout(). La guía de buenas prácticas de Playwright desaconseja los timeouts fijos (“Nunca esperes con timeout en producción”). La alternativa obvia, waitForLoadState('networkidle'), también está desaconsejada — la documentación de Playwright dice “confiar en las aserciones web para evaluar la disponibilidad.” El enfoque correcto son las aserciones web-first y las esperas explícitas de elementos, y toHaveScreenshot() ya reintenta automáticamente hasta que dos capturas de pantalla consecutivas coincidan, lo que absorbe por sí solo la mayor parte del ruido de temporización residual.
Para las regiones genuinamente dinámicas que quieres mantener visibles pero ignorar, la opción mask de Playwright acepta un array de locators y pinta cada uno con un bloque sólido antes de comparar:
await expect(page).toHaveScreenshot('dashboard.png', {
mask: [page.getByTestId('last-login'), page.locator('img[data-avatar]')],
});
Esto es más limpio que los trucos de visibilidad CSS porque la región enmascarada sigue ocupando espacio en el layout, por lo que el layout circundante se compara exactamente como se renderiza.
¿Cómo ejecuto pruebas visuales en CI sin falsos positivos?
El renderizado de fuentes difiere entre Ubuntu, macOS y Alpine Linux incluso con la misma versión del navegador, por lo que una referencia capturada en el MacBook de un desarrollador producirá falsos positivos al compararse con una captura tomada en un runner de Ubuntu de GitHub Actions — fija tu CI a una imagen Docker oficial específica mcr.microsoft.com/playwright:v1.61.0-noble para eliminar esta variable. Las etiquetas móviles como :latest ya no se publican para estas imágenes, por lo que una etiqueta de versión fija es obligatoria, no opcional.
La regla práctica que se deriva: genera las referencias dentro del mismo contenedor que usa el CI. Una referencia capturada localmente en un sistema operativo diferente es la fuente más común de diferencias exclusivas de CI. Genera las referencias en CI (o en el contenedor fijo localmente) y confírmalas en el repositorio.
Para las propias referencias, Git LFS es una práctica habitual — los archivos PNG son binarios y hacen crecer el repositorio con el tiempo. Una entrada en .gitattributes los redirige a Git LFS:
tests/**/__screenshots__/** filter=lfs diff=lfs merge=lfs -text
Revisa las diferencias de referencias en los pull requests exactamente como revisas el código. Cuando una prueba visual falla en CI, sube el informe HTML de Playwright como artefacto del build para que los revisores puedan abrir la comparación lado a lado. Un cambio aprobado es una confirmación deliberada de la referencia en el mismo PR que el código que lo causó — nunca un commit separado de “arreglar las pruebas” que oculta qué cambió.
¿Qué debo probar y qué debo dejar sin probar?
Prueba los componentes y flujos donde un cambio visual te costaría algo — un botón de pago roto, una navegación colapsada, un componente del sistema de diseño usado en 40 páginas; omite los dashboards altamente dinámicos y los widgets de terceros donde el contenido cambia legítimamente en cada renderizado. El coste de una prueba visual es el tiempo de revisión en cada diferencia, así que inviértelo donde una regresión sea costosa y el renderizado sea estable.
Buenos candidatos: componentes del sistema de diseño, flujos críticos de conversión (autenticación, pago), navegación, estados de error y vacíos, y breakpoints responsivos. Malos candidatos: dashboards en tiempo real, contenido generado por el usuario, espacios publicitarios y embeds de terceros que no controlas — estos producen diferencias constantes que entrenan a los revisores a aprobar sin mirar, el peor resultado posible.
Dónde se detienen las pruebas visuales: producción
Las pruebas de regresión visual validan la UI contra referencias controladas en CI, pero la producción se renderiza en dispositivos reales, viewports no controlados, fuentes que pueden fallar al cargar y scripts de terceros que refluyen la página — la reproducción de sesión reconstruye y reproduce la sesión grabada, de modo que una rotura de layout que se coló por tu suite de pruebas aparece en las grabaciones de las sesiones afectadas.
Tu matriz de pruebas fija un viewport y un navegador; un usuario en un portátil de 1366×768 con una fuente web que agotó el tiempo de espera y un idioma que alargó cada etiqueta se encuentra con un layout que tus referencias nunca describieron. Las pruebas visuales son prevención en el límite del PR; la reproducción de sesión es detección en el límite de producción. Cubren modos de fallo distintos y son complementarias, no redundantes.
¿Cómo elijo una herramienta de pruebas de regresión visual?
Las herramientas de regresión visual se dividen en tres categorías — runners nativos del framework, servicios en la nube y herramientas auto-alojadas — y la elección responde a las necesidades del equipo más que a las listas de características. Los runners nativos del framework — Playwright y el modo Browser de Vitest — son gratuitos, se ejecutan en tu CI existente y almacenan las referencias en tu repositorio; la compensación es que gestionas tú mismo las referencias y la consistencia entre entornos. Los servicios en la nube como Percy y Chromatic se encargan del almacenamiento de referencias y los flujos de revisión, y ofrecen niveles gratuitos (Percy incluye 5.000 capturas de pantalla al mes; los planes de pago de Chromatic comienzan en 179 $/mes), a costa de una dependencia externa. Las opciones auto-alojadas como BackstopJS mantienen todo en casa con más sobrecarga de configuración. Para un equipo que ya ejecuta Playwright o Vitest en CI, la vía nativa no tiene coste adicional y es suficiente si la inestabilidad se controla desde el origen.
La vía nativa del framework cierra la clase de bug que las pruebas funcionales estructuralmente no pueden detectar, sin ningún proveedor nuevo y sin coste recurrente. Comienza añadiendo una aserción toHaveScreenshot() a tu componente más crítico — el botón de pago, la navegación principal — genera la referencia dentro del mismo contenedor que usa tu CI, y trata la primera diferencia fallida como una revisión de código en lugar de una prueba que silenciar.
Preguntas Frecuentes
¿Cuál es la diferencia entre las pruebas de regresión visual y las pruebas de snapshot?
Las pruebas de snapshot serializan el markup renderizado de un componente en texto y comparan esa cadena, mientras que las pruebas de regresión visual comparan los píxeles reales renderizados de la captura de pantalla. Los snapshots a nivel de markup no detectan nada puramente visual, como un cambio de z-index, un intercambio de token de color o una fuente de respaldo, porque el DOM serializado permanece idéntico incluso cuando lo que el usuario ve no lo es. La regresión visual detecta los bugs puramente visuales que las comparaciones de snapshots estructuralmente no pueden detectar.
¿Por qué mi prueba visual pasa localmente pero falla en CI?
El renderizado de fuentes y el antialiasing subpíxel difieren entre sistemas operativos, por lo que una referencia capturada en un portátil con macOS produce falsos positivos al compararse con una captura tomada en un runner de Ubuntu de GitHub Actions. La solución es generar las referencias dentro del mismo contenedor fijo que usa tu CI, como la imagen Docker oficial mcr.microsoft.com/playwright:v1.61.0-noble, en lugar de confirmar referencias capturadas en un sistema operativo diferente. Las referencias entre entornos son la fuente más común de diferencias exclusivas de CI.
¿Necesito un servicio de pago como Percy o Applitools para hacer pruebas de regresión visual?
No. Playwright incluye la comparación de capturas de pantalla como una aserción integrada mediante expect(page).toHaveScreenshot(), y Vitest 4.x añadió regresión visual integrada mediante toMatchScreenshot() en el modo Browser estable, ambas gratuitas y ejecutándose en tu CI existente con las referencias almacenadas en tu repositorio. Los servicios de pago como Percy y Chromatic añaden almacenamiento de referencias y flujos de revisión alojados, y Applitools añade comparación basada en IA, pero la comparación de píxeles nativa del framework con umbrales configurables es suficiente para la mayoría de los equipos si la inestabilidad se controla desde el origen.
¿Debo capturar la página completa o un único componente en una prueba visual?
Limita la captura de pantalla a un locator de elemento específico en lugar de a la página completa siempre que sea posible. La captura a nivel de componente mantiene la comparación enfocada, reduce el ruido, acelera la revisión y evita actualizar la referencia de toda la página ante cada cambio no relacionado. La propia documentación de Vitest señala que capturar la página completa es un antipatrón. Reserva la captura a nivel de página para flujos end-to-end críticos como el pago, donde importan las interacciones de layout entre elementos, y usa pruebas a nivel de componente para las piezas del sistema de diseño y la UI aislada.