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
- 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.
- Control:
route.fulfill()gives you full control over the HTTP response, allowing you to easily simulate various scenarios critical for testing:- Success:
status: 200with valid data. - Failure:
status: 500orstatus: 404to test error handling paths. - Latency: You can introduce delays to test UI loading states.
- Success:
- Scope: The
page.route()handler is scoped to thePageobject, meaning it applies only to that specific page instance within the test. - Pattern: This pattern is essential for creating reliable, fast, and maintainable E2E test suites.