API Testing with Playwright and TypeScript: GET, POST, PUT, PATCH, DELETE

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.

Advertisement

๐Ÿ“– 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 expect assertions, 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 optionPurpose
paramsQuery string, e.g. { userId: 1 }
dataJSON body (object) or raw string
formURL-encoded form body
multipartMultipart body with files
headersExtra headers for this request
timeoutRequest timeout in ms
failOnStatusCodeThrow automatically if the status is not 2xx or 3xx
Response methodReturns
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:

  1. Create an api project with baseURL and a *.api.spec.ts file.
  2. GET /users/1 and assert the user has id: 1 and a string email.
  3. GET /comments with params: { postId: 1 } and assert every comment has postId 1.
  4. POST a new todo to /todos and assert status 201 and the returned title.
  5. PATCH /todos/1 to completed: true and assert the response.
  6. DELETE /todos/1 and assert status 200.
  7. Bonus: write an interface for Todo and a generic helper getJson<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

๐ŸŽฌ 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”

Leave a Comment