Skip to main content

Command Palette

Search for a command to run...

Pruebas de Regresión Visual con Playwright: Detectando Cambios en la UI Automáticamente

Updated
9 min read
Pruebas de Regresión Visual con Playwright: Detectando Cambios en la UI Automáticamente
J
I started in QA writing test cases. Somewhere along the way I got more interested in the systems underneath the tests — the architecture, the tooling, the conventions that make a whole team faster. Now I build those systems. Based in Lima, Peru, with 5+ years working across the full quality stack: functional, accessibility (WCAG 2.0 AA), security (OWASP ZAP), i18n, and visual regression. This blog covers the domains most QA engineers don't write about publicly. Writing in Spanish first, English to follow.

Serie: Calidad y Accesibilidad en Aplicaciones Web — Artículo 1 de 3


Introducción

Cuando probamos aplicaciones web, solemos enfocarnos en la funcionalidad — pero ¿cómo garantizamos que la interfaz de usuario se vea correcta después de cada despliegue?

Ahí es donde entran las pruebas visuales.

Las pruebas visuales comparan automáticamente capturas de pantalla de la UI entre ejecuciones, ayudándonos a detectar cambios inesperados en el diseño, elementos faltantes o regresiones de estilos. Un cambio de padding, un botón que se achicó, un componente que se desplazó dos píxeles — ese tipo de cosas que pasan desapercibidas en code review pero que el equipo de UX y QA necesita detectar antes de que lleguen a producción.

Aunque existen herramientas dedicadas como Percy de BrowserStack que ofrecen capacidades avanzadas de comparación visual, tienen un límite de screenshots. La funcionalidad nativa de Playwright no tiene ese costo directo — los snapshots viven en tu repositorio. El "costo" real es el tamaño en disco y en tu historial de Git, algo que puedes gestionar con una estrategia de almacenamiento adecuada. Para equipos y clientes que priorizan herramientas open source, esto es un argumento sólido: obtienes comparación visual, evidencia histórica, soporte multi-browser y multi-breakpoint, todo dentro de la misma herramienta que ya usas para tus pruebas funcionales.

En este artículo voy a mostrarte cómo configurar pruebas de regresión visual con Playwright usando ejemplos reales de AutoCatalog, un catálogo de autos que construí en Next.js + TypeScript para experimentar con estos patrones.

Al finalizar, podrás:

  • Capturar capturas base (baselines) de los componentes clave de la UI

  • Compararlas automáticamente en futuras ejecuciones

  • Detectar cambios visuales no deseados de forma temprana en tu pipeline de CI


⚙️ Prerrequisitos

Antes de comenzar, asegúrate de contar con:

  • Node.js (versión LTS recomendada)

  • Un proyecto de Playwright ya configurado:

pnpm create playwright
  • Conocimiento básico de pruebas en Playwright y del patrón Page Object Model (POM)

Configuración de pruebas visuales

1. Aserciones básicas de capturas de pantalla

Playwright incluye soporte nativo para comparaciones visuales mediante expect(page).toHaveScreenshot() o expect(element).toHaveScreenshot().

La primera vez que ejecutas una prueba, Playwright guarda una imagen base (baseline). En ejecuciones posteriores, compara la nueva captura con la base y señala cualquier diferencia.

Para ilustrar el valor real de esto: imagina que alguien modifica el tamaño o el padding del botón "Add Product" — un cambio pequeño que fácilmente pasa desapercibido en code review. La prueba de regresión visual lo detecta de inmediato.

test('Product Management - Default layout page', async ({ page }) => {
    await test.step('Capture full-page baseline', async () => {
        await expect(page).toHaveScreenshot('manage_default_layout.png')
    })
})

Si el layout cambia, Playwright genera una imagen con las diferencias (diff) y el reporte incluye un slider interactivo para comparar el antes y el después:

Playwright visual regression report showing slider comparison — juniordiazbriceno.dev

📸 El slider del reporte de Playwright permite comparar visualmente el baseline con el estado actual — ideal para detectar cambios sutiles de spacing o tamaño.


2. Pruebas visuales a nivel de componente

En lugar de capturar páginas completas, puedes enfocarte en componentes específicos o modales. Esto hace que los tests sean más estables: un cambio en el header no rompe la prueba del modal.

test('Product Management - Add Product modal default layout matches baseline', async ({ page }) => {
    await test.step('Open Add Product modal', async () => {
        await managePage.openAddProductModal()
        await managePage.expectAddProductModalVisible()
    })

    await test.step('Capture modal component screenshot', async () => {
        const productModal = managePage.modalTitle.locator('..')
        await expect(productModal).toHaveScreenshot(
            'add_product_modal_default.png'
        )
    })
})

El scope del screenshot es el modal en sí — no la página completa. Así, la prueba verifica que el layout, espaciado y elementos del modal se mantengan consistentes sin depender del resto de la UI.

Add Product modal snapshot baseline — AutoCatalog demo sit

📸 Snapshot del modal Add Product — la prueba verifica que el layout, espaciado y elementos del modal se mantengan consistentes.


3. Captura de snapshots en estados clave de la UI

Las pruebas visuales son más efectivas cuando capturas estados relevantes de la interfaz, no cada interacción.

El carrusel de la home: el problema del contenido dinámico

Verificamos que el diseño de la home se vea correcto sin ruido generado por animaciones. El carrusel muestra imágenes en posiciones aleatorias en cada ejecución — sin mask, el test falla constantemente aunque el layout esté perfecto.

test('Home - Full page screenshot with carousel masked — stable test', async ({ page }) => {
    await test.step('Wait for carousels to be visible', async () => {
        await homePage.expectCarouselsVisible()
    })

    await test.step('Capture full page with all carousels masked', async () => {
        const allCarousels = await homePage.getAllCarousels()
        await expect(page).toHaveScreenshot('home_with_mask.png', {
            mask: allCarousels,
        })
    })
})

👉 La opción mask: Locator[] ignora las áreas que cambian dinámicamente, como el carrusel.

Veamos qué ocurre en ambos casos:

Sin mask: diff report with red highlights showing carousel differences — juniordiazbriceno.dev

📸 Sin mask: el test falla porque el carrusel muestra imágenes diferentes entre ejecuciones — Playwright detecta las diferencias y las marca en rojo.

Con mask aplicado: test passing with magenta masked areas — juniordiazbriceno.dev Reporte de Playwright mostrando el test aprobado con mask aplicado — 1 passed, 0 failed — juniordiazbriceno.dev

📸 Con mask aplicado: las áreas dinámicas son ignoradas y el test pasa de forma consistente.


4. Gestión de imágenes base

La primera vez que ejecutas las pruebas, Playwright guarda las capturas base en:

/tests/screenshots/

Si un cambio visual es intencional, actualiza los snapshots con:

pnpm exec playwright test --update-snapshots

⚠️ Nota para CI: Evita ejecutar --update-snapshots de forma automática en tu pipeline. Reserva este comando para actualizaciones manuales y revisadas. Considera crear un workflow separado o protegerlo detrás de una confirmación explícita.

Para revisar las diferencias:

  1. Abre la carpeta test-results

  2. Compara las imágenes: base, actual y diff

  3. Aprueba o rechaza los cambios durante la revisión de código


Buenas prácticas

1. Organiza las pruebas por funcionalidad

Agrupa las pruebas visuales lógicamente usando test.describe():

test.describe('@ProductManagement @Visual', () => {
    test.describe('@LayoutBaseline', () => {
        // Prueba del layout completo
    })

    test.describe('@ModalBaseline', () => {
        // Pruebas de componentes modales
    })
})

2. Usa nombres descriptivos para los screenshots

Nombra las capturas de forma clara para indicar qué están probando:

manage_default_layout.png
add_product_modal_masked_dropdowns.png
screenshot1.png

3. Prepara correctamente el estado de la prueba

Asegúrate de que la aplicación esté en el estado correcto antes de capturar una pantalla:

test.beforeEach(async ({ page }) => {
    poManager = new POManager(page)
    managePage = poManager.getManagePage()
    await managePage.navigateToManage()
    await managePage.expectManagePageLoaded()
})

4. Usa test.step con snapshots

Es posible tomar snapshots dentro de test.step, lo que permite capturar múltiples estados en un mismo test. La ventaja es que el test queda más cohesivo y el reporte de Playwright muestra exactamente qué paso falló. La contrapartida: si un step falla, los siguientes no se ejecutan — en algunos casos es mejor separar los tests.

test('Add Product modal — múltiples estados', async ({ page }) => {
    await test.step('Open modal', async () => {
        await managePage.openAddProductModal()
        await managePage.expectAddProductModalVisible()
    })

    await test.step('Capture default state', async () => {
        await expect(productModal).toHaveScreenshot('modal_default.png')
    })

    await test.step('Capture focus on submit button', async () => {
        await managePage.activateTabKeyboard()
        await managePage.focusModalSubmitButton()
        await expect(productModal).toHaveScreenshot('modal_submit_focus.png')
    })
})

5. Prueba diseños responsivos

Verifica que los layouts responsivos funcionen correctamente en distintos tamaños de pantalla:

test('Home - mobile layout', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 667 })
    await expect(page).toHaveScreenshot('home-mobile.png')
})

🧠 Solución de problemas frecuentes

Problema Solución
Diferencias mínimas de píxeles entre ejecuciones Diferencias mínimas de píxeles entre ejecuciones → Identifica la causa raíz: usa mask para contenido dinámico, addStyleTag para scrollbars, o espera explícita para elementos que aún están renderizando. Evita maxDiffPixelRatio — acepta píxeles incorrectos como válidos y puede ocultar regresiones reales.
Contenido dinámico (carruseles, timestamps, animaciones) Usa la opción mask para excluirlo del snapshot
Falsos positivos en CI Asegura resolución de pantalla y versión del navegador consistentes entre entornos
Scrollbars que generan diffs Ocúltalos con addStyleTag antes del screenshot

⚠️ Sobre maxDiffPixelRatio: Aunque Playwright ofrece esta opción para tolerar diferencias mínimas de píxeles, úsala con precaución. Un valor de 0.01 puede parecer pequeño, pero en una pantalla de 1280×720 equivale a más de 9.000 píxeles — suficiente para ocultar un botón desalineado o un cambio de color. En la mayoría de los casos, la solución correcta es eliminar la fuente de inestabilidad, no tolerarla.


🎯 Conclusión

Al combinar las pruebas funcionales de Playwright con aserciones visuales, puedes detectar y prevenir con confianza cambios no deseados en la UI.

Este enfoque le da a tu equipo de QA y desarrollo una capa de seguridad adicional: garantizando que tanto el comportamiento como la apariencia de la aplicación se mantengan consistentes con el tiempo.

Puntos clave:

  • Usa toHaveScreenshot() para capturar y comparar estados de la UI

  • Enmascara elementos dinámicos para obtener diffs confiables

  • Versiona las imágenes base y revisa los cambios a través del proceso de revisión de código

  • Evita actualizar snapshots automáticamente en CI sin una revisión explícita

  • Usa test.step para organizar tests con múltiples capturas y obtener reportes más granulares


Este es el primero de una serie sobre QA automation — próximamente:

  • Automatizando Pruebas de Accesibilidad Web: Combinando axe-core y Playwright

  • WCAG 2.4.7 Foco Visible: Pruebas de Regresión Visual con Playwright


¿Tu equipo tiene cobertura de regresión visual? Si quieres explorar cómo implementar este tipo de testing en tu proyecto, cuéntame sobre tu equipo aquí.


📚 Lecturas adicionales

Calidad y Accesibilidad en Aplicaciones Web

Part 1 of 1

Una serie práctica sobre automatización de pruebas web — cubriendo regresión visual, accesibilidad y pruebas de foco visible con Playwright. Ejemplos reales, código propio, sin teoría vacía.