API Testing in Playwright

By Syncfusion®, Inc. · Last updated

Why test APIs directly, the request fixture, sending GET, POST, PUT, PATCH and DELETE requests with JSON, form data, query params and headers, checking status, headers and JSON with toBeOK and toMatchObject, API authentication with tokens and cookies, a full create-read-update-delete flow, error cases, separate request contexts, and mixing API and UI steps in one test.

API Testing

  • Behind most web pages is an API: a set of URLs that send and receive data, usually as JSON.
  • Playwright can call that API directly, with no browser page at all. These tests are very fast.
  • In this lesson, you will test the Practice Shop's API, and use it to set up data for UI tests.

Simple lesson: an API test sends a request and checks the answer. No page, no clicks.

A Real-World Example

  • The shop's API has a product endpoint: GET /api/products/2.
  • This test asks for product 2 and checks the answer. It needs no page, so it finishes in milliseconds.
  • The test uses the request fixture instead of page. No browser page opens.

What You Will Learn

  1. Why API tests are useful, and how they fit with UI tests.
  2. The request fixture and the five common methods.
  3. Checking status codes, headers and JSON.
  4. Sending JSON, form data, query params and headers.
  5. Logging in to an API with a token or a cookie.
  6. A full create, read, update, delete flow, with error cases.
  7. Using the API to prepare data for UI tests.

Why Test the API

Faster and More Focused

  • A UI test loads pages, waits for elements and clicks. An API test sends one request.
  • API tests are often 10 to 100 times faster. You can run many more of them.
  • They check the rules of the app directly: prices, validation, permissions.
  • UI tests are still needed. They check what the user actually sees and does.

The Shop's API

MethodURLWhat it does
GET/api/productsList products. ?category=Fitness filters them
GET/api/products/:idOne product
POST/api/loginLog in. Returns a token and sets a cookie
GET/api/meThe logged-in user
GET/api/ordersYour orders
POST/api/ordersPlace an order
GET, PATCH, PUT, DELETE/api/orders/:idRead, change, replace or delete one order

Simple lesson: test the rules through the API. Test what users see through the UI.

The request Fixture

Sending Requests

  • request.get(url), request.post(url), request.put(url), request.patch(url) and request.delete(url) send the five common methods.
  • Relative URLs use baseURL from the config, just like page.goto().
  • Each call returns an API response.

Animation: the test sends POST /api/orders with a JSON body straight to the server. The server answers 201 Created with the new order as JSON. No browser page takes part

Your test request.post()

Server API /api/orders

POST productId: 1 201 id: 1004, …

An API test talks to the server directly. There is no page in between.

Checking the Response

  • await expect(response).toBeOK() passes for any status from 200 to 299.
  • response.status() gives the exact code. response.json() reads the body. response.headers() gives the headers, with lower-case names.
  • toMatchObject() checks some fields and ignores the rest. expect.any(Number) accepts any number, for IDs you cannot know in advance.
  • params adds the query string for you: ?category=Fitness.

toBeOK() is a web-first style matcher, so write await before it. On failure it prints the status and the start of the body, which often explains the problem.

Simple lesson: toBeOK() for "it worked", status() for the exact code, json() for the data.

Sending Data

JSON, Forms and Headers

  • data: { ... } sends a JSON body and sets Content-Type: application/json.
  • form: { ... } sends a classic HTML form body.
  • headers: { ... } adds headers to one request.
  • The shop's login accepts both JSON and forms.

Authenticated APIs

Tokens in Headers

  • Many APIs want a token in the Authorization header: Bearer <token>.
  • Set it once for a whole file with test.use({ extraHTTPHeaders: { ... } }), or for the whole project in the config's use.
  • In a real project, the token comes from the login call or an environment variable, never written in the test.

Cookies Carry Over

  • The request fixture keeps cookies between calls in one test, like a browser.
  • Log in once with POST /api/login, and later calls are logged in through the cookie.
  • Since Playwright 1.64, a request context can also manage cookies directly: request.addCookies([...]), request.cookies() and request.clearCookies(), like a browser context.

Simple lesson: send a token in a header, or log in once and let the cookie do the rest.

A Full CRUD Flow

Create, Read, Update, Delete

  • CRUD means create, read, update and delete. Most APIs follow this pattern.
  • POST creates and usually answers 201 Created. GET reads. PATCH changes some fields. PUT replaces the whole thing. DELETE removes it, often with 204 No Content.
  • This test follows one order through its whole life.

Testing the Error Cases

  • Good API tests check the unhappy paths too. They are where bugs hide.
  • 400 or 422: bad input. 401: not logged in. 403: logged in, but not allowed. 404: not found. 409: a conflict, like changing a delivered order.

Tests that create data should clean it up, or use data that cannot clash. The practice API resets for every test. A real test server usually does not, so delete what you create, often in afterEach.

Simple lesson: test each status code your API promises, not only 200.

Request Contexts and Options

Your Own Request Context

  • The request fixture is made fresh for each test.
  • For setup work outside a test, like beforeAll, make your own with request.newContext() from @playwright/test.
  • Give it a baseURL and headers. Call dispose() when done.

failOnStatusCode

  • By default, a 404 or 500 does not throw. You check the status yourself.
  • failOnStatusCode: true makes any non-2xx status throw at once, with the status in the error.
  • Use it for setup calls that must work. Leave it off when you are testing error codes.

A setup call that fails quietly makes the real check fail later, with a confusing message. failOnStatusCode: true stops the test at the call that broke.

Mixing API and UI

API for Setup, UI for the Check

  • Creating test data through the UI is slow. Creating it through the API is fast.
  • page.request is a request context that shares cookies with the page. Log in or create data with it, then open the page.
  • The reverse also works: act in the UI, then check the result through the API.

Simple lesson: set up with the API, check with the UI. Each part does what it is best at.

Try It in Your Project

  • These steps take the lesson from the lab to your own app.
  • Write API tests for your three most important endpoints, including one error status each.
  • Replace one slow UI data setup with an API call through page.request.
  • Clean up the data your tests create, or use unique names per run.

Simple lesson: use one idea from this lesson on your own app this week.

TL;DR

  • The request fixture sends get, post, put, patch and delete with no page.
  • await expect(response).toBeOK(), status(), headers() and json() check the answer.
  • data sends JSON, form sends forms, params builds the query string.
  • Tokens go in extraHTTPHeaders. Login cookies carry over between calls.
  • Test every status code: 201, 204, 401, 403, 404, 409, 422.
  • page.request shares cookies with the page. Use it to set up data for UI tests.

Common mistakes

MistakeWhat happensFix
expect(response).toBeOK() without awaitThe check may not run before the test endsWrite await expect(response).toBeOK()
Only testing 200 answersValidation and permission bugs go unseenAdd tests for 4xx codes
Expecting a 404 to throwIt does not. The test carries on with bad dataCheck status(), or use failOnStatusCode: true
Checking the whole JSON with toEqual when IDs changeThe test breaks on every runtoMatchObject with expect.any(Number)
Tokens written in the test fileSecrets leak into gitRead them from environment variables

Check Yourself

Exercises

What to Remember

  • request sends API calls with no page. They are fast and focused.
  • await expect(response).toBeOK(), then status() and json().
  • data, form, params and headers shape the request.
  • Tokens in extraHTTPHeaders. Cookies carry over after a login.
  • Test the error codes, and clean up data you create.
  • API for setup, UI for what users see.

Next: Page Object Model