Network Interception and Mocking

By Syncfusion®, Inc. · Last updated

Watching requests and responses, waiting for a call with waitForRequest and waitForResponse, intercepting calls with page.route, URL patterns, mocking with route.fulfill, blocking with route.abort, changing requests with route.continue, chaining with route.fallback, editing real responses with route.fetch, error, empty, slow and offline states, context-wide routes, and HAR files.

Network Interception and Mocking

  • Modern pages load their data in the background. The page asks an API for products, orders or prices.
  • A test can watch those calls, wait for them, and even answer them itself.
  • That lets you test the empty list, the server error and the slow network, without breaking a real server.

Simple lesson: Playwright sits between the page and the server. It can watch, change or answer every call.

A Real-World Example

  • The Practice Shop's Catalog page asks /api/products for the list.
  • What does a shopper see when the shop has no products? The real server always has some.
  • This test answers the call with an empty list and checks the message.
  • The page never reached the server for products. Playwright answered with [].

What You Will Learn

  1. How a page talks to the server, and how to watch it.
  2. Waiting for a call with waitForResponse() and waitForRequest().
  3. Intercepting calls with page.route() and URL patterns.
  4. Mocking, blocking, changing and chaining requests.
  5. Testing error, empty, slow and offline states.
  6. When to mock, and when to use the real backend.

How Pages Talk to Servers

Requests and Responses

  • A request is the page asking for something: a method (GET, POST), a URL, headers and maybe a body.
  • A response is the answer. It has a status code (200 OK, 404 Not Found, 500 Server Error), headers and a body, often JSON.
  • The browser's fetch() sends API requests. Playwright sees every one.

Animation: a request leaves the page, passes the route handler and reaches the server, and the response comes back. Then a second request is answered by route.fulfill without reaching the server

Page fetch()

page.route() your handler

Server /api/products

GET 200 [8] GET fulfill []

First call: passed to the server. Second call: answered by the handler.

A route handler stands between the page and the server. It can let a call pass, or answer it.

Watching Requests and Responses

  • page.on('request') runs for every request. page.on('response') runs for every answer.
  • request.method(), request.url() and response.status() tell you what happened.
  • The page also calls a weather service on another site. The lab has no internet, so that call fails and the page shows Weather unavailable.

Simple lesson: page.on('request') and page.on('response') show every call the page makes.

Waiting for Network Calls

waitForResponse()

  • Sometimes you need the data the page received, not just what it shows.
  • page.waitForResponse(url) returns the response. Start waiting before the action that sends the call.

waitForRequest()

  • page.waitForRequest() returns the request. Use it to check what the page sent.
  • A function works as a filter: request => request.url().includes('/api/analytics').
  • request.postDataJSON() reads a JSON body.

Start waitForResponse() or waitForRequest() before the click or goto() that sends the call. If you start after, the call may already be done and the wait times out.

Simple lesson: make the promise first, act second, await the promise third.

Intercepting with page.route()

URL Patterns

  • page.route(pattern, handler) catches every request whose URL matches.
  • A glob string: ** matches anything, including slashes. '**/api/products' matches the URL https://practice.devtutorials.ai/api/products.
  • A glob must match the whole URL. '**/api/products' does not match /api/products?category=Shoes. Add * at the end: '**/api/products*'.
  • A regular expression (/\/api\/products/) or a function (url => url.pathname === '/api/products') also works.
  • Register routes before goto(). Calls made before the route exists are not caught.

Mocking a Response with route.fulfill()

  • route.fulfill({ json }) answers with JSON and status 200.
  • Add status for errors, body and contentType for other content, headers for custom headers.
  • Mocking a third-party service, like the weather API, is very common. Your tests should not fail because someone else's server is down.

Blocking Requests with route.abort()

  • route.abort() makes the request fail, as if the network broke.
  • Use it to block analytics, ads and trackers in tests, or to test what happens when one call fails.

Changing Requests with route.continue()

  • route.continue() sends the request on to the server. You can change it on the way: url, method, headers or postData.
  • Here, the page's orders call gets an Authorization header, so the server treats it as Ben's.

Chaining Handlers with route.fallback()

  • If several routes match, the one added last runs first.
  • route.fallback() passes the request to the next matching handler. route.continue() skips the others and goes to the server.
  • { times: 1 } makes a route work only once. page.unroute() removes it.

Simple lesson: fulfill answers, abort fails, continue sends on, fallback asks the next handler.

Modifying Real Responses

route.fetch() Then fulfill()

  • Sometimes you want real data with one small change.
  • route.fetch() gets the real response. Change the JSON, then route.fulfill({ response, json }).
  • The status and headers stay real. Only the body changes.

Testing Hard-to-Reach States

Server Errors and Slow Responses

  • Real servers rarely fail when you want them to. A mock fails on demand.
  • status: 500 tests the error message. A delay before route.continue() tests the loading state.

Going Offline

  • context.setOffline(true) cuts the network for the whole context. Every call fails.
  • setOffline(false) turns it back on. The config option offline: true starts offline.

In the lab, page.goto() still works while offline, because the practice pages are built in. A real offline browser cannot load new pages at all.

Routes for the Whole Context

  • context.route() works like page.route(), but for every page in the context, including popups and new tabs.
  • Put it in a fixture or beforeEach to block trackers in all tests.

Simple lesson: mocks make rare states easy: errors, empty lists, slow and offline.

Recording and Replaying (HAR)

What a HAR File Is

  • A HAR file is a recording of real network traffic, saved as JSON.
  • page.routeFromHAR('example.har', { update: true }) records the real answers once.
  • Later runs, without update, answer every call from the file. No server needed.
  • The lab cannot record real traffic, so here it is plain code.
test('catalog from a recording', async ({ page }) => {
  // First run: record. Then remove update: true to replay.
  await page.routeFromHAR('hars/catalog.har', {
    url: '**/api/**',
    update: true,
  });
  await page.goto('/catalog');
  await expect(page.getByRole('status').first()).toHaveText('8 products');
});
  • npx playwright open --save-har=hars/catalog.har https://practice.devtutorials.ai/catalog records while you click around.
  • page.routeWebSocket() does the same kind of mocking for WebSockets, the live connections that chats use.

When to Mock

Mocks Versus the Real Backend

  • Mock when the state is hard to make: errors, empty data, slow calls, third-party services.
  • Use the real backend for the main happy paths. A test full of mocks can pass while the real app is broken.
  • A good mix: most tests hit a real test server. A few focused tests mock the rare cases.

If the real API changes its JSON shape, your mocks will not notice. Keep mock data close to real data, and use API Testing to check the real API shape.

Simple lesson: mock the rare and the external. Test the main flows for real.

Try It in Your Project

  • These steps take the lesson from the lab to your own app.
  • Mock one third-party service your tests depend on, like payments or maps.
  • Write one test each for a 500 error, an empty list and a slow response.
  • Keep the main happy path on the real backend.

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

TL;DR

  • page.on('request' | 'response') watches calls. waitForResponse() and waitForRequest() wait for one.
  • page.route(pattern, handler) catches calls. Add it before goto().
  • Globs match the whole URL. Add * for query strings.
  • fulfill answers, abort fails, continue sends on, fallback passes to the next handler.
  • route.fetch() plus fulfill({ response, json }) edits real data.
  • setOffline(true) cuts the network. context.route() covers every page.

Common mistakes

MistakeWhat happensFix
page.route() after page.goto()The first calls are not caughtAdd routes before goto()
'**/api/products' for a URL with ?category=The route never matchesUse '**/api/products*' or a regex
Starting waitForResponse() after the clickThe wait times outCreate the promise first, then click
A handler that never calls fulfill, abort, continue or fallbackThe request hangs and the page waits foreverAlways finish the route
Mocking every call in every testTests pass while the real app is brokenMock rare and external cases only

Check Yourself

Exercises

What to Remember

  • Watch with page.on('request' | 'response'). Wait with waitForResponse() and waitForRequest().
  • Routes go in before goto(). Globs match the whole URL.
  • fulfill, abort, continue, fallback: answer, fail, send on, pass along.
  • Mocks make error, empty, slow and offline states easy.
  • Mock the rare and external. Keep main flows real.

Next: API Testing