API Testing With Playwright: The Complete Guide Beyond UI Automation
Playwright is widely known for UI automation, but it also provides robust capabilities for API testing — allowing teams to validate both frontend and backend within a single framework. No more maintaining separate tools for API and UI tests.
Contents
Why API Testing in Playwright?
- Reduces dependency on UI for test data setup
- Improves test execution speed (API calls are 10-50x faster than UI interactions)
- Enables early validation of backend logic before UI is ready
- Supports true end-to-end testing: create data via API, validate via UI
Basic API Requests
import { test, expect } from '@playwright/test';
test('GET request - fetch users', async ({ request }) => {
const response = await request.get('/api/users');
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
const body = await response.json();
expect(body.users).toHaveLength(10);
expect(body.users[0]).toHaveProperty('email');
});
test('POST request - create user', async ({ request }) => {
const response = await request.post('/api/users', {
data: {
name: 'Test User',
email: 'test@example.com',
role: 'admin'
}
});
expect(response.status()).toBe(201);
const user = await response.json();
expect(user.id).toBeDefined();
});
Authentication Flows
test('API with Bearer token auth', async ({ request }) => {
// Login to get token
const loginResponse = await request.post('/api/auth/login', {
data: { email: 'admin@test.com', password: 'secure123' }
});
const { token } = await loginResponse.json();
// Use token for authenticated request
const response = await request.get('/api/admin/dashboard', {
headers: { 'Authorization': 'Bearer ' + token }
});
expect(response.ok()).toBeTruthy();
});
Hybrid Pattern: API Setup + UI Validation
test('create product via API, verify in UI', async ({ page, request }) => {
// API: Create test data (fast, reliable)
const response = await request.post('/api/products', {
data: { name: 'Test Widget', price: 29.99, category: 'electronics' }
});
const product = await response.json();
// UI: Verify it renders correctly
await page.goto('/products/' + product.id);
await expect(page.getByText('Test Widget')).toBeVisible();
await expect(page.getByText('$29.99')).toBeVisible();
});
Response Schema Validation
test('validate response schema', async ({ request }) => {
const response = await request.get('/api/products/1');
const product = await response.json();
// Validate structure
expect(product).toHaveProperty('id');
expect(product).toHaveProperty('name');
expect(product).toHaveProperty('price');
expect(typeof product.price).toBe('number');
expect(product.price).toBeGreaterThan(0);
// Validate response time
const timing = response.headers()['x-response-time'];
expect(parseInt(timing)).toBeLessThan(500);
});
Mocking External APIs
test('mock payment gateway', async ({ page }) => {
// Intercept Stripe API calls
await page.route('**/api.stripe.com/**', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'ch_mock', status: 'succeeded' })
});
});
await page.goto('/checkout');
await page.getByRole('button', { name: 'Pay Now' }).click();
await expect(page.getByText('Payment successful')).toBeVisible();
});
