Playwright guide
Steps for API Testing in Playwright
A complete, practical walkthrough for building fast, reliable API tests—from your first request to authenticated CRUD flows and CI-ready reporting.
Introduction
Modern applications are built on APIs, and ensuring those APIs work correctly is just as important as testing the UI. While Playwright is widely known as a powerful end-to-end browser automation tool, it also ships with a built-in APIRequestContext for API testing.
You can validate backend services, mock data for UI tests, or build a full API test suite—all without installing a separate HTTP client library. This guide walks through the complete process, from installation to robust, production-ready test suites.
What is API testing?
API testing verifies that an application programming interface meets expectations for functionality, reliability, performance, and security. Instead of interacting with a UI, API tests send requests directly to REST, GraphQL, or other endpoints and validate status codes, headers, response bodies, and timing.
Why use Playwright for API testing?
Playwright includes a dedicated request context that lets you:
- Send
GET,POST,PUT,PATCH, andDELETErequests without a browser. - Reuse authentication state between API calls and UI tests.
- Combine API setup and teardown with UI tests in one file.
- Run requests in parallel with Playwright's test runner.
- Use built-in retries, timeouts, tracing, and reporting.
Step-by-step: API testing in Playwright
Prerequisites
- Node.js v16 or later
- Basic JavaScript or TypeScript knowledge
- A code editor (VS Code recommended)
- An API endpoint to test
Step 1: Install Playwright
Create a new project, or install the test runner in an existing project:
npm init playwright@latest
# or
npm install -D @playwright/testStep 2: Understand the request fixture
The built-in fixture provides an APIRequestContext. No browser or page is needed.
import { test, expect } from '@playwright/test';
test('get list of users', async ({ request }) => {
const response = await request.get(
'https://jsonplaceholder.typicode.com/users'
);
expect(response.status()).toBe(200);
});Step 3: Write your first API test
Create tests/api.spec.ts and assert both the response and its data:
const BASE_URL = 'https://jsonplaceholder.typicode.com';
test('GET /users returns a list', async ({ request }) => {
const response = await request.get(`${BASE_URL}/users`);
expect(response.ok()).toBeTruthy();
const users = await response.json();
expect(Array.isArray(users)).toBeTruthy();
expect(users.length).toBeGreaterThan(0);
});Run the test with npx playwright test.
Step 4: Handle HTTP methods
The request object exposes all common HTTP verbs:
// GET
await request.get(`${BASE_URL}/posts/1`);
// POST
await request.post(`${BASE_URL}/posts`, {
data: { title: 'Playwright API Testing', body: 'Learning APIs', userId: 1 }
});
// PUT
await request.put(`${BASE_URL}/posts/1`, {
data: { id: 1, title: 'Updated Title', body: 'Updated body', userId: 1 }
});
// DELETE
await request.delete(`${BASE_URL}/posts/1`);Step 5: Validate responses with assertions
Validate status codes, headers, and body content together for expressive, useful failures.
const response = await request.post(`${BASE_URL}/posts`, {
data: { title: 'foo', body: 'bar', userId: 1 },
});
expect(response.status()).toBe(201);
expect(response.headers()['content-type']).toContain('application/json');
const body = await response.json();
expect(body.title).toBe('foo');
expect(body).toHaveProperty('id');Step 6: Handle authentication
Set default headers on a context so every request includes the required token:
const apiContext = await request.newContext({
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
},
});
const response = await apiContext.get('/protected-resource');
expect(response.status()).toBe(200);
await apiContext.dispose();Step 7: Use fixtures and custom contexts
For larger suites, create an authenticated context once and dispose of it after use:
type MyFixtures = { apiContext: APIRequestContext };
export const test = base.extend({
apiContext: async ({}, use) => {
const context = await request.newContext({
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
},
});
await use(context);
await context.dispose();
},
}); Step 8: Chain requests
Model real workflows by creating, updating, verifying, and deleting a resource:
const createRes = await request.post(`${BASE_URL}/posts`, {
data: { title: 'New Post', body: 'Content', userId: 1 },
});
const { id } = await createRes.json();
const updateRes = await request.put(`${BASE_URL}/posts/${id}`, {
data: { title: 'Updated Post' },
});
expect(updateRes.status()).toBe(200);
const getRes = await request.get(`${BASE_URL}/posts/${id}`);
expect(getRes.status()).toBe(200);
const deleteRes = await request.delete(`${BASE_URL}/posts/${id}`);
expect(deleteRes.status()).toBe(200);Step 9: Test errors and negative cases
Do not test only the happy path. Check invalid input, missing authentication, and unknown resources:
test('returns 404 for a missing resource', async ({ request }) => {
const response = await request.get(`${BASE_URL}/posts/999999`);
expect(response.status()).toBe(404);
});
test('returns 401 without authentication', async ({ request }) => {
const response = await request.get('https://api.example.com/protected');
expect(response.status()).toBe(401);
});Step 10: Generate and review reports
Playwright's HTML reporter works for API tests too:
npx playwright test
npx playwright show-reportConfigure multiple reporters in playwright.config.ts:
export default {
reporter: [['html'], ['junit', { outputFile: 'results.xml' }]],
};Best practices
- Isolate API and UI tests with separate project configurations when useful.
- Use environment variables for URLs, tokens, and credentials.
- Create reusable fixtures for authentication and setup/teardown.
- Validate schemas with libraries such as
zodorajv. - Clean up records created during tests.
- Run independent API tests in parallel in CI.
- Use API calls to set up data quickly before UI assertions.
Common pitfalls to avoid
- Forgetting
dispose()on manually created request contexts. - Hardcoding tokens or secrets in test files.
- Parsing a body without first checking
response.ok(). - Relying only on status codes instead of validating content.
- Running tests against production instead of a dedicated test environment.
Conclusion
Playwright's built-in API capabilities provide an all-in-one way to validate both the UI and backend. Start with one GET request, then expand into CRUD flows, authentication, schema checks, and negative scenarios. You will quickly have a maintainable regression suite that catches issues before production.
Set up a Playwright project, choose one endpoint, and write your first automated test today.
FAQs
APIRequestContext sends HTTP requests directly, making tests fast and lightweight.request.post() and place the query or mutation in the JSON payload.ajv or zod alongside Playwright assertions for deeper schema validation.