skip to content

Network Interception

How a Playwright run takes over the request layer: matching routes, fulfilling or aborting them, replaying HAR files, and calling APIs directly to reach states no real backend will produce.

on this pageshow

explore

questions

page 1 of 2

In Playwright, what does the built-in request fixture let a test do without opening a page?

level: juniorimportance: must knowfreq 72%

answer

  1. A per-test HTTP client fixture
  2. No page, no navigation involved
  3. get, post, put, delete, fetch
  4. Returns an APIResponse you await
  5. Honours baseURL from the config

basics

~20 s

Playwright's request fixture is an APIRequestContext, a per-test HTTP client. It sends get, post, put, patch, delete or fetch calls straight to the server, with no browser page and no navigation, honouring config options such as baseURL.

solid answer

~40 s

The `request` fixture is an `APIRequestContext` built fresh for each test and disposed when the test ends. Destructure it like any fixture — `test('...', async ({ request }) => ...)` — then call `request.get()`, `post()`, `put()`, `patch()`, `delete()`, `head()`, or the generic `request.fetch()`. Each returns an `APIResponse` you inspect with `status()`, `ok()`, `headers()`, `json()`, `text()` or `body()`. Per-call options cover `data` (the body), `form`, `multipart`, `params` (the query string), `headers` and `timeout`. It respects `use` options from the config such as `baseURL` and `extraHTTPHeaders`, so tests can pass paths instead of absolute URLs. Nothing renders: there is no page, no browser cookies and no navigation, which makes it the tool both for pure API tests and for seeding or checking server state around a browser test.

code

typescript · 16 lines
typescript
import { test, expect } from '@playwright/test';

test('the forecast endpoint answers for a saved city', async ({ request }) => {
  const created = await request.post('/api/cities', {
    data: { name: 'Bergen', units: 'metric' },
  });
  await expect(created).toBeOK();

  const forecast = await request.get('/api/forecast', {
    params: { city: 'Bergen', days: 3 },
  });
  await expect(forecast).toBeOK();

  const body = await forecast.json();
  expect(body.days).toHaveLength(3);
});

go deeper

for a junior

Recall that destructuring request in a test hands you an HTTP client, and that every call is awaited and returns a response object you then inspect.

for a middle

Explain that it is an APIRequestContext created and disposed per test, which config use options it inherits, and how data, form, params and headers map onto the wire.

for a senior

Show judgment about which parts of a suite should talk to the server directly, and about making a bad status fail loudly instead of being swallowed by an unasserted response.

for a principal

Own the boundary: how much of the product a suite may exercise over HTTP before it stops proving the shipped user path still works, and who maintains those endpoints.

Playwright's test runner ships a fixture named `request`. Ask for it in a test's argument list and you get an `APIRequestContext` — a complete HTTP client running in the Node process that executes the test. No browser is launched, no page is created, and nothing is rendered. ## What the fixture actually is `APIRequestContext` is the same class Playwright uses wherever it speaks HTTP on your behalf. The runner builds a fresh, isolated instance before each test and disposes it after, so one test cannot leak a cookie or a buffered response into the next. It is **test-scoped and lazy**: name it in the arguments and it is created; ignore it and nothing is built. Because no browser sits in the path, a call costs one round trip rather than a page load. That makes the fixture the natural tool for three jobs: - **Pure API tests**, where a browser never opens at all. - **Preconditions** — creating the rows a browser test needs before it navigates. - **Postconditions** — asking the server what it actually stored after the user clicked something. ## The call surface The verb methods are `get`, `post`, `put`, `patch`, `delete` and `head`, plus `fetch(urlOrRequest, options)`, which takes the method as an option and is what the others delegate to. Every one of them is asynchronous and returns an `APIResponse`, so every call must be awaited. The second argument carries the request: | Option | What it does | |---|---| | `data` | The body. An object is serialised as JSON with a JSON content type; a string or Buffer is sent verbatim. | | `form` | Sends the fields url-encoded instead of as JSON. | | `multipart` | Sends a multipart body, which is how you upload a file. | | `params` | Builds the query string from an object or `URLSearchParams`. | | `headers` | Per-call headers, merged over the context's `extraHTTPHeaders`. | | `timeout` | Per-call deadline in milliseconds; the context default is 30000. | | `failOnStatusCode` | When true, throws instead of returning a response outside 2xx and 3xx. | | `maxRedirects` | How many redirects to follow; `0` returns the 3xx response itself. | In Playwright 1.63 `failOnStatusCode` and `maxRedirects` can also be set once on the context rather than per call. ## Reading the response `APIResponse` exposes `status()`, `statusText()`, `ok()`, `url()`, `headers()`, `headersArray()`, and three body readers — `json()`, `text()` and `body()`. All three are asynchronous. The bodies are buffered and held by the context until it is disposed, which is exactly why the per-test fixture is disposed for you. The default is **permissive**: a 404 or a 500 is not an error, it is a response object. If a test does not assert on the status, a broken endpoint sails through silently. The idiomatic guard is `await expect(response).toBeOK()`. ## Where the configuration comes from The fixture is not configured at the call site alone. It respects the project's `use` block, so a config that sets: - `baseURL` lets calls pass `/api/forecast` rather than a full origin; - `extraHTTPHeaders` attaches an auth token or an `Accept` header to every call; - `httpCredentials`, `ignoreHTTPSErrors` and `proxy` apply the same way. `test.use({ ... })` narrows the same options to one file or `describe` block. ## A weather-dashboard shape For a dashboard backed by a third-party forecast API, a test typically does this: 1. `POST /api/cities` to register the city the test needs. 2. Assert the create call with `await expect(created).toBeOK()`. 3. `GET /api/forecast` with `params: { city, days }` and read `await response.json()`. 4. Let the fixture's automatic disposal clean up when the test ends. ## Lifetime and the usual mistakes The fixture belongs to the test. A context taken in `test.beforeAll` is disposed when that hook finishes, and Playwright reports exactly that if a test later tries to use it — for shared setup, create your own context with `playwright.request.newContext()` and dispose it yourself. The other frequent errors are forgetting an `await` (you then assert on a Promise, which is never a failing status), putting a JSON payload in `params` instead of `data`, and assuming the fixture carries the cookies of a browser context under test. It does not: it is an isolated client whose cookie jar starts empty.

  • Which per-call option puts a JSON body on a Playwright request.post, and which one builds the query string?
    `data` carries the body: pass an object and Playwright serialises it as JSON with a JSON content type, while a string or Buffer is sent as-is. `params` builds the query string from an object or `URLSearchParams`. `form` sends url-encoded fields and `multipart` sends an upload body.
  • Does a Playwright request.get() against an endpoint that returns 500 throw?
    No. By default a response object is returned for every status code, so the test must assert on it — `await expect(response).toBeOK()`, or read `response.status()`. Pass `failOnStatusCode: true`, per call or on the context, if you would rather Playwright throw on anything outside 2xx and 3xx.

saying these in an interview costs you the question

  • Thinks the request fixture drives a hidden browser page
  • Believes it automatically sends the browser context's cookies
  • Calls request.get without awaiting the response
  • Puts a JSON payload in params instead of data
  • Assumes a 404 or 500 response throws by default
  • Keeps a fixture context from beforeAll and reuses it in tests
open as a page

In Playwright, how do you capture a page's API response and assert on its body?

level: juniorimportance: must knowfreq 74%

basics

~10 s

Create the wait first with page.waitForResponse, matching by URL glob, RegExp or predicate, then trigger the action and await the promise. The resolved Response exposes status(), headers() and json() for assertions.

open as a page

In Playwright, how does route.fulfill() answer a request without it reaching the network?

level: juniorimportance: must knowfreq 84%

basics

~20 s

route.fulfill() completes a Playwright route by returning a response the test supplies, so the browser never contacts the real server. You set the status, headers and body, or pass a JSON object or a file path instead.

open as a page

In Playwright, what does browserContext.routeFromHAR() do to the requests a page makes?

level: juniorimportance: must knowfreq 62%

basics

~20 s

It installs a route that answers matching requests from a previously recorded HAR archive instead of the real server, so the test replays saved traffic. Requests the archive does not contain are aborted by default.

open as a page

In Playwright, what URL matchers can you pass to page.route() when intercepting a request?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Playwright's page.route() accepts three matcher forms: a glob string, a RegExp tested against the whole URL, or a predicate function receiving a parsed URL object. Only the URL is matched; method and headers are checked inside the handler.

open as a page

In Playwright, how do page.request and the request fixture differ in the cookies they send?

level: middleimportance: must knowfreq 58%

basics

~20 s

page.request and context.request share the browser context's cookie jar in both directions: they send its cookies and store any Set-Cookie they receive. The request fixture is a separate APIRequestContext with its own cookies and no link to the browser.

open as a page

Why must page.waitForResponse be created before the click that triggers the request in Playwright?

level: middleimportance: must knowfreq 61%

basics

~10 s

Because the wait subscribes to the page's network events at the moment it is called. A response that arrived before that call is never seen, so the promise sits unresolved until it times out.

open as a page

In Playwright, when do you call route.fallback() instead of route.continue()?

level: middleimportance: must knowfreq 57%

basics

~20 s

Call route.fallback() when this handler declines the request and another registered handler should decide; call route.continue() when the request should go straight to the real network. Continue skips every remaining handler, fallback runs the next one.

open as a page

In Playwright, what happens to a request that routeFromHAR() finds no matching entry for?

level: middleimportance: must knowfreq 52%

basics

~20 s

By default it is aborted, so the application sees a failed request rather than a response. Passing notFound: 'fallback' instead lets the request continue to the next matching route handler or to the real network.

open as a page

In Playwright, if a page.route() and a context.route() handler both match a request, which one runs first?

level: middleimportance: must knowfreq 58%

basics

~10 s

Playwright consults page-level handlers before context-level ones, and within each scope the most recently registered matching handler wins. Earlier handlers run only if the winner steps aside with route.fallback().

open as a page

In Playwright, what happens to a page's WebSocket traffic once page.routeWebSocket() handles it?

level: middleimportance: must knowfreq 55%

basics

~20 s

The route replaces the server. Playwright hands the handler a WebSocketRoute and, in Playwright 1.63, makes no connection to the real endpoint unless connectToServer() is called, so the test answers the page's messages with send() and closes with close().

open as a page

A Playwright test stubs the forecast endpoint with page.route(), but the real API is still called. How do you find out why the handler never fired?

level: seniorimportance: must knowfreq 52%

basics

~20 s

Log the real URLs with a catch-all page.route that falls back, then compare them to your matcher. Usual causes: the route was registered after page.goto, the pattern misses the host or query string, or a Service Worker answers the request.

open as a page

What does expect(response).toBeOK() assert about a Playwright APIResponse?

level: juniorimportance: should knowfreq 44%

basics

~20 s

It asserts that the response status is within the 200 to 299 range. Any 3xx, 4xx or 5xx fails, and the failure message carries the request log plus, for textual responses, the body Playwright received.

open as a page

In Playwright, how do you observe the frames a page sends and receives over a WebSocket?

level: juniorimportance: should knowfreq 38%

basics

~10 s

Playwright fires page.on('websocket') with a WebSocket object for each socket the page opens. Listen to that object's framesent and framereceived events and read frame.payload. The listener only watches; it cannot change or block traffic.

open as a page

In Playwright, how does the baseURL option resolve the path you pass to request.get()?

level: middleimportance: should knowfreq 38%

basics

~20 s

Playwright combines them with the URL constructor, not by string concatenation. An absolute URL wins outright, a leading-slash path replaces the base's whole path, and a relative path resolves against the base, where a missing trailing slash drops the base's last segment.

open as a page

In Playwright, how do you assert on the payload your app sent to an API?

level: middleimportance: should knowfreq 52%

basics

~10 s

Capture the outgoing Request with page.waitForRequest or a page.on('request') listener, then read request.postDataJSON() for the parsed body, request.postData() for the raw string, and method(), url() and headers() for the rest.

open as a page

In Playwright, what is the difference between the requestfailed and requestfinished page events?

level: middleimportance: should knowfreq 44%

basics

~20 s

requestfinished fires when a request completes and its response body has downloaded. requestfailed fires when the exchange never completed at all, such as a DNS or connection error. An HTTP 500 counts as finished, not failed.

open as a page

In Playwright, what does the error code passed to route.abort() change?

level: middleimportance: should knowfreq 44%

basics

~20 s

It picks which browser-level failure the request reports — the default is failed, with codes such as connectionrefused, namenotresolved and blockedbyclient available. Page JavaScript still sees only a generic network error, so the code affects fidelity and diagnosis, not app branching.

open as a page

In Playwright, what changes when you pass update: true to routeFromHAR()?

level: middleimportance: should knowfreq 44%

basics

~20 s

The call stops serving from the file. Requests go to the real network, their traffic is recorded, and the HAR is rewritten when the context closes, so the same test that replays a recording can also refresh it.

open as a page

In Playwright, how do you stop a page.route() handler from applying for the rest of a test?

level: middleimportance: should knowfreq 42%

basics

~10 s

Playwright removes handlers with page.unroute(matcher, handler) for one registration or page.unrouteAll() for all of them, and passing a times option to page.route() retires a handler automatically after that many matches.

open as a page

In Playwright, what does WebSocketRoute.connectToServer() change about a routed socket's message flow?

level: middleimportance: should knowfreq 40%

basics

~20 s

It opens the real connection the route suppressed and returns a second WebSocketRoute for the server side. Messages then flow through automatically in both directions until onMessage is called on a side, which makes that direction the test's responsibility.

open as a page

When should a Playwright test create its own APIRequestContext with playwright.request.newContext()?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Build one when the fixture's per-test lifetime or its configured options do not fit: shared setup in beforeAll, a different base URL or token, a captured session, or a deliberately isolated cookie jar. Then dispose it yourself when the work is done.

open as a page

Your Playwright test waits for a forecast response but the dashboard fires three similar calls; how do you match the right one?

level: seniorimportance: should knowfreq 37%

basics

~20 s

Replace the URL matcher with a predicate. page.waitForResponse accepts a function receiving the Response, so the test can require the method, status, query string or request body, not just a URL all three calls share.

open as a page

How do you make a Playwright route return the real forecast response with one field changed?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Call route.fetch() to perform the real request, parse the returned APIResponse, edit the parsed body, then settle the route with route.fulfill({ response, json }). Passing the response keeps the real status and headers while the json option replaces the payload.

open as a page

A Playwright route handler runs but the page hangs until the test times out — why?

level: seniorimportance: should knowfreq 31%

basics

~20 s

The handler almost certainly returned without settling the route. Every route owes exactly one call to fulfill, abort, continue or fallback; miss it on any branch, or throw before reaching it, and the request stays pending until an unrelated assertion times out.

open as a page

A Playwright HAR replay leaves the weather dashboard's forecast panel empty in CI though it passes locally — how do you diagnose it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Establish first whether the archive answered the request at all. Then compare the URL the app asks for against the recorded one, and check that the archive's response bodies actually travelled into CI rather than staying in uncommitted attachment files.

open as a page

Your Playwright test calls page.routeWebSocket() but the dashboard still reaches the live socket — why?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Usually the handler never ran: the route was registered after the navigation that opened the socket, the pattern does not match the ws:// URL, the traffic is not a WebSocket, or another page opened it.

open as a page

How do you decide how much of a Playwright suite's setup runs over HTTP instead of through the UI?

level: principalimportance: should knowfreq 28%

basics

~20 s

Default to HTTP for preconditions: it is fast and stops a test breaking on screens it is not about. Keep one test per journey that still does it as a user, and seed onto the session the browser will use.

open as a page

How would you scope a Playwright recordHar capture before those archives are committed to a repository?

level: principalimportance: should knowfreq 28%

basics

~20 s

Decide before recording what belongs in the file: narrow urlFilter to the endpoints under test, pick minimal mode and a zip path so archives stay small and self-contained, and treat everything captured as permanently readable repository data.

open as a page

How would you structure shared Playwright route stubs so individual tests can still override one endpoint?

level: principalimportance: should knowfreq 33%

basics

~20 s

Register broad defaults with context.route in an auto fixture, and let a test override one endpoint with page.route, which is consulted first. Keep the URL patterns in one shared module so a default and its override cannot drift apart.

open as a page

showing 1–30 of 32