From 79849c7d699bf7fee25cdf76064a6dc28f86f4f1 Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 22:21:17 +0000 Subject: [PATCH] fix(sync): filter shutdown console wait and remove callback before cancel --- playwright/_impl/_greenlets.py | 10 ++++++++-- tests/sync/python/test_sync.py | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/playwright/_impl/_greenlets.py b/playwright/_impl/_greenlets.py index b62a4f1f2..c5b3fd4dd 100644 --- a/playwright/_impl/_greenlets.py +++ b/playwright/_impl/_greenlets.py @@ -94,12 +94,14 @@ def wait_for_future( ) -> None: __tracebackhide__ = True g_self = greenlet.getcurrent() + waiting = True def resume(_: "asyncio.Future") -> None: - g_self.switch() + if waiting: + g_self.switch() # The wait owns the callback that resumes this greenlet, so that it can - # remove it again when the wait is abandoned. A callback left behind would + # remove it again when the wait ends. A callback left behind would # resume this greenlet at an arbitrary later point. future.add_done_callback(resume) try: @@ -118,6 +120,10 @@ def resume(_: "asyncio.Future") -> None: future.cancel() raise finally: + # A done future has already queued its callbacks; removing them from the + # future cannot stop them, so also make a queued callback inactive. + waiting = False + future.remove_done_callback(resume) # The loop is only running for as long as the dispatcher fiber is alive. # Also after a signal handler interrupted it: run_forever() cleared this # on its way out, but the dispatcher is going to run the loop again. diff --git a/tests/sync/python/test_sync.py b/tests/sync/python/test_sync.py index 3420d43c1..965724c1a 100644 --- a/tests/sync/python/test_sync.py +++ b/tests/sync/python/test_sync.py @@ -186,6 +186,9 @@ def _test_call_sync_method_after_playwright_close_with_own_loop( context = browser.new_context() page = context.new_page() page.goto(empty_page) + with page.expect_console_message(lambda m: m.text == "hello") as console_info: + page.evaluate("console.log('hello')") + assert console_info.value.text == "hello" playwright.stop() with pytest.raises(Error) as exc: page.evaluate("1+1") @@ -196,6 +199,7 @@ def _test_call_sync_method_after_playwright_close_with_own_loop( def test_call_sync_method_after_playwright_close_with_own_loop( server: Server, browser_name: str, launch_arguments: Dict[str, Any] ) -> None: + # Regression test for https://github.com/microsoft/playwright-python/issues/3307. p = multiprocessing.get_context("spawn").Process( target=_test_call_sync_method_after_playwright_close_with_own_loop, args=[browser_name, launch_arguments, server.EMPTY_PAGE],