Home / Services / Playwright API testing

Playwright API testing, in the same project as your browser tests

The HTTP calls your suite needs before and after it touches the screen: the login, the record a test starts from, and the question of whether the server recorded what the interface claimed.

Short answer

We build a Playwright suite's HTTP layer: the request fixtures, the login over the API, the seeded state a browser test needs, and the checks that ask the server whether the write landed. It runs in the same project and pipeline as your browser tests. Per engineer, hourly, from $50 an hour, minimum one full-time engineer for one month.

Who this is for

You already run Playwright. There is a suite, it goes green most mornings, and somebody on the team owns it. Then one of these happened.

  • Every spec signs in through the login form, and somebody has now worked out what that costs across a day of runs.
  • A test reaches the screen it is named after by clicking through four screens first, and those four break more often than the fifth one does.
  • A release went out where the interface showed a confirmation and the order never reached the database. The suite was green the whole way through. That one is reproduced further down this page against a confirm endpoint that answers 200 and stores nothing: both browser assertions still pass, and the run fails only on the line that reads the server.

The engagement assumes a repository somebody else already shaped. If there is no Playwright project yet, or forty specs with no fixtures and no CI job, the request layer arrives inside the work of building that repository and this is not the page you want.

If what you need to know is whether every endpoint returns the right shape for every input, that is a contract suite, and it is a different purchase from this one. The section below says where we think it should live.

Should this live in Playwright at all?

Playwright's request fixture sends real HTTP from a test file, in the same runner as the browser tests. There is no client to construct, no second tool to install and no second pipeline for somebody to own.

It is also not an API-testing tool in the way REST Assured, Postman or Karate are. APIRequestContext carries nine methods and not one of them concerns a schema; one assertion ships for an API response, toBeOK. Schema validation and contract testing are not in the box. The guide listed under Read first takes that surface apart method by method; what belongs here is what it changes about the purchase.

So for a contract suite — every endpoint, every status, every field checked against a schema, owned by the team that ships the API and run on the API's own pipeline when the API deploys — a dedicated tool is usually the better home. The reason is ownership and the shape of the coverage, not sophistication. If that is what you already have, keep it where it is.

What Playwright reaches that a dedicated tool cannot is the half that has a browser in it: setup and teardown over the API for a test that then uses the screen; data seeded over HTTP and asserted in the interface; the user action performed properly, then the server asked whether it agreed. The engagement on this page is that hybrid one, and a team that wants only the contract suite is not being sold the strongest thing we have.

What you get

Three things land in your repository, and each of them is a file somebody can open in review. The contents depend on what your API turns out to look like.

The request layer, as files

A fixtures/api.ts that a spec asks for the way it asks for page, wrapping the HTTP calls your tests make into named operations instead of raw URLs scattered through specs. The base URL and the headers every call needs are set once, per environment, in playwright.config.ts. Authentication is minted once over the API and handed to the tests that need it, so signing in stops being something each spec does for itself.

The seeded-state helpers

The calls that create the record a test needs and remove it when the test is finished, run as fixture setup and teardown, so a test that fails still clears the records it made. A browser test then starts on the screen it is named after, with the order, the account or the invoice it needs already there.

The checks, in the pipeline you have

The postcondition assertions that ask the server whether the interface told the truth, running in the same CI job, reported the way your suite is already reported, and traced the way it is already traced.

The file names above are the TypeScript ones, and the shapes they describe are not all language-neutral. Playwright's test runner ships only with the JavaScript and TypeScript binding: the playwright.config.ts model and the fixture that arrives on a test's argument list belong to it. The request context itself is in all four bindings, and Playwright's own API-testing documentation shows the same setup, teardown and postcondition work in Python, Java and .NET — built inside a pytest, JUnit or NUnit fixture, which is where a Python or Java version of card one lands.

How it works

  1. The read

    We run your suite, read how each test gets itself into position today, and read whatever API documentation exists — a schema file, a Postman collection, a wiki page somebody last touched in March. Out of that comes a list of the calls worth having and the order to build them in. You give us read access to the test repository, and an environment the tests are allowed to create and delete data in. That second one is the commonest reason this work stalls: a suite that seeds over the API needs somewhere it may write, and a shared staging environment three teams hand-curate is not that place.

  2. The first slice

    One flow, moved end to end. Its login goes over the API, its setup goes over the API, and one postcondition check is added to it. It is merged and running in CI before the second flow is started, so the shape every other flow will copy gets argued about on one file. You give us a review, and a decision on the shape from whoever maintains these tests after we go.

  3. The rest, and the handover

    The remaining flows in batches, the fixture generalised as more of them need it, the teardown made real, and the conventions written down: which calls belong in a test, which belong in a fixture, and which are the backend team's rather than ours. Each batch merges on its own.

None of the phases above has a duration attached. The length depends on how many flows there are, how much of your API is reachable without a browser session, whether a test environment exists that tolerates writes, and how much of the setup currently happening through the screen has an endpoint behind it at all.

The artifact, in two files

In practice the first two cards are one pair of files. The fixture wraps the calls a test needs into named operations and cleans up after itself; the spec takes it on its argument list beside page, so the browser and the HTTP client arrive from the same place.

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

type OrdersApi = {
  create(reference: string): Promise<string>;
  read(id: string): Promise<APIResponse>;
};

export const test = base.extend<{ api: OrdersApi }>({
  api: async ({ request }, use) => {
    const created: string[] = [];

    await use({
      async create(reference) {
        const response = await request.post('/api/orders', { data: { reference } });
        expect(response, `POST /api/orders for ${reference}`).toBeOK();
        const { id } = await response.json();
        created.push(id);
        return id;
      },
      read(id) {
        return request.get(`/api/orders/${id}`);
      },
    });

    for (const id of created)
      await request.delete(`/api/orders/${id}`);
  },
});

export { expect };
Both samples were written for this page and run against Playwright 1.62.1.

Nothing in the fixture is clever. It creates what a test asks for, remembers the ids it made, and deletes them after the test has finished with them, so a failing run does not leave a trail of half-made records for the next one to trip over.

// tests/orders.spec.ts
import { test, expect } from '../fixtures/api';

test('confirming an order in the UI reaches the server', async ({ page, api }) => {
  const id = await api.create('ORD-4417');

  await page.goto(`/orders/${id}`);
  await expect(page.getByRole('heading', { name: 'ORD-4417' })).toBeVisible();

  await page.getByRole('button', { name: 'Confirm' }).click();
  await expect(page.getByText('Confirmed')).toBeVisible();

  const response = await api.read(id);
  await expect(response).toBeOK();
  expect((await response.json()).status).toBe('confirmed');
});

The browser half of that test is ordinary. The last two lines add the question the interface cannot answer about itself. We ran this against a small application with the confirmation endpoint changed so that it returns a success and never records anything: the page still shows the confirmation, both browser assertions still pass, the status check on the response passes too because the endpoint answers 200 perfectly honestly, and the run fails only on Expected: "confirmed" against Received: "draft". Neither the browser half of the suite nor a check on the response code could have produced that failure.

How the request context works underneath, and what else it can be asked to do, is a guide of its own, written for the reader who has decided to do this themselves.

Where this stops

"API testing" is a wide enough phrase that a buyer and a vendor can agree on it and mean two different engagements, so here is the edge of this one.

  • We do not load test an API. A loop of request.get() is not a load test. Playwright measures one real browser doing one thing; k6, JMeter and Gatling exist for the other question, and no load or performance engagement is sold by this company in any framework.
  • We do not security test or penetration test an API. Auth bypass, injection, a dependency CVE list: none of it is offered here, in Playwright or anywhere else.
  • We do not build your API, and we do not write the application's own integration tests inside the application repository. We work in the test repository, against the API as it stands, and a missing endpoint is a finding rather than a task we pick up.
  • A contract suite the backend team owns stays where it is. If what you need is every endpoint validated against a schema on the API's own pipeline, the tool doing that today is the tool to keep, and we will say so before anything is signed.
  • Playwright drives browsers. If the API you want covered serves a phone app, the API is something we can call and the app is not. Playwright emulates a mobile browser — a phone-sized viewport, a mobile user agent, touch input — and it cannot drive a native iOS or Android application.

What it costs

There is no per-endpoint price and no package here; the unit is engineer time. Engineers are billed hourly, from $50 an hour, depending on where the engineer sits. The minimum engagement is one full-time engineer for one month. All seventy-five engineers here work in Playwright and nothing else, so this is not work that comes off a general QA bench.

The size of the job is countable without asking us. How many flows currently sign in and set themselves up through the screen. How much of that setup has an endpoint behind it, and how much of it only exists as a sequence of clicks. Whether there is an environment the tests may create and delete data in, or whether one has to be argued for first. And whether authentication is a form post or a redirect through an identity provider, which is the item that varies most between two applications of the same size.

The audit is a separate route and it is priced separately. It is optional, it is billed by the hour, and how long it takes depends on the project. The minimum of one full-time engineer for one month does not apply to it.

Read first

Questions

Can Playwright replace Postman or REST Assured?

For the calls a browser test needs around itself — signing in, seeding a record, asking afterwards whether the server kept what the screen claimed — yes, and gladly, because those calls belong in the same project and the same CI job as the test that depends on them. For a contract suite that walks every endpoint and checks every response against a schema, usually not, and Playwright's own API surface is the reason: no schema validation ships with it. So if that suite exists and the API team owns it, our advice is to leave it there and build the browser-side calls beside it, and you get that advice before anything is signed rather than after the first invoice.

What language will the tests be in?

All four of Playwright's official bindings are on the table: TypeScript or JavaScript, Python, Java and .NET. The HTTP half is in all four — the request context, the state seeded before a test, the check made after it, the authentication reused across tests — so the work is written in whichever language your team already maintains. Playwright's own test runner ships only with the JavaScript and TypeScript binding, so the fixture arriving on a test's argument list and the config that holds base URLs and headers are shapes from that binding; in Python, Java or .NET that request context is built inside a pytest, JUnit or NUnit fixture instead. Code on this site is TypeScript by default; your repository would be written in whatever your team picked.

Do we need to already have a Playwright suite?

This engagement assumes one: a suite that runs, that somebody maintains, whose setup and login still go through the screen. If there is no Playwright repository yet, or one nobody has shaped, the request layer gets built as part of building that repository, and that is Playwright framework development — that is a different engagement and it is bought on its own. Say which of the two you are looking at in the first email and we will tell you which one you are asking for.

Can the API calls run against a different environment from the UI?

Yes. A request context can be created with a baseURL of its own through request.newContext, so the HTTP calls reach one host while the browser reaches another. On the JavaScript and TypeScript runner the same split can also be expressed as a project per environment in the config. Where that seam falls gets decided in the first phase, because it changes what the pipeline has to hold and what the credentials have to cover.

What does this cost, and do we need the audit first?

Engineers are billed hourly, from $50 an hour, depending on where the engineer sits. The minimum engagement is one full-time engineer for one month. It moves on how many flows set themselves up through the screen today, how much of that setup has an endpoint behind it, whether there is an environment the tests may write to, and what your authentication does. You do not need one first. The audit is there for a team that wants to know what it has before it picks a direction. It is optional, it is billed by the hour, and how long it takes depends on the project. The minimum of one full-time engineer for one month does not apply to it.

Do API tests already exist somewhere in your company?

That is the answer that decides this engagement, so it is the one to put in the first email: whether somebody is already testing these endpoints, in what, and which team owns it. If the answer is a contract suite the API team runs, the conversation is about leaving it alone and building the calls your browser tests need beside it. If the answer is nobody, it is a shorter conversation. Either way the first exchange is about which of your flows have an endpoint behind them and what the first slice would be. We do not need an audit to start.