TL;DR
- Playwright es el framework de automatización de navegadores de Microsoft con auto-wait y assertions incorporados
- Soporta Chromium, Firefox y WebKit con una sola API
- TypeScript-first con excelente soporte IDE y generación de código
- Ejecución paralela incluida — ejecuta tests más rápido que Selenium o Cypress
- Trace viewer y grabación de video para depurar tests fallidos
Ideal para: Equipos que quieren herramientas modernas, soporte TypeScript y ejecución paralela rápida Omite si: Necesitas Safari real en dispositivos o tienes gran infraestructura Selenium existente Tiempo de lectura: 15 minutos
Tus tests de Selenium tardan 45 minutos. Tus tests de Cypress no pueden correr en paralelo sin pagar por su nube. Tus testers pasan horas depurando waits flaky.
Playwright resuelve estos problemas. Auto-wait elimina problemas de timing. La ejecución paralela es gratis e incluida. El trace viewer muestra exactamente qué pasó cuando un test falló.
¿Qué es Playwright?
Playwright es un framework de automatización de navegadores open-source de Microsoft. Controla Chromium, Firefox y WebKit a través de una API unificada.
Características clave:
- Auto-wait — espera a que los elementos sean accionables antes de interactuar
- Web-first assertions — lógica de reintentos incorporada para assertions
- Ejecución paralela — ejecuta tests en múltiples workers por defecto
- Trace viewer — depuración con viaje en el tiempo para tests fallidos
- Codegen — genera tests grabando acciones del navegador
Instalación y Configuración
Crear Nuevo Proyecto
# Crear nuevo proyecto Playwright
npm init playwright@latest
# Responde las preguntas:
# - ¿TypeScript o JavaScript? → TypeScript
# - ¿Dónde poner tests? → tests
# - ¿Agregar GitHub Actions? → Yes
# - ¿Instalar navegadores? → Yes
Estructura del Proyecto
my-project/
├── tests/
│ └── example.spec.ts
├── playwright.config.ts
├── package.json
└── .github/
└── workflows/
└── playwright.yml
Configuración
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html', { open: 'never' }],
['list']
],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-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'] } },
],
});
Escribiendo Tu Primer Test
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test('user can login with valid credentials', async ({ page }) => {
// Navegar
await page.goto('/login');
// Llenar formulario
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password123');
// Enviar
await page.getByRole('button', { name: 'Sign In' }).click();
// Assertions
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome back')).toBeVisible();
});
test('shows error for invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('wrong@example.com');
await page.getByLabel('Password').fill('wrongpass');
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page.getByText('Invalid credentials')).toBeVisible();
await expect(page).toHaveURL('/login');
});
Ejecutando Tests
# Ejecutar todos los tests
npx playwright test
# Ejecutar archivo específico
npx playwright test tests/login.spec.ts
# Ejecutar en modo headed (ver navegador)
npx playwright test --headed
# Ejecutar navegador específico
npx playwright test --project=chromium
# Modo debug
npx playwright test --debug
# Modo UI (interactivo)
npx playwright test --ui
Localizadores: Encontrando Elementos
Playwright recomienda localizadores orientados al usuario sobre selectores CSS/XPath.
Localizadores Recomendados
// Por role (basado en accesibilidad)
page.getByRole('button', { name: 'Submit' })
page.getByRole('link', { name: 'Home' })
page.getByRole('textbox', { name: 'Email' })
page.getByRole('checkbox', { name: 'Remember me' })
// Por label (inputs de formulario)
page.getByLabel('Email')
page.getByLabel('Password')
// Por placeholder
page.getByPlaceholder('Search...')
// Por texto
page.getByText('Welcome')
page.getByText('Welcome', { exact: true })
// Por test ID (cuando otras opciones fallan)
page.getByTestId('submit-button')
CSS y XPath (Cuando Se Necesitan)
// Selector CSS
page.locator('button.primary')
page.locator('[data-testid="submit"]')
page.locator('#login-form input[type="email"]')
// XPath
page.locator('xpath=//button[contains(text(), "Submit")]')
// Encadenando localizadores
page.locator('.card').filter({ hasText: 'Premium' }).getByRole('button')
Assertions
Los assertions de Playwright reintentan automáticamente hasta el timeout.
Assertions Comunes
import { test, expect } from '@playwright/test';
test('ejemplos de assertions', async ({ page }) => {
await page.goto('/dashboard');
// Visibilidad
await expect(page.getByText('Welcome')).toBeVisible();
await expect(page.getByText('Loading')).toBeHidden();
// Contenido de texto
await expect(page.getByRole('heading')).toHaveText('Dashboard');
await expect(page.getByRole('heading')).toContainText('Dash');
// Atributos
await expect(page.getByRole('button')).toBeEnabled();
await expect(page.getByRole('button')).toBeDisabled();
await expect(page.getByRole('checkbox')).toBeChecked();
// URL y title
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveURL(/dashboard/);
await expect(page).toHaveTitle('My App - Dashboard');
// Cantidad
await expect(page.getByRole('listitem')).toHaveCount(5);
// Valor
await expect(page.getByLabel('Email')).toHaveValue('user@example.com');
});
Page Object Model
// pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign In' });
this.errorMessage = page.getByRole('alert');
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async expectError(message: string) {
await expect(this.errorMessage).toContainText(message);
}
}
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
test.describe('Login', () => {
test('successful login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL('/dashboard');
});
test('invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('wrong@example.com', 'wrong');
await loginPage.expectError('Invalid credentials');
});
});
Intercepción de Red
test('mockear respuesta API', async ({ page }) => {
// Mockear API
await page.route('/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Mock User', email: 'mock@example.com' }
])
});
});
await page.goto('/users');
await expect(page.getByText('Mock User')).toBeVisible();
});
test('bloquear requests', async ({ page }) => {
// Bloquear analytics
await page.route('**/*google-analytics*', route => route.abort());
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
await page.goto('/');
});
Depuración
Trace Viewer
# Habilitar traces
npx playwright test --trace on
# Ver traces
npx playwright show-trace trace.zip
Modo Debug
# Paso a paso por el test
npx playwright test --debug
# Pausa en punto específico
await page.pause();
Integración CI/CD
# .github/workflows/playwright.yml
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Desarrollo Asistido por IA con Playwright
Las herramientas de IA se integran bien con la API legible de Playwright.
Lo que la IA hace bien:
- Generar tests desde user stories o requisitos
- Convertir tests de Selenium/Cypress a Playwright
- Escribir clases Page Object desde estructura HTML
- Crear assertions para validación de datos compleja
- Explicar métodos y patrones del API de Playwright
Lo que aún necesita humanos:
- Decisiones de estrategia y cobertura de tests
- Depurar fallos visuales o relacionados con timing
- Elegir entre estrategias de localizadores
- Optimización de performance para test suites grandes
FAQ
¿Es Playwright mejor que Selenium?
Playwright ofrece varias ventajas: auto-wait elimina la mayoría de problemas de timing, la ejecución es más rápida gracias a la comunicación por protocolo de navegador, y la API es más moderna. Selenium tiene soporte más amplio de navegadores y mayor comunidad. Para proyectos nuevos, Playwright suele ser la mejor opción.
¿Es Playwright gratis?
Sí, completamente. Playwright es open-source bajo licencia Apache 2.0. A diferencia de Cypress, no hay planes pagos. Ejecución paralela, trace viewer, grabación de video — todo gratis. El único costo es tu propia infraestructura CI.
¿Puede Playwright testear apps móviles?
Playwright testea navegadores web móviles mediante emulación de dispositivos — simula viewports de iPhone, Android y tablets. Para apps móviles nativas (apps iOS/Android de tiendas), necesitas Appium o herramientas específicas de plataforma.
¿Qué lenguajes soporta Playwright?
Playwright soporta oficialmente TypeScript, JavaScript, Python, Java y C#. TypeScript/JavaScript tienen más funciones y mejor documentación. Python es excelente para equipos que ya usan pytest.
Cuándo Elegir Playwright
Elige Playwright cuando:
- Empiezas un nuevo proyecto de automatización
- El equipo usa TypeScript/JavaScript
- Necesitas ejecución paralela rápida
- Quieres herramientas modernas de depuración (trace viewer)
- Testing en Chromium, Firefox, WebKit
Considera alternativas cuando:
- Necesitas Safari real en macOS (Selenium + Safari)
- Gran infraestructura Selenium existente
- El equipo prefiere herramientas Python/Java-first
Recursos Oficiales
Ver También
- Playwright: Guía Completa - Patrones avanzados y mejores prácticas
- Cypress vs Playwright - Comparación detallada para elegir la herramienta correcta
- Selenium Tutorial - Cuando Selenium sigue siendo la opción correcta
- Cypress Deep Dive - Profundización en Cypress
