playwright-python icon indicating copy to clipboard operation
playwright-python copied to clipboard

[Feature] Document page.route

Open EvanTheB opened this issue 1 year ago • 1 comments

I am having some trouble using page.route within pytest. I think the issue stems from the way the route functions are run. I believe the routing is run in a background thread, and the routes are eventually run inside a coroutine event loop.

Relevant docs:

https://playwright.dev/python/docs/network

https://playwright.dev/python/docs/api/class-route

https://playwright.dev/python/docs/api/class-page#page-route

I think the documentation needs to call out the expected behavior of the route functions clearly:

What happens when a route function throws an exception?

What happens if the route function returns without calling one of the route methods (eg route.fulfill)?

I have observed that in pytest asserting or calling pytest.fail from a route function does not fail the test reliably. I am also observing some other non deterministic behaviour, but I do not have a simple reproduction yet. I think clarity on the above points may help.

EvanTheB avatar Oct 25 '23 23:10 EvanTheB

As an example, here are some tests:

import time
from playwright.sync_api import Page, Route


def test_routing_ok(page: Page) -> None:

    def handle_err(route: Route):
        print("this should not happen", route.request.url)
        route.continue_()

    def handle_ok(route: Route):
        print("this is ok", route.request.url)
        route.continue_()

    page.route("http://10.42.0.10/**", handle_err)
    page.route("http://10.42.0.10/**", handle_ok)
    page.goto("http://10.42.0.10/cgi-bin/luci/admin/network/network")
    page.wait_for_load_state("networkidle")


def test_routing_no_route(page: Page) -> None:
    """This example times out.
    """
    def handle_err(route: Route):
        print("this should not happen", route.request.url)
        route.continue_()

    def handle_ok(route: Route):
        print("this is ok", route.request.url)

    page.route("http://10.42.0.10/**", handle_err)
    page.route("http://10.42.0.10/**", handle_ok)
    page.goto("http://10.42.0.10/cgi-bin/luci/admin/network/network")
    page.wait_for_load_state("networkidle")

def test_routing_raise_before(page: Page) -> None:
    """This example times out.
    How is the exception from the handle_ok function treated?
    We also see a message in the logs:
    ERROR    asyncio:base_events.py:1758 Task exception was never retrieved
    """
    def handle_err(route: Route):
        print("this should not happen", route.request.url)
        route.continue_()

    def handle_ok(route: Route):
        print("this is ok", route.request.url)
        assert False
        route.continue_()

    page.route("http://10.42.0.10/**", handle_err)
    page.route("http://10.42.0.10/**", handle_ok)
    page.goto("http://10.42.0.10/cgi-bin/luci/admin/network/network")
    page.wait_for_load_state("networkidle")

def test_routing_raise_after(page: Page) -> None:
    """This example passes. The assert? is printed, but the assertion does not fail the test"""

    def handle_err(route: Route):
        print("this should not happen", route.request.url)
        route.continue_()

    def handle_ok(route: Route):
        print("this is ok", route.request.url)
        route.continue_()
        print("assert?")
        assert False

    page.route("http://10.42.0.10/**", handle_err)
    page.route("http://10.42.0.10/**", handle_ok)
    page.goto("http://10.42.0.10/cgi-bin/luci/admin/network/network")
    page.wait_for_load_state("networkidle")

With no route.* call, the playwright times out. Raising before does the same thing, except with a message that indicates a bug in the library. Raising after does nothing, the exception is raised but swallowed somewhere in the library.

EvanTheB avatar Oct 26 '23 00:10 EvanTheB