skip to content

A Playwright test calls context.setOffline(true), yet the booking page still renders room data. What would you check?

level: seniorimportance: should knowfreq 44%

answer

  1. Stops future traffic, not past renders
  2. Cache and service worker still answer
  3. Nothing forces a reload
  4. The Node-side request client is separate
  5. Context-wide, and only that context

basics

~20 s

Check what is actually serving the data. Playwright's context.setOffline only stops new browser network traffic in that context; already-rendered markup, cached responses, a service worker and the Node-side request client all keep working, and nothing forces the page to reload.

solid answer

~50 s

`context.setOffline(true)` flips one switch: the **browser context** goes offline, so `navigator.onLine` reports `false`, new requests from any page or popup in that context fail, in-flight WebSockets close and even service-worker `fetch` calls reject. What it does not do is invalidate what is already there. Work down this list: is the room list already in the DOM or in memory from the previous navigation; is a service worker or the HTTP cache answering from storage; did anything trigger a new request at all, or is the app simply not listening for the `offline` event; and did the test fetch data through Playwright's Node-side API request client, which is a separate HTTP client that `setOffline` does not touch? Also confirm the call is on the right context — it is context-wide, not per page, and does not affect other contexts.

code

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

test('search page surfaces an offline notice on the next request', async ({ page, context }) => {
  await page.goto('/search?city=rome');
  await expect(page.getByTestId('room-card').first()).toBeVisible();

  await context.setOffline(true);
  expect(await page.evaluate(() => navigator.onLine)).toBe(false);

  // Force traffic: the already-rendered list alone proves nothing.
  await page.getByRole('button', { name: 'Next page' }).click();
  await expect(page.getByRole('alert')).toContainText('You are offline');

  await context.setOffline(false);
});

go deeper

for a junior

Know the call and its shape: context.setOffline(true) puts the whole browser context offline, and navigator.onLine reports false afterwards. Switch it back with setOffline(false) before the test ends.

for a middle

Explain that it only blocks new browser traffic. Already-rendered markup, in-memory data, HTTP cache hits and a service worker can all keep the page looking healthy, and nothing forces a reload.

for a senior

Drive the diagnosis: confirm the flag landed, force a request that must hit the network, look for a service worker, and check whether the app has an offline branch at all before blaming the test.

for a principal

Set the expectation for the team about what offline coverage means here. A binary switch tests the disconnected branch and its recovery; it says nothing about degraded connectivity, and that gap should be stated rather than assumed away.

## What the switch actually does `context.setOffline(true)` is a runtime setter on a browser context; the same state can be set at creation with `browser.newContext({ offline: true })` or `test.use({ offline: true })`. When it is on: - `navigator.onLine` returns `false` on every page in that context, and the browser fires the `offline` event; - new navigations and `fetch`/`XHR` from those pages fail with a network error; - open WebSockets close; - a service worker's own `fetch()` calls reject; - pages opened afterwards, and popups opened by an existing page, inherit the offline state because they belong to the same context. Flipping it back with `context.setOffline(false)` restores traffic immediately — a `page.goto()` after that returns a normal 200. | Stopped by the switch | Untouched by the switch | |---|---| | New navigations, `fetch` and `XHR` from the context's pages | Markup and data already rendered or held in memory | | A service worker's own `fetch()` calls | A service worker answering from Cache Storage, and HTTP cache hits | | Open WebSocket connections in that context | Playwright's Node-side API request client, and every other context | ## Why the page can still look online The switch stops *future browser traffic*. It does not undo the past, and it does not push the app into an offline UI by itself. In a hotel-booking search page, any of these keeps rooms on screen: 1. **The data is already rendered.** The list was fetched before the switch, so the DOM still holds it. Going offline never blanks a page. 2. **The data is already in memory.** A client store holds the previous search result and the app re-renders from it without asking the network. 3. **Something local answers.** An HTTP cache hit, or a service worker serving from Cache Storage, satisfies the request without a network round trip. 4. **Nothing new was requested.** If the test never triggers a fresh fetch — no pagination click, no filter change, no reload — there is no request to fail, so nothing can surface an error state. 5. **The app does not listen.** If the code never handles the `offline` event or a failed request, the only visible symptom is a spinner that never resolves, not an offline banner. 6. **The data came from the Node side.** Playwright's API request client (`context.request` and the `request` fixture) is an HTTP client in the test process, not browser traffic, so seeding or asserting through it keeps succeeding while the browser is offline. ## Scoping mistakes Two more checks are about *which* context you switched: - **It is context-wide, not page-wide.** Every page and popup in that context is offline together; you cannot take one page offline and leave its sibling online. - **It is context-local.** Another browser context — a second logged-in role, an admin session — is unaffected, so a test that asserts through the wrong context sees a healthy site. There is also a browser-level wrinkle worth knowing: in current releases the emulated offline state is not reliably preserved across a **cross-process navigation** in Chromium, so a test that navigates to a different origin while offline may find `navigator.onLine` back to `true`. If your scenario needs that, assert the flag after the navigation rather than assuming it carried over. ## A diagnosis order that works - Read the flag first: `await page.evaluate(() => navigator.onLine)` should be `false`. If it is `true`, you switched the wrong context or a navigation dropped the state. - Then force a new request — click "next page", change a filter, or reload — and see whether it fails. If the failure never happens, the data is local. - Then check for a service worker on the origin; if one is registered, it is the most likely reason a request "succeeded" offline. - Then check whether the app has an offline branch at all. A missing banner may be a genuine product gap rather than a test bug, and that is a finding, not a workaround. ## What to assert instead Once the mechanism is understood, the reliable shape is: navigate and let the page settle, switch the context offline, **trigger an action that must hit the network**, and assert the user-visible consequence — an alert, a disabled "Book" button, a retry prompt. Asserting only `navigator.onLine` proves Playwright applied the emulation, not that the booking site behaves when the guest's connection drops. Finally, remember what `setOffline` is not: it is a binary on/off switch on the context's network, with no notion of a slow or lossy link. Anything shaped like "behaves acceptably on a bad connection" is a different question with different tooling.

  • Does context.setOffline(true) affect requests made through Playwright's API request client?
    No. `context.request` and the `request` fixture are an HTTP client running in the Node test process, not browser traffic, so they keep working while the browser context is offline. That is convenient for seeding data mid-scenario, and it is also a common reason a test seems not to go offline.
  • How would you prove the offline state reached the page before asserting the UI?
    Read it back: `await page.evaluate(() => navigator.onLine)` should be `false`. Treat that as a precondition check rather than the test's assertion — the real assertion is the user-visible consequence after an action that must hit the network.
  • Is setOffline a way to test behaviour on a slow connection?
    No. It is binary: the context's browser network is either on or off. There is no latency, bandwidth or packet-loss dimension to it, so a question about degraded-but-present connectivity needs different tooling and a different kind of assertion entirely.

saying these in an interview costs you the question

  • Expects the page to blank or reload the moment offline is set
  • Thinks setOffline applies to a single page rather than the context
  • Forgets a service worker can answer requests from cache
  • Assumes the Node-side request client goes offline too
  • Asserts navigator.onLine and calls the offline behaviour tested
  • Treats setOffline as a network-throttling knob