playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Feature] Intercepting HAR requests/responses

Open shuckster opened this issue 2 years ago • 8 comments

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:

  1. Permit the use of both routeFromHAR and page.route together.

  2. To extend the options object of routeFromHAR with a handler in a similar way to page.route works.

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.

shuckster avatar Jan 26 '24 10:01 shuckster

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.

yury-s avatar Jan 26 '24 23:01 yury-s

Thanks you for getting back so quickly. To your points:

  1. I will look into route.fallback(), thank you for the suggestion.

  2. 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.

  3. 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.

shuckster avatar Jan 26 '24 23:01 shuckster

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?

shuckster avatar Jan 27 '24 22:01 shuckster

Just dropping for later reference, I've not looked into it yet:

  • https://github.com/microsoft/playwright/issues/21405

shuckster avatar Jan 31 '24 21:01 shuckster

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:

NoamGaash avatar Feb 20 '24 14:02 NoamGaash

Thank you for this!

shuckster avatar Feb 24 '24 11:02 shuckster

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)?

yuriteixeira avatar Jun 28 '24 13:06 yuriteixeira

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

NoamGaash avatar Jun 28 '24 17:06 NoamGaash