Playwright API Automation
API Testing with Playwright (TypeScript) — CRUD Guide
API testing doesn’t need a heavyweight framework. Playwright ships a built-in request fixture that can fire GET, POST, PUT, PATCH, and DELETE calls, assert on status codes, and validate response bodies — all without spinning up a browser. Below is a complete, beginner-friendly CRUD walkthrough using the dummy test API (https://test.in/public/v2/users), followed by an interview prep section.
Why Playwright for API Testing?
- No browser overhead —
requestcontext works standalone, making tests fast. - Built-in assertions —
expect(response.status())reads like plain English. - One framework, two jobs — the same Playwright project can run UI and API tests together.
- First-class TypeScript support — strong typing catches mistakes before runtime.
Setup
npm init playwright@latestThis scaffolds a project with a tests/ folder and playwright.config.ts. Every test below uses the request fixture injected by Playwright Test.
1. GET — Read (List Users)
What it does: Fetches all users and confirms the API responded successfully with data.
import { test, expect } from '@playwright/test';test('GET - should fetch list of users', async ({ request }) => {
const response = await request.get('https://test.in/public/v2/users'); expect(response.status()).toBe(200); const responseBody = await response.json();
expect(responseBody.length).toBeGreaterThan(0);
});
In short: GET is read-only and idempotent — calling it repeatedly should never change server state. Always assert both the status code and that the payload looks like real data.
2. POST — Create
What it does: Creates a brand-new user using a Bearer token and a unique email (timestamp avoids duplicate-email failures).
test('POST - should create a new user', async ({ request }) => {
const response = await request.post('https://test.in/public/v2/users', {
headers: {
Authorization: 'Bearer demo-token',
'Content-Type': 'application/json',
},
data: {
name: 'Testab123',
email: `testab123_${Date.now()}@example.com`,
gender: 'M',
status: 'active',
},
});expect(response.status()).toBe(201); const responseBody = await response.json();
expect(responseBody.name).toBe('Testab123');
});
In short: POST creates a resource and should return 201 Created, plus the created object in the response body — always confirm the data you sent matches what came back.
Get Roshan Tiwari’s stories in your inbox
Join Medium for free to get updates from this writer.
3. PUT — Full Update
What it does: Replaces every field of an existing user (ID 1032 here). Any field you omit may get wiped/reset, since PUT expects the complete object.
test('PUT - should fully update a user', async ({ request }) => {
const response = await request.put('https://test.in/public/v2/users/1032', {
headers: {
Authorization: 'Bearer demo-token',
'Content-Type': 'application/json',
},
data: {
name: 'Testab123',
email: `testab123_updated_${Date.now()}@example.com`,
gender: 'M',
status: 'active',
},
}); expect(response.status()).toBe(200);
});In short: PUT = full replace. Send the complete payload, not a partial one, or you risk losing existing field values.
4. PATCH — Partial Update
What it does: Updates only the status field, leaving everything else untouched.
test('PATCH - should partially update a user', async ({ request }) => {
const response = await request.patch('https://test.in/public/v2/users/1032', {
headers: {
Authorization: 'Bearer demo-token',
'Content-Type': 'application/json',
},
data: {
status: 'inactive',
},
});expect(response.status()).toBe(200); const responseBody = await response.json();
expect(responseBody.status).toBe('inactive');
});
In short: PATCH = surgical update. Send only the fields that change — it’s lighter and safer than PUT for small edits.
5. DELETE — Remove
What it does: Deletes a user by ID and confirms the server returned no content.
test('DELETE - should delete a user', async ({ request }) => {
const response = await request.delete('https://test.in/public/v2/users/1031', {
headers: {
Authorization: 'Bearer demo-token',
},
}); expect(response.status()).toBe(204);
});In short: A successful DELETE typically returns 204 No Content — there’s nothing to validate in the body, only the status code.
6. GET with Query Params — Filtering
What it does: Fetches only users whose status equals inactive using query parameters instead of manually building a URL string.
test('GET - should filter users by status', async ({ request }) => {
const response = await request.get('https://test.in/public/v2/users', {
params: { status: 'inactive' },
});expect(response.status()).toBe(200); const responseBody = await response.json();
expect(responseBody.length).toBeGreaterThan(0);
});
In short: The params option keeps query strings type-safe and readable instead of string-concatenating ?status=inactive by hand.
Summary
This suite covers all major CRUD operations and query-parameter handling using Playwright’s built-in request API, with clear assertions for status codes and response bodies — a solid, interview-ready foundation for API test automation.






