level 05 / rest-api-testing
REST API Testing with Playwright
Use Playwright's request fixture to test REST APIs directly — no browser required.
practice site: Restful Booker
Why API Testing Matters
API tests run 10–100× faster than UI tests and catch integration bugs at the contract boundary. Playwright’s request fixture makes REST testing first-class — same runner, same config, same CI pipeline.
The test pyramid puts API tests above unit tests but below UI tests. Shift coverage down: every user flow that touches an API should have an API-layer test, not just a UI test.
The request Fixture
Playwright exposes APIRequestContext as the request fixture. It handles cookies, base URL, and response assertions natively.
import { test, expect } from '@playwright/test';
test('GET /bookings returns list', async ({ request }) => {
const res = await request.get('https://restful-booker.herokuapp.com/booking');
expect(res.status()).toBe(200);
const body = await res.json();
expect(Array.isArray(body)).toBe(true);
});
CRUD Flow
test('full booking CRUD', async ({ request }) => {
// Create
const create = await request.post('/booking', {
data: {
firstname: 'Jane',
lastname: 'Doe',
totalprice: 150,
depositpaid: true,
bookingdates: { checkin: '2025-01-01', checkout: '2025-01-07' },
},
});
expect(create.status()).toBe(200);
const { bookingid } = await create.json();
// Read
const get = await request.get(`/booking/${bookingid}`);
expect(get.status()).toBe(200);
expect(await get.json()).toMatchObject({ firstname: 'Jane' });
// Update (requires auth token)
const token = await getToken(request);
const patch = await request.patch(`/booking/${bookingid}`, {
headers: { Cookie: `token=${token}` },
data: { firstname: 'Updated' },
});
expect(patch.status()).toBe(200);
// Delete
const del = await request.delete(`/booking/${bookingid}`, {
headers: { Cookie: `token=${token}` },
});
expect(del.status()).toBe(201);
});
async function getToken(request: any) {
const res = await request.post('/auth', {
data: { username: 'admin', password: 'password123' },
});
return (await res.json()).token;
}
APIRequestContext vs. fetch
| Feature | request fixture | fetch |
|---|---|---|
| Base URL from config | ✅ | ❌ manual |
| Cookie jar | ✅ | ❌ |
expect(res).toBeOK() | ✅ | ❌ |
Works in beforeAll | ✅ | ✅ |
| TypeScript response types | via .json() | via generics |
Response Assertions
// Status helpers
expect(res.ok()).toBe(true); // 200–299
expect(res.status()).toBe(200);
expect(res.statusText()).toBe('OK');
// Body
const json = await res.json();
expect(json).toMatchObject({ id: expect.any(Number) });
// Headers
expect(res.headers()['content-type']).toContain('application/json');
Practice: Restful Booker
| Endpoint | Method | Auth required |
|---|---|---|
/booking | GET | No |
/booking | POST | No |
/booking/:id | PUT/PATCH | Yes (token) |
/booking/:id | DELETE | Yes (token) |
/auth | POST | — (generates token) |