Overview
Playwright provides a powerful API testing framework through the request context, allowing you to test REST APIs, GraphQL endpoints, and web services without launching a browser.
Getting Started
Basic API Request
Make HTTP requests using the request context:
import { test, expect } from '@playwright/test';
test('GET request', async ({ request }) => {
const response = await request.get('https://api.github.com/users/microsoft');
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
const data = await response.json();
expect(data.login).toBe('microsoft');
expect(data.type).toBe('Organization');
});
HTTP Methods
const response = await request.get('https://api.example.com/users');
const users = await response.json();
Request Configuration
Set Base URL and Headers
Configure common request options:
import { test, expect } from '@playwright/test';
test.use({
baseURL: 'https://api.github.com',
extraHTTPHeaders: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': `token ${process.env.API_TOKEN}`,
},
});
test('create repository', async ({ request }) => {
const response = await request.post('/user/repos', {
data: { name: 'test-repo' },
});
expect(response.ok()).toBeTruthy();
});
Global Configuration
Set API defaults in playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
},
});
Authentication
Token-Based Authentication
Set authorization header
test.use({
extraHTTPHeaders: {
'Authorization': `Bearer ${process.env.API_TOKEN}`,
},
});
Make authenticated requests
test('get user profile', async ({ request }) => {
const response = await request.get('/api/profile');
expect(response.ok()).toBeTruthy();
const profile = await response.json();
expect(profile.email).toBeTruthy();
});
API Key Authentication
import { test, expect } from '@playwright/test';
test('weather API with key', async ({ request }) => {
const response = await request.get('https://api.weather.com/data', {
params: {
apikey: process.env.WEATHER_API_KEY,
city: 'London',
},
});
expect(response.ok()).toBeTruthy();
const weather = await response.json();
expect(weather.city).toBe('London');
});
Request Options
Query Parameters
const response = await request.get('/api/search', {
params: {
q: 'playwright',
page: 1,
per_page: 10,
},
});
const response = await request.post('/api/data', {
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'custom-value',
},
data: { key: 'value' },
});
const response = await request.post('/api/upload', {
form: {
username: 'john',
password: 'secret',
},
});
import fs from 'fs';
const response = await request.post('/api/upload', {
multipart: {
file: {
name: 'document.pdf',
mimeType: 'application/pdf',
buffer: fs.readFileSync('document.pdf'),
},
title: 'My Document',
},
});
Response Handling
Parse Response Body
const response = await request.get('/api/users');
const data = await response.json();
console.log(data);
Response Validation
import { test, expect } from '@playwright/test';
test('validate API response', async ({ request }) => {
const response = await request.get('/api/users/1');
// Status code
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
// Headers
expect(response.headers()['content-type']).toContain('application/json');
// Body
const user = await response.json();
expect(user).toMatchObject({
id: expect.any(Number),
name: expect.any(String),
email: expect.stringMatching(/^[^@]+@[^@]+$/),
});
});
Real-World Example: GitHub API
Complete example testing GitHub API from the Playwright repository:
import { test, expect } from '@playwright/test';
const user = process.env.GITHUB_USER;
const repo = 'Test-Repo-1';
test.use({
baseURL: 'https://api.github.com',
extraHTTPHeaders: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': `token ${process.env.API_TOKEN}`,
}
});
test.beforeAll(async ({ request }) => {
// Create repository
const response = await request.post('/user/repos', {
data: { name: repo }
});
expect(response.ok()).toBeTruthy();
});
test.afterAll(async ({ request }) => {
// Delete repository
const response = await request.delete(`/repos/${user}/${repo}`);
expect(response.ok()).toBeTruthy();
});
test('should create bug report', async ({ request }) => {
const newIssue = await request.post(`/repos/${user}/${repo}/issues`, {
data: {
title: '[Bug] report 1',
body: 'Bug description',
}
});
expect(newIssue.ok()).toBeTruthy();
const issues = await request.get(`/repos/${user}/${repo}/issues`);
expect(issues.ok()).toBeTruthy();
expect(await issues.json()).toContainEqual(expect.objectContaining({
title: '[Bug] report 1',
body: 'Bug description'
}));
});
test('should create feature request', async ({ request }) => {
const newIssue = await request.post(`/repos/${user}/${repo}/issues`, {
data: {
title: '[Feature] request 1',
body: 'Feature description',
}
});
expect(newIssue.ok()).toBeTruthy();
const issues = await request.get(`/repos/${user}/${repo}/issues`);
expect(issues.ok()).toBeTruthy();
expect(await issues.json()).toContainEqual(expect.objectContaining({
title: '[Feature] request 1',
body: 'Feature description'
}));
});
Error Handling
Handle Failed Requests
import { test, expect } from '@playwright/test';
test('handle 404 error', async ({ request }) => {
const response = await request.get('/api/users/99999');
expect(response.ok()).toBeFalsy();
expect(response.status()).toBe(404);
const error = await response.json();
expect(error.message).toBe('User not found');
});
test('handle validation error', async ({ request }) => {
const response = await request.post('/api/users', {
data: { name: '' }, // Invalid data
});
expect(response.status()).toBe(422);
const error = await response.json();
expect(error.errors).toContainEqual(
expect.objectContaining({
field: 'name',
message: 'Name is required',
})
);
});
Advanced Patterns
Retry Failed Requests
import { test, expect } from '@playwright/test';
test('retry on failure', async ({ request }) => {
const maxRetries = 3;
let attempts = 0;
let response;
while (attempts < maxRetries) {
response = await request.get('/api/unstable-endpoint');
if (response.ok()) break;
attempts++;
await new Promise(resolve => setTimeout(resolve, 1000));
}
expect(response.ok()).toBeTruthy();
});
Chain API Calls
test('create and update user', async ({ request }) => {
// Create user
const createResponse = await request.post('/api/users', {
data: { name: 'John Doe', email: '[email protected]' },
});
const user = await createResponse.json();
const userId = user.id;
// Update user
const updateResponse = await request.put(`/api/users/${userId}`, {
data: { name: 'John Smith' },
});
expect(updateResponse.ok()).toBeTruthy();
// Verify update
const getResponse = await request.get(`/api/users/${userId}`);
const updatedUser = await getResponse.json();
expect(updatedUser.name).toBe('John Smith');
});
Parallel API Requests
test('parallel requests', async ({ request }) => {
const [users, posts, comments] = await Promise.all([
request.get('/api/users').then(r => r.json()),
request.get('/api/posts').then(r => r.json()),
request.get('/api/comments').then(r => r.json()),
]);
expect(users.length).toBeGreaterThan(0);
expect(posts.length).toBeGreaterThan(0);
expect(comments.length).toBeGreaterThan(0);
});
Best Practices
Use Setup and Teardown: Create test data in beforeAll and clean up in afterAll to ensure isolated tests.
- Store credentials securely: Use environment variables for API keys and tokens
- Use base URLs: Configure
baseURL to avoid repeating the same URL prefix
- Test error scenarios: Verify your API handles errors gracefully
- Validate response schemas: Ensure API responses match expected structure
- Clean up test data: Delete created resources in teardown hooks
- Use meaningful assertions: Check both status codes and response bodies
API tests run faster than browser tests and can be used for comprehensive integration testing.
Troubleshooting
Request timeout
Increase timeout for slow endpoints:
const response = await request.get('/api/slow-endpoint', {
timeout: 30000, // 30 seconds
});
CORS errors
CORS doesn’t apply to API testing since requests are made directly:
// No CORS issues with direct API requests
const response = await request.get('https://api.example.com/data');
Debug requests
Log request and response details:
test('debug API call', async ({ request }) => {
const response = await request.get('/api/users');
console.log('URL:', response.url());
console.log('Status:', response.status());
console.log('Headers:', response.headers());
console.log('Body:', await response.text());
});
Always validate SSL certificates in production. Use ignoreHTTPSErrors only for testing against development servers.