playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Bug]: Firefox not preserving fragment after `context.route()` interception

Open Disnaming opened this issue 11 months ago • 3 comments

Version

1.49.1

Steps to reproduce

from playwright.async_api import async_playwright

async def main():
    playwright = await async_playwright().start()
    browser = await playwright.firefox.launch()
    context = await browser.new_context()
    page = await context.new_page()
    new_page = await context.new_page()
    await page.goto("http://example.com#no-rerouted-navigation")
    print(page.url)
    await context.route("**/*", lambda route, request: route.continue_(url=request.url))
    await page.goto("http://example.com#hashchange-avoided-navigation")
    await new_page.goto("http://example.com#new-navigation-with-reroute")
    print(page.url)
    print(new_page.url) # expecting to see hash, but its not there
    await context.close()
    await browser.close()
    await playwright.stop()

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

Expected behavior

I expect to see the hash preserved after navigation.

Actual behavior

The hash is gone.

Additional context

No response

Environment

- Operating System: [Ubuntu 22.04]
- CPU: [arm64]
- Browser: [Chromium, Firefox]
- Python Version: [3.12]
- Other info:

Disnaming avatar Jan 28 '25 07:01 Disnaming

As I suspected in your sample code, the issue lies in the line:

 await context.route("**/*", lambda route, request: route.continue_(url=request.url))

specifically

route.continue_(url=request.url))

URL fragments (the URL hash) is not intended to be preserved when communicating with a server, thus our routing does not represent them.

The target URI excludes the reference's fragment component, if any, since fragment identifiers are reserved for client-side processing.

From Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing: RFC 7230

When the lambda executes, request.url is simply "http://example.com", thus causing your test to redirect to the page without any fragment.

For your particular example, you can preserve the full URL by not rewriting the URL in the request:

 await context.route("**/*", lambda route, request: route.continue_())

agg23 avatar Jan 28 '25 14:01 agg23

Sorry to re-alive this issue, but I just noticed that the Firefox browser type seems to be the only one that drops the hash; Chromium and Webkit keep this, and this should probably be consistent across all browsers to avoid surprises.

Also, while not my current use case, preserving the hash when rerouting requests could be useful if one needed to test client-side behaviour that depends on the URL fragment in a context where service workers/the geolocation API needs to be disabled. (Specifically, useful for prototyping exploits against domains which are hosted over HTTP, sometimes seen in CTFs, where it's common to deploy the challenge server on localhost for testing).

Disnaming avatar Jan 28 '25 15:01 Disnaming

I'm sorry, you're right. Firefox will drop the fragment when a route intercepts the message, but Chromium and WebKit do not.

agg23 avatar Jan 28 '25 15:01 agg23

Why was this issue closed?

Thank you for your contribution to our project. This issue has been closed due to its limited upvotes and recent activity, and insufficient feedback for us to effectively act upon. Our priority is to focus on bugs that reflect higher user engagement and have actionable feedback, to ensure our bug database stays manageable.

Should you feel this closure was in error, please create a new issue and reference this one. We're open to revisiting it given increased support or additional clarity. Your understanding and cooperation are greatly appreciated.

pavelfeldman avatar Sep 04 '25 01:09 pavelfeldman