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