Playwright·Cookbook Field Manual
Part 01 · Network Mocking Card 02

Mock Your First API

Card 02: Mock Your First API

What This Pattern Solves

When testing a page that fetches from an external API (like SWAPI), you need deterministic, fast tests that don’t depend on network availability. Without mocking, your tests are slow, flaky, and fail when the API is down (see Card 01).

How It Works

  1. Register a route handler BEFORE navigating to the page
  2. Use page.route() to intercept requests matching a pattern
  3. Use route.fulfill() to return mock JSON data
  4. The page receives your mock response instead of hitting the real API
  5. Assert on the UI rendering your deterministic data

This is the fundamental pattern for all API mocking in Playwright - every other card builds on this.

Code Example

import { test, expect } from '@playwright/test';
import type { SwapiPerson } from '../swapi/schema';

test.describe('02-mock-first-api: Playwright + page.route', () => {
  test('GET people/1 returns mocked person in UI', async ({ page }) => {
    const luke = {
      name: 'Luke Skywalker',
      height: '172',
    } satisfies Partial<SwapiPerson>;

    // 1. Register route BEFORE navigation
    await page.route('**/swapi.dev/api/people/1/**', (route) =>
      route.fulfill({ json: luke }),
    );

    // 2. Navigate - fetch is intercepted
    await page.goto('/cards/02');

    // 3. Assert on deterministic data
    await expect(page.getByTestId('person-name')).toHaveText('Luke Skywalker');
    await expect(page.getByTestId('person-height')).toHaveText('172');
  });
});

Run This Example

pnpm test src/02-mock-first-api

Prerequisites

Key Concepts

When to Use This Pattern

Common Mistakes

  1. Registering route AFTER navigation:

    // ❌ WRONG - too late, request already sent
    await page.goto('/');
    await page.route('**/api/**', handler);
    
    // ✓ CORRECT - route registered first
    await page.route('**/api/**', handler);
    await page.goto('/cards/02');
  2. Pattern doesn’t match the actual request:

    • Use browser DevTools Network tab to see exact URLs
    • Test your pattern: '**/people/1/**' matches https://swapi.dev/api/people/1/
    • Be specific enough to avoid matching unintended requests
  3. Hand-stringifying when json does it for you:

    // ❌ Verbose - body must be a string, and you must set contentType
    route.fulfill({
      contentType: 'application/json',
      body: JSON.stringify({ name: 'Luke' }),
    })
    
    // ✓ CORRECT - the json option serializes and sets content-type
    route.fulfill({ json: { name: 'Luke' } })
  4. Using body without contentType:

    • If you pass a raw body string, set contentType: 'application/json' for JSON
    • Prefer the json option, which handles both for you

Flow Diagram

sequenceDiagram
    participant Test
    participant Playwright
    participant Page
    participant Route Handler

    Test->>Playwright: page.route('**/api/**', handler)
    Note over Playwright: Route registered
    Test->>Playwright: page.goto('/cards/02')
    Playwright->>Page: Load HTML/JS
    Page->>Route Handler: fetch('https://swapi.dev/api/people/1/')
    Route Handler->>Route Handler: Match pattern ✓
    Route Handler->>Route Handler: fulfill(mock JSON)
    Route Handler-->>Page: 200 + mock data
    Page->>Page: Render UI
    Test->>Page: expect(...).toHaveText('Luke')
    Page-->>Test: ✓ Assertion passes

Live Demo

👇 This component is what the Playwright test interacts with (test mocks the API):

Loading…

Run This Example

pnpm test src/02-mock-first-api