← Volver al Blog Automatización QA

Playwright Tutorial: guía completa para automatización web moderna

Nestor Alonso · marzo 21, 2026
Playwright Tutorial: guía completa para automatización web moderna

Playwright se ha convertido en una de las herramientas más fuertes para automatización web moderna porque no sólo permite interactuar con el navegador, sino que además incorpora un test runner, assertions, aislamiento entre pruebas, paralelización y tooling de depuración en un mismo stack. La documentación oficial lo presenta como un framework de testing end-to-end para aplicaciones web modernas, con soporte para Chromium, Firefox y WebKit, ejecución local o en CI, modo headless o headed, e incluso emulación móvil nativa.

En otras palabras: con Playwright no montas sólo “scripts de navegador”, sino una base real para pruebas E2E mantenibles. Y para la mayoría de proyectos de testing end-to-end, la propia documentación recomienda usar @playwright/test en lugar de la librería playwright a secas, porque Playwright Test ya trae la experiencia completa de ejecución, reportes y configuración. Si vienes de otros ecosistemas, notarás que es una alternativa muy sólida a lo que ofrecen herramientas como Cypress.

En esta guía vas a aprender cómo instalar Playwright tutorial paso a paso, cómo escribir pruebas robustas, qué tipo de locators usar, cómo reutilizar autenticación, cómo combinar UI y API testing, cómo depurar fallos con Trace Viewer y cómo dejar una suite lista para CI. Todo el enfoque está orientado a calidad técnica y a reducir el clásico problema de tests frágiles o flaky. Además, exploraremos cómo la QA con IA está transformando la forma en que escribimos y mantenemos estos scripts.

Por qué Playwright destaca frente a otras opciones

Uno de los puntos más importantes de Playwright es que los playwright locators son el centro del auto-waiting y de la capacidad de reintento. Eso significa que la herramienta espera automáticamente a que el elemento esté en condiciones de ser interactuado, en lugar de obligarte a llenar tu suite de pausas manuales y sleeps innecesarios. Para casos donde los selectores de texto o rol no son suficientes, siempre puedes apoyarte en XPath para localizar elementos complejos basándote en la estructura del DOM.

Ese diseño reduce una gran parte de la fragilidad típica en automatización web. A esto se suman las web-first assertions, que esperan y reintentan hasta que el estado esperado se cumple, en lugar de hacer verificaciones prematuras. La documentación oficial de Playwright insiste precisamente en esta filosofía: acciones sobre la UI, más expectativas declarativas y menos sincronización manual.

Cuándo conviene usar Playwright

Playwright encaja especialmente bien cuando necesitas una suite E2E moderna con buen soporte multi-browser, debugging visual, integración con CI y una experiencia sólida en playwright typescript o JavaScript. También es muy útil cuando quieres combinar pruebas de interfaz con preparación de datos por API o mocking de red sin salir del mismo ecosistema.

Si lo que buscas es un stack moderno, rápido de adoptar y con menos fricción que un enfoque puramente WebDriver, Playwright suele ser una elección excelente. Esa valoración es una inferencia práctica basada en que Playwright empaqueta runner, assertions, aislamiento, tooling y soporte de navegación en una sola solución.

Instalación de Playwright paso a paso

La forma más rápida de empezar con Playwright es usar el inicializador oficial:

npm init playwright@latest

Ese flujo te crea la estructura básica del proyecto, un test de ejemplo y la configuración necesaria para empezar a ejecutar pruebas.

npx playwright test
npx playwright show-report

Estructura mínima recomendada del proyecto

Una estructura sencilla y mantenible puede verse así:

playwright-project/
├─ tests/
│  ├─ auth.setup.ts
│  ├─ home.spec.ts
│  ├─ tasks.spec.ts
├─ page-objects/
│  ├─ LoginPage.ts
│  ├─ DashboardPage.ts
├─ playwright.config.ts
├─ package.json
└─ tsconfig.json

Configuración inicial recomendada

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

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  reporter: [['html'], ['list']],
  use: {
    baseURL: 'https://tu-app.com',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    headless: true,
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
});

Tu primer test en Playwright

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

test('homepage shows main heading', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page.getByRole('heading')).toBeVisible();
});

Locators: la forma correcta de seleccionar elementos

Mal enfoque

await page.click('.btn-primary');
await page.fill('#email-input', 'user@example.com');
await page.click('div.card:nth-child(3) button');

Mejor enfoque

await page.getByLabel('Email').fill('user@example.com');
await page.getByRole('button', { name: 'Iniciar sesión' }).click();
await page.getByTestId('save-task').click();

Assertions web-first: menos flakiness, más estabilidad

En Playwright, lo correcto es apoyarte en assertions que esperen hasta que el estado deseado se materialice:

await expect(page.getByRole('status')).toHaveText(/guardado/i);
await expect(page).toHaveURL(/dashboard/);
await expect(page.getByTestId('task-item')).toHaveCount(3);

Por qué no deberías usar waitForTimeout() salvo casos excepcionales

Una prueba mejor escrita sería así:

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

test('user creates a task', async ({ page }) => {
  await page.goto('/tasks');

  await page.getByRole('button', { name: 'Nueva tarea' }).click();
  await page.getByLabel('Título').fill('Escribir mejor artículo sobre Playwright');
  await page.getByRole('button', { name: 'Guardar' }).click();

  await expect(
    page.getByRole('listitem').filter({ hasText: 'Escribir mejor artículo sobre Playwright' })
  ).toBeVisible();

  await expect(page.getByRole('status')).toHaveText(/guardado/i);
});

Autenticación reutilizable con storageState

Hacer login al inicio de cada test suele ser una mala idea. La recomendación oficial actual es crear un proyecto de setup para playwright auth.

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

export default defineConfig({
  testDir: './tests',
  use: {
    baseURL: 'https://tu-app.com',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'setup',
      testMatch: /.*\.setup\.ts/,
    },
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],
});

Test de autenticación

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(process.env.E2E_PASSWORD || '');
  await page.getByRole('button', { name: 'Iniciar sesión' }).click();

  await expect(page).toHaveURL(/dashboard/);

  await page.context().storageState({ path: authFile });
});

Fixtures y reutilización inteligente

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

type AppFixtures = {
  openDashboard: () => Promise;
};

export const test = base.extend({
  openDashboard: async ({ page }, use) => {
    await use(async () => {
      await page.goto('/dashboard');
      await expect(
        page.getByRole('heading', { name: /dashboard/i })
      ).toBeVisible();
    });
  },
});

export { expect };

Y luego en tu spec:

import { test, expect } from './fixtures/app';

test('dashboard displays user menu', async ({ page, openDashboard }) => {
  await openDashboard();
  await expect(page.getByRole('button', { name: /perfil/i })).toBeVisible();
});

Cuándo usar Page Object Model

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

export class LoginPage {
  readonly page: Page;
  readonly email: Locator;
  readonly password: Locator;
  readonly submit: Locator;

  constructor(page: Page) {
    this.page = page;
    this.email = page.getByLabel('Email');
    this.password = page.getByLabel('Password');
    this.submit = page.getByRole('button', { name: 'Iniciar sesión' });
  }

  async login(email: string, password: string) {
    await this.email.fill(email);
    await this.password.fill(password);
    await this.submit.click();
    await expect(this.page).toHaveURL(/dashboard/);
  }
}

API testing dentro del mismo stack

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

test('creates item by API and validates it in UI', async ({ page }) => {
  const response = await page.request.post('/api/tasks', {
    data: { title: 'Task created by API' },
  });

  await expect(response).toBeOK();

  await page.goto('/tasks');
  await expect(page.getByText('Task created by API')).toBeVisible();
});

Mocking de red para reducir dependencia externa

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

test('shows mocked user profile', async ({ page }) => {
  await page.route('**/api/profile', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        name: 'Néstor',
        role: 'Admin',
      }),
    });
  });

  await page.goto('/profile');

  await expect(page.getByText('Néstor')).toBeVisible();
  await expect(page.getByText('Admin')).toBeVisible();
});

Debugging real: UI Mode, reports y Trace Viewer

npx playwright test --ui
npx playwright show-report
use: {
  trace: 'on-first-retry',
  screenshot: 'only-on-failure',
  video: 'retain-on-failure',
}

CI con GitHub Actions

Para playwright ci, GitHub Actions es excelente.

name: Playwright Tests
on:
  push:
    branches: [main, master]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - 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/

Accesibilidad en Playwright

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

test('login form is accessible at basic interaction level', async ({ page }) => {
  await page.goto('/login');
  await expect(page.getByLabel('Email')).toBeVisible();
  await expect(page.getByLabel('Password')).toBeVisible();
  await expect(page.getByRole('button', { name: 'Iniciar sesión' })).toBeEnabled();
});

Playwright vs Selenium vs Cypress

Playwright vs Selenium: Selenium sigue siendo una referencia histórica en automatización. WebDriver conduce el navegador de forma nativa. Playwright suele destacar cuando buscas una combinación de runner integrado + multi-browser + tooling potente en un mismo stack.

Playwright vs Cypress: Cypress se enfoca en testing de aplicaciones que corren en navegador y ofrece su propia app. Playwright usa contextos de navegador aislados y no inyecta código dentro de la misma ventana de la app.

Buenas prácticas para una suite Playwright mantenible

  1. Usa @playwright/test para E2E
  2. Prioriza getByRole, getByLabel, getByText y getByTestId
  3. Evita waitForTimeout() salvo casos excepcionales
  4. Reutiliza autenticación con storageState
  5. Usa fixtures y, cuando tenga sentido, Page Object Model
  6. Activa trace, screenshots y vídeo sólo cuando aporten valor
  7. Combina UI testing con API testing y mocking

Conclusión

Playwright no destaca sólo porque automatiza navegadores. Destaca porque ofrece una forma moderna de construir una suite E2E completa: runner integrado, locators robustos, assertions reintentables, aislamiento por contexto, autenticación reutilizable, mocking, API testing, debugging visual, reportes y ejecución en CI. Todo eso está alineado con la filosofía oficial del framework y explica por qué se ha convertido en una de las opciones más sólidas para testing web moderno.

Pregunta Frecuente

¿Qué es Playwright? expand_more
Playwright es un framework de testing end-to-end para aplicaciones web modernas que incluye test runner, assertions, aislamiento, paralelización y tooling de depuración. Soporta Chromium, Firefox y WebKit.
¿Qué lenguaje usar con Playwright? expand_more
Playwright se usa mucho con TypeScript y JavaScript. Para E2E, la documentación oficial recomienda @playwright/test.
¿Qué son los locators en Playwright? expand_more
Son el mecanismo principal para encontrar elementos y forman el núcleo del auto-waiting y de la retry-ability de Playwright.
¿Cómo evitar tests flaky en Playwright? expand_more
Usando locators semánticos, assertions web-first, aislamiento por test, autenticación reutilizable y reduciendo sleeps manuales. Esa recomendación se apoya en las mejores prácticas, auto-waiting y modelo de browser contexts de la documentación oficial.
¿Playwright sirve también para API testing? expand_more
Sí. Playwright documenta soporte para Web API testing mediante APIRequestContext, lo que permite preparar datos, validar respuestas y combinar API con UI testing.
¿Cómo depurar errores en Playwright? expand_more
Puedes usar UI Mode, el reporte HTML y Trace Viewer. Playwright recomienda especialmente las trazas para investigar fallos en CI.