Envolver una aserción de Playwright en try/catch silencia el fallo: el test pasa cuando no debería, y el bug llega a producción. Esta guía cubre cuándo el manejo de errores pertenece al código de tests (fallos de setup, lógica de reintentos, limpieza) y cuándo no, más finally para garantizar el teardown, mensajes throw personalizados que reemplazan los errores genéricos de aserciones, y los patrones async que evitan que los rechazos no manejados oculten fallos en los tests.

Lo básico: try/catch

Un bloque try/catch permite ejecutar código y manejar errores sin crashear el programa:

try {
  // Código que podría lanzar un error
  const resultado = JSON.parse('json inválido');
} catch (error) {
  // Esto corre si el bloque try lanza un error
  console.log('El parseo falló:', error.message);
}

Sin try/catch, JSON.parse('json inválido') crashearía el script con un error no manejado. Con try/catch, lo manejas de forma controlada.

La variable error en el bloque catch es el objeto Error, que tiene error.message (descripción legible para humanos), error.name (tipo de error: 'SyntaxError', 'TypeError', etc.) y error.stack (stack trace completo).

El bloque finally

finally corre sin importar si hubo un error o no. Es útil para limpieza:

test('carga de archivo con limpieza', async ({ page }) => {
  const archivoTemp = await createTempFile();

  try {
    await page.setInputFiles('[data-testid="upload"]', archivoTemp);
    await expect(page.locator('[data-testid="upload-success"]')).toBeVisible();
  } catch (error) {
    console.error('El test de carga falló:', error.message);
    throw error; // Re-lanzar para que el test siga fallando
  } finally {
    await deleteTempFile(archivoTemp); // Siempre limpia, incluso si hay fallo
  }
});

finally siempre corre. Es valioso para el código de limpieza de tests.

async/await y errores

En Playwright, casi todo es async. Los errores en código async funcionan igual: solo necesitas await en el lugar correcto:

// Sin try/catch: rechazo no manejado = el test falla con un error feo
async function getUserFromApi() {
  const response = await fetch('/api/user/999');
  const data = await response.json();
  return data;
}

// Con try/catch: rechazo manejado = vos controlás el mensaje de error
async function getUserFromApi() {
  try {
    const response = await fetch('/api/user/999');
    if (!response.ok) {
      throw new Error(`Error de API: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('No se pudo obtener el usuario:', error.message);
    throw error; // Re-lanzar si querés que el llamador también lo maneje
  }
}

Lanzar tus propios errores

Puedes lanzar errores intencionalmente para señalar un fallo:

function validateEmail(email: string): void {
  if (!email.includes('@')) {
    throw new Error(`Formato de email inválido: "${email}"`);
  }
  if (email.length > 254) {
    throw new Error(`Email demasiado largo: ${email.length} caracteres (máximo 254)`);
  }
}

try {
  validateEmail('no-es-un-email');
} catch (error) {
  console.log(error.message); // 'Formato de email inválido: "no-es-un-email"'
}

En los tests, lanzar errores es cómo fallas con un mensaje específico en lugar de un fallo genérico de aserción:

async function getAuthToken(request: APIRequestContext): Promise<string> {
  const response = await request.post('/api/auth/login', {
    data: { email: 'test@test.com', password: 'PassTest1' },
  });

  if (response.status() !== 200) {
    throw new Error(`Login fallido: ${response.status()}. No se puede continuar con los tests`);
  }

  const { token } = await response.json();
  if (!token) {
    throw new Error('La respuesta del login no tiene el campo token');
  }

  return token;
}

Tipos de errores

JavaScript tiene varios tipos de error integrados:

| Tipo de error | Cuándo aparece |

|--------------|----------------|

| Error | Error genérico (clase base) |

| TypeError | Tipo incorrecto: null.algo, llamar algo que no es función |

| SyntaxError | JavaScript inválido o parseo de JSON |

| RangeError | Valor fuera de rango: new Array(-1) |

| ReferenceError | La variable no existe |

Puedes verificar el tipo para manejar errores distintos de forma diferente:

try {
  await doSomething();
} catch (error) {
  if (error instanceof TypeError) {
    console.log('Error de tipo: verificá los tipos de tus datos');
  } else if (error instanceof SyntaxError) {
    console.log('Error de sintaxis: JSON inválido o similar');
  } else {
    throw error; // Error desconocido: re-lanzarlo
  }
}

Patrones comunes en tests de Playwright

1. Reintentar una operación ante un fallo

async function waitForApiReady(request: APIRequestContext, maxIntentos = 5): Promise<void> {
  for (let i = 0; i < maxIntentos; i++) {
    try {
      const response = await request.get('/api/health');
      if (response.status() === 200) return;
    } catch (error) {
      // La API todavía no está lista, reintentar
    }
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
  throw new Error(`La API no está lista después de ${maxIntentos} intentos`);
}

2. Recolectar errores sin detener la ejecución

const errores: string[] = [];

for (const email of emailsDePrueba) {
  try {
    await validateEmailField(page, email);
  } catch (error) {
    errores.push(`${email}: ${error.message}`);
  }
}

if (errores.length > 0) {
  throw new Error(`Fallos de validación:\n${errores.join('\n')}`);
}

3. Clase de error personalizada para código más limpio

class TestDataError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'TestDataError';
  }
}

// Ahora podés distinguir errores de configuración de datos de prueba
// de errores de aserción del test
try {
  const usuario = await createTestUser(request);
} catch (error) {
  if (error instanceof TestDataError) {
    console.error('El setup del test falló: saltando el test');
    test.skip();
  }
  throw error;
}

Errores en Playwright: lo que vas a ver

TimeoutError

El error más común en Playwright. Un locator no se volvió visible a tiempo:

TimeoutError: locator.click: Timeout 30000ms exceeded.
Locator: [data-testid="submit-button"]

En la mayoría de los casos no es un error de JavaScript que necesites capturar: Playwright lo maneja y falla el test con un mensaje claro. Pero si querés manejar un timeout de forma controlada (intentar otra cosa si un elemento no aparece), podés hacerlo:

try {
  await page.locator('[data-testid="popup"]').click({ timeout: 2000 });
} catch (error) {
  // El popup no apareció en 2 segundos: está bien
  // Continuar el test sin interactuar con el popup
}

Rechazo de promesa no manejado

Si llamás una función async sin await y lanza un error, puede ser un "unhandled rejection": no falla el test inmediatamente y el mensaje de error puede ser confuso.

// Mal: sin await, el error puede silenciarse
page.goto('/admin');  // Si falla, el test podría pasar con un estado incorrecto

// Bien: el error aparece inmediatamente
await page.goto('/admin');

Siempre pon await a las operaciones async en los tests.

Cuándo NO usar try/catch en los tests

No uses try/catch para ocultar fallos de aserciones. Si una aserción falla, el test debe fallar:

// Mal: silencia el fallo de la aserción, el test "pasa" cuando no debería
try {
  await expect(page.locator('[data-testid="success"]')).toBeVisible();
} catch (error) {
  console.log('El elemento no es visible pero continuamos...');
}

// Bien: dejá que los fallos de aserciones fallen el test
await expect(page.locator('[data-testid="success"]')).toBeVisible();

try/catch en los tests sirve para código de setup y teardown donde el fallo no debería enmascarar el test real, resiliencia intencional (lógica de reintentos, interacciones opcionales) y creación de datos de prueba donde quieres un mensaje de error claro y personalizado.

No sirve para silenciar aserciones.

Resumen rápido

  • try/catch maneja errores: el código en try corre, catch maneja cualquier error lanzado
  • finally siempre corre: úsalo para limpieza
  • Siempre pon await a las operaciones async: los fallos async sin await son difíciles de depurar
  • throw intencionalmente cuando quieres un mensaje de error específico
  • No captures fallos de aserciones: deben fallar el test
  • instanceof permite verificar el tipo de error y manejar distintos errores de forma diferente
→ See also: JavaScript para QA Engineers: El Mínimo que Necesitas para Empezar a Automatizar | Async/Await en Español Sencillo (para Testers que se Confunden con las Promesas) | Cómo Leer los Mensajes de Error de Playwright