
Cypress estuvo bien, pero Playwright cambió las reglas del juego. Te explico por qué lo uso en todos mis proyectos y cómo empezar desde cero.
Si llevas tiempo en el mundo del frontend, probablemente hayas escuchado hablar de Cypress. Y sí, Cypress fue un gran salto respecto a Selenium. Pero desde que llegó Playwright, no he vuelto atrás.
Playwright es un framework de testing E2E (end-to-end) desarrollado por Microsoft. Lo que lo hace diferente es que no está construido sobre WebDriver — tiene su propio protocolo de comunicación con los navegadores, lo que lo hace significativamente más rápido y estable.
Soporta Chromium, Firefox y WebKit (Safari) desde una misma API. Eso significa que con un solo test verificas que tu aplicación funciona en los tres motores de navegador más importantes del mundo.
Con Cypress estaba bien, pero tenía fricciones constantes:
Playwright resuelve todos esos puntos. Y lo hace con una API que, una vez que la aprendes, se siente natural.
npm init playwright@latestEl wizard te pregunta si quieres TypeScript o JavaScript, dónde poner los tests y si quiere instalar los navegadores. En menos de dos minutos tienes todo listo.
La estructura que genera:
playwright.config.ts
tests/
example.spec.ts
tests-examples/
demo-todo-app.spec.ts
import { test, expect } from '@playwright/test'
test('la página de inicio carga correctamente', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page).toHaveTitle(/LHZ/)
await expect(page.getByRole('heading', { name: /software/i })).toBeVisible()
})Tres cosas que hay que notar aquí:
page.goto espera automáticamente a que la página cargueexpect tiene assertions específicos para el DOM — no necesitas cy.wait()async/await puro — sin sintaxis especial que aprenderEl mayor problema del testing E2E tradicional son los selectores frágiles. Los tests que seleccionan por clase CSS o ID se rompen con cualquier refactor.
Playwright promueve los locators semánticos:
// ❌ Frágil — se rompe si cambia el CSS
page.locator('.btn-primary')
// ✅ Robusto — selecciona por rol accesible
page.getByRole('button', { name: 'Enviar' })
// ✅ Robusto — selecciona por label del formulario
page.getByLabel('Email')
// ✅ Robusto — selecciona por texto visible
page.getByText('Contacto')
// ✅ Robusto — test ID explícito
page.getByTestId('submit-button')Un efecto secundario positivo: si tus locators semánticos fallan, tu app probablemente tiene problemas de accesibilidad.
Por defecto, Playwright corre los tests en paralelo entre archivos. Puedes configurar cuántos workers usar:
// playwright.config.ts
export default defineConfig({
workers: process.env.CI ? 2 : '50%',
})En CI usas 2 workers para no saturar la máquina. En local usas el 50% de los cores disponibles. Esto solo es posible porque Playwright tiene aislamiento real entre tests — cada test tiene su propio contexto de navegador.
npx playwright test --uiEsto abre una interfaz visual donde puedes:
Es el Devtools del testing E2E. Una vez que lo usas, debuggear tests con console.log parece prehistórico.
npx playwright codegen http://localhost:3000Playwright abre el navegador y graba cada acción que realizas, generando el código del test en tiempo real. No es perfecto para tests de producción, pero es una forma brutal de empezar un test complejo sin escribir desde cero.
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/Lo más importante de este workflow: si un test falla, sube el reporte como artifact. Puedes descargarlo y ver exactamente qué falló, con capturas de pantalla y video incluidos.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile', use: { ...devices['iPhone 14'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})El webServer levanta automáticamente Next.js antes de correr los tests. En CI siempre levanta uno nuevo; en local reutiliza el que ya tengas corriendo.
Los tests E2E no reemplazan los tests unitarios — los complementan.
Mi regla: los flujos que, si se rompen, impactan directamente en dinero o en la experiencia del usuario van a Playwright. Login, checkout, formularios de contacto, navegación principal.
El resto — tests unitarios con Vitest o Jest.
Playwright es hoy la herramienta de testing E2E más completa del ecosistema JavaScript. Rápido, estable, con soporte multi-navegador real y una DX que hace que escribir tests sea, si no divertido, al menos tolerable.
Si estás empezando un proyecto nuevo, ponlo desde el primer día. Si tienes un proyecto existente, empieza por los flujos más críticos y ve añadiendo cobertura gradualmente.