[Feature] Intercepting HAR requests/responses
Problem
We wish to intercept and modify PlayWright HAR requests and responses.
There does not appear to be a way of doing this directly on routeFromHAR, so the repo below demonstrates an attempt to use both page.route and routeFromHAR together.
- https://github.com/shuckster/playwright-har-intercept
Needless to say, the technique does not work.
Use-case
The JSON RPC spec requires an id at the root-level of a request.
In our own JSON RPC implementation, we are using a randomly generated prefix for our ids. They are generated on Application startup, and include an incrementing counter as a suffix:
const id = `${jsonRpcAppLoadId}_${requestCounter}`;
Unfortunately, a random value in a payload means the HAR feature of PlayWright treats all requests as unique, and it will fail to load and serve a saved HAR from file.
We're reluctant to change the way these ids are calculated because we have a micro-frontend architecture. This means multiple apps could, in the same page, call the same back-end APIs, and we'd like to ensure responses get routed to the right requesters.
Proposition
To either:
-
Permit the use of both
routeFromHARandpage.routetogether. -
To extend the
optionsobject ofrouteFromHARwith a handler in a similar way topage.routeworks.
Either solution would allow requests to be modified before the HAR files are saved, and responses to also be modified after they have been loaded from file.
You can already combine route.fallback() with routeFromHar() today. You could rewrite the id prefix before routing from HAR by adding page.route() after page.routeFromHar call, so that the request first modified by the handler and then matched against what is in the har file. This however would return the id that was written in the har file, which may not be what the client expects. In this scenario it's arguably better to have a test mode in the app where it would produce predictable test prefix for the ids. Alternatively, you can mock these requests manually without using routeFromHar at all.
Leaving this request open to collect more feedback. But currently it feels like providing pre-/post- processing for the har data would defeat the purpose of the automatic HAR routing and could be easier to implement manually by mocking the requests.
Thanks you for getting back so quickly. To your points:
-
I will look into
route.fallback(), thank you for the suggestion. -
We try to avoid the practice of modifying apps to support "test modes". Such modifications mean the app no longer behaves the same way as it does in production, which is already difficult to simulate.
-
We did originally mock requests manually. We have saved dozens of hours and cleaned-up hundreds of lines of code by discovering HAR support. We strongly prefer a record/playback solution if it's possible.
I've tried route.fallback and route.fulfill in the playground I linked to (so I've not tried in our production app yet) and I'm curious about the content of the HAR file.
It appears that the intercepts work to deliver, back to the client, modified values.
However, the HAR itself does not seem to take them (id should be '12345').
Am I doing something wrong here please?
Just dropping for later reference, I've not looked into it yet:
- https://github.com/microsoft/playwright/issues/21405
In my workplace we're having a similar use case.
We have a giant userinfo endpoint that retrieves a LOT of information regarding the current logged in user.
For some tests, we would like to intercept this specific endpoint, and change specific fields of that object such as role, available feature flags and so on.
I've released a new version of the advancedRouteFromHAR fixture, and now HAR responses can be intercepted and altered.
For example:
test("get the largest number... squared!", async ({ page, advancedRouteFromHAR }) => {
// the file contains 3 responses - 42, 1234, 5
await advancedRouteFromHAR("tests/har/differentNumbers.har", {
matcher: {
postProcess(entry) {
entry.response.content.text = (parseInt(entry.response.content.text || "0") ** 2).toString();
return entry;
},
matchFunction: customMatcher({
scoring: (request, entry) => parseInt(entry.response.content.text || "0"),
}),
}
});
await page.goto("https://noam-gaash.co.il");
await page.getByText((1234**2).toString()).waitFor();
});
Read the README for more details about the fixture - https://github.com/NoamGaash/playwright-advanced-har And feel free to :star: the repo it you find it helpful :smiley:
Thank you for this!
Hello @NoamGaash! Do you have an example on how we could match the whole request but ignoring a specific header (eg: the authorization header we get from oauth flows)?
Playwright is not using the headers to filter the responses in the HAR file. It's only used when multiple har entries found matching the browser request.
If you want to edit the response headers, you can use the postProcess argument