API AUTOMATIONINTERMEDIATE

API Testing with Playwright: Request Context, Auth, and Assertions

Use Playwright's request context to test REST APIs — send requests, verify status and JSON, share auth state with UI tests.

iff Solution Academy July 4, 2026 15 min read Updated July 7, 2026
Playwright API REST Request Context

Introduction

Most teams learn Playwright for UI automation and never realise it also ships with a first-class HTTP client. Playwright's request context — accessed as request in every test — is a fully featured Node.js HTTP client that supports cookies, storage state, base URLs, custom headers, HTTPS options, and shared authentication with UI tests. That means you can write REST and GraphQL tests without pulling in axios, supertest, or a separate framework.

This tutorial walks through building a complete API test suite with Playwright: from your first GET request, through a full CRUD flow, to sharing an authenticated session across both API and UI tests. The examples are runnable against the public https://reqres.in and https://jsonplaceholder.typicode.com sandboxes, so you can follow along without any backend of your own.

Why API Testing with Playwright?

If you already own a Playwright test suite for UI, using the same runner for APIs is a substantial productivity win:

  • One test runner, one config, one HTML report covering both API and UI.
  • One CI pipeline — no separate Postman/Newman or REST-assured job.
  • Shared TypeScript types between UI and API tests.
  • Hybrid tests: seed data via API in one line, verify it in the UI in the next.
  • Built-in retries, sharding, parallelisation, and trace viewer — same features you already use for UI.
  • No extra dependencies — request is part of @playwright/test.

You also inherit Playwright's excellent debugging story: failing API requests appear in the HTML report with full request/response bodies, timing information, and headers.

Prerequisites

  • Playwright installed and running — see our Getting Started guide if not.
  • Node.js 18+ and TypeScript basics.
  • Familiarity with REST concepts: HTTP methods, status codes, JSON bodies, headers.
  • A REST API to test — this tutorial uses https://reqres.in as a free sandbox.

Step 1: The Request Context

The simplest API test uses the built-in request fixture. Each test gets an isolated context, so cookies and auth do not leak between tests:

tests/api/users.spec.ts
import { test, expect } from '@playwright/test';

test('list users returns 200 and a data array', async ({ request }) => {
  const res = await request.get('https://reqres.in/api/users?page=2');

  expect(res.status()).toBe(200);
  expect(res.ok()).toBeTruthy();

  const body = await res.json();
  expect(Array.isArray(body.data)).toBe(true);
  expect(body.data.length).toBeGreaterThan(0);
});

To share a base URL and default headers across a whole suite, configure them once in playwright.config.ts:

playwright.config.ts
use: {
  baseURL: 'https://reqres.in',
  extraHTTPHeaders: {
    Accept: 'application/json',
    'x-api-key': process.env.API_KEY ?? '',
  },
},

Now every request({ ... }) call can use relative paths.

Step 2: A Full CRUD Example

A realistic API suite covers the full lifecycle of a resource. This spec creates a user, updates them, reads them back, and deletes them — asserting the correct status code at each step:

tests/api/user-crud.spec.ts
import { test, expect } from '@playwright/test';

test('user CRUD lifecycle', async ({ request }) => {
  // CREATE
  const created = await request.post('/api/users', {
    data: { name: 'Ada Lovelace', job: 'Engineer' },
  });
  expect(created.status()).toBe(201);
  const user = await created.json();
  expect(user).toMatchObject({ name: 'Ada Lovelace', job: 'Engineer' });
  const id = user.id;

  // UPDATE
  const updated = await request.put(`/api/users/${id}`, {
    data: { name: 'Ada L.', job: 'Author of first algorithm' },
  });
  expect(updated.ok()).toBeTruthy();

  // DELETE
  const deleted = await request.delete(`/api/users/${id}`);
  expect(deleted.status()).toBe(204);
});

Notice how Playwright's expect().toMatchObject() lets you assert on partial JSON — you check what matters and ignore server-generated fields like createdAt.

Step 3: Sharing Auth State with UI Tests

UI logins are slow and flaky. Real teams log in once via API, save the cookies and localStorage, and reuse them for every UI test. Playwright makes this trivial with storageState.

global-setup.ts
import { request } from '@playwright/test';

export default async () => {
  const api = await request.newContext();
  await api.post('https://reqres.in/api/login', {
    data: { email: 'eve.holt@reqres.in', password: 'cityslicka' },
  });
  // Persist cookies + localStorage for every UI project to reuse.
  await api.storageState({ path: 'state.json' });
  await api.dispose();
};
playwright.config.ts
export default defineConfig({
  globalSetup: './global-setup.ts',
  use: { storageState: 'state.json' },
});

Every subsequent test starts with the user already logged in — reducing suite time by 30–70% on login-heavy apps.

Step 4: Response Assertions

Beyond status codes, Playwright's expect() can assert on headers, JSON shape, and even schemas via matchers:

ts
// Status
expect(res.status()).toBe(201);
expect(res.ok()).toBeTruthy();

// Headers
expect(res.headers()['content-type']).toContain('application/json');

// JSON shape
const body = await res.json();
expect(body).toMatchObject({ id: expect.any(Number), name: 'Ada' });
expect(body.tags).toEqual(expect.arrayContaining(['engineer']));

// Negative assertion
expect(body).not.toHaveProperty('password');
For strict schema validation, combine Playwright with a library like zod or ajv to assert against a full JSON schema.

Step 5: Hybrid API + UI Flows

The killer use case: seed test data via API (fast, deterministic) then verify it in the UI (real user behaviour).

ts
test('newly created order appears in the dashboard', async ({ page, request }) => {
  // 1. Seed via API — fast
  const res = await request.post('/api/orders', { data: { total: 42 } });
  const order = await res.json();

  // 2. Verify via UI — real user path
  await page.goto('/dashboard');
  await expect(page.getByRole('row', { name: `Order ${order.id}` })).toBeVisible();
});

Step 6: Reusable API Fixtures

Once you have more than a handful of tests, extract an authenticated API client as a custom fixture:

fixtures/api.ts
import { test as base, expect, APIRequestContext } from '@playwright/test';

type ApiFixtures = { api: APIRequestContext };

export const test = base.extend<ApiFixtures>({
  api: async ({ playwright }, use) => {
    const api = await playwright.request.newContext({
      baseURL: 'https://reqres.in',
      extraHTTPHeaders: { Authorization: `Bearer ${process.env.TOKEN}` },
    });
    await use(api);
    await api.dispose();
  },
});
export { expect };

Every test that imports { test } from this file receives a preconfigured, authenticated API client — no boilerplate per test.

Common Pitfalls

  • Forgetting to await request.* — every HTTP call is async.
  • Calling res.json() twice — the body stream can only be consumed once. Store it in a variable.
  • Hard-coding cookies or tokens in the test file — always read from process.env or storageState.
  • Testing against a shared staging environment without cleanup — flaky, order-dependent tests. Prefer isolated test data per run.
  • Not calling api.dispose() on manually created contexts — resource leaks in long suites.
  • Assuming reqres.in / jsonplaceholder actually persist data — they simulate responses but do not save.
  • Mixing baseURL with absolute URLs by accident — either use one or the other consistently.

Debugging Tips

  • Log the raw body when an assertion fails: console.log(await res.text()) — always call text() (not json()) in a catch so malformed responses do not throw again.
  • Enable trace: 'on' in playwright.config.ts — the Trace Viewer shows every API request with headers, body, and timing.
  • Use --reporter=list temporarily for line-by-line output during local debugging.
  • Wrap a failing endpoint in a request.get(..., { timeout: 30_000 }) to rule out slow-network flakes.
  • Check res.headers() when authentication mysteriously fails — 401s often come with a WWW-Authenticate header explaining why.
  • Curl the same endpoint outside of Playwright to confirm the failure is not in your test code.

When to Use Playwright for API Testing (and When Not To)

Great fit

  • Teams that already own Playwright for UI and want to consolidate tooling.
  • Hybrid API + UI test flows (seed via API, verify via UI).
  • End-to-end regression suites where API and UI are covered together.
  • Auth flows that need cookies or storage state shared with UI tests.

Not the best choice

  • Contract testing — use Pact or Spring Cloud Contract.
  • Heavy load or performance testing — use k6, JMeter, or Gatling.
  • Deep protocol testing (gRPC streaming, WebSocket load) — use specialised tools.
  • Teams with no UI tests at all — a lighter tool like supertest may be simpler.

FAQ

Does the request context share cookies with page?

Only if you pass storageState between them, or use the same BrowserContext. By default request.newContext() is isolated from any page.

Can Playwright test GraphQL?

Yes — send a POST to the GraphQL endpoint with { query, variables } in the body. All the assertion tools work the same way.

How do I upload a file?

Pass a multipart object: request.post('/upload', { multipart: { file: fs.createReadStream('cv.pdf') } }).

How do I set a per-test header?

Pass headers directly on the call: request.get('/users', { headers: { 'x-trace': 'abc' } }). This overrides extraHTTPHeaders from the config.

Should I write API tests in the same file as UI tests?

For hybrid flows (seed + verify) yes. For pure API regression, keep them in a separate tests/api folder with their own project in playwright.config.ts.

Summary

Playwright's request context turns your UI automation framework into a complete API testing tool. You get status-code checks, JSON assertions, header validation, file uploads, cookie management, and storage-state sharing — all with the same familiar expect() API and the same Trace Viewer for debugging.

For most teams the biggest wins come from two patterns: (1) log in once via API and reuse storageState for every UI test, and (2) seed test data through the API before UI assertions. Together they cut suite time significantly while making tests dramatically more deterministic.

What Belongs in an API Test

The most useful question to ask before writing an API test is what would break if this rule were wrong. Status codes, response shape, validation errors, authorisation boundaries, pagination behaviour, and idempotency are all things only the API can tell you, and they are all cheap to verify. Rendering, layout, and client-side navigation are not — those belong to UI tests, and duplicating them at the API layer wastes runtime without adding confidence.

A well-shaped API test has three visible parts: arrange the data, perform one request, assert the outcome. When a test performs six requests, it is really an integration flow, and it should be named and treated as one — with the setup calls pushed into a fixture so a failure in setup is distinguishable from a failure in the behaviour under test.

Combining layers is where Playwright shines relative to dedicated API tools. Seeding a shopping cart through the API and then asserting the checkout page in the browser gives you a fast, realistic end-to-end test without twelve clicks of setup. The rule of thumb is to use the API for everything up to the behaviour you are actually testing, and the UI only for that behaviour.

Common Mistakes

  • Testing third-party APIs you do not control. Mock them at your boundary and test your own handling of their responses instead.
  • Writing one test per endpoint regardless of risk, so trivial read endpoints get the same attention as payment authorisation.
  • Skipping negative cases because the happy path passes. Most production incidents live in error handling.
  • Asserting on response times inside functional tests. Performance deserves its own tooling and its own thresholds.
  • Letting API tests depend on UI-created data, which reintroduces every source of UI flakiness into a suite meant to avoid it.

Frequently Asked Questions

Should API tests run against a mocked backend or a real one?

Against a real deployed environment for regression, because that is where configuration and integration bugs live. Mocks are appropriate for third-party dependencies and for simulating failure responses you cannot trigger on demand.

How do I test rate limiting without breaking the environment?

Give the test its own client or API key with a low limit, then assert the 429 and the Retry-After header. Never hammer a shared limit in a shared environment.

Can Playwright replace a dedicated contract testing tool?

Partially. Schema validation on live responses catches most drift, but true consumer-driven contract testing verifies both sides independently and remains worthwhile in service-heavy architectures.

Get Playwright tutorials in your inbox

Weekly tips, real-world examples, and framework patterns – no spam, unsubscribe anytime.

Playwright Framework Series

View all →
  1. 1Getting Started with Playwright: Installation, Setup, and Your First Test
  2. 2Playwright Locators: The Complete Guide with Real-World Examples
  3. 3Playwright Actions: Complete Guide to Click, Fill, Hover, Keyboard, Mouse & File Upload
  4. 4Playwright Assertions: The Complete Guide with Real-World Examples
  5. 5Playwright Auto Waiting: The Complete Guide with Real-World Examples
  6. 6Playwright Fixtures: The Complete Guide with Real-World Examples
  7. 7Playwright Browser Context & Multiple Tabs: The Complete Guide with Real-World Examples
  8. 8Playwright Authentication & Session Management: Complete Guide with Enterprise Examples
  9. 9Playwright Network Interception & API Mocking: Complete Guide with Real-World Examples
  10. 10Playwright Page Object Model (POM): The Complete Guide with Enterprise Examples
  11. 11Page Object Model with Playwright: A Practical Guide
  12. 12How to Build a Robust Professional Playwright Framework from Scratch (Step-by-Step)
  13. 13Building an Enterprise Playwright Framework from Scratch

API Testing Series

View all →
  1. 1Playwright API Testing: The Complete Guide with Real-World Examples
  2. 2Part 1 : Playwright API Testing Tutorial: Build Enterprise-Level API Automation Framework
  3. 3Part 2: Playwright API Testing Tutorial: Setting Up Project & Sending Your First API Request
  4. 4Part 3: Mastering CRUD Operations in Playwright API Testing
  5. 5Part 4: Authentication in Playwright API Testing (Bearer Token, JWT, API Key & Reusable Fixtures)
  6. 6Part 5: Building an Enterprise-Level Playwright API Automation Framework
  7. 7Part 6: API Models, Schema Validation & Test Data Management in Playwright
  8. 8Part 7: Custom Fixtures, Hooks, Logging & Parallel Execution in Playwright API Testing
  9. 9Part 8: Building a Production-Ready Playwright API Framework (Environment Management, CI/CD, Reporting & Best Practices)
  10. 10Part 9: Advanced Playwright API Testing Techniques for Enterprise Automation
  11. 11Part 10: Building a Complete Enterprise Playwright API Automation Framework (Final Part)
  12. 12API Testing with Playwright: Request Context, Auth, and Assertions

Recommended Next Articles