In Playwright, what does browserContext.routeFromHAR() do to the requests a page makes?
answer
- Real traffic saved to a file
- Recorded with a context option
- Replayed by a route handler
- Lookup by method and URL
- Missing entries abort by default
basics
~20 sIt 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.
solid answer
~40 s`browserContext.routeFromHAR(har)` points a whole context at a HAR archive: each request a page makes is looked up in that file by method and URL, and the recorded status, headers and body are served back without touching the network. `page.routeFromHAR()` does the same for one page only. You get the archive either from the `recordHar` context option (`{ path: 'har/weather.har' }`), which writes the file when the context closes, or from the CLI with `npx playwright open --save-har=har/weather.har`. Replay does not consume entries, so a weather dashboard polling the forecast endpoint repeatedly keeps getting the same recorded response. Two options shape the edges: `url` narrows which requests the archive may answer, and `notFound` decides what a request with no entry does — `'abort'` by default, or `'fallback'` to pass it on.
code
typescript · 11 linesimport { test, expect } from '@playwright/test';
test('forecast panel renders from a recorded session', async ({ page }) => {
await page.routeFromHAR('har/weather.har', {
url: '**/api/forecast**',
notFound: 'abort',
});
await page.goto('/dashboard');
await expect(page.getByTestId('forecast-high')).toHaveText('21');
});go deeper
Be able to say what a HAR file holds and that routeFromHAR replays it in place of the real server. Knowing that the recordHar context option produces the file is enough at this stage.
Explain the mechanics: lookup by method and URL, entries that answer repeated requests, the file flushed on context close, and the difference between page and context scope.
Show judgment about which endpoints an archive should carry and how a replaying suite behaves when the real service is unreachable, so CI never depends on a third party being up.
Own the policy around archives as repository artefacts: where they live, who refreshes them, how large they may grow, and what must never be captured into one.
## What a HAR archive holds A **HAR** (HTTP Archive) file is a JSON log of HTTP traffic. Each entry records one exchange: the request method, URL, headers and post data, plus the response status, headers and body. Playwright can both write that log and read it back, which turns a single real session against the third-party forecast API behind a weather dashboard into a fixed backend that later runs replay offline. ## Producing the file There are two ways to record, and both flush the archive at the end of a session rather than as it goes: - **The `recordHar` context option** — `browser.newContext({ recordHar: { path: 'har/weather.har' } })`. Every request the context makes is logged, and the file appears when `context.close()` runs. Under Playwright Test you set it through `use: { contextOptions: { recordHar: { path: 'har/weather.har' } } }`, and the built-in `context` fixture closes at the end of each test. - **The CLI** — `npx playwright open --save-har=har/weather.har --save-har-glob='**/api/**' https://dashboard.example` drives a real browser by hand and saves the same format with no test code at all. `npx playwright codegen` accepts the same two flags. Besides `path`, `recordHar` takes `urlFilter` (a glob or `RegExp` limiting what is logged), `mode` (`'full'` or `'minimal'`) and `content` (`'omit'`, `'embed'` or `'attach'`), which decides whether response bodies are dropped, inlined into the JSON, or written as separate files beside the archive. ## Replaying the file `await context.routeFromHAR('har/weather.har')` installs a route across the whole browser context; `await page.routeFromHAR('har/weather.har')` scopes the same behaviour to one page. From then on Playwright looks each outgoing request up in the archive **by method and URL** and fulfils it from the recording — status, headers and body — without reaching the network. Two consequences follow: - Entries are **not consumed**. If the dashboard polls `/api/forecast?city=oslo` every thirty seconds, every poll is answered from that one recorded entry. - The lookup key is the URL as requested, so a recording made against one origin will not answer requests to another. `baseURL` differences and cache-busting query parameters both change the key. A relative `har` path is resolved against the **current working directory** of the run, not against the spec file, so build the path with `path.join(__dirname, ...)` if the suite might be started from another folder. ## The options at the edges | option | values | default | what it decides | |---|---|---|---| | `url` | glob or `RegExp` | every request | which requests the archive is allowed to answer | | `notFound` | `'abort'`, `'fallback'` | `'abort'` | what a request with no entry does | | `update` | boolean | `false` | re-record from the live network instead of serving the file | | `updateContent` | `'embed'`, `'attach'` | `'attach'` | where bodies go when re-recording | | `updateMode` | `'full'`, `'minimal'` | `'minimal'` | how much HAR detail is written back | `url` and `notFound` are the pair people confuse. `url` filters what the archive is even consulted about: a request outside the pattern is left alone and goes to the network or a later handler. `notFound` applies only to a request the archive **was** consulted about and could not answer. ## The usual workflow 1. Record once against the real forecast API, with `urlFilter` narrowed to the endpoints under test. 2. Commit the archive next to the spec — with its attachment files, or using a `.zip` path so the whole recording travels as a single file. 3. Replay it with `routeFromHAR` so CI never calls the third party, and re-record later by running the same call with `update: true`. ## Where it earns its keep - A dashboard that renders a dozen widgets from one large payload is tedious to stub by hand; a recording captures the response verbatim, including the fields nobody remembered. - The suite stops depending on a service you do not control, which removes a third party from your CI failure modes. - The scope is yours to pick: `page.routeFromHAR` lets one page replay while another page in the same context still talks to a local server. The cost is that an archive is a snapshot. It answers exactly what was recorded, and anything the app newly requests is a miss — which is why the `notFound` policy is a deliberate decision rather than a detail. `'abort'` makes an incomplete recording fail loudly; `'fallback'` quietly lets the request through to the real world.
- How do you get a HAR file without writing any test code first?Drive the app by hand from the CLI: `npx playwright open --save-har=har/weather.har --save-har-glob='**/api/**' https://dashboard.example`. It launches a browser, records what you click through, keeps only the URLs matching the glob and writes the archive when you close it. `npx playwright codegen` takes the same two flags.
- What is the difference between page.routeFromHAR() and browserContext.routeFromHAR()?Scope, and nothing else. The page form serves the archive to that one page, so a popup or a second page in the same context still reaches the network. The context form covers every page the context has or later opens. Options and matching behave identically in both.
A HAR archive is a tape of a conversation with the server: replay plays the same answers back on cue, and anything that was never said on tape has no answer at all.
saying these in an interview costs you the question
- Thinks a HAR file replays automatically once it exists on disk
- Believes each recorded entry can only be replayed once
- Expects the archive to be written before the context closes
- Assumes routeFromHAR proxies requests through to the real server
- Thinks recording requires changing the application's network code