Skip to content

Browser: add route.fulfill #4961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: agnestoulet/poc-page-route
Choose a base branch
from

Conversation

AgnesToulet
Copy link
Contributor

@AgnesToulet AgnesToulet commented Jul 22, 2025

What?

This PR adds the route.fulfill function. Used in page.route, it allows to fulfill a request with a given response.

Why?

This helps with Playwright compatibility and allow to mock requests while testing.

Checklist

  • I have performed a self-review of my code.
  • I have commented on my code, particularly in hard-to-understand areas.
  • I have added tests for my changes.
  • I have run linter and tests locally (make check) and all pass.

Checklist: Documentation (only for k6 maintainers and if relevant)

Please do not merge this PR until the following items are filled out.

  • I have added the correct milestone and labels to the PR.
  • I have updated the release notes: link
  • I have updated or added an issue to the k6-documentation: grafana/k6-docs#NUMBER if applicable
  • I have updated or added an issue to the TypeScript definitions: grafana/k6-DefinitelyTyped#NUMBER if applicable

Related PR(s)/Issue(s)

Part of #4502

Known limitation

As you can see in the test script below, if you want to fulfill a request to an external website you need to handle CORS yourself. Playwright had the same issue described here with multiple proposed solution. They fixed it by adding CORS for external request if no overrides are provided: see this function. I tried to do the same but struggled with the request origin not being set on our side. I don't think it's blocking this PR though but I'd like to know your thoughts about this issue, if we should try to follow Playwright or choose another option (document it or allow it if an option is set).

Test script

To test the full workflow, I'm using the following k6 script. It's not part of the PR because it depends on a change on quickpizza: grafana/quickpizza#225 but you can play with it if you run QuickPizza locally.

import { check, sleep } from "k6";
import { browser } from "k6/browser";

export const options = {
	scenarios: {
		ui: {
			executor: "shared-iterations",
			options: {
				browser: {
					type: "chromium",
				},
			},
		},
	},
};

export default async function () {
	const page = await browser.newPage();

	await page.route(
		"https://jsonplaceholder.typicode.com/todos/1",
		function (route) {
			console.log("Intercepting request to external API");
			route.fulfill({
				status: 200,
				body: JSON.stringify({
					id: 1,
					title: "Test Todo",
					completed: false,
				}),
				contentType: "application/json",
				headers: {
					"Access-Control-Allow-Origin": "*",
					"Access-Control-Allow-Credentials": "true",
				},
			});
		}
	);

	await page.goto("http://localhost:3333/browser.php");

	await page.getByRole("button", { name: "external data" }).click();

	sleep(1);

	const checkData = await page.locator("#external-data-display").innerText();
	check(page, {
		externalData: checkData === "External data: Test Todo",
	});

	sleep(3);

	await page.close();
}

@AgnesToulet AgnesToulet requested a review from a team as a code owner July 22, 2025 13:22
@AgnesToulet AgnesToulet requested review from inancgumus and oleiade and removed request for a team July 22, 2025 13:22
@AgnesToulet AgnesToulet added enhancement area: browser browser: playwright related to Playwright compatibility labels Jul 22, 2025
@AgnesToulet AgnesToulet added this to the v1.2.0 milestone Jul 22, 2025
@AgnesToulet AgnesToulet mentioned this pull request Jul 22, 2025
8 tasks
Copy link
Contributor

@inancgumus inancgumus left a comment

Choose a reason for hiding this comment

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

Good work, despite it being your first PRs 🙇. I've posted some suggestions. We should also fix the linter errors.

@@ -0,0 +1,73 @@
package common
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure about this refactoring. I'd keep all HTTP-related code in http.go as before, as I don't mind about the file line length. No strong opinion, though.

Copy link
Contributor Author

@AgnesToulet AgnesToulet Jul 24, 2025

Choose a reason for hiding this comment

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

I'll revert this change! Now that I think again about this, I think it makes sense to keep it in http.go. And if the file size becomes an issue, it would probably make more sense to split it into three to have response.go, request.go and route.go. Reverted in 2e31eae

Copy link
Contributor

Choose a reason for hiding this comment

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

❤️ In that case, I'd suggest http_response.go, http_request.go, etc.

Copy link
Contributor

@oleiade oleiade left a comment

Choose a reason for hiding this comment

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

Great job @AgnesToulet 👏🏻 Nothing stood out to me, besides what @inancgumus mentioned, so I'll wait on his further review comments to 👍🏻

Copy link
Contributor

@ankur22 ankur22 left a comment

Choose a reason for hiding this comment

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

Nice! Looks good. Just a couple of things to consider first before we can merge this in.

route.Continue()

if err := route.Continue(); err != nil {
m.logger.Warnf("FrameManager:requestStarted",
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this also be an error log, like it is in onRequestPaused?

Copy link
Contributor

@inancgumus inancgumus Jul 25, 2025

Choose a reason for hiding this comment

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

I believe the other one better be a warning to reduce log clutter.


action := fetch.FulfillRequest(request.interceptionID, responseCode)

if opts.ContentType != "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Still need to check for nil opts? Maybe we need to check for nil opts at the beginning and fulfill with no extra options? Otherwise adding the options fields that aren't nil.

Copy link
Contributor

@inancgumus inancgumus Jul 25, 2025

Choose a reason for hiding this comment

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

Is there a reason for parseFulfillOptions to return a pointer: *common.FulfillOption? If not, we could just return a zero-value FulfillOption.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, you're right, I fixed it in the following PR but can do it here (I'm focusing on merging page.route first, then update this PR with your feedback). I'll look into removing the pointer as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: browser browser: playwright related to Playwright compatibility enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants