Cómo estructurar pruebas E2E sin que consuman el presupuesto de CI/CD

← Volver
Terminal con logs de pruebas automatizadas y métricas de ejecución en tiempo real
Foto de Pankaj Patel en Unsplash

Los tests E2E son la red de seguridad más cercana a lo que vive el usuario en producción. También son, sin una estrategia clara, la parte del pipeline que más minutos consume y la que con mayor frecuencia se convierte en el cuello de botella de cada pull request.

No hay una respuesta única sobre cuántos tests E2E son suficientes. La respuesta correcta depende del proyecto, del presupuesto de CI y de cuánto dolor tolera el equipo esperando feedback. Este post recoge el criterio que aplico en proyectos propios —entre ellos Cartas Rápidas y Utilibox— para mantener la suite útil y el pipeline barato.

1. El problema no es Playwright, es la estrategia de selección

Playwright es rápido. El problema aparece cuando la suite crece sin criterio: se añaden tests para cada caso de uso, flujo alternativo y estado de borde, y el resultado es un pipeline que tarda veinte minutos en dar feedback sobre un cambio de dos líneas.

A medida que una suite E2E crece, los tests de extremo a extremo se convierten frecuentemente en la parte más lenta del pipeline. Lo que en su momento tardaba minutos puede crecer gradualmente hacia los veinte o treinta, añadiendo fricción en cada pull request.

El primer paso, antes de tocar ninguna configuración de paralelismo, es responder una pregunta editorial: ¿qué merece ser un test E2E y qué no?

Lo que sí merece un test E2E

  • Flujos críticos de negocio: registro, login, acción de compra, envío de formulario de contacto.
  • Rutas cuya rotura implica pérdida directa de conversión o de datos del usuario.
  • Integraciones entre capas que no se pueden cubrir con tests unitarios o de integración.

Lo que normalmente no lo merece

  • Validaciones de UI puras (color de un botón, texto de un label): cúbrelas con tests visuales de snapshot o con revisión manual.
  • Lógica de negocio aislada: esto es territorio de tests unitarios, más rápidos y sin overhead de navegador.
  • Flujos secundarios de bajo riesgo: si un caso de uso falla de forma aislada y no afecta al flujo principal, considera si necesita E2E o basta con un test de integración.

La taxonomía smoke / suite completa

La distinción más útil en la práctica es entre smoke tests y suite completa:

  • Smoke tests: un subconjunto pequeño (entre cinco y quince tests) que cubre los flujos más críticos. Se ejecutan en cada push y en cada PR. Su objetivo es detectar regresiones graves en menos de tres minutos.
  • Suite completa: todos los tests E2E. Se ejecuta en merges a main o en despliegues a staging, no en cada commit de feature branch.

Esta separación sola —y sin tocar nada más— puede reducir el consumo de minutos en un proyecto de tamaño mediano a la mitad.

# .github/workflows/e2e-smoke.yml — se ejecuta en todo push
name: Smoke E2E
on:
  push:
    branches-ignore:
      - main

jobs:
  smoke:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --grep "@smoke"

El truco es etiquetar los tests críticos con una anotación que permita filtrarlos. En Playwright basta con añadir @smoke en el nombre del test:

test('@smoke usuario puede enviar el formulario de contacto', async ({ page }) => {
  // ...
});

2. Paralelización eficiente: workers primero, sharding después

Una vez definido qué tests se ejecutan y cuándo, el siguiente vector es el tiempo de ejecución. Aquí hay dos palancas distintas que conviene entender antes de combinarlas.

Workers: paralelismo dentro de un mismo runner

Los workers suelen ser la primera palanca que se ajusta al optimizar el rendimiento de Playwright, y con razón: permiten ejecutar tests en paralelo en una sola máquina, aprovechando todos los núcleos disponibles.

Por ejemplo, una suite que tarda cuarenta minutos con un solo worker puede reducirse de forma significativa en la misma máquina con cuatro workers. Pero este enfoque tiene límites: cuando la CPU y la memoria están saturadas, añadir más workers genera rendimientos decrecientes o incluso tests inestables.

La documentación oficial de Playwright recomienda establecer workers a 1 en entornos CI para priorizar estabilidad y reproducibilidad, ya que ejecutar los tests de forma secuencial garantiza que cada test dispone de todos los recursos del sistema. Sin embargo, si el CI dispone de runners potentes, es válido habilitar tests en paralelo.

En la práctica, un punto de partida razonable para un runner ubuntu-latest estándar (2 cores) es workers: 2. Si priorizas estabilidad sobre velocidad o el runner tiene recursos limitados, usa workers: 1 tal como recomienda la documentación oficial. Mide antes de subir.

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  workers: process.env.CI ? 2 : undefined, // Ajusta a 1 si buscas máxima estabilidad
  // ...
});

Sharding: paralelismo entre múltiples runners

El sharding en Playwright consiste en dividir los tests en partes más pequeñas llamadas shards. Cada shard es un job independiente que puede ejecutarse por separado, y el objetivo es distribuir los tests para reducir el tiempo total de ejecución.

Los workers ejecutan varios tests simultáneamente dentro de un único proceso de Playwright; los shards ejecutan procesos de Playwright separados, cada uno gestionando un subconjunto de la suite. Son palancas complementarias, no alternativas.

El overhead por shard —checkout, instalación de dependencias, instalación de browsers— ronda los tres a cinco minutos de coste fijo. A medida que se añaden shards, ese overhead fijo se multiplica y puede superar el ahorro en tiempo de reloj. Como orientación general: para una suite de sesenta minutos con cinco minutos de overhead, tres o cuatro shards suele ser un punto de equilibrio razonable. Ajusta según tu caso real.

Cuando fullyParallel: true está habilitado, Playwright distribuye tests individuales entre shards, asegurando una distribución equitativa. Es el modo preferido para garantizar una carga balanceada cuando se usa sharding, ya que Playwright puede optimizar la ejecución en función del número total de tests.

La configuración de sharding en GitHub Actions usa la estrategia matrix:

# .github/workflows/e2e-full.yml — solo en merge a main o en staging
name: Full E2E Suite
on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
      - name: Cache Playwright browsers
        uses: actions/cache@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
      - run: npm ci
      - run: npx playwright install --with-deps
        if: steps.playwright-cache.outputs.cache-hit != 'true'
      - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
        env:
          CI: true
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: blob-report-${{ matrix.shardIndex }}
          path: blob-report/
          retention-days: 1

  merge-reports:
    if: ${{ always() && needs.test.result != 'skipped' }}
    needs: [test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
      - run: npm ci
      - uses: actions/download-artifact@v4
        with:
          path: blob-reports
          pattern: blob-report-*
          merge-multiple: true
      - run: npx playwright merge-reports --reporter html ./blob-reports
      - uses: actions/upload-artifact@v4
        with:
          name: playwright-html-report
          path: playwright-report/
          retention-days: 14

Si quieres reutilizar los binarios del navegador entre ejecuciones de CI, cachea esos directorios en la configuración de CI usando un hash de la versión de Playwright como clave. El paso de caché del workflow anterior hace exactamente eso: la clave incluye el hash de package-lock.json, lo que invalida la caché automáticamente cuando Playwright se actualiza.

3. El coste real de los minutos en GitHub Actions

GitHub introdujo a partir de enero de 2026 una reducción de precios de hasta un 39% en runners hospedados, con variación según el tamaño del runner. Para los runners hospedados por GitHub, el nuevo cargo de plataforma de $0,002 por minuto ya está incluido en el precio reducido.

El uso de runners estándar hospedados por GitHub en repositorios públicos sigue siendo gratuito. Para repositorios privados, cada plan incluye una cuota mensual de minutos.

El impacto práctico del sharding en términos de coste no siempre es inmediato. El sharding es más rápido en tiempo de reloj, pero no siempre más barato. El overhead fijo por shard —checkout, instalación de dependencias, instalación de browsers— se factura en cada runner. Con cuatro shards, ese overhead se multiplica por cuatro. La caché de browsers mitiga una parte de ese coste, pero no lo elimina.

Para un proyecto de tamaño reducido como un side project o un MVP, la estrategia más eficiente es habitualmente:

  1. Smoke tests sin sharding en cada push (rápidos, baratos).
  2. Suite completa con dos o tres shards solo en merge a main o en deploys a staging.
  3. Caché de browsers habilitada para reducir el overhead de instalación.

4. maxFailures: corta el pipeline cuando ya no aporta

Playwright permite configurar el número máximo de tests fallidos en la suite completa mediante maxFailures. Cuando se alcanza ese límite, Playwright detiene la ejecución y omite los tests restantes. Esto resulta útil para evitar desperdiciar recursos en suites rotas.

En la práctica, si los primeros diez tests ya fallan, continuar ejecutando doscientos más solo consume minutos sin aportar información nueva. Para CI, un valor entre cinco y diez suele ser razonable.

// playwright.config.ts
export default defineConfig({
  maxFailures: process.env.CI ? 10 : undefined,
  workers: process.env.CI ? 2 : undefined,
  reporter: process.env.CI ? 'blob' : 'html',
  // ...
});

5. Cómo lo resuelve Cartas Rápidas en producción

En Cartas Rápidas la suite E2E creció junto con el soporte multilingüe y las verificaciones de traducciones. La estrategia adoptada en esa release combina exactamente los elementos de este post: una suite Playwright completa que se activa en el pipeline de CI, presupuestos Lighthouse integrados en el mismo workflow y separación por tipo de evento (push vs. merge). El resultado es un pipeline que da feedback útil en el tiempo que requiere sin generar costes innecesarios en ramas de trabajo diario.

La clave no está en ninguna herramienta concreta, sino en la disciplina editorial previa: decidir qué entra en la suite, separar smoke de regresión completa, y dimensionar el paralelismo en función del tamaño real del proyecto, no de lo que admite la infraestructura.


Si tienes una suite E2E que ya empieza a ralentizar tus deploys o no estás seguro de si merece la pena añadir sharding, cuéntame en qué punto estás y revisamos juntos qué configuración tiene más sentido para tu caso.