Un 500 de la API de productos, un gateway lento que dispara un spinner de carga, un fallo de red que testea si el mensaje de error es útil: ninguno de estos escenarios es fácil de reproducir contra un backend real a demanda. page.route() intercepta cualquier petición que hace tu aplicación y te permite devolver route.fulfill() con cualquier status y body, retrasar y dejar pasar con route.continue(), o simular un fallo de red con route.abort(). Esta guía cubre los patrones principales: fixtures JSON, testing de estados de error, modificar respuestas reales con route.fetch(), bloquear scripts de terceros, y waitForRequest/waitForResponse para verificar qué envía realmente la app.

Por qué interceptar peticiones de red

Testear estados difíciles de reproducir

El servidor devuelve 500 (¿qué muestra la UI?), la API tarda 10 segundos (¿hay un spinner de carga?) o el gateway de pago está caído (¿el checkout muestra un error útil?).

Hacer los tests más rápidos y confiables

Mockea el backend para que los tests no dependan de datos reales, bloquea analytics, ads y scripts de tracking que ralentizan la carga de página, y evita rate limits en APIs externas.

Testear sin un backend

Desarrolla tests de UI antes de que la API esté construida y testea casos extremos difíciles de disparar en un sistema real.

Mocking básico de rutas

page.route() intercepta peticiones que coinciden con un patrón de URL:

test('muestra estado de carga', async ({ page }) => {
  // Interceptar y retrasar la API de productos
  await page.route('/api/products', async (route) => {
    await new Promise(resolve => setTimeout(resolve, 3000));  // 3 segundos de delay
    await route.continue();  // Luego dejar pasar la petición real
  });
  
  await page.goto('/products');
  
  // El spinner debería ser visible durante el delay
  await expect(page.getByTestId('loading-spinner')).toBeVisible();
  
  // Esperar a que carguen los productos
  await expect(page.getByTestId('product-card').first()).toBeVisible();
});

Devolver respuestas mockeadas

En lugar de reenviar al servidor real, devuelve datos falsos:

const mockUsers = [
  { id: 1, name: 'Alice', email: 'alice@test.com', role: 'admin' },
  { id: 2, name: 'Bob', email: 'bob@test.com', role: 'member' },
];

test('la tabla de usuarios muestra todos los usuarios', async ({ page }) => {
  await page.route('/api/users', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify(mockUsers),
    });
  });
  
  await page.goto('/admin/users');
  
  const rows = page.getByTestId('user-row');
  await expect(rows).toHaveCount(2);
  await expect(rows.first()).toContainText('Alice');
  await expect(rows.last()).toContainText('Bob');
});

Testear estados de error

test('muestra error cuando la API falla', async ({ page }) => {
  await page.route('/api/products', async (route) => {
    await route.fulfill({
      status: 500,
      contentType: 'application/json',
      body: JSON.stringify({ error: 'Internal Server Error' }),
    });
  });
  
  await page.goto('/products');
  
  await expect(page.getByTestId('error-message')).toBeVisible();
  await expect(page.getByTestId('error-message')).toContainText('Something went wrong');
  await expect(page.getByTestId('retry-button')).toBeVisible();
});

test('muestra estado vacío cuando no hay productos', async ({ page }) => {
  await page.route('/api/products', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([]),
    });
  });
  
  await page.goto('/products');
  
  await expect(page.getByTestId('empty-state')).toBeVisible();
  await expect(page.getByTestId('empty-state')).toContainText('No products found');
});

test('muestra mensaje de error de red', async ({ page }) => {
  await page.route('/api/products', async (route) => {
    await route.abort('failed');  // Simular fallo de red
  });
  
  await page.goto('/products');
  
  await expect(page.getByTestId('network-error')).toBeVisible();
});

Patrones de URL

page.route() soporta patrones glob y regex:

// URL exacta
await page.route('/api/users', handler);

// Wildcard
await page.route('/api/users/*', handler);  // /api/users/1, /api/users/abc
await page.route('/api/**', handler);        // Todas las rutas de la API

// Regex
await page.route(/\/api\/users\/\d+/, handler);  // /api/users/123

// Glob con query string
await page.route('/api/products?*', handler);  // /api/products?page=1&limit=10

Interceptar y modificar peticiones

Lee la petición real antes de decidir qué hacer:

test('usa el header de auth correcto', async ({ page }) => {
  let capturedAuthHeader = '';
  
  await page.route('/api/users', async (route) => {
    // Capturar el header real de la petición
    capturedAuthHeader = route.request().headers()['authorization'] || '';
    
    await route.fulfill({
      status: 200,
      body: JSON.stringify([]),
    });
  });
  
  // Hacer algo que dispare la petición
  await page.goto('/admin/users');
  
  expect(capturedAuthHeader).toMatch(/Bearer .+/);
});

test('envía el body de petición correcto', async ({ page }) => {
  let capturedBody = '';
  
  await page.route('/api/auth/login', async (route) => {
    capturedBody = route.request().postData() || '';
    
    await route.fulfill({
      status: 200,
      body: JSON.stringify({ token: 'fake-token', user: { id: 1 } }),
    });
  });
  
  await page.goto('/login');
  await page.fill('[data-testid="email"]', 'user@test.com');
  await page.fill('[data-testid="password"]', 'ValidPass1');
  await page.click('[data-testid="submit"]');
  
  const body = JSON.parse(capturedBody);
  expect(body.email).toBe('user@test.com');
  expect(body.password).toBe('ValidPass1');
});

Modificar respuestas reales

Intercepta una petición real y modifica su respuesta:

test('maneja roles de usuario adicionales correctamente', async ({ page }) => {
  await page.route('/api/users/1', async (route) => {
    // Dejar pasar la petición real
    const response = await route.fetch();
    const body = await response.json();
    
    // Modificar la respuesta
    body.role = 'super-admin';  // Este rol puede no existir en la DB de test
    
    await route.fulfill({
      status: 200,
      body: JSON.stringify(body),
    });
  });
  
  await page.goto('/users/1');
  
  // Testear cómo maneja la UI valores de rol inesperados
  await expect(page.getByTestId('role-badge')).toBeVisible();
});

Bloquear peticiones de terceros

Acelera los tests bloqueando tracking, analytics y ads:

test.beforeEach(async ({ page }) => {
  // Bloquear scripts comunes de terceros
  await page.route('**/*.{png,jpg,jpeg,gif,svg,ico,woff,woff2}', route => route.abort());
  await page.route('**/google-analytics.com/**', route => route.abort());
  await page.route('**/googletagmanager.com/**', route => route.abort());
  await page.route('**/hotjar.com/**', route => route.abort());
  await page.route('**/intercom.io/**', route => route.abort());
  await page.route('**/sentry.io/**', route => route.abort());
});

Bloquear imágenes puede afectar tests visuales o aserciones sensibles al layout.

Usar waitForRequest y waitForResponse

Esperá a que ocurra actividad de red específica:

test('el envío del formulario envía los datos correctos', async ({ page }) => {
  await page.goto('/login');
  
  // Empezar a esperar la petición ANTES de la acción que la dispara
  const requestPromise = page.waitForRequest(req => 
    req.url().includes('/api/auth/login') && req.method() === 'POST'
  );
  
  await page.fill('[data-testid="email"]', 'user@test.com');
  await page.fill('[data-testid="password"]', 'ValidPass1');
  await page.click('[data-testid="submit"]');
  
  const request = await requestPromise;
  const body = JSON.parse(request.postData() || '{}');
  
  expect(body.email).toBe('user@test.com');
});

test('la página recarga después de guardar', async ({ page }) => {
  await page.goto('/profile');
  
  // Esperar la llamada a la API de guardado
  const responsePromise = page.waitForResponse(resp => 
    resp.url().includes('/api/users') && resp.status() === 200
  );
  
  await page.fill('[data-testid="name"]', 'New Name');
  await page.click('[data-testid="save"]');
  
  const response = await responsePromise;
  const body = await response.json();
  expect(body.name).toBe('New Name');
});

Patrones de datos mock realistas

// data/mocks/users.ts
export const mockUser = (overrides = {}) => ({
  id: Math.floor(Math.random() * 10000),
  email: `user_${Date.now()}@test.com`,
  name: 'Test User',
  role: 'member',
  createdAt: new Date().toISOString(),
  ...overrides,
});

export const mockPaginatedResponse = <T>(items: T[], page = 1, limit = 10) => ({
  data: items,
  page,
  limit,
  total: items.length,
  totalPages: Math.ceil(items.length / limit),
});

test('la tabla de admin maneja 100 usuarios', async ({ page }) => {
  const users = Array.from({ length: 100 }, (_, i) => 
    mockUser({ id: i + 1, name: `User ${i + 1}` })
  );
  
  await page.route('/api/users', async (route) => {
    const url = new URL(route.request().url());
    const pageNum = parseInt(url.searchParams.get('page') || '1');
    const limit = parseInt(url.searchParams.get('limit') || '10');
    const start = (pageNum - 1) * limit;
    const pageUsers = users.slice(start, start + limit);
    
    await route.fulfill({
      status: 200,
      body: JSON.stringify(mockPaginatedResponse(pageUsers, pageNum, limit)),
    });
  });
  
  await page.goto('/admin/users');
  
  // Verificar que la paginación muestra el conteo correcto
  await expect(page.getByTestId('total-count')).toContainText('100');
  await expect(page.getByTestId('user-row')).toHaveCount(10);  // Primera página
});

Referencia de métodos

| Método | Qué hace |

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

| page.route(url, handler) | Intercepta peticiones que coinciden con la URL |

| route.fulfill({...}) | Devuelve una respuesta mockeada |

| route.continue() | Deja pasar la petición real |

| route.abort('failed') | Simula un fallo de red |

| route.fetch() | Realiza la petición real y obtiene la respuesta |

| page.waitForRequest(filter) | Espera a que ocurra una petición específica |

| page.waitForResponse(filter) | Espera a que ocurra una respuesta específica |

→ See also: Interceptación de Red, Mocks y Stubs en Playwright | API Testing con Playwright: Más Allá de la UI | Estrategias de Espera en Playwright: Sin Más sleep()