Skip to content

feat(routeFromHar): add interceptAPIRequests option#41294

Merged
dcrousso merged 9 commits into
microsoft:mainfrom
stkevintan:fix-22869
Jul 1, 2026
Merged

feat(routeFromHar): add interceptAPIRequests option#41294
dcrousso merged 9 commits into
microsoft:mainfrom
stkevintan:fix-22869

Conversation

@stkevintan

@stkevintan stkevintan commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #22869

Summary

  • Adds opt-in interceptAPIRequests option to BrowserContext.routeFromHAR so page.request.* / context.request.* calls are also served from the HAR file.
  • Defaults to false — fully backward compatible.
  • API-request lookup is restricted to entries marked _apiRequest: true (already written by the HAR recorder), so browser-side recordings are never served to API requests for the same URL.

Make `BrowserContext.routeFromHAR` also intercept requests issued via
APIRequestContext (`page.request.*` / `context.request.*`) when the new
`interceptAPIRequests: true` option is set. Defaults to `false` so existing
behavior is unchanged.

Under the hood, `HarBackend.lookup` gains an `apiRequestOnly` flag that
filters matches to entries with `_apiRequest: true` (already written by the
HAR recorder), so API-side replay never picks up a browser-side recording
for the same URL.

Fixes microsoft#22869
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@dgozman dgozman requested a review from dcrousso June 15, 2026 15:45
@dgozman

dgozman commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

@dcrousso Could you please take a look?

@stkevintan

Copy link
Copy Markdown
Contributor Author

Could you please help review? @dcrousso @yury-s

@dcrousso dcrousso left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks so much for taking the time to submit a PR!

this is a really great start, but i think it's missing a few things in order to fully work

please let me know if any of my comments are unclear

Comment thread packages/playwright-core/src/server/fetch.ts
if (postData)
setHeader(headers, 'content-length', String(postData.byteLength));
const { body, log, response } = await this._sendRequestWithRetries(progress, requestUrl, options, postData, params.maxRetries);
const harResponse = await this._lookupInHar(progress, requestUrl, method, headers, postData);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this will prevent the dispatch of APIRequestContext.Events.Request and APIRequestContext.Events.RequestFinished meaning that if a new recording is captured while replaying from the HAR then it wont include any previously captured API requests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. The short-circuit bypassed _sendRequest, which is the only emitter of Request/RequestFinished. The HAR path now emits both events (mirroring _sendRequest), so capturing a new recording while replaying still includes the API requests. Added a test (should re-record intercepted APIRequestContext requests into a new HAR).

Comment thread packages/playwright-core/src/server/fetch.ts Outdated
Comment thread packages/playwright-core/src/server/fetch.ts Outdated
Comment thread packages/playwright-core/src/server/browserContext.ts Outdated
Comment thread packages/playwright-core/src/server/fetch.ts Outdated
log.push(`HAR: ${lookupResult.message ?? 'lookup failed'}`);
continue;
}
if (lookupResult.action === 'noentry') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: 'missing' would be a better name here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept 'noentry' here — the action is part of the LocalUtilsHarLookupResult wire protocol (localUtils.yml), so renaming it would be a breaking change for cross-version client/server compatibility. Happy to rename if you'd still prefer it and are OK treating it as a protocol change.

Comment thread packages/playwright-core/src/server/fetch.ts
Comment thread packages/playwright-core/src/client/harRouter.ts
Comment thread packages/playwright-core/src/server/fetch.ts Outdated
Make the HAR-replay path for APIRequestContext behave like the live
network path and tighten the server-side plumbing:

- Emit Request/RequestFinished events so a recording captured while
  replaying still includes the API requests.
- Apply set-cookie side-effects via addCookies, like the live path.
- Honor maxRedirects (throw when exceeded) and report the final entry
  URL on redirects.
- Populate statusText, securityDetails and serverAddr on the response;
  log via progress.log alongside the in-memory log.
- Reuse the already-open HarBackend (looked up by harId) instead of
  opening a second backend for the same HAR file.
- Rename to routeAPIRequestsFromHar/unrouteAPIRequestsFromHar, give the
  newest registration priority (unshift), and fold the registration
  bookkeeping into the dispatcher's _disposables.

Fixes: microsoft#22869
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@stkevintan stkevintan requested a review from dcrousso June 23, 2026 15:43
# Conflicts:
#	packages/playwright-core/src/client/harRouter.ts
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@dcrousso dcrousso left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice work! :)

sorry it took me a minute to get to this

Comment thread packages/playwright-core/src/server/fetch.ts
Comment thread packages/playwright-core/src/server/fetch.ts Outdated
Comment thread packages/playwright-core/src/client/harRouter.ts Outdated
// Reuse the HarBackend that was already opened via localUtils.harOpen for the page-side
// route, rather than opening a second backend for the same HAR file. The backend is owned
// by LocalUtils and closed via harClose, so this registration must not dispose it.
const localUtils = [...this.connection._dispatcherByGuid.values()].find(d => d._type === 'LocalUtils') as LocalUtilsDispatcher | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: is there no better way of finding this than by looking at the _type? that seems somewhat fragile

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Replaced the inline _dispatcherByGuid scan with a typed helper on DispatcherConnectiongetDispatcher<T>(type) (sits next to the existing existingDispatcher<T>(object)) — so the call site is now this.connection.getDispatcher<LocalUtilsDispatcher>('LocalUtils'). Done in f5c0b22.

Comment thread tests/library/browsercontext-har.spec.ts
Co-authored-by: Devin Rousso <hi@devinrousso.com>
Signed-off-by: Kevin Tan <stkevintan@zju.edu.cn>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

…equests

- abort HAR lookup errors when notFound:'abort' (was silently falling through)
- iterate a copy of the registrations to survive concurrent unroute
- close the HAR last in HarRouter.dispose, after unrouting API registrations
- look up LocalUtils via a typed DispatcherConnection.getDispatcher<T>() helper
  instead of an inline _dispatcherByGuid scan
- assert response.serverAddr() in the intercepted-request test (record in 'full'
  mode so serverIPAddress/serverPort are persisted)
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@stkevintan stkevintan requested a review from dcrousso July 1, 2026 00:33
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Test results for "MCP"

3 failed
❌ [chrome] › mcp/cli-killall.spec.ts:42 › kill-all kills filtered dashboard pid @mcp-macos-latest-chrome
❌ [chromium] › mcp/cli-killall.spec.ts:42 › kill-all kills filtered dashboard pid @mcp-macos-latest-chromium
❌ [firefox] › mcp/annotate.spec.ts:446 › should switch screencast to -s session on show --annotate @mcp-macos-latest-firefox

7458 passed, 1132 skipped


Merge workflow run.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Test results for "tests 1"

4 flaky ⚠️ [chromium-library] › library/video.spec.ts:275 › screencast › should capture navigation `@realtime-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:717 › screencast › should work with video+trace `@realtime-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:680 › screencast › should capture full viewport on hidpi `@chromium-ubuntu-22.04-node24`
⚠️ [playwright-test] › ui-mode-test-output.spec.ts:118 › should collapse repeated console messages for test `@ubuntu-latest-node22`

49329 passed, 1163 skipped


Merge workflow run.

@dcrousso dcrousso merged commit d3d436d into microsoft:main Jul 1, 2026
45 of 48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] routeFromHar should also apply to apiRequestContext

3 participants