Styrow.dev
Question 25 of 28

How do you use Playwright's request interception capabilities to stub a specific API endpoint for isolated E2E testing?

Question

How do you use Playwright’s request interception capabilities to stub a specific API endpoint for isolated E2E testing?

Answer

When writing robust End-to-End (E2E) tests, relying on live external services is a major source of flakiness and slow execution. Playwright provides a powerful mechanism to isolate the frontend behavior from the backend service by intercepting network requests.

The primary method for this is page.route(). This function allows you to define a handler that intercepts requests matching a specific URL pattern. Once intercepted, you can tell Playwright what the response should be, completely bypassing the actual network call.

Production-Grade Example

Here is a complete example demonstrating how to intercept a GET request to /api/user-data and force it to return a predefined, successful JSON payload.

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

test('should display user data from mocked API response', async ({ page }) => {
  // 1. Define the mock data
  const mockUserData = {
    id: 123,
    name: 'Mocked Senior Engineer',
    role: 'Staff Developer',
  };

  // 2. Set up the request interception
  await page.route('**/api/user-data', async route => {
    // 3. Use route.fulfill() to define the mock response
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify(mockUserData),
    });
  });

  // 4. Navigate to the page that calls the API
  await page.goto('/dashboard');

  // 5. Assert the application rendered the mocked data correctly
  // We assert against the mocked data, not the real backend response.
  await expect(page.locator('#user-name')).toHaveText('Mocked Senior Engineer');
  await expect(page.locator('#user-role')).toHaveText('Staff Developer');
});

Key Takeaways for Senior Engineers

  1. Isolation: The core benefit is achieving test isolation. Your test only validates the client-side logic (e.g., “If the API returns a 200, does the UI display the data correctly?”), not the server-side logic.
  2. Control: route.fulfill() gives you full control over the HTTP response, allowing you to easily simulate various scenarios critical for testing:
    • Success: status: 200 with valid data.
    • Failure: status: 500 or status: 404 to test error handling paths.
    • Latency: You can introduce delays to test UI loading states.
  3. Scope: The page.route() handler is scoped to the Page object, meaning it applies only to that specific page instance within the test.
  4. Pattern: This pattern is essential for creating reliable, fast, and maintainable E2E test suites.