Cuando un test de Playwright falla, la mayoría de los ingenieros va directo a la línea del stack trace y lee la parte incorrecta primero. El call log en el medio del mensaje de error es donde está el diagnóstico real: muestra qué condición estaba sondeando Playwright y qué observó, indicándote si el elemento faltaba, estaba oculto, deshabilitado o cubierto por un overlay. Esta guía explica cómo leer las tres partes de un error de Playwright, qué significan los tipos de error más comunes, y el flujo de depuración de cuatro pasos que resuelve la mayoría de los fallos.

La anatomía de un error de Playwright

Error: locator.click: Timeout 30000ms exceeded.
Call log:
  - waiting for getByRole('button', { name: 'Submit' })

    waiting for element to be visible, enabled and stable
    element is not visible - waiting...
    element is not visible - waiting...
    element is not visible - waiting...

  at Object.<anonymous> (tests/checkout.spec.ts:45:40)

Tres partes:

1. El tipo de error y el mensaje: locator.click: Timeout 30000ms exceeded — qué acción falló y qué expiró

2. Call log: qué estaba haciendo y observando Playwright mientras esperaba

3. Stack trace: dónde en el código del test ocurrió

Lelos en ese orden: tipo de error primero, call log segundo, stack trace tercero. El call log es el que más información da.

Errores comunes y qué significan

Timeout superado

Error: locator.click: Timeout 30000ms exceeded.

Playwright no pudo completar la acción dentro del timeout. El call log te dice por qué. Causas comunes:

  • El elemento no existe en el DOM
  • El elemento existe pero está oculto (display: none, visibility: hidden, opacity: 0)
  • El elemento existe y es visible pero está deshabilitado
  • El elemento está cubierto por otro elemento (overlay, modal, notificación toast)
  • La navegación de página no se completó antes de la siguiente acción

Lee el call log. Lista qué condición no se cumplió (element is not visible, element is not stable, element is disabled). Ese es el problema real.

Violación de modo estricto

Error: strict mode violation: getByRole('button', { name: 'OK' }) resolved to 2 elements

Tu locator coincidió con más de un elemento. Playwright requiere que los locators sean únicos salvo que lo indiques explícitamente.

Sé más específico. Agrega un filtro de contexto:

// Demasiado amplio
page.getByRole('button', { name: 'OK' })

// Más específico — limitado al modal
page.getByRole('dialog').getByRole('button', { name: 'OK' })

// O usá nth si el orden es estable
page.getByRole('button', { name: 'OK' }).first()

Locator no encontrado

Error: locator.fill: Error: strict mode violation: getByLabel('Email') resolved to 0 elements

Ningún elemento coincidió. El locator está mal, el elemento aún no está en la página, o estás en la página incorrecta.

Verifica el locator. Abre el test con el flag --debug para inspeccionar el estado de la página:

npx playwright test --debug tests/login.spec.ts

Timeout de navegación

Error: page.goto: Timeout 30000ms exceeded.
call log:
  navigating to "http://localhost:3000/login", waiting until "load"

La página no terminó de cargar. El servidor puede ser lento o no estar corriendo, la opción waitUntil puede estar configurada con un criterio muy estricto ('networkidle' en una página con polling en background), o un recurso (imagen, script) puede estar bloqueando el evento load.

Verifica si el servidor está corriendo. Prueba waitUntil: 'domcontentloaded' si no necesitas esperar todos los recursos. Usa la pestaña Network en Trace Viewer para ver qué está bloqueando.

Timeout de expect

Error: expect(locator).toHaveText() with timeout 5000ms
Received string: ""
Expected string: "Orden confirmada"

La aserción no pasó dentro del timeout. El elemento existe pero tiene el contenido incorrecto (o todavía no tiene contenido).

¿El contenido realmente está cargando? Agrega un timeout más largo temporalmente para descartar un problema de timing: await expect(el).toHaveText('Orden confirmada', { timeout: 15000 }). Si pasa con más tiempo, tienes un problema de performance, no de selector.

Contexto de ejecución destruido

Error: Execution context was destroyed, most likely because of a navigation.

Playwright tenía una referencia a un elemento de la página cuando esta navegó a otra. Causa común: hiciste const text = page.getByText('foo') y luego navegaste antes de evaluarlo.

Evaluá los locators antes de la navegación:

// Roto: locator evaluado después de la navegación
const text = page.getByText('Confirmación');
await page.getByRole('button', { name: 'Enviar' }).click(); // dispara navegación
await expect(text).toBeVisible(); // la página ya no existe

// Corregido: verificar antes de que se dispare la navegación, o volver a consultar después
await page.getByRole('button', { name: 'Enviar' }).click();
await expect(page).toHaveURL('/confirmacion');
await expect(page.getByText('Orden confirmada')).toBeVisible();

Target cerrado

Error: Target page, context or browser has been closed

El contexto del navegador se cerró antes de que el test terminara. Suele ocurrir cuando un test cierra el contexto manualmente en el lugar incorrecto, cuando un bloque beforeAll cerró un contexto que afterAll u otros tests todavía necesitan, o cuando un test falló y el código de limpieza se ejecutó mientras otras cosas todavía estaban corriendo.

Errores de red

Error: net::ERR_CONNECTION_REFUSED at http://localhost:3000

El servidor no está corriendo. Verifica que el servidor de desarrollo esté iniciado antes de los tests, que el baseURL sea correcto y que el puerto sea correcto.

En CI, agrega un paso para iniciar el servidor antes de que corra Playwright, o usá webServer en la config:

// playwright.config.ts
webServer: {
  command: 'npm run start',
  url: 'http://localhost:3000',
  reuseExistingServer: !process.env.CI,
},

Flujo de depuración

Paso 1: Lee el tipo de error y el call log. El call log te dice qué estaba esperando Playwright y qué observó. Paso 2: Abre Trace Viewer. npx playwright show-trace test-results/.../trace.zip: el snapshot del DOM te muestra exactamente qué había en la página cuando ocurrió el fallo. Paso 3: Ejecuta en modo debug (--debug) para recorrer el test paso a paso de forma interactiva. Paso 4: Usa page.pause() para congelar el test en un punto específico e inspeccionar manualmente.

await page.goto('/checkout');
await page.pause(); // El test se congela acá y abre el Inspector
await page.getByRole('button', { name: 'Pagar' }).click();

El Inspector abre un navegador con la barra de herramientas de Playwright: puedes interactuar con la página, ejecutar consultas de locators y ver las acciones en tiempo real.

→ See also: Playwright Trace Viewer: Depura Tests Fallidos Como un Pro | Depurando Tests Inestables: Una Guía Práctica | Assertions en Playwright: La Guía Completa