Use Cases (Actualizado: 3/6/2026)

Pruebas E2E con Claude Code: guía Playwright/Cypress y CI

Diseña pruebas E2E Playwright con Claude Code: CI, errores reales, ejemplos ejecutables y CTA.

Pruebas E2E con Claude Code: guía Playwright/Cypress y CI

Define primero qué debe proteger la prueba E2E

Que Claude Code genere una prueba de inicio de sesión y todo salga verde no significa que el producto esté protegido. Una prueba E2E valida un flujo de usuario en un navegador real, por eso es más lenta y más cara de depurar que una prueba unitaria. Si conviertes cada detalle visual en E2E, el CI se vuelve ruidoso y el equipo deja de creer en los fallos.

Elige los flujos que cuestan dinero cuando se rompen: inicio de sesión, compra, formulario de consulta, acciones peligrosas de administración y CTA que llevan de un artículo a un producto o a una sesión de formación. En ClaudeCodeLab, Masa vio un caso claro: la página se renderizaba bien, pero el enlace final hacia la formación estaba roto. Ese tipo de fallo no siempre aparece en una captura, pero sí rompe la ruta de monetización.

Esta guía se basa en la documentación actual de instalación de Playwright, locators, CI y Trace Viewer. Para completar la estrategia, revisa también automatización de pruebas de API y la guía de estrategias de testing.

flowchart LR
  A["Pedir a Claude Code"] --> B["Elegir tres flujos críticos"]
  B --> C["Implementar pruebas Playwright"]
  C --> D["Depurar con trace e informe"]
  D --> E["Bloquear CI si falla"]
  C --> F["Verificar CTA e ingresos"]

Un prompt más útil para Claude Code

Incluye alcance, criterios de éxito, criterios de fallo, archivos permitidos y comandos de verificación. Sin esa información, Claude Code suele escribir pruebas frágiles basadas en clases CSS. Playwright recomienda locators como getByRole, getByLabel, getByText y getByTestId, porque funcionan bien con la espera automática y las aserciones reintentables.

Objetivo: pruebas E2E para inicio de sesión, compra y newsletter.
Herramienta: Playwright Test. Añade una comparación breve con Cypress.
Reglas:
- Priorizar role, label, text o testid antes que clases CSS.
- Cubrir errores, no solo el camino feliz.
- Usar workers: 1 en CI y trace: on-first-retry.
- Verificar que el href del CTA de monetización existe.
Archivos permitidos:
- tests/e2e/**/*.spec.ts
- playwright.config.ts
Verificación:
- npx playwright test
- npx playwright test --headed para depuración local

También conviene decir qué queda fuera. Pagos reales, envío real de correos, clics en anuncios o escrituras en CRM no deberían ejecutarse en cada E2E. Usa modo de prueba, mocks o pruebas de contrato de API. Para esos bordes, combina esta guía con webhooks y analítica.

Comandos de instalación

En un proyecto nuevo, empieza con estos comandos. La documentación oficial explica que cada versión de Playwright usa binarios concretos de navegador, así que después de actualizar Playwright también debes reinstalar navegadores. En CI Linux, usa --with-deps para instalar dependencias del sistema.

npm init playwright@latest
npx playwright install
npx playwright test
npx playwright test --headed
npx playwright test --ui
npx playwright show-report

--headed abre un navegador visible para investigar fallos locales. --ui abre el modo interactivo. En CI normalmente se ejecuta headless. Si algo pasa en local y falla en CI, revisa animaciones, tamaño de pantalla, fuentes, zona horaria, velocidad de CPU o esperas de API externas.

Ejemplo Playwright ejecutable

El siguiente spec es autónomo. Crea una página pequeña con page.setContent, por lo que no necesitas levantar una aplicación. Copia el archivo en tests/e2e/claude-code-e2e.spec.ts después de instalar Playwright. En una aplicación real, reemplaza renderDemoApp(page) por page.goto('/login') y ajusta etiquetas o data-testid.

// tests/e2e/claude-code-e2e.spec.ts
import { test, expect, type Page } from '@playwright/test';

async function renderDemoApp(page: Page) {
  await page.setContent(`
    <!doctype html>
    <main>
      <form id="login-form" aria-label="Login form">
        <label>Email <input id="email" aria-label="Email" /></label>
        <label>Password <input id="password" aria-label="Password" type="password" /></label>
        <button type="submit">Log in</button>
        <p id="login-error" role="alert" hidden></p>
      </form>

      <section id="dashboard" hidden>
        <h1>Dashboard</h1>
        <a data-testid="training-cta" href="/training/">Book Claude Code training</a>
        <button id="add-plan">Add Pro plan to cart</button>
        <span data-testid="cart-count">0</span>
        <button id="buy">Complete purchase</button>
        <p data-testid="order-status" role="status"></p>

        <label>Newsletter email <input id="newsletter-email" aria-label="Newsletter email" /></label>
        <button id="newsletter-button" type="button">Join newsletter</button>
        <p id="newsletter-error" role="alert" hidden></p>
        <p data-testid="newsletter-status" role="status"></p>
      </section>
    </main>

    <script>
      const state = { cart: 0 };
      document.querySelector('#login-form').addEventListener('submit', (event) => {
        event.preventDefault();
        const email = document.querySelector('#email').value;
        const password = document.querySelector('#password').value;
        const error = document.querySelector('#login-error');

        if (email === 'masa@example.com' && password === 'password123') {
          document.querySelector('#login-form').hidden = true;
          document.querySelector('#dashboard').hidden = false;
          error.hidden = true;
          return;
        }

        error.textContent = 'Authentication failed';
        error.hidden = false;
      });

      document.querySelector('#add-plan').addEventListener('click', () => {
        state.cart += 1;
        document.querySelector('[data-testid="cart-count"]').textContent = String(state.cart);
      });

      document.querySelector('#buy').addEventListener('click', () => {
        const status = document.querySelector('[data-testid="order-status"]');
        status.textContent = state.cart === 0 ? 'Cart is empty' : 'Order ORD-1001 completed';
      });

      document.querySelector('#newsletter-button').addEventListener('click', () => {
        const email = document.querySelector('#newsletter-email').value;
        const error = document.querySelector('#newsletter-error');
        const status = document.querySelector('[data-testid="newsletter-status"]');

        if (!email.includes('@')) {
          error.textContent = 'Enter a valid email';
          error.hidden = false;
          status.textContent = '';
          return;
        }

        error.hidden = true;
        status.textContent = 'Thanks, we will send the checklist.';
      });
    </script>
  `);
}

async function loginAsDemoUser(page: Page) {
  await page.getByLabel('Email').fill('masa@example.com');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Log in' }).click();
}

test.describe('Claude Code E2E starter', () => {
  test('use case 1: login shows dashboard and keeps the training CTA', async ({ page }) => {
    await renderDemoApp(page);
    await loginAsDemoUser(page);

    await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
    await expect(page.getByTestId('training-cta')).toHaveAttribute('href', '/training/');
  });

  test('use case 2: checkout flow creates an order number', async ({ page }) => {
    await renderDemoApp(page);
    await loginAsDemoUser(page);

    await page.getByRole('button', { name: 'Add Pro plan to cart' }).click();
    await expect(page.getByTestId('cart-count')).toHaveText('1');
    await page.getByRole('button', { name: 'Complete purchase' }).click();
    await expect(page.getByTestId('order-status')).toContainText('ORD-1001');
  });

  test('use case 3: newsletter validation blocks invalid leads', async ({ page }) => {
    await renderDemoApp(page);
    await loginAsDemoUser(page);

    await page.getByRole('button', { name: 'Join newsletter' }).click();
    await expect(page.getByRole('alert')).toContainText('Enter a valid email');

    await page.getByLabel('Newsletter email').fill('reader@example.com');
    await page.getByRole('button', { name: 'Join newsletter' }).click();
    await expect(page.getByTestId('newsletter-status')).toContainText('Thanks');
  });
});

Este archivo cubre tres casos concretos: inicio de sesión, compra y captación de leads. Además verifica el href del CTA de formación, un detalle que muchas suites superficiales no revisan.

Configuración práctica de Playwright

La configuración debe dejar evidencia cuando algo falla. La guía de CI de Playwright recomienda workers: 1 en CI para priorizar estabilidad y reproducibilidad.

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

export default defineConfig({
  testDir: './tests/e2e',
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  use: {
    baseURL: process.env.BASE_URL ?? 'http://127.0.0.1:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
});

trace: 'on-first-retry' permite ver qué estaba visible, qué clic falló y qué aserción agotó el tiempo. Con ese material, Claude Code puede investigar mucho mejor que con un error genérico de CI.

GitHub Actions para CI

En CI, muchos fallos vienen de navegadores o dependencias Linux, no de la prueba. Empieza con una configuración simple y añade caché o particionado solo cuando haga falta.

# .github/workflows/playwright.yml
name: Playwright Tests

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v6
        with:
          node-version: lts/*
      - name: Install dependencies
        run: npm ci
      - name: Install Playwright browsers
        run: npx playwright install --with-deps
      - name: Run E2E tests
        run: npx playwright test
      - uses: actions/upload-artifact@v5
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

Si pruebas una URL de vista previa, pasa BASE_URL al flujo. Si la aplicación debe arrancar dentro de CI, añade webServer en playwright.config.ts.

Casos de uso que sí merecen E2E

CasoPor qué probarlo con E2EExpectativas clave
Inicio de sesión a panelUne autenticación, redirección y permisosTítulo, estado de usuario, salida, error
Compra o formulario de consultaAfecta ingresos o leadsNúmero de orden, confirmación, no duplicar envío
Artículo hacia PDF o formaciónProtege monetización más allá de anuncioshref del CTA, evento, visibilidad móvil
Acción peligrosa de adminEvita borrados y escaladas de permisoModal, log de auditoría, control por rol
Páginas localizadasLa traducción rompe enlaces con frecuenciaURL con locale, texto natural, enlaces externos

Entrega esta tabla a Claude Code antes de pedir archivos. Así evita crear pruebas que solo pulsan botones sin demostrar nada importante.

Fallos frecuentes

Evita waitForTimeout(3000). Las esperas fijas pasan en una máquina y fallan en CI. Espera el estado real: elemento visible, URL cambiada o mensaje de estado.

Evita selectores como .btn-primary:nth-child(2). Un cambio de diseño los rompe. Prioriza role, label, text y data-testid estable.

No compartas estado entre pruebas. El carrito, Cookie o registro de base de datos creado por una prueba no debe ser requisito para la siguiente.

No ignores la diferencia entre headed y headless. Fuentes, GPU, scroll, portapapeles, subida de archivos y viewport pueden cambiar en CI. Usa headless en CI y --headed o --ui para investigar.

No llames pagos, correos, anuncios o CRM reales en cada ejecución. Usa modo de prueba, mocks o pruebas de contrato de API.

Playwright o Cypress

AspectoPlaywrightCypress
NavegadoresChromium, Firefox, WebKitFamilia Chrome, Firefox, Edge
ParalelizaciónIncluida en Playwright TestSuele diseñarse con Dashboard
Varias pestañas y contextosSoporte fuerteMás limitado
DepuraciónTrace Viewer, UI mode, HTML reportGUI interactiva muy amigable
Mejor ajusteCI, varios navegadores, auth aisladaEquipos frontend con Cypress existente

Cypress sigue siendo una buena opción. Si ya tienes cobertura, no migres por moda. Añade Playwright a un flujo crítico y compara flakiness, calidad de trace y tiempo de CI. Confirma siempre con la documentación oficial de Playwright y Cypress.

CTA de monetización y resultado

E2E también protege ingresos. Un enlace de consulta, una tarjeta de producto, un formulario gratuito o un CTA de formación roto puede costar más que un bug visual. Para empezar solo, usa la chuleta gratuita de Claude Code. Para prompts reutilizables, revisa productos y plantillas. Para equipos que necesitan CI, CLAUDE.md, reglas de revisión y responsabilidad de E2E, usa formación y consultoría de Claude Code.

Probé el ejemplo extrayendo el spec y la configuración como fragmentos TypeScript para validación de sintaxis. El hábito más útil fue nombrar la expectativa de negocio en la prueba: mantener el CTA de formación. En un proyecto real, empieza con tres pruebas: login, compra o lead, y CTA del artículo. Amplía después de estabilizarlas en CI.

#Claude Code #pruebas E2E #Playwright #Cypress #CI
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.