Playwright is not only for browsers: it includes a complete HTTP client for API testing. In this lesson you will test a REST API with GET, POST, PUT, PATCH and DELETE requests, handle authentication, type your responses and combine API calls with UI tests.
๐ What You’ll Learn: why API testing in Playwright, an API project with baseURL, the request fixture, GET with params, POST, PUT, PATCH and DELETE, request options and response methods, authentication with tokens, typed responses, and combining API setup with UI checks.
๐ Why API Testing with Playwright?
Most applications are a user interface on top of REST APIs. Testing those APIs directly is fast, stable and catches backend bugs early. Playwright includes a full HTTP client, so you can write API tests in the same framework, language and report as your UI tests, with no Postman collection or separate library to maintain.
API tests in Playwright:
- Run in milliseconds, without opening a browser.
- Use the same
expectassertions, fixtures, tags, retries and HTML report. - Can be combined with UI tests, for example to create test data via API before checking it in the browser.
We will use JSONPlaceholder (https://jsonplaceholder.typicode.com), a free fake REST API. It accepts POST, PUT, PATCH and DELETE requests and returns realistic responses, but does not really save changes.
โ๏ธ Setup: baseURL for API Tests
Set the API address once in playwright.config.ts. A separate project keeps API tests apart from browser tests:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'api',
testMatch: /.*\.api\.spec\.ts/,
use: {
baseURL: 'https://jsonplaceholder.typicode.com',
extraHTTPHeaders: { Accept: 'application/json' },
},
},
{ name: 'chromium', testIgnore: /.*\.api\.spec\.ts/, use: { ...devices['Desktop Chrome'] } },
],
});
npx playwright test --project=api
๐ฅ GET: Read Data
The built-in request fixture is an APIRequestContext. Relative URLs use the baseURL:
// tests/posts.api.spec.ts
import { test, expect } from '@playwright/test';
test('GET a single post', async ({ request }) => {
const response = await request.get('/posts/1');
expect(response.status()).toBe(200);
await expect(response).toBeOK(); // any 2xx status
const post = await response.json();
expect(post).toMatchObject({ id: 1, userId: 1 });
expect(typeof post.title).toBe('string');
});
test('GET with query parameters', async ({ request }) => {
const response = await request.get('/posts', { params: { userId: 1 } });
await expect(response).toBeOK();
const posts = await response.json();
expect(posts).toHaveLength(10);
for (const p of posts) {
expect(p.userId).toBe(1);
}
});
๐ค POST: Create Data
test('POST creates a post', async ({ request }) => {
const newPost = { title: 'Learn Playwright API testing', body: 'It is fast!', userId: 1 };
const response = await request.post('/posts', { data: newPost });
expect(response.status()).toBe(201);
const created = await response.json();
expect(created).toMatchObject(newPost);
expect(created.id).toEqual(expect.any(Number));
});
data is sent as JSON automatically when you pass an object. For HTML forms use form: { ... }, and for file uploads use multipart: { ... }.
โ๏ธ PUT and PATCH: Update Data
test('PUT replaces a post', async ({ request }) => {
const response = await request.put('/posts/1', {
data: { id: 1, title: 'Updated title', body: 'Updated body', userId: 1 },
});
await expect(response).toBeOK();
expect((await response.json()).title).toBe('Updated title');
});
test('PATCH updates one field', async ({ request }) => {
const response = await request.patch('/posts/1', { data: { title: 'Only the title changed' } });
await expect(response).toBeOK();
expect((await response.json()).title).toBe('Only the title changed');
});
PUT replaces the whole resource; PATCH changes only the fields you send.
๐๏ธ DELETE: Remove Data
test('DELETE removes a post', async ({ request }) => {
const response = await request.delete('/posts/1');
expect(response.status()).toBe(200);
});
๐งฐ Request Options and Response Methods
| Request option | Purpose |
|---|---|
params | Query string, e.g. { userId: 1 } |
data | JSON body (object) or raw string |
form | URL-encoded form body |
multipart | Multipart body with files |
headers | Extra headers for this request |
timeout | Request timeout in ms |
failOnStatusCode | Throw automatically if the status is not 2xx or 3xx |
| Response method | Returns |
|---|---|
status() | Status code, e.g. 200 |
ok() | true for 200 to 299 |
json() | Parsed JSON body |
text() | Body as a string |
headers() | Response headers as an object |
body() | Raw body as a Buffer |
๐ Authentication: Tokens and Headers
Real APIs need authentication. Keep secrets in environment variables (.env files) and send them as headers:
// For every request in the project
use: {
baseURL: process.env.API_URL,
extraHTTPHeaders: { Authorization: 'Bearer ' + process.env.API_TOKEN },
},
Or log in once and create an authenticated context yourself:
import { test, expect, request as playwrightRequest, APIRequestContext } from '@playwright/test';
let api: APIRequestContext;
test.beforeAll(async () => {
const auth = await playwrightRequest.newContext({ baseURL: process.env.API_URL });
const login = await auth.post('/auth/login', { data: { username: process.env.API_USER, password: process.env.API_PASSWORD } });
const { token } = await login.json();
api = await playwrightRequest.newContext({
baseURL: process.env.API_URL,
extraHTTPHeaders: { Authorization: 'Bearer ' + token },
});
});
test.afterAll(async () => {
await api.dispose();
});
test('get my profile', async () => {
const response = await api.get('/me');
await expect(response).toBeOK();
});
๐งพ Type Your API Responses
Give responses a TypeScript type so your editor autocompletes fields and catches typos (interfaces, generics):
interface Post { userId: number; id: number; title: string; body: string }
test('typed response', async ({ request }) => {
const post: Post = await (await request.get('/posts/1')).json();
expect(post.title.length).toBeGreaterThan(0);
});
Types do not validate data at runtime. For strict contract checks, combine toMatchObject and expect.any(Number) as above, or add a JSON-schema library.
๐ Combine API and UI Tests
The real power: use the API for setup and the browser for verification. page.request shares cookies with the browser page, so it acts as the logged-in user:
test('order created via API appears in the UI', async ({ page }) => {
await page.goto('https://your-app.example.com/login');
// ...log in through the UI or reuse a saved session...
const response = await page.request.post('/api/orders', { data: { product: 'Backpack', qty: 1 } });
await expect(response).toBeOK();
const order = await response.json();
await page.goto('/orders');
await expect(page.getByText('Order #' + order.id)).toBeVisible();
});
This is faster and more reliable than creating the order by clicking through five screens. Organizing API calls in classes is covered in API automation with Page Object Model.
๐ Assignment
Use https://jsonplaceholder.typicode.com:
- Create an
apiproject withbaseURLand a*.api.spec.tsfile. - GET
/users/1and assert the user hasid: 1and a stringemail. - GET
/commentswithparams: { postId: 1 }and assert every comment haspostId1. - POST a new todo to
/todosand assert status 201 and the returned title. - PATCH
/todos/1tocompleted: trueand assert the response. - DELETE
/todos/1and assert status 200. - Bonus: write an interface for
Todoand a generic helpergetJson<T>(request, url)that returns typed data.
โ FAQ
๐ค Can Playwright be used for API testing?
Yes. The built-in request fixture (APIRequestContext) sends GET, POST, PUT, PATCH and DELETE requests, and you assert responses with the same expect as UI tests.
๐ค How do I send a POST request in Playwright?
await request.post('/posts', { data: { title: 'x' } }). Passing an object as data sends it as JSON.
๐ค How do I add an authorization header in Playwright API tests?
Set extraHTTPHeaders: { Authorization: 'Bearer ' + token } in the config or in request.newContext(), or pass headers on a single request.
๐ค Do Playwright API tests need a browser?
No. The request fixture sends HTTP requests directly, so API tests run without launching a browser.
๐ค Is Playwright a replacement for Postman?
For automated API tests in CI, yes: tests live in code, run with your UI suite and share one report. Postman remains handy for manual exploration.
๐ฏ Next Steps
- ๐ญ API Automation with Page Object Model
- ๐ญ Network Interception and API Mocking
- ๐ญ Page Object Model (POM) Step by Step
- ๐ญ Playwright Tutorial with TypeScript
๐ฌ Watch the full Playwright tutorial series on YouTube!
Subscribe to SqaToolsByDY • Practice on sqatools.in
4 thoughts on “API Testing with Playwright and TypeScript: GET, POST, PUT, PATCH, DELETE”