API Testing & Network Mocking
Testing REST APIs with the request fixture and mocking backend responses with page.route.
API testing with the request fixture
Playwright Test can test REST APIs directly — no browser needed. The built-in request fixture is an HTTP client with the same expect assertions and reports as your UI tests.
The examples use JSONPlaceholder (https://jsonplaceholder.typicode.com), a free fake REST API.
// tests/api/posts.spec.ts
import { test, expect } from '@playwright/test';
test.use({ baseURL: 'https://jsonplaceholder.typicode.com' });
test('GET /posts/1 returns a post', async ({ request }) => {
const response = await request.get('/posts/1');
expect(response.status()).toBe(200);
expect(response.headers()['content-type']).toContain('application/json');
const post = await response.json();
expect(post).toMatchObject({ id: 1, userId: 1 });
expect(typeof post.title).toBe('string');
});
test('GET /posts?userId=1 filters by user', async ({ request }) => {
const response = await request.get('/posts', { params: { userId: 1 } });
expect(response.ok()).toBeTruthy();
const posts = await response.json();
expect(posts.length).toBeGreaterThan(0);
for (const post of posts) {
expect(post.userId).toBe(1);
}
});
test('POST /posts creates a post', async ({ request }) => {
const response = await request.post('/posts', {
data: { title: 'QA with Playwright', body: 'API tests are fast.', userId: 7 },
});
expect(response.status()).toBe(201);
expect(await response.json()).toMatchObject({ id: 101, title: 'QA with Playwright', userId: 7 });
});
test('GET an unknown post returns 404', async ({ request }) => {
const response = await request.get('/posts/99999');
expect(response.status()).toBe(404);
});
(JSONPlaceholder fakes writes: POST returns id: 101 but nothing is stored.)
Authenticated API tests
Set common headers once in config or with test.use:
test.use({
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
Accept: 'application/json',
},
});
Combining API and UI: fast setup
The most powerful pattern is using the API to create test data, then testing the UI that displays it:
test('order appears in the order history', async ({ page, request }) => {
// Arrange via API — fast and reliable
const created = await request.post('/api/orders', { data: { sku: 'LAPTOP-STAND', quantity: 1 } });
const order = await created.json();
// Act + assert via UI
await page.goto('/account/orders');
await expect(page.getByRole('row', { name: new RegExp(order.number) })).toBeVisible();
});
(This uses a hypothetical shop API — the pattern is what matters.) The UI test now covers only what the UI is responsible for.
Network mocking with page.route
page.route() intercepts browser requests so you can mock responses, modify them or block them. Playwright's demo page https://demo.playwright.dev/api-mocking loads a list of fruits from an API — perfect for practice.
Mock a response
// tests/mocking.spec.ts
import { test, expect } from '@playwright/test';
test('shows mocked fruits', async ({ page }) => {
await page.route('*/**/api/v1/fruits', async (route) => {
await route.fulfill({ json: [{ name: 'Strawberry', id: 21 }] });
});
await page.goto('https://demo.playwright.dev/api-mocking');
await expect(page.getByText('Strawberry')).toBeVisible();
});
Register the route before the page makes the request.
Modify a real response
test('adds a fruit to the real response', async ({ page }) => {
await page.route('*/**/api/v1/fruits', async (route) => {
const response = await route.fetch(); // call the real API
const json = await response.json();
json.push({ name: 'Mango (from test)', id: 100 });
await route.fulfill({ response, json });
});
await page.goto('https://demo.playwright.dev/api-mocking');
await expect(page.getByText('Mango (from test)')).toBeVisible();
});
Test error states
test('handles a server error', async ({ page }) => {
await page.route('*/**/api/v1/fruits', (route) =>
route.fulfill({ status: 500, json: { message: 'Internal error' } }));
await page.goto('https://demo.playwright.dev/api-mocking');
// assert your app's error UI here, e.g.:
// await expect(page.getByRole('alert')).toContainText('Something went wrong');
});
Block requests
Speed up tests by blocking analytics, ads or large images:
await page.route(/(google-analytics|doubleclick)\.com/, (route) => route.abort());
await page.route('**/*.{png,jpg,jpeg}', (route) => route.abort());
Waiting for a request or response
const responsePromise = page.waitForResponse('**/api/v1/fruits');
await page.goto('https://demo.playwright.dev/api-mocking');
const response = await responsePromise;
expect(response.status()).toBe(200);
Start waiting before the action that triggers the request, then await the promise.
When to mock
Mock when you're testing front-end behaviour — error states, empty lists, edge-case data — or when the backend is slow or third-party. Keep a few unmocked end-to-end tests so you also know the real frontend and backend work together. Next: Parallel Runs, Traces & CI.