diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index 325276959..05ad08449 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -528,6 +528,10 @@ def dispatch(self, msg: ParsedMessagePayload) -> None: # Event handlers like route/locatorHandlerTriggered require us to perform async work. # In order to report their potential errors to the user, we need to catch it and store it in the connection def _done_callback(future: asyncio.Future) -> None: + # Pending handlers are cancelled when the sync context manager exits, + # and Future.exception() raises on a cancelled future. + if future.cancelled(): + return exc = future.exception() if exc: self._on_event_listener_error(exc) diff --git a/tests/sync/test_sync.py b/tests/sync/test_sync.py index 22832517e..9a19bc3e2 100644 --- a/tests/sync/test_sync.py +++ b/tests/sync/test_sync.py @@ -13,6 +13,7 @@ # limitations under the License. import contextlib +import logging import multiprocessing import os import signal @@ -34,6 +35,7 @@ Dialog, Error, Page, + Route, TimeoutError, sync_playwright, ) @@ -339,6 +341,43 @@ def test_expect_response_should_use_context_timeout( assert "Timeout 1000ms exceeded" in exc_info.value.message +def _test_sync_playwright_should_not_log_cancelled_route_handler_on_exit( + browser_name: str, launch_arguments: Dict[str, Any], empty_page: str +) -> None: + records: List[logging.LogRecord] = [] + + class RecordingHandler(logging.Handler): + def emit(self, record: logging.LogRecord) -> None: + records.append(record) + + logging.getLogger("asyncio").addHandler(RecordingHandler()) + held: List[Route] = [] + with sync_playwright() as p: + browser = p[browser_name].launch(**launch_arguments) + page = browser.new_page() + page.route("**/held", lambda route: held.append(route)) + page.goto(empty_page) + page.evaluate("() => { fetch('/held').catch(() => {}); }") + while not held: + page.wait_for_timeout(50) + browser.close() + # Exiting the context manager cancels the pending route handler task. + assert [r.getMessage() for r in records] == [] + + +def test_sync_playwright_should_not_log_cancelled_route_handler_on_exit( + server: Server, browser_name: str, launch_arguments: Dict[str, Any] +) -> None: + # https://github.com/microsoft/playwright/issues/43463 + p = multiprocessing.Process( + target=_test_sync_playwright_should_not_log_cancelled_route_handler_on_exit, + args=[browser_name, launch_arguments, server.EMPTY_PAGE], + ) + p.start() + p.join() + assert p.exitcode == 0 + + def _test_sync_playwright_stop_multiple_times() -> None: playwright = sync_playwright().start() playwright.stop()