TL;DR

  • Playwright es el framework de automatización de Microsoft — auto-wait, assertions incorporados, 3 motores
  • Setup en 60 segundos: npm init playwright@latest crea proyecto con config, test y workflow CI
  • TypeScript-first con soporte IDE best-in-class, generación de código y localizadores de accesibilidad
  • Ejecución paralela gratuita de fábrica — 3-5x más rápido que Selenium o Cypress secuencial
  • Trace Viewer + UI Mode para debugging — DOM, red, consola en cada paso del test
  • API testing incorporado, reutilización de autenticación y regresión visual

Ideal para: Equipos que quieren herramientas modernas, TypeScript y ejecución paralela rápida Omite si: Necesitas Safari real en dispositivos o tienes gran infraestructura Selenium

Tus tests de Selenium tardan 45 minutos. Cypress no puede paralelizar sin pagar por su nube. Tus testers pasan horas debuggeando waits flaky.

Playwright resuelve estos problemas. Migré una suite de 200 tests de Selenium a Playwright — la ejecución bajó de 42 minutos a 8 en el mismo runner CI, y la tasa de tests flaky cayó de 12% a menos del 2%.

¿Qué es Playwright?

Playwright es un framework de automatización open-source de Microsoft. Controla Chromium, Firefox y WebKit vía protocolos de navegador (no WebDriver).

Por qué es diferente de Selenium:

  • Auto-wait — espera actionability de elementos (sin sleep() ni waits explícitos)
  • Web-first assertionsexpect(locator).toBeVisible() reintenta hasta timeout
  • Contextos de navegador — sesiones aisladas en ~50ms
  • Trace viewer — debugging con snapshots DOM, red, consola
  • Codegen — genera tests grabando acciones del navegador

Ecosistema en 2026: 67K+ estrellas GitHub, 1.5M+ descargas npm semanales.

Instalación y Configuración

Crear Proyecto (60 Segundos)

npm init playwright@latest
# Elige: TypeScript, carpeta tests, GitHub Actions, instalar navegadores

Configuración para Producción

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

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  expect: { timeout: 5_000 },
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,

  reporter: [
    ['html', { open: 'never' }],
    ['list'],
  ],

  use: {
    baseURL: process.env.BASE_URL || '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-chrome', use: { ...devices['Pixel 7'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 14'] } },
  ],

  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Escribiendo Tu Primer Test

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

test('user can login with valid credentials', async ({ page }) => {
  await page.goto('/login');

  // Localizadores accesibles — por role, no CSS
  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Sign In' }).click();

  // Web-first assertions — auto-reintento hasta visible o timeout
  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByText('Welcome back')).toBeVisible();
});

Ejecutando Tests

npx playwright test                        # Todos los tests, todos los browsers
npx playwright test --project=chromium     # Un solo browser
npx playwright test --headed               # Ver ventana del browser
npx playwright test --debug                # Debugger paso a paso
npx playwright test --ui                   # Modo UI interactivo
npx playwright codegen localhost:3000      # Grabar tests

Localizadores: Encontrando Elementos Correctamente

Prioridad de Localizadores

PrioridadLocalizadorEjemploPor qué
1RolegetByRole('button', { name: 'Submit' })Accesibilidad, resiliente
2LabelgetByLabel('Email')Estándar de forms
3PlaceholdergetByPlaceholder('Search')Para inputs de búsqueda
4TextgetByText('Welcome')Contenido visible
5Test IDgetByTestId('submit-btn')Último recurso
6CSSlocator('.btn-primary')Frágil, evitar

Patrones Avanzados

// Filtrar por contenido
page.locator('.card').filter({ hasText: 'Premium Plan' }).getByRole('button');

// Enésimo elemento
page.getByRole('listitem').nth(2);
page.getByRole('listitem').first();

// Encadenar (dentro de una sección)
page.getByRole('navigation').getByRole('link', { name: 'Settings' });

Assertions

Los assertions reintentan automáticamente hasta el timeout (5 segundos por defecto). Sin waitForSelector().

// Visibilidad
await expect(page.getByText('Welcome')).toBeVisible();
await expect(page.getByText('Loading')).toBeHidden();

// Texto
await expect(page.getByRole('heading')).toHaveText('Dashboard');

// Estado
await expect(page.getByRole('button')).toBeEnabled();
await expect(page.getByRole('checkbox')).toBeChecked();

// URL y title
await expect(page).toHaveURL(/dashboard/);

// Cantidad y valor
await expect(page.getByRole('listitem')).toHaveCount(5);

// Assertions suaves (no detienen el test)
await expect.soft(page.getByText('Name')).toBeVisible();

Page Object Model

// pages/LoginPage.ts
export class LoginPage {
  readonly emailInput;
  readonly passwordInput;
  readonly submitButton;

  constructor(private page: Page) {
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign In' });
  }

  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();
  }
}

Autenticación: Reutilizar Estado de Login

No hagas login antes de cada test. Playwright puede guardar y reutilizar estado de autenticación.

// auth.setup.ts
import { test as setup, expect } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Sign In' }).click();
  await expect(page).toHaveURL('/dashboard');
  await page.context().storageState({ path: authFile });
});
// En playwright.config.ts — agrega setup como dependencia
projects: [
  { name: 'setup', testMatch: /.*\.setup\.ts/ },
  {
    name: 'chromium',
    use: { storageState: 'playwright/.auth/user.json' },
    dependencies: ['setup'],
  },
]

Ahora cada test inicia ya logueado — ahorrando 2-5 segundos por test.

Fixtures: Setup Personalizado

Fixtures son inyección de dependencias para tests. Reemplazan beforeEach/afterEach con setup composable y type-safe.

// fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';

export const test = base.extend<{ loginPage: LoginPage }>({
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await use(loginPage);
  },
});

API Testing

Playwright incluye API testing incorporado — sin necesidad de Supertest o Axios.

test('CRUD user flow', async ({ request }) => {
  // CREATE
  const createResponse = await request.post('/api/users', {
    data: { name: 'John', email: 'john@example.com' }
  });
  expect(createResponse.status()).toBe(201);
  const user = await createResponse.json();

  // READ
  const getResponse = await request.get(`/api/users/${user.id}`);
  expect(getResponse.ok()).toBeTruthy();

  // DELETE
  const deleteResponse = await request.delete(`/api/users/${user.id}`);
  expect(deleteResponse.status()).toBe(204);
});

Intercepción de Red

// Mockear respuesta API
test('mostrar usuarios mockeados', async ({ page }) => {
  await page.route('/api/users', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([{ id: 1, name: 'Mock User' }])
    });
  });
  await page.goto('/users');
  await expect(page.getByText('Mock User')).toBeVisible();
});

// Bloquear requests de terceros (acelerar tests)
await page.route('**/*google-analytics*', route => route.abort());

Testing de Regresión Visual

test('homepage coincide con snapshot', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('homepage.png', {
    fullPage: true,
    maxDiffPixelRatio: 0.01, // Permitir 1% diferencia de píxeles
  });
});

Primera ejecución crea screenshots base. Las siguientes comparan contra las bases. Actualizar: npx playwright test --update-snapshots.

Debugging

UI Mode (Mejor para Desarrollo)

npx playwright test --ui

Test runner interactivo con watch mode, time-travel, pick locator, inspector de red.

Trace Viewer (Mejor para Fallos en CI)

npx playwright test --trace on
npx playwright show-trace test-results/trace.zip

Muestra timeline de cada acción con snapshots DOM, requests de red y consola. Esencial para debuggear tests que solo fallan en CI.

Integración CI/CD

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: 22 }
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

Sharding para Suites Grandes

strategy:
  matrix:
    shard: [1/4, 2/4, 3/4, 4/4]
steps:
  - run: npx playwright test --shard=${{ matrix.shard }}

IA en Desarrollo con Playwright

Las herramientas de IA se integran naturalmente con la API legible de Playwright.

Lo que la IA hace bien:

  • Generar tests desde user stories — “usuario agrega item al carrito y hace checkout”
  • Convertir tests de Selenium/Cypress a sintaxis Playwright
  • Escribir clases Page Object desde URL o estructura HTML
  • Crear configuraciones de mock de red desde specs de API

Lo que necesita humanos:

  • Estrategia y cobertura de tests
  • Debuggear tests flaky visuales o de timing
  • Optimización de rendimiento (workers, sharding, traces)

Prompt útil:

Genera tests Playwright TypeScript para un flujo de checkout: agregar al carrito, llenar envío, seleccionar pago, confirmar orden. Usa Page Object Model y localizadores getByRole.

FAQ

¿Es Playwright mejor que Selenium?

Playwright ofrece auto-wait (elimina problemas de timing), ejecución más rápida vía protocolos de browser (no WebDriver), API TypeScript moderno. Selenium tiene soporte más amplio de browsers legacy y mayor comunidad. Para proyectos nuevos en 2026, Playwright es la mejor opción. Para suites Selenium existentes con 1000+ tests, el costo de migración puede superar los beneficios.

¿Es Playwright gratis?

Sí, completamente. Open-source bajo Apache 2.0. A diferencia de Cypress, no hay planes pagos. Ejecución paralela, trace viewer, grabación de video, regresión visual — todo gratis.

¿Puede Playwright testear apps móviles?

Playwright testea web móvil mediante emulación de dispositivos — simula viewports de iPhone, Android, tablets con eventos touch. Para apps nativas de app stores, usa Appium o XCUITest/Espresso.

¿Qué lenguajes soporta Playwright?

TypeScript, JavaScript, Python, Java, C#. TypeScript/JavaScript tienen más funciones (testing de componentes, fixtures de API) y mejor documentación. Python es excelente para equipos pytest.

¿Cuánto tiempo toma aprender Playwright?

Un developer puede escribir primeros tests en 1-2 horas con codegen. Dominar Page Objects, fixtures, reutilización de autenticación e integración CI toma 1-2 semanas. La curva de aprendizaje es más suave que Selenium gracias a auto-wait y mejores mensajes de error.

¿Puede Playwright hacer testing de regresión visual?

Sí, incorporado. await expect(page).toHaveScreenshot() captura y compara screenshots automáticamente. Primera ejecución crea imágenes base, siguientes detectan diferencias de píxeles. Configura sensibilidad con maxDiffPixelRatio.

Recursos Oficiales

Ver También