Series Navigation
Why Mock Networks in Tests?
Real APIs make tests slow, flaky, and hard to control:
Problem 1: Speed
Real API call → 200-800ms per request
10 API calls per test × 50 tests = minutes of waiting
Problem 2: Test data
Your API returns real data that changes
Test that checks "3 items in cart" fails when data changes
Problem 3: Edge cases
How do you test "what happens when the API returns 500"?
You can't trigger that on a real server reliably
Problem 4: External dependencies
Third-party APIs go down, rate-limit, or change responses
Your tests should not depend on Stripe/Twilio/etc. being up
Solution: Mock the network
Opening the Network Mock Studio
- Open Chrome DevTools → Playwright Studio panel
- Click "Network Mock Studio" tab
- Click "+ Add Mock Rule"
Creating a Mock Rule
Each rule has three parts:
URL Pattern → which requests to intercept
Method → GET, POST, PUT, DELETE, or *
Mock Response → status code + headers + body
Example — mock a product listing API:
URL Pattern: **/api/products**
Method: GET
Status: 200
Body:
{
"products": [
{ "id": 1, "name": "Widget Pro", "price": 49.99 },
{ "id": 2, "name": "Gadget Plus", "price": 29.99 }
],
"total": 2
}
The exported Playwright code:
await page.route('**/api/products', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
products: [
{ id: 1, name: 'Widget Pro', price: 49.99 },
{ id: 2, name: 'Gadget Plus', price: 29.99 }
],
total: 2
})
});
});
await page.goto('/products');
await expect(page.locator('.product-card')).toHaveCount(2);
URL Pattern Matching
// Exact URL
await page.route('https://api.example.com/users', handler);
// Wildcard — any path under /api/
await page.route('**/api/**', handler);
// Specific endpoint regardless of domain
await page.route('**/products*', handler);
// Regex pattern
await page.route(/api\/users\/\d+/, handler);
// Query parameters — match the path, ignore params
await page.route('**/search**', handler);
// Matches: /search?q=test&page=2
Common Mock Scenarios
Simulate API Error (500)
await page.route('**/api/checkout', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Internal server error' })
});
});
await page.click('#checkout-btn');
await expect(page.locator('.error-banner')).toBeVisible();
await expect(page.locator('.error-banner')).toContainText('Something went wrong');
Simulate 401 Unauthorized
await page.route('**/api/profile', async route => {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ error: 'Unauthorized' })
});
});
await page.goto('/profile');
// App should redirect to login
await expect(page).toHaveURL(/login/);
Simulate Slow Network
await page.route('**/api/reports/generate', async route => {
// Delay 3 seconds
await new Promise(r => setTimeout(r, 3000));
await route.fulfill({
status: 200,
body: JSON.stringify({ status: 'complete', url: '/reports/123' })
});
});
// Test that loading state appears
await page.click('#generate-report');
await expect(page.locator('.loading-spinner')).toBeVisible();
await expect(page.locator('.report-link')).toBeVisible({ timeout: 10000 });
Simulate Network Failure (no response)
await page.route('**/api/save', async route => {
await route.abort('failed'); // simulates network failure
});
await page.click('#save-btn');
await expect(page.locator('.offline-warning')).toBeVisible();
Partial Mock — Let Some Requests Through
// Only mock POST to /api/login, let everything else through
await page.route('**/api/login', async route => {
if (route.request().method() === 'POST') {
await route.fulfill({
status: 200,
body: JSON.stringify({ token: 'fake-jwt-token', userId: 42 })
});
} else {
await route.continue(); // pass through to real server
}
});
Inspecting Captured Requests
The Network Mock Studio also shows you all network requests made by the page — useful for understanding what your app actually calls before writing mocks:
- Navigate to the page you want to test
- Open Playwright Studio → Network Mock Studio
- Click "Capture Mode"
- Interact with the page
- See every request: URL, method, status, response body
This tells you exactly what to mock without guessing.
Best Practices
// ✅ Mock in beforeEach for consistent test state
test.beforeEach(async ({ page }) => {
await page.route('**/api/user', async route => {
await route.fulfill({
status: 200,
body: JSON.stringify({ id: 1, name: 'Test User', role: 'admin' })
});
});
});
// ✅ Use meaningful test data that matches what your UI expects
// ❌ Don't use generic placeholder data like { id: 1, name: 'test' }
// ✅ Use realistic data: { id: 42, name: 'Alice Chen', email: 'alice@acme.com' }
// ✅ Assert on the mocked data — verify your UI renders it correctly
await expect(page.locator('.user-name')).toHaveText('Alice Chen');
// ✅ Clean up routes after tests that need real network
test.afterEach(async ({ page }) => {
await page.unrouteAll();
});
Discussion
Loading...Leave a Comment
All comments are reviewed before appearing. No links please.