Skip to content

Network mocking

Use backendMocks to mock, inspect, modify, or fail outbound network calls made by your Node application while a Playwright test drives the real UI.

ts
test("checkout handles a declined payment", async ({ page, backendMocks }) => {
  await backendMocks.route("https://payments.example.test/charges", async (route) => {
    await route.fulfill({
      status: 402,
      json: { error: "card_declined" },
    });
  });

  await page.goto("/checkout");
  await page.getByRole("button", { name: "Pay" }).click();
  await expect(page.getByText("Your card was declined")).toBeVisible();
});

The core APIs

APIUse it to
route(matcher, handler, options?)Intercept matching Node requests.
unroute(matcher?, handler?)Remove one or more HTTP routes.
unrouteAll(options?)Remove all HTTP routes for the test.
routeFromHAR(file, options?)Replay or record Playwright-style HAR files.
routeWebSocket(url, handler)Intercept globalThis.WebSocket connections.
waitForRequest(matcher, options?)Wait for a future outbound request.
waitForResponse(matcher, options?)Wait for a future outbound response.
requests(matcher?)Read observed requests for the test.
takeErrors()Drain proxy errors intentionally triggered by the test.

What a route handler can do

MethodTerminalDescription
route.fulfill(options?)YesReturn a mocked response.
route.continue(options?)YesSend the request upstream, optionally with overrides.
route.abort(errorCode?)YesFail the request with a network-style error.
route.fallback(options?)NoApply local overrides and pass to the next matching handler.
route.fetch(options?)NoFetch upstream from the handler, then decide what to return.

route.fetch() does not settle the route. Finish with fulfill, continue, or abort.

Inspect requests and responses

Request and response accessors are methods, like Playwright:

ts
await backendMocks.route("https://api.example.test/users", async (route, request) => {
  expect(request.method()).toBe("POST");
  expect(request.url()).toContain("/users");
  expect(request.postDataJSON()).toEqual({ name: "Ada" });

  const upstream = await route.fetch();
  expect(upstream.status()).toBe(200);

  const body = (await upstream.json()) as { users: unknown[] };
  await route.fulfill({ response: upstream, json: body });
});

Use response.status() and await response.json() on the response returned by route.fetch() or waitForResponse().

Passthrough by default

Unmatched requests continue to the real network. This keeps setup small, but it also means a missing route can hit a real third-party service.

TIP

While authoring tests, inspect GET /api/history on the proxy to confirm which requests were mocked, continued, aborted, or passed through.

Typical workflows

GoalPage
Choose glob, RegExp, predicate, URLPattern, method, or clientId matchingMatching requests
Return hard-coded JSON, a file, or a fetched responseMock responses
Rewrite upstream requests or modify upstream responsesModify and passthrough
Simulate network errorsAbort and failures
Assert your app sent a requestSpying and waiting
Record/replay HARHAR

Mock the outside world. Keep the real app.