main content

Cómo implementar tests end-to-end automatizados con Playwright y CI/CD para garantizar la calidad continua de tu app a medida

Te cuento una de la trinchera. Un viernes a las seis de la tarde, después de un deploy "sin riesgo", un cliente nos escribió porque sus usuarios no podían firmar contratos. El bug llevaba dos semanas en producción y se había colado por un cambio de tres líneas en la lógica de roles. Nadie lo vio porque nadie lo probó en el flujo completo. Ese es exactamente el tipo de incendio que matan los tests end-to-end automatizados con Playwright integrados en un pipeline de CI/CD: ejecutan el flujo entero del usuario en cada commit y rompen el build cuando algo se cae. Esta guía es lo que me habría gustado tener entonces, contado desde la silla del que ya se ha comido varios postmortem.

Por qué dejamos Selenium y Cypress por Playwright

Durante años tiramos de Selenium. Funciona, sí, pero su arquitectura basada en WebDriver mete una capa de red entre el test y el navegador que lo vuelve lento y, sobre todo, frágil. En CI eso se traduce en suites que tardan 40 minutos y que fallan una de cada tres veces sin razón aparente. Cypress nos alegró la vida un tiempo, hasta que tocamos un flujo con varias pestañas y un iframe de pasarela de pago. Ahí Cypress dijo basta.

Playwright, lanzado por Microsoft en 2020, habla directamente con el protocolo del navegador (Chrome DevTools Protocol para Chromium, equivalentes para Firefox y WebKit). En la práctica eso significa que puedes manejar varias pestañas, interceptar peticiones de red, simular un iPhone y compartir cookies entre tests sin recurrir a apaños. La API es coherente y, lo más importante para el día a día, los traces son brutales cuando algo se rompe.

Para una app a medida lo notas en cosas muy concretas. El auto-waiting elimina la mitad de los sleeps que solíamos meter "por si acaso". TypeScript funciona sin configuración extra, lo cual ahorra una tarde de tocar tsconfigs. El modo trace graba vídeo y capturas paso a paso, así que cuando un test cae en CI no tienes que reproducirlo a ciegas en local. Y la paralelización nativa nos bajó una suite de 28 minutos a poco más de seis.

Qué automatizar y qué dejar fuera para no morir de mantenimiento

El error más caro que vi cometer (y que cometí) fue intentar automatizar todo. Los tests E2E son caros: tardan, dependen de infraestructura y rompen con cambios cosméticos. Su valor está en verificar los flujos críticos de negocio tal como los toca un usuario real, no en comprobar que el menú lateral se despliega.

La regla que aplicamos ahora es mapear los flujos cuyo fallo le cuesta dinero al cliente. En una app de gestión de contratos, el flujo crítico no es el menú: es que un usuario pueda crear un contrato, añadir cláusulas, mandarlo a firmar y recibir confirmación. Eso se automatiza primero, antes que nada.

A partir de ahí, ampliamos en orden de prioridad: autenticación y permisos por rol, CRUD sobre las entidades principales, integraciones con servicios externos (pagos, firmas, emails transaccionales) y manejo de errores que el usuario realmente ve. Los tests de regresión visual los dejamos para más tarde. Comparar capturas suena precioso hasta que tu pipeline empieza a fallar porque una fuente renderiza medio píxel distinta entre Ubuntu 22.04 y 24.04.

Estructura del repo de tests, sin reinventar la rueda

Una suite bien organizada se trata como código de producción. Separación de responsabilidades, reutilización y que cualquiera del equipo pueda entender un test en treinta segundos.

El Page Object Model (POM) es el patrón recomendado por la propia documentación de Playwright y es el que usamos por defecto. Cada página o componente importante de la app tiene su clase, que encapsula los selectores y las acciones. El test solo habla con esos objetos, nunca con selectores crudos. Cuando el equipo de front cambió todos los data-testid por un sistema nuevo, actualizamos cinco archivos en /pages y la suite siguió verde. Sin POM habría sido una mañana entera de buscar y reemplazar.

La estructura que nos funciona separa tests por dominio (/tests/auth, /tests/contracts, /tests/payments), los Page Objects en /pages, los datos de prueba en /fixtures y los helpers en /utils. La configuración global (URLs base, timeouts, navegadores) vive en playwright.config.ts y nadie la toca sin abrir PR.

Capítulo aparte para los datos de prueba. Hardcodear emails como test@test.com parece inofensivo hasta que dos tests pisan el mismo registro en paralelo. Lo que hacemos ahora es crear los datos necesarios en el setup mediante llamadas directas a la API de la propia app y borrarlos en el teardown. Así cada test es independiente, corre en cualquier entorno y no deja basura.

El pipeline que bloquea despliegues rotos

Aquí es donde los tests dejan de ser un capricho del que los escribió y se convierten en barrera de calidad del equipo. Si los tests no bloquean el merge, no sirven.

En GitHub Actions, la configuración mínima dispara un workflow en cada push a ramas de feature y en cada pull request contra main. El job instala dependencias, levanta la app en modo de prueba (o apunta a un staging dedicado), corre los tests y publica el informe como artefacto. Si algo falla, el check rojo bloquea el merge. Punto.

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run test:e2e
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

Para implementar tests end-to-end automatizados con Playwright y CI/CD sin meterte en líos, los entornos son la parte que más quema. Un error que vi más de una vez fue lanzar tests E2E contra una base de datos compartida. Cada ejecución debe tener su propia base de datos efímera, inicializada con datos conocidos antes de empezar y destruida al acabar. Docker Compose hace ese trabajo bastante bien: levanta app, base de datos y servicios auxiliares como contenedores dentro del propio job y los tira al terminar. Bonus: si funciona en CI, funciona en el portátil del nuevo el primer día.

Flaky tests: el enemigo silencioso que te deja sin equipo

Un test flaky es ese que a veces pasa y a veces no, sin que nadie haya tocado nada. Es, con diferencia, lo que más mata una suite E2E. La dinámica es siempre la misma: el equipo empieza a asumir que los fallos son falsos positivos, le da a "rerun" sin mirar, y un día un bug real se cuela porque ya nadie lee el informe. He visto suites enteras morir así en tres meses.

Playwright reduce la flakiness con su auto-waiting, pero no la elimina. Las causas reales que nos hemos encontrado son tres, casi siempre las mismas: animaciones CSS que bloquean clics, llamadas a APIs externas con latencia variable, y tests que comparten estado sin querer.

Las medidas que funcionan: usar await expect(locator).toBeVisible() en vez de sleeps fijos, mockear las APIs externas con page.route() para quitar la latencia real de la ecuación, y configurar retries: 2 en playwright.config.ts solo para que un fallo transitorio no rompa el pipeline mientras se investiga. Ese retries: 2 es una herramienta, no una alfombra. Cualquier test que reintenta de forma recurrente entra en una lista de "a arreglar esta semana", no se ignora.

Cuando algo cae, el modo trace de Playwright es lo primero que abro. Graba cada acción, captura pantalla en cada paso y guarda la pila de llamadas en el momento del fallo. Diagnostico que antes me costaba dos horas ahora me lleva diez minutos.

Métricas que te dicen si la suite sigue viva

Una suite E2E es una inversión que pide mantenimiento. Si no la mides, se pudre. Las métricas que revisamos cada par de semanas son cuatro.

Tiempo total de ejecución: si pasa de 15-20 minutos, el feedback en CI se vuelve demasiado lento y la gente empieza a mergear sin esperar. Tasa de flakiness por test en las últimas 30 ejecuciones: cualquier test por encima del 5% entra en la cola de revisión. Porcentaje de tests activos respecto a los flujos críticos identificados: si baja, hay deuda de cobertura. Y tiempo medio para resolver un fallo de test: si crece, el equipo está ignorando la suite.

La política que más nos ha cundido es la de "test antes de merge para nuevas funcionalidades". Cualquier PR que añade un flujo de usuario nuevo trae su test E2E. Suena estricto y al principio genera roces, pero a los dos meses nadie se acuerda. La cobertura crece al ritmo del producto sin acumular deuda.

Lo que hemos aprendido peleándonos con Playwright en proyectos reales

Resumiendo lo que dejaría escrito en el CLAUDE.md del proyecto si pudiera. Empieza por uno o dos flujos críticos de verdad, no por veinte mediocres. Mete el pipeline desde el día uno aunque tengas un solo test; integrar más tarde siempre cuesta el triple. Trata el código de tests con el mismo cariño que el de producción: revisiones de PR, refactors, dependencias actualizadas. Y cuando un test sea flaky, abre issue el mismo día, no la semana que viene.

Implementar tests end-to-end automatizados con Playwright y CI/CD en una app a medida no es lujo de equipos grandes; es una decisión de arquitectura que define cuánto te va a doler mantener el producto dentro de un año. Un proyecto sin tests E2E avanza rápido las primeras semanas y luego empieza a sangrar cada release. Con un pipeline decente, cada cambio pasa por una validación automática de los flujos críticos antes de llegar a usuarios reales. Despliegas con más calma, respondes a bugs en horas en vez de días, y puedes enseñarle al cliente evidencia objetiva de que la calidad no es marketing.

Si estás construyendo o reescribiendo una aplicación web a medida y quieres montar una estrategia de testing seria desde el primer sprint, hablemos y diseñamos juntos tu pipeline de calidad.