El fixture request de Playwright hace llamadas HTTP sin abrir un navegador y completa en 50 a 200 milisegundos, frente a los 3 a 5 segundos de un test de UI. Está disponible junto con page en cualquier test de Playwright, lo que permite un patrón híbrido: crear datos de test vía API, verificarlos a través de la UI y limpiar vía API, todo en un único archivo de test sin herramientas adicionales. Este artículo cubre GET, POST, autenticación, testing de respuestas de error y los patrones combinados de API más UI que eliminan el setup inestable.

Por qué los tests de API pertenecen a tu suite de Playwright

El argumento para el testing de API no es reemplazar los tests de UI. Es testear las cosas correctas en el nivel correcto.

Un test de UI que crea un ítem de viaje pasa por el navegador, renderiza el formulario, llena los inputs, hace clic en enviar, espera que la página se actualice y luego verifica el resultado. Tarda 3 a 5 segundos y puede fallar por una docena de razones sin relación con la funcionalidad real: una animación lenta, un localizador cambiado, una solicitud de red inestable.

Un test de API que crea un ítem de viaje envía una solicitud HTTP y verifica la respuesta. Tarda 50 a 200 milisegundos y falla solo cuando la API misma está rota.

Usa tests de API para: validación de datos, reglas de autenticación, respuestas de error, lógica de negocio en el backend. Usa tests de UI para: flujos del usuario, renderizado visual, interacciones del frontend. Los dos tienen su lugar. El error es usar solo tests de UI cuando el bug está en la API.

El fixture request

Playwright expone un APIRequestContext a través del fixture request. Está disponible en cualquier test de la misma manera que page:

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

test('GET /api/items devuelve una lista', async ({ request }) => {
  const response = await request.get('https://lab.becomeqa.com/api/items');

  expect(response.status()).toBe(200);

  const items = await response.json();
  expect(items.length).toBeGreaterThan(0);
});

No se abre ningún navegador. No se carga ninguna página. Solo una solicitud HTTP y una respuesta. El fixture request maneja cookies, headers y la configuración de la base URL automáticamente.

Solicitudes GET y validación de la respuesta

El patrón más común en tests de API: llamar a un endpoint, verificar el estado y verificar la forma de los datos.

test('GET /api/items devuelve la estructura correcta', async ({ request }) => {
  const response = await request.get('https://lab.becomeqa.com/api/items');

  // Verificar el estado HTTP
  expect(response.status()).toBe(200);
  expect(response.ok()).toBeTruthy(); // true para códigos de estado 2xx

  // Parsear el body de la respuesta
  const items = await response.json();

  // Verificar el array
  expect(Array.isArray(items)).toBe(true);
  expect(items.length).toBeGreaterThan(0);

  // Verificar la forma del primer ítem
  const firstItem = items[0];
  expect(firstItem).toHaveProperty('id');
  expect(firstItem).toHaveProperty('destination');
  expect(firstItem).toHaveProperty('status');
});

response.ok() es un atajo para status >= 200 && status < 300. Úsalo cuando solo te importa que la solicitud tuvo éxito.

Para verificar headers de la respuesta:

const contentType = response.headers()['content-type'];
expect(contentType).toContain('application/json');

Solicitudes POST

Las solicitudes POST envían datos para crear un recurso. Pasa el body como JSON:

test('POST /api/items crea un nuevo ítem', async ({ request }) => {
  const response = await request.post('https://lab.becomeqa.com/api/items', {
    data: {
      destination: 'Tokio',
      status: 'planned',
      notes: 'Temporada de cerezos'
    }
  });

  expect(response.status()).toBe(201);

  const created = await response.json();
  expect(created).toHaveProperty('id');
  expect(created.destination).toBe('Tokio');
  expect(created.status).toBe('planned');
});

La opción data serializa automáticamente a JSON y configura el header Content-Type: application/json.

Para solicitudes codificadas como formulario, usa form en lugar de data:

const response = await request.post('/api/login', {
  form: {
    username: 'admin@becomeqa.com',
    password: 'testpass123'
  }
});

Autenticación

La mayoría de las APIs reales requieren autenticación. Hay dos patrones comunes.

Token Bearer en el header

test('GET autenticado devuelve datos del usuario', async ({ request }) => {
  // Primero, obtener un token
  const loginResponse = await request.post('https://lab.becomeqa.com/api/auth/login', {
    data: {
      username: 'admin@becomeqa.com',
      password: 'testpass123'
    }
  });

  const { token } = await loginResponse.json();

  // Usar el token en las solicitudes siguientes
  const response = await request.get('https://lab.becomeqa.com/api/items', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

  expect(response.status()).toBe(200);
});

Auth basada en cookies a través del login en la UI

Si tu app usa cookies para la autenticación, puedes iniciar sesión a través del navegador y reutilizar la sesión para llamadas a la API. El objeto page.request comparte cookies con el contexto del navegador:

test('llamada a la API con sesión del navegador', async ({ page, request }) => {
  // Iniciar sesión a través de la UI
  await page.goto('https://lab.becomeqa.com');
  await page.getByRole('button', { name: 'Login' }).click();
  await page.getByLabel('Username').fill('admin@becomeqa.com');
  await page.getByLabel('Password').fill('testpass123');
  await page.getByRole('button', { name: 'Submit' }).click();

  // Usar page.request: lleva la cookie de auth
  const response = await page.request.get('https://lab.becomeqa.com/api/items');
  expect(response.status()).toBe(200);
});

La diferencia es importante: request (el fixture) es un contexto independiente sin cookies del navegador. page.request comparte cookies con la sesión actual del navegador.

Testear respuestas de error

Las APIs deben devolver errores significativos. Testéalos de forma explícita.

test('GET /api/items/:id devuelve 404 para un id desconocido', async ({ request }) => {
  const response = await request.get('https://lab.becomeqa.com/api/items/id-inexistente-99999');

  expect(response.status()).toBe(404);

  const body = await response.json();
  expect(body).toHaveProperty('error');
});

test('POST /api/items devuelve 400 por campos obligatorios faltantes', async ({ request }) => {
  const response = await request.post('https://lab.becomeqa.com/api/items', {
    data: {
      // falta el campo obligatorio 'destination'
      status: 'planned'
    }
  });

  expect(response.status()).toBe(400);
});

test('el endpoint protegido devuelve 401 sin auth', async ({ request }) => {
  const response = await request.get('https://lab.becomeqa.com/api/items');
  // sin header Authorization
  expect(response.status()).toBe(401);
});

Estos tests verifican que la API maneja el input incorrecto correctamente, no solo el camino feliz.

Combinar tests de API y UI

Uno de los patrones más potentes: usar la API para preparar los datos de test y luego verificarlos a través de la UI. O al revés: interactuar a través de la UI y verificar a través de la API.

Setup vía API, verificación vía UI

test('el ítem creado aparece en la tabla de la UI', async ({ page, request }) => {
  // Crear ítem vía API: rápido y confiable
  const createResponse = await request.post('https://lab.becomeqa.com/api/items', {
    data: { destination: 'Lisboa', status: 'planned' }
  });
  const { id } = await createResponse.json();

  // Verificar que aparece en la UI
  await page.goto('https://lab.becomeqa.com');
  // ... pasos de login ...
  await expect(page.getByText('Lisboa')).toBeVisible();

  // Limpiar vía API después del test
  await request.delete(`https://lab.becomeqa.com/api/items/${id}`);
});

Este test es más rápido y más confiable. El setup vía UI es la fuente más común de inestabilidad en tests que no están testeando la UI en sí.

Acción vía UI, verificación vía API

test('eliminar un ítem vía UI lo quita de la API', async ({ page, request }) => {
  // Setup vía API
  const createResponse = await request.post('https://lab.becomeqa.com/api/items', {
    data: { destination: 'Oslo', status: 'planned' }
  });
  const { id } = await createResponse.json();

  // Acción vía UI
  await page.goto('https://lab.becomeqa.com');
  // ... login, encontrar ítem, hacer clic en eliminar ...

  // Verificar vía API
  const checkResponse = await request.get(`https://lab.becomeqa.com/api/items/${id}`);
  expect(checkResponse.status()).toBe(404);
});

Configurar una base URL para tests de API

Repetir la URL completa en cada test genera ruido. Configura baseURL en playwright.config.ts:

export default defineConfig({
  use: {
    baseURL: 'https://lab.becomeqa.com',
  },
});

Ahora puedes usar rutas relativas:

const response = await request.get('/api/items');
const response = await request.post('/api/items', { data: { ... } });

Para proyectos con URLs base separadas para UI y API, creá un fixture personalizado que configure el contexto de la API por separado.

Agregá extraHTTPHeaders a la configuración si tu API requiere un header común en cada solicitud (como una API key o un header X-App-Version personalizado). Configuralo una sola vez en la config en lugar de en cada test.

export default defineConfig({
  use: {
    baseURL: 'https://lab.becomeqa.com',
    extraHTTPHeaders: {
      'X-Api-Key': process.env.API_KEY || '',
    },
  },
});

Organizar los tests de API por separado

Mantenelos en su propia carpeta para poder ejecutarlos de forma independiente de los tests de UI:

tests/
  ui/
    login.spec.ts
    items.spec.ts
  api/
    items-api.spec.ts
    auth-api.spec.ts

# Ejecutar solo los tests de API: rápido, sin navegador
npx playwright test tests/api/

# Ejecutar solo los tests de UI
npx playwright test tests/ui/

Los tests de API corren significativamente más rápido que los de UI. Ejecutarlos por separado en CI permite obtener feedback rápido sobre bugs del backend antes de que termine el suite de UI más lento.

FAQ

¿Sigo necesitando Postman si uso Playwright para el testing de API?

Postman es útil para la exploración manual de la API: descubrir qué endpoints existen, qué parámetros aceptan, cómo se ven las respuestas. Una vez que sabés qué testear, escribe la versión automatizada en Playwright. Los dos tienen su lugar.

¿Debería cada endpoint de la API tener un test?

Enfócate en los endpoints que importan: autenticación, operaciones de datos principales y cualquier endpoint con lógica de validación compleja. Una API CRUD para ítems de viaje necesita tests para crear, leer, actualizar, eliminar y al menos los casos de error principales (404, 400, 401). No todas las combinaciones de inputs.

¿Cómo testeo las subidas de archivos vía API?

Usa la opción multipart:

const response = await request.post('/api/upload', {
  multipart: {
    file: {
      name: 'test.pdf',
      mimeType: 'application/pdf',
      buffer: Buffer.from('contenido pdf de prueba'),
    }
  }
});

¿Puedo usar el fixture request sin una page?

Sí. Los tests que solo usan request y no page corren sin abrir ningún navegador. Son tests HTTP puros y corren tan rápido como cualquier otro cliente HTTP.

→ See also: API Testing 101: Todo lo que Todo QA Engineer Necesita Saber en 2026 | Pruebas de API con el APIRequestContext de Playwright (Sin Postman) | Autenticación en Tests de API: Claves API, Tokens Bearer, OAuth2, JWT | Testing de API Avanzado con Playwright: Patrones para Proyectos Reales