Network Mocking and API Testing
Playwright can intercept, modify, and mock network requests at the browser level — without a proxy server. This lets you test edge cases (server errors, slow responses, empty states) that are difficult or impossible to reproduce with a live backend, and it lets you combine UI tests with API verification in a single test.
Request Interception
page.route() intercepts requests matching a URL pattern and lets you fulfill, abort, or modify them.
Mocking API Responses
test('shows empty state when no products exist', async ({ page }) => {
// Mock the API to return an empty array
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.getByText('No products found')).toBeVisible();
});
test('handles server error gracefully', async ({ page }) => {
await page.route('**/api/products', async (route) => {
await route.fulfill({ status: 500 });
});
await page.goto('/products');
await expect(page.getByRole('alert')).toHaveText(/something went wrong/i);
});
Modifying Requests
// Add an auth header to all API requests
await page.route('**/api/**', async (route) => {
await route.continue({
headers: {
...route.request().headers(),
'Authorization': 'Bearer test-token',
},
});
});
// Modify response data (e.g., inject a test flag)
await page.route('**/api/config', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.featureFlags.newCheckout = true;
await route.fulfill({ response, body: JSON.stringify(json) });
});
Aborting Requests
// Block analytics and tracking in tests
await page.route('**/{analytics,tracking}/**', (route) => route.abort());
// Block images to speed up tests
await page.route('**/*.{png,jpg,jpeg,svg}', (route) => route.abort());
Waiting for Network Events
// Wait for a specific API response after triggering an action
const responsePromise = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: 'Place Order' }).click();
const response = await responsePromise;
expect(response.status()).toBe(201);
// Wait for a request to be sent
const requestPromise = page.waitForRequest('**/api/search*');
await page.getByRole('searchbox').fill('laptop');
const request = await requestPromise;
expect(request.url()).toContain('q=laptop');
API Testing with request Fixture
Playwright's request fixture makes direct HTTP calls without a browser — ideal for API setup, teardown, and pure API testing.
test('API: create and retrieve a product', async ({ request }) => {
// Create
const createResponse = await request.post('/api/products', {
data: { name: 'Widget', price: 9.99 },
});
expect(createResponse.status()).toBe(201);
const product = await createResponse.json();
// Retrieve
const getResponse = await request.get(`/api/products/${product.id}`);
expect(getResponse.status()).toBe(200);
const retrieved = await getResponse.json();
expect(retrieved.name).toBe('Widget');
});
Combining UI + API Tests
The most powerful pattern: use the API to set up data, verify through the UI, then validate via API.
test('user can update their profile', async ({ page, request }) => {
// Setup: create user via API
await request.post('/api/users', {
data: { name: 'Alice', email: 'alice@test.com' },
});
// UI: update the profile
await page.goto('/profile');
await page.getByLabel('Name').fill('Alice Smith');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('alert')).toHaveText('Profile updated');
// Verify: check via API that the change persisted
const response = await request.get('/api/users/alice');
const user = await response.json();
expect(user.name).toBe('Alice Smith');
});
Network Mocking Patterns
| Pattern | Use Case |
|---|---|
| Full mock | Test UI in isolation from backend |
| Error simulation | Verify error handling (500, 403, timeout) |
| Slow response | Test loading states and spinners |
| Modified response | Inject feature flags or test-specific data |
| Abort requests | Block analytics, images, third-party scripts |
| Record & replay | Snapshot real responses for deterministic tests |
Simulating Slow Responses
test('shows loading spinner for slow API', async ({ page }) => {
await page.route('**/api/reports', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 3000));
await route.fulfill({ status: 200, body: '[]' });
});
await page.goto('/reports');
await expect(page.getByTestId('spinner')).toBeVisible();
await expect(page.getByTestId('spinner')).not.toBeVisible({ timeout: 5000 });
});
Key Takeaways
page.route()intercepts requests at the browser level — no proxy server needed- Mock APIs to test edge cases: empty states, errors, slow responses, feature flags
- Use
requestfixture for direct API calls — faster than UI for setup and verification - Combine UI + API testing: set up via API, interact via UI, verify via API
- Block analytics and unnecessary resources to speed up test execution