Las pruebas end-to-end (E2E) verifican el flujo completo de una aplicación, desde lo que el usuario ve en el navegador hasta los servicios de backend y las bases de datos, comprobando que todas las capas funcionan correctamente en conjunto. Las pruebas unitarias y de integración detectan errores en componentes aislados, pero pueden pasar por alto problemas que solo aparecen cuando el sistema completo está en ejecución. Las pruebas E2E cubren esa brecha.

Playwright es una buena opción para esto. Es open-source, está respaldado por Microsoft, soporta Chromium, Firefox y WebKit, y tiene soporte de primera clase para TypeScript. Este post cubre los fundamentos de las pruebas E2E y cómo implementar una suite funcional con Playwright.

La Importancia de las Pruebas End-to-End

Las pruebas unitarias verifican que una función devuelva el valor correcto. Las pruebas de integración verifican que dos módulos intercambien datos correctamente. Ninguna de ellas indica si un usuario puede realmente iniciar sesión, completar un checkout o enviar un formulario sin que algo falle en el camino.

Las pruebas E2E simulan el comportamiento real del usuario: hacer clic en botones, rellenar formularios, navegar entre páginas y verificar que aparezca el contenido correcto. Detectan regresiones que cruzan límites de servicios, donde un cambio en una API de backend rompe un flujo de frontend que ninguna prueba unitaria capturaría.

La contrapartida es la velocidad y el costo de mantenimiento. Las pruebas E2E son más lentas que las pruebas unitarias y más sensibles a los cambios de UI. Ejecútalas en los caminos críticos del usuario, no en cada variación menor de componente.

Por Qué Elegir Playwright

Playwright ejecuta pruebas en Chromium, Firefox y WebKit desde una única configuración, lo que detecta diferencias de renderizado específicas de cada navegador. Su espera automática significa que rara vez es necesario escribir llamadas explícitas a sleep. Captura screenshots, graba traces y genera vídeos ante fallos, lo que hace que depurar una ejecución fallida en CI sea mucho más rápido que intentar reproducirla localmente.

El soporte para TypeScript está integrado. Las pruebas se ejecutan en paralelo entre navegadores. Se integra limpiamente con GitHub Actions, GitLab CI y plataformas similares.

Configurando el Entorno de Playwright

Instala Playwright en un proyecto Node.js existente o nuevo:

mkdir playwright-e2e-tests
cd playwright-e2e-tests
npm init -y
npm install --save-dev @playwright/test
npx playwright install

npx playwright install descarga los binarios de los navegadores para Chromium, Firefox y WebKit. Para proyectos TypeScript, inicializa la configuración:

npx tsc --init

Una estructura mínima de proyecto:

playwright-e2e-tests/
├── tests/
│   └── example.spec.ts
├── package.json
├── tsconfig.json
└── playwright.config.ts

Una Prueba Básica de Ejemplo

import { test, expect } from '@playwright/test';

test('homepage has expected title and welcome message', async ({ page }) => {
  await page.goto('https://example.com');
  
  // Check the page title
  await expect(page).toHaveTitle(/Example Domain/);

  // Check if 'Example Domain' text is present
  const heading = await page.locator('h1');
  await expect(heading).toContainText('Example Domain');
});

Playwright espera automáticamente a que la página se cargue y a que los elementos estén listos antes de interactuar con ellos. La prueba navega a una URL, verifica el título y comprueba el encabezado. Ejecútala con:

npx playwright test

Configuración Avanzada de Playwright

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: 'tests',
  timeout: 30000,
  expect: {
    timeout: 5000,
  },
  use: {
    headless: true,
    screenshot: 'only-on-failure',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'Chromium',
      use: { browserName: 'chromium' },
    },
    {
      name: 'Firefox',
      use: { browserName: 'firefox' },
    },
    {
      name: 'WebKit',
      use: { browserName: 'webkit' },
    },
  ],
});

testDir define dónde se encuentran los archivos de prueba. timeout limita cada prueba a 30 segundos. screenshot: 'only-on-failure' y trace: 'on-first-retry' proporcionan artefactos de depuración sin saturar las ejecuciones exitosas. El array projects ejecuta las mismas pruebas en los tres motores de navegador en paralelo.

Pruebas en Aplicaciones Dinámicas y con Autenticación

Las aplicaciones de página única frecuentemente requieren una sesión autenticada antes de poder ejecutar cualquier prueba significativa. Iniciar sesión antes de cada prueba es lento. Playwright resuelve esto con el estado de almacenamiento: inicia sesión una sola vez en un paso de configuración global, guarda la sesión y reutilízala en todas las pruebas.

// global-setup.ts
import { test as setup, expect } from '@playwright/test';

setup('authenticate once', async ({ page }) => {
  await page.goto('https://my-app.com/login');
  await page.fill('#username', 'testuser');
  await page.fill('#password', 'testpassword');
  await page.click('button[type="submit"]');
  // Wait for a known element after login
  await expect(page.locator('#dashboard')).toBeVisible();
  // Save state
  await page.context().storageState({ path: 'authState.json' });
});

Luego en playwright.config.ts:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  globalSetup: require.resolve('./global-setup'),
  use: {
    storageState: 'authState.json',
  },
  // ... other configurations
});

Todas las pruebas posteriores inician con la sesión guardada. Sin flujos de login repetidos, sin tiempo adicional de prueba para la autenticación.

Depurando Pruebas E2E con Fallos

Cuando una prueba falla en CI, necesitas información. Las herramientas integradas de Playwright cubren los principales escenarios:

Los screenshots ante fallos muestran exactamente lo que el navegador mostró cuando la prueba falló. Los traces capturan una línea de tiempo completa de cada acción, solicitud de red y mensaje de consola. Ejecutar con --headed localmente permite observar la ejecución de la prueba en una ventana real del navegador e inspeccionar el DOM.

test('debugging example', async ({ page }) => {
  await page.goto('https://example.com');
  // Intentionally failing step
  await expect(page.locator('.non-existent')).toBeVisible();
});

Para ejecutar con tracing activado:

npx playwright test --headed --trace=on

Integración con CI/CD y DevOps

Un workflow básico para GitHub Actions:

name: Playwright E2E Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Node
        uses: actions/setup-node@v2
        with:
          node-version: '16'
      - name: Install dependencies
        run: npm install
      - name: Install browsers
        run: npx playwright install
      - name: Run tests
        run: npx playwright test

Cada push ejecuta la suite E2E completa. Las pruebas fallidas producen screenshots y traces como artefactos de CI que puedes descargar e inspeccionar sin necesidad de reproducirlos localmente.

Buenas Prácticas para Playwright y Pruebas E2E

Escribe pruebas que no dependan del orden de ejecución. Cada prueba debe configurar su propio estado de forma independiente. Una prueba que depende de que otra haya sido ejecutada previamente es frágil y difícil de depurar cuando falla.

Usa atributos data-test-id en los elementos en lugar de selectores CSS vinculados a nombres de clase. Las clases CSS cambian cuando se actualizan los diseños. Los atributos de prueba son contratos explícitos entre la prueba y la UI.

Cubre tus flujos de usuario más importantes: inicio de sesión, acciones clave de negocio, checkout, envío de formularios. No intentes cubrir con pruebas E2E cada variación menor de la UI. Mantén las pruebas E2E centradas en caminos que atraviesen múltiples capas.

Limpia los datos de prueba después de cada ejecución si tus pruebas escriben en una base de datos. Los entornos de prueba efímeros son más fáciles de razonar que los entornos con estado acumulado.

Expandiendo Más Allá de los Escenarios Básicos

Playwright maneja más que simples verificaciones de página. Puedes emular dispositivos móviles y probar diseños responsivos. Puedes interceptar solicitudes de red y simular respuestas de APIs de terceros para evitar activar pagos reales o correos electrónicos durante las pruebas. Las pruebas de regresión visual con snapshots detectan cambios de estilo no intencionados. Para pruebas de API, puedes usar el contexto de solicitud de Playwright para acceder directamente a los endpoints mientras reutilizas el mismo estado de autenticación.

Conclusión

Las pruebas E2E con Playwright brindan confianza en que los flujos de usuario más importantes de tu aplicación funcionan de extremo a extremo, en múltiples navegadores y en condiciones cercanas a producción. No reemplazan las pruebas unitarias o de integración, pero detectan una categoría de errores que esas pruebas no pueden capturar. Con ejecución paralela en Chromium, Firefox y WebKit, buenos artefactos ante fallos e integración limpia con CI, Playwright hace que las pruebas E2E sean algo práctico en lugar de una carga.