notes

API Testing

Mock API Testing in Playwright

Intercepting requests with page.route() to test error, loading, and empty states.

Mock API testing involves intercepting network requests and returning predefined responses instead of relying on a real backend.

It allows you to test different application states, including server errors, loading states, empty responses, and unavailable resources.

1. Key Concept: page.route()

Use page.route() to intercept network requests matching a URL pattern and control how they are handled.

await page.route('**/api/products', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify([
      { id: 1, name: 'Laptop', price: 50000 }
    ]),
  });
});
  • page.route() — Intercepts matching network requests.
  • route.fulfill() — Returns a custom response without calling the actual server.
  • route.continue() — Sends the request to the actual server.
  • route.abort() — Aborts the request.

2. Common Mocking Scenarios

Simulating Server Errors

Test how the frontend handles a 500 Internal Server Error.

test('should display an error when the server fails', 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.getByText(/something went wrong/i))
    .toBeVisible();
});

Testing Loading States

Introduce an artificial delay to simulate a slow API.

test('should display a loading indicator', async ({ page }) => {
  await page.route('**/api/products', async (route) => {
    await new Promise((resolve) => setTimeout(resolve, 3000));

    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([]),
    });
  });

  await page.goto('/products');

  await expect(page.getByRole('status')).toBeVisible();
});

The application must expose a matching accessible status element for this assertion to work.

Controlling API Data

Mock specific data to test different UI states without changing the database.

test('should display an out-of-stock message', async ({ page }) => {
  await page.route('**/api/products/1', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        id: 1,
        name: 'Laptop',
        stock: 0,
      }),
    });
  });

  await page.goto('/products/1');

  await expect(page.getByText(/out of stock/i))
    .toBeVisible();
});

Mocking Only Specific Requests

Intercept a POST request while allowing GET requests to reach the actual server.

await page.route('**/api/products', async (route) => {
  if (route.request().method() === 'POST') {
    await route.fulfill({
      status: 201,
      contentType: 'application/json',
      body: JSON.stringify({
        id: 2,
        name: 'Phone',
      }),
    });
  } else {
    await route.continue();
  }
});

3. Other Useful Mocking Scenarios

  • Empty responses: Return [] to test empty states.
  • Unauthorized requests: Return 401 to test authentication handling.
  • Forbidden requests: Return 403 to test permission handling.
  • Not found: Return 404 for missing resources.
  • Network failures: Use route.abort() to simulate failed requests.
  • Malformed responses: Return unexpected data to test defensive handling.
  • Large datasets: Return many records to test pagination and rendering.

4. When to Mock and When Not to Mock

When to Mock When Not to Mock
Testing complex error states Verifying real API integration
Simulating slow or failed requests Testing actual backend business logic
Testing third-party APIs Verifying database persistence
Reproducing rare conditions End-to-end integration testing
Testing specific data states When mocking removes the behavior being tested

5. Best Practices

  • Register routes before navigating to the page or triggering the request.
  • Match URL patterns precisely to avoid intercepting unrelated requests.
  • Mock only the requests necessary for the test.
  • Verify visible behavior, not just that a mock was configured.
  • Keep mocked data consistent with the API's expected response schema.
  • Use real API tests separately to verify backend behavior and integration.
  • Avoid arbitrary delays where possible; use deterministic synchronization for normal tests.

6. Running Mock API Tests

# Run all tests
npx playwright test

# Run a specific test file
npx playwright test tests/mock-api.spec.ts

# Run with a visible browser
npx playwright test tests/mock-api.spec.ts --headed

# Debug a test
npx playwright test tests/mock-api.spec.ts --debug

# Run tests matching a title
npx playwright test -g "out-of-stock"

Key takeaway: Mock API testing gives you control over network responses, making it possible to test frontend behavior under predictable success, failure, and edge-case conditions without depending on a live backend.

#playwright#mocking#page-routecreated 12 Oct 2026

Local graph