Repository navigation
[Bug]: Python sync API logs CancelledError when exiting sync_playwright() with an unanswered route #3272
Description
Version
1.64.0 (latest python). Also reproduces on 1.63.0.
Steps to reproduce
Save as repro.py and run python repro.py.
import logging
from playwright.sync_api import sync_playwright
logging.basicConfig(level=logging.ERROR)
held = []
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.route("**/held", lambda route: held.append(route)) # hold, never answer
page.set_content("<p>x</p>")
page.evaluate("() => { fetch('http://example.test/held').catch(() => {}); }")
page.wait_for_timeout(500)
browser.close()
# Exiting the with block cancels pending tasks in Playwright's own event loop.Expected behavior
A route handler task that is still pending when exiting with sync_playwright() gets cancelled without an error being logged. Cancelling pending tasks during context-manager shutdown is the behavior microsoft/playwright-python#1430 introduced while fixing microsoft/playwright-python#1402.
Actual behavior
During context-manager shutdown, asyncio logs an error from Playwright's own done callback:
ERROR:asyncio:Exception in callback Connection.dispatch.<locals>._done_callback(<Task cancell..._page.py:298>>) at .../playwright/_impl/_connection.py:546
handle: <Handle Connection.dispatch.<locals>._done_callback(<Task cancell..._page.py:298>>) at .../playwright/_impl/_connection.py:546>
Traceback (most recent call last):
File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run
self._context.run(self._callback, *self._args)
File ".../playwright/_impl/_connection.py", line 547, in _done_callback
exc = future.exception()
^^^^^^^^^^^^^^^^^^
File ".../playwright/_impl/_page.py", line 312, in _on_route
handled = await route_handler.handle(route)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../playwright/_impl/_helper.py", line 443, in handle
return await self._handle_internal(route)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../playwright/_impl/_helper.py", line 485, in _handle_internal
return await handled_future
^^^^^^^^^^^^^^^^^^^^
asyncio.exceptions.CancelledError
Additional context
-
Cause:
_done_callbackinConnection.dispatch(sync branch only) callsfuture.exception()without checkingfuture.cancelled()first. On a cancelled future,Future.exception()raisesCancelledErrorinstead of returning it, so the callback raises andasyncioreports it. The affected code is in the Python binding'splaywright/_impl/_connection.py. -
Shutdown timing:
PlaywrightContextManager.__exit__cancels pending tasks before closing the event loop it owns. In this reproduction, that happens when leaving thewithblock afterbrowser.close(), rather than necessarily insidebrowser.close()itself. -
Fix: an early return for a cancelled future prevents the callback from raising. With this change the repro ran clean 5 times under
python -X dev:def _done_callback(future: asyncio.Future) -> None: if future.cancelled(): return exc = future.exception() ...
Environment
- Operating System: Ubuntu 24.04.3 LTS (Linux 6.8.0-101-generic x86_64)
- CPU: x86_64, 8 cores
- Browser: Chromium 156.0.8078.4 (bundled with playwright 1.64.0, headless)
- Python Version: 3.12.3
- Other info: sync API
Also repro'd on Windows:
- Windows, Python 3.12.10
- Playwright 1.64.0
- Bundled headless Chromium 156.0.8078.4
Activity
dcrousso commentedon Oct 21, 2026
Hi, the Playwright bot looked into this on my behalf and reproduced it locally.
Reproduced in 1.63.0 and 1.64.0, already fixed on main, not yet released. The client replacement in #3280 handles the cancelled route task without producing this callback error. The latest release, 1.64.0, remains affected.
Reproduction
The bot extended the existing owned-loop shutdown test, using its local test server rather than an external URL. An ERROR-level handler on the
asynciologger captured the output. This is the core of the test extension, whereempty_pagecomes from the local server andoutputis the captured log stream:The final assertion fails in both released versions with
Connection.dispatch.<locals>._done_callbackandasyncio.exceptions.CancelledError.What the bot checked
All checks ran on macOS 27.0.1 arm64 with Python 3.14.6. Every run confirmed that one route was held and its fetch remained unsettled before shutdown.
CancelledErrorCancelledError4e1ce7ad72ddd538dff046a0bc1b61c825574c43CancelledError36d698e1776181c8c7d31bc9fdc32af072cab081c4ee1239d2662011275d79d62e844d5f9c19bd38The fix parent and fix commit used the same Node driver and browsers, isolating the change to the Python client. No release tag contains the fix yet. Linux, Windows and Python 3.12 were not checked locally.