diff --git a/DRIVER_VERSION b/DRIVER_VERSION index 483850256..eb650ea4c 100644 --- a/DRIVER_VERSION +++ b/DRIVER_VERSION @@ -1 +1 @@ -eac236da288cdc3d0891278a61e1a876348950a0 +0ea36910324ed8aa2a5f460317cbd35881ee88d4 diff --git a/README.md b/README.md index 229cde671..ea25d51b6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H | :--- | :---: | :---: | :---: | | Chromium 158.0.8127.2 | ✅ | ✅ | ✅ | | WebKit 27.2 | ✅ | ✅ | ✅ | -| Firefox 158.0 | ✅ | ✅ | ✅ | +| Firefox 159.0 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/browser_context.py b/playwright/_impl/browser_context.py index 2ac048522..306c47f4d 100644 --- a/playwright/_impl/browser_context.py +++ b/playwright/_impl/browser_context.py @@ -387,8 +387,8 @@ def _on_request_finished( async def _on_route(self, route: network.Route) -> None: route._context = self page = route.request._safe_page() - route_handlers = self._routes[:] - for route_handler in route_handlers: + route_handlers = self._routes + for route_handler in route_handlers[:]: # If the page or the context was closed we stall all requests right away. if (page and page._close_was_called) or self.is_closed(): return @@ -404,7 +404,8 @@ async def _on_route(self, route: network.Route) -> None: if route_handler.will_expire(): del self._routes[index] handled = await route_handler.handle(route) - if not self._routes: + # unrouteAll() replaces _routes and updates interception after waiting for handlers. + if route_handlers is self._routes and not self._routes: fire_and_forget(self._disable_interception()) if handled: return diff --git a/playwright/_impl/event_emitter.py b/playwright/_impl/event_emitter.py index 8ed4de6f0..f09b69275 100644 --- a/playwright/_impl/event_emitter.py +++ b/playwright/_impl/event_emitter.py @@ -82,11 +82,15 @@ def emit(self, type: EventType, *args: Any) -> bool: return True def _call_handler(self, type: EventType, handler: Listener, args: Any) -> None: - promise = handler(*args) - if not inspect.isawaitable(promise): + try: + promise = handler(*args) + if not inspect.isawaitable(promise): + return + # Python: an awaitable is the counterpart of a JS promise or thenable. + future = asyncio.ensure_future(promise) + except Exception as error: + self._handle_listener_error(type, error) return - # Python: the coroutine of an async def handler runs as a task. - future = asyncio.ensure_future(promise) pending = self._pending_handlers.get(type) if pending is None: pending = set() @@ -103,11 +107,19 @@ def on_done(f: "asyncio.Future[Any]") -> None: if self._rejection_handler: self._rejection_handler(e) else: - # Python: the counterpart of an unhandled rejection. - self._report_unhandled_error(e) + try: + self._handle_listener_error(type, e) + except Exception as error: + # Python: report an unhandled rejection outside the task callback. + self._report_unhandled_error(error) future.add_done_callback(on_done) + def _handle_listener_error(self, type: EventType, error: BaseException) -> None: + if type == "error" or not self.listener_count("error"): + raise error + self.emit("error", error) + def _report_unhandled_error(self, error: BaseException) -> None: # Python: an unhandled rejection crashes Node.js; see report_unhandled_error(). report_unhandled_error(error) diff --git a/playwright/_impl/page.py b/playwright/_impl/page.py index edafd68b5..6e6bb55ef 100644 --- a/playwright/_impl/page.py +++ b/playwright/_impl/page.py @@ -330,8 +330,8 @@ def _on_frame_detached(self, frame: Frame) -> None: async def _on_route(self, route: Route) -> None: route._context = self.context - route_handlers = list(self._routes) - for route_handler in route_handlers: + route_handlers = self._routes + for route_handler in route_handlers[:]: # If the page was closed we stall all requests right away. if self._close_was_called or self._browser_context.is_closed(): return @@ -346,7 +346,8 @@ async def _on_route(self, route: Route) -> None: if route_handler.will_expire(): self._routes.pop(index) handled = await route_handler.handle(route) - if not self._routes: + # unrouteAll() replaces _routes and updates interception after waiting for handlers. + if route_handlers is self._routes and not self._routes: fire_and_forget(self._disable_interception()) if handled: return diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 22f9554e0..20a507beb 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -4009,6 +4009,10 @@ async def connect_over_cdp( endpoint_url : str A CDP websocket endpoint or http url to connect to. For example `http://localhost:9222/` or `ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4`. + + Alternatively, a browser channel name such as `chrome` or `msedge` to connect to a browser with remote debugging + enabled in its default user data directory. On Linux, Playwright uses `CHROME_USER_DATA_DIR` when set. Otherwise, + it uses `CHROME_CONFIG_HOME`, then `XDG_CONFIG_HOME`, then `~/.config` to locate that directory. artifacts_dir : Union[pathlib.Path, str, None] If specified, browser artifacts (such as traces and downloads) are saved into this directory. headers : Union[Dict[str, str], None] @@ -4018,10 +4022,10 @@ async def connect_over_cdp( upon the file system being the same between Playwright and the Browser. no_defaults : Union[bool, None] When true, Playwright will not apply its default overrides to the existing default browser context. Specifically, - `accept_downloads` is left at the browser's setting, focus emulation is not enabled, and media emulation options - (such as `color_scheme`, `reduced_motion`, `forced_colors`, and `contrast`) are not applied. Useful when attaching - to a user's daily-driver browser where these overrides would interfere with existing browser state. New contexts - created via `browser.new_context()` are not affected. Defaults to `false`. + `accept_downloads` and font families are left at the browser's settings, focus emulation is not enabled, and media + emulation options (such as `color_scheme`, `reduced_motion`, `forced_colors`, and `contrast`) are not applied. + Useful when attaching to a user's daily-driver browser where these overrides would interfere with existing browser + state. New contexts created via `browser.new_context()` are not affected. Defaults to `false`. slow_mo : Union[float, None] Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. Defaults to 0. @@ -10735,7 +10739,9 @@ async def aria_snapshot( An AI-optimized snapshot, controlled by `mode`, is different from a default snapshot: 1. Includes element references `[ref=e2]`. 2. Does not wait for an element matching the locator, and throws when - no elements match. 3. Includes snapshots of `