From 63ff66e9e895e1b70d97301541b4834c0e52187a Mon Sep 17 00:00:00 2001 From: reginaldalfret Date: Mon, 19 Oct 2026 23:40:20 +0530 Subject: [PATCH] fix: ignore CancelledError in sync event listener done callback --- playwright/_impl/_connection.py | 2 ++ tests/sync/test_sync.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index 3c2c7f773..74730499f 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -435,6 +435,8 @@ 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: + 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..ec8acc055 100644 --- a/tests/sync/test_sync.py +++ b/tests/sync/test_sync.py @@ -549,3 +549,28 @@ def handler(message: ConsoleMessage) -> None: def test_click_should_accept_timedelta_for_timeout(page: Page) -> None: with pytest.raises(TimeoutError, match="Timeout 1ms exceeded"): page.click("does-not-exist", timeout=timedelta(milliseconds=1)) + + +def test_sync_event_listener_cancelled_future() -> None: + import asyncio + from unittest.mock import MagicMock + from playwright._impl._connection import Channel, Connection + + loop = asyncio.new_event_loop() + conn = Connection(MagicMock(), MagicMock(), MagicMock(), loop=loop) + conn._is_sync = True + + dummy_obj = MagicMock() + dummy_obj._guid = "dummy" + dummy_channel = Channel(conn, dummy_obj) + dummy_obj._channel = dummy_channel + conn._objects["dummy"] = dummy_obj + + fut = loop.create_future() + fut.cancel() + + dummy_channel._emitter.on("event", lambda p: fut) + conn.dispatch({"guid": "dummy", "method": "event", "params": {}}) + loop.run_until_complete(asyncio.sleep(0.01)) + loop.close() +