From 6004e4cfaa91159bf863d024d14a490ae459842f Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Tue, 24 Jun 2025 08:53:55 -0700 Subject: [PATCH 1/8] chore: explicit timeout config for send --- playwright/_impl/_accessibility.py | 2 +- playwright/_impl/_artifact.py | 40 +++++- playwright/_impl/_browser.py | 12 +- playwright/_impl/_browser_context.py | 35 +++--- playwright/_impl/_browser_type.py | 9 +- playwright/_impl/_cdp_session.py | 7 +- playwright/_impl/_clock.py | 20 +-- playwright/_impl/_connection.py | 46 ++++--- playwright/_impl/_dialog.py | 7 +- playwright/_impl/_element_handle.py | 118 +++++++++++------ playwright/_impl/_fetch.py | 15 ++- playwright/_impl/_frame.py | 126 ++++++++++++------- playwright/_impl/_har_router.py | 4 +- playwright/_impl/_input.py | 24 ++-- playwright/_impl/_js_handle.py | 21 +++- playwright/_impl/_json_pipe.py | 4 +- playwright/_impl/_local_utils.py | 15 +-- playwright/_impl/_locator.py | 3 + playwright/_impl/_network.py | 74 ++++++++--- playwright/_impl/_page.py | 89 ++++++++----- playwright/_impl/_selectors.py | 4 +- playwright/_impl/_set_input_files_helpers.py | 1 + playwright/_impl/_stream.py | 4 +- playwright/_impl/_tracing.py | 23 ++-- playwright/_impl/_waiter.py | 3 + playwright/_impl/_writable_stream.py | 4 +- 26 files changed, 482 insertions(+), 228 deletions(-) diff --git a/playwright/_impl/_accessibility.py b/playwright/_impl/_accessibility.py index 010b4e8c5..fe6909c21 100644 --- a/playwright/_impl/_accessibility.py +++ b/playwright/_impl/_accessibility.py @@ -65,5 +65,5 @@ async def snapshot( params = locals_to_params(locals()) if root: params["root"] = root._channel - result = await self._channel.send("accessibilitySnapshot", params) + result = await self._channel.send("accessibilitySnapshot", None, params) return _ax_node_from_protocol(result) if result else None diff --git a/playwright/_impl/_artifact.py b/playwright/_impl/_artifact.py index a5af44573..a08294cbe 100644 --- a/playwright/_impl/_artifact.py +++ b/playwright/_impl/_artifact.py @@ -33,27 +33,55 @@ async def path_after_finished(self) -> pathlib.Path: raise Error( "Path is not available when using browser_type.connect(). Use save_as() to save a local copy." ) - path = await self._channel.send("pathAfterFinished") + path = await self._channel.send( + "pathAfterFinished", + None, + ) return pathlib.Path(path) async def save_as(self, path: Union[str, Path]) -> None: - stream = cast(Stream, from_channel(await self._channel.send("saveAsStream"))) + stream = cast( + Stream, + from_channel( + await self._channel.send( + "saveAsStream", + None, + ) + ), + ) make_dirs_for_file(path) await stream.save_as(path) async def failure(self) -> Optional[str]: - reason = await self._channel.send("failure") + reason = await self._channel.send( + "failure", + None, + ) if reason is None: return None return patch_error_message(reason) async def delete(self) -> None: - await self._channel.send("delete") + await self._channel.send( + "delete", + None, + ) async def read_info_buffer(self) -> bytes: - stream = cast(Stream, from_channel(await self._channel.send("stream"))) + stream = cast( + Stream, + from_channel( + await self._channel.send( + "stream", + None, + ) + ), + ) buffer = await stream.read_all() return buffer async def cancel(self) -> None: # pyright: ignore[reportIncompatibleMethodOverride] - await self._channel.send("cancel") + await self._channel.send( + "cancel", + None, + ) diff --git a/playwright/_impl/_browser.py b/playwright/_impl/_browser.py index 9b3c1cacc..5a9a87450 100644 --- a/playwright/_impl/_browser.py +++ b/playwright/_impl/_browser.py @@ -168,7 +168,7 @@ async def new_context( assert self._browser_type is not None await self._browser_type._prepare_browser_context_params(params) - channel = await self._channel.send("newContext", params) + channel = await self._channel.send("newContext", None, params) context = cast(BrowserContext, from_channel(channel)) await context._initialize_har_from_options( record_har_content=recordHarContent, @@ -235,7 +235,7 @@ async def close(self, reason: str = None) -> None: if self._should_close_connection_on_close: await self._connection.stop_async() else: - await self._channel.send("close", {"reason": reason}) + await self._channel.send("close", None, {"reason": reason}) except Exception as e: if not is_target_closed_error(e): raise e @@ -245,7 +245,7 @@ def version(self) -> str: return self._initializer["version"] async def new_browser_cdp_session(self) -> CDPSession: - return from_channel(await self._channel.send("newBrowserCDPSession")) + return from_channel(await self._channel.send("newBrowserCDPSession", None)) async def start_tracing( self, @@ -260,10 +260,12 @@ async def start_tracing( if path: self._cr_tracing_path = str(path) params["path"] = str(path) - await self._channel.send("startTracing", params) + await self._channel.send("startTracing", None, params) async def stop_tracing(self) -> bytes: - artifact = cast(Artifact, from_channel(await self._channel.send("stopTracing"))) + artifact = cast( + Artifact, from_channel(await self._channel.send("stopTracing", None)) + ) buffer = await artifact.read_info_buffer() await artifact.delete() if self._cr_tracing_path: diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py index 1264d3f8b..60b60c46e 100644 --- a/playwright/_impl/_browser_context.py +++ b/playwright/_impl/_browser_context.py @@ -330,17 +330,17 @@ async def _initialize_har_from_options( async def new_page(self) -> Page: if self._owner_page: raise Error("Please use browser.new_context()") - return from_channel(await self._channel.send("newPage")) + return from_channel(await self._channel.send("newPage", None)) async def cookies(self, urls: Union[str, Sequence[str]] = None) -> List[Cookie]: if urls is None: urls = [] if isinstance(urls, str): urls = [urls] - return await self._channel.send("cookies", dict(urls=urls)) + return await self._channel.send("cookies", None, dict(urls=urls)) async def add_cookies(self, cookies: Sequence[SetCookieParam]) -> None: - await self._channel.send("addCookies", dict(cookies=cookies)) + await self._channel.send("addCookies", None, dict(cookies=cookies)) async def clear_cookies( self, @@ -350,6 +350,7 @@ async def clear_cookies( ) -> None: await self._channel.send( "clearCookies", + None, { "name": name if isinstance(name, str) else None, "nameRegexSource": name.pattern if isinstance(name, Pattern) else None, @@ -374,21 +375,21 @@ async def clear_cookies( async def grant_permissions( self, permissions: Sequence[str], origin: str = None ) -> None: - await self._channel.send("grantPermissions", locals_to_params(locals())) + await self._channel.send("grantPermissions", None, locals_to_params(locals())) async def clear_permissions(self) -> None: - await self._channel.send("clearPermissions") + await self._channel.send("clearPermissions", None) async def set_geolocation(self, geolocation: Geolocation = None) -> None: - await self._channel.send("setGeolocation", locals_to_params(locals())) + await self._channel.send("setGeolocation", None, locals_to_params(locals())) async def set_extra_http_headers(self, headers: Dict[str, str]) -> None: await self._channel.send( - "setExtraHTTPHeaders", dict(headers=serialize_headers(headers)) + "setExtraHTTPHeaders", None, dict(headers=serialize_headers(headers)) ) async def set_offline(self, offline: bool) -> None: - await self._channel.send("setOffline", dict(offline=offline)) + await self._channel.send("setOffline", None, dict(offline=offline)) async def add_init_script( self, script: str = None, path: Union[str, Path] = None @@ -397,7 +398,7 @@ async def add_init_script( script = (await async_readfile(path)).decode() if not isinstance(script, str): raise Error("Either path or script parameter must be specified") - await self._channel.send("addInitScript", dict(source=script)) + await self._channel.send("addInitScript", None, dict(source=script)) async def expose_binding( self, name: str, callback: Callable, handle: bool = None @@ -411,7 +412,7 @@ async def expose_binding( raise Error(f'Function "{name}" has been already registered') self._bindings[name] = callback await self._channel.send( - "exposeBinding", dict(name=name, needsHandle=handle or False) + "exposeBinding", None, dict(name=name, needsHandle=handle or False) ) async def expose_function(self, name: str, callback: Callable) -> None: @@ -499,7 +500,7 @@ async def _record_into_har( } if page: params["page"] = page._channel - har_id = await self._channel.send("harStart", params) + har_id = await self._channel.send("harStart", None, params) self._har_recorders[har_id] = { "path": str(har), "content": update_content, @@ -535,7 +536,7 @@ async def route_from_har( async def _update_interception_patterns(self) -> None: patterns = RouteHandler.prepare_interception_patterns(self._routes) await self._channel.send( - "setNetworkInterceptionPatterns", {"patterns": patterns} + "setNetworkInterceptionPatterns", None, {"patterns": patterns} ) async def _update_web_socket_interception_patterns(self) -> None: @@ -543,7 +544,7 @@ async def _update_web_socket_interception_patterns(self) -> None: self._web_socket_routes ) await self._channel.send( - "setWebSocketInterceptionPatterns", {"patterns": patterns} + "setWebSocketInterceptionPatterns", None, {"patterns": patterns} ) def expect_event( @@ -596,7 +597,7 @@ async def _inner_close() -> None: har = cast( Artifact, from_channel( - await self._channel.send("harExport", {"harId": har_id}) + await self._channel.send("harExport", None, {"harId": har_id}) ), ) # Server side will compress artifact if content is attach or if file is .zip. @@ -615,14 +616,14 @@ async def _inner_close() -> None: await har.delete() await self._channel._connection.wrap_api_call(_inner_close, True) - await self._channel.send("close", {"reason": reason}) + await self._channel.send("close", None, {"reason": reason}) await self._closed_future async def storage_state( self, path: Union[str, Path] = None, indexedDB: bool = None ) -> StorageState: result = await self._channel.send_return_as_dict( - "storageState", {"indexedDB": indexedDB} + "storageState", None, {"indexedDB": indexedDB} ) if path: await async_writefile(path, json.dumps(result)) @@ -749,7 +750,7 @@ async def new_cdp_session(self, page: Union[Page, Frame]) -> CDPSession: params["frame"] = page._channel else: raise Error("page: expected Page or Frame") - return from_channel(await self._channel.send("newCDPSession", params)) + return from_channel(await self._channel.send("newCDPSession", None, params)) @property def tracing(self) -> Tracing: diff --git a/playwright/_impl/_browser_type.py b/playwright/_impl/_browser_type.py index ab8c00e97..050080fbe 100644 --- a/playwright/_impl/_browser_type.py +++ b/playwright/_impl/_browser_type.py @@ -93,7 +93,7 @@ async def launch( params = locals_to_params(locals()) normalize_launch_params(params) browser = cast( - Browser, from_channel(await self._channel.send("launch", params)) + Browser, from_channel(await self._channel.send("launch", None, params)) ) browser._connect_to_browser_type( self, str(tracesDir) if tracesDir is not None else None @@ -159,7 +159,7 @@ async def launch_persistent_context( await self._prepare_browser_context_params(params) normalize_launch_params(params) result = await self._channel.send_return_as_dict( - "launchPersistentContext", params + "launchPersistentContext", None, params ) browser = cast( Browser, @@ -200,7 +200,9 @@ async def connect_over_cdp( params["timeout"] = TimeoutSettings.launch_timeout(timeout) if params.get("headers"): params["headers"] = serialize_headers(params["headers"]) - response = await self._channel.send_return_as_dict("connectOverCDP", params) + response = await self._channel.send_return_as_dict( + "connectOverCDP", None, params + ) browser = cast(Browser, from_channel(response["browser"])) browser._connect_to_browser_type(self, None) @@ -222,6 +224,7 @@ async def connect( pipe_channel = ( await local_utils._channel.send_return_as_dict( "connect", + None, { "wsEndpoint": wsEndpoint, "headers": headers, diff --git a/playwright/_impl/_cdp_session.py b/playwright/_impl/_cdp_session.py index b6e383ff2..95e65c57a 100644 --- a/playwright/_impl/_cdp_session.py +++ b/playwright/_impl/_cdp_session.py @@ -29,7 +29,10 @@ def _on_event(self, params: Any) -> None: self.emit(params["method"], params.get("params")) async def send(self, method: str, params: Dict = None) -> Dict: - return await self._channel.send("send", locals_to_params(locals())) + return await self._channel.send("send", None, locals_to_params(locals())) async def detach(self) -> None: - await self._channel.send("detach") + await self._channel.send( + "detach", + None, + ) diff --git a/playwright/_impl/_clock.py b/playwright/_impl/_clock.py index d8bb58718..3fc0dcde5 100644 --- a/playwright/_impl/_clock.py +++ b/playwright/_impl/_clock.py @@ -27,7 +27,7 @@ def __init__(self, browser_context: "BrowserContext") -> None: async def install(self, time: Union[float, str, datetime.datetime] = None) -> None: await self._browser_context._channel.send( - "clockInstall", parse_time(time) if time is not None else {} + "clockInstall", None, parse_time(time) if time is not None else {} ) async def fast_forward( @@ -35,38 +35,44 @@ async def fast_forward( ticks: Union[int, str], ) -> None: await self._browser_context._channel.send( - "clockFastForward", parse_ticks(ticks) + "clockFastForward", None, parse_ticks(ticks) ) async def pause_at( self, time: Union[float, str, datetime.datetime], ) -> None: - await self._browser_context._channel.send("clockPauseAt", parse_time(time)) + await self._browser_context._channel.send( + "clockPauseAt", None, parse_time(time) + ) async def resume( self, ) -> None: - await self._browser_context._channel.send("clockResume") + await self._browser_context._channel.send("clockResume", None) async def run_for( self, ticks: Union[int, str], ) -> None: - await self._browser_context._channel.send("clockRunFor", parse_ticks(ticks)) + await self._browser_context._channel.send( + "clockRunFor", None, parse_ticks(ticks) + ) async def set_fixed_time( self, time: Union[float, str, datetime.datetime], ) -> None: - await self._browser_context._channel.send("clockSetFixedTime", parse_time(time)) + await self._browser_context._channel.send( + "clockSetFixedTime", None, parse_time(time) + ) async def set_system_time( self, time: Union[float, str, datetime.datetime], ) -> None: await self._browser_context._channel.send( - "clockSetSystemTime", parse_time(time) + "clockSetSystemTime", None, parse_time(time) ) diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index 3519aeebd..e01b3a212 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -47,6 +47,8 @@ from playwright._impl._local_utils import LocalUtils from playwright._impl._playwright import Playwright +TimeoutCalculator = Optional[Callable[[Optional[float]], float]] + class Channel(AsyncIOEventEmitter): def __init__(self, connection: "Connection", object: "ChannelOwner") -> None: @@ -55,17 +57,18 @@ def __init__(self, connection: "Connection", object: "ChannelOwner") -> None: self._guid = object._guid self._object = object self.on("error", lambda exc: self._connection._on_event_listener_error(exc)) - self._timeout_calculator: Optional[Callable[[Optional[float]], float]] = None + self._timeout_calculator: TimeoutCalculator = None async def send( self, method: str, + timeout_calculator: TimeoutCalculator, params: Dict = None, is_internal: bool = False, title: str = None, ) -> Any: return await self._connection.wrap_api_call( - lambda: self._inner_send(method, params, False), + lambda: self._inner_send(method, timeout_calculator, params, False), is_internal, title, ) @@ -73,12 +76,13 @@ async def send( async def send_return_as_dict( self, method: str, + timeout_calculator: TimeoutCalculator, params: Dict = None, is_internal: bool = False, title: str = None, ) -> Any: return await self._connection.wrap_api_call( - lambda: self._inner_send(method, params, True), + lambda: self._inner_send(method, timeout_calculator, params, True), is_internal, title, ) @@ -86,6 +90,7 @@ async def send_return_as_dict( def send_no_reply( self, method: str, + timeout_calculator: TimeoutCalculator, params: Dict = None, is_internal: bool = False, title: str = None, @@ -93,25 +98,25 @@ def send_no_reply( # No reply messages are used to e.g. waitForEventInfo(after). self._connection.wrap_api_call_sync( lambda: self._connection._send_message_to_server( - self._object, method, {} if params is None else params, True + self._object, method, _augment_params(params, timeout_calculator), True ), is_internal, title, ) async def _inner_send( - self, method: str, params: Optional[Dict], return_as_dict: bool + self, + method: str, + timeout_calculator: TimeoutCalculator, + params: Optional[Dict], + return_as_dict: bool, ) -> Any: - if params is None: - params = {} - if self._timeout_calculator is not None: - params["timeout"] = self._timeout_calculator(params.get("timeout")) if self._connection._error: error = self._connection._error self._connection._error = None raise error callback = self._connection._send_message_to_server( - self._object, method, _filter_none(params) + self._object, method, _augment_params(params, timeout_calculator) ) done, _ = await asyncio.wait( { @@ -136,11 +141,6 @@ async def _inner_send( key = next(iter(result)) return result[key] - def _set_timeout_calculator( - self, timeout_calculator: Callable[[Optional[float]], float] - ) -> None: - self._timeout_calculator = timeout_calculator - class ChannelOwner(AsyncIOEventEmitter): def __init__( @@ -197,7 +197,9 @@ def _update_subscription(self, event: str, enabled: bool) -> None: if protocol_event: self._connection.wrap_api_call_sync( lambda: self._channel.send_no_reply( - "updateSubscription", {"event": protocol_event, "enabled": enabled} + "updateSubscription", + None, + {"event": protocol_event, "enabled": enabled}, ), True, ) @@ -244,6 +246,7 @@ async def initialize(self) -> "Playwright": return from_channel( await self._channel.send( "initialize", + None, { "sdkLanguage": "python", }, @@ -639,6 +642,17 @@ def _extract_stack_trace_information_from_stack( } +def _augment_params( + params: Optional[Dict], + timeout_calculator: Optional[Callable[[Optional[float]], float]], +) -> Dict: + if params is None: + params = {} + if timeout_calculator is not None: + params["timeout"] = timeout_calculator(params.get("timeout")) + return _filter_none(params) + + def _filter_none(d: Mapping) -> Dict: result = {} for k, v in d.items(): diff --git a/playwright/_impl/_dialog.py b/playwright/_impl/_dialog.py index a0c6ca77f..226e703b9 100644 --- a/playwright/_impl/_dialog.py +++ b/playwright/_impl/_dialog.py @@ -48,7 +48,10 @@ def page(self) -> Optional["Page"]: return self._page async def accept(self, promptText: str = None) -> None: - await self._channel.send("accept", locals_to_params(locals())) + await self._channel.send("accept", None, locals_to_params(locals())) async def dismiss(self) -> None: - await self._channel.send("dismiss") + await self._channel.send( + "dismiss", + None, + ) diff --git a/playwright/_impl/_element_handle.py b/playwright/_impl/_element_handle.py index fd99c0b00..ea03d3314 100644 --- a/playwright/_impl/_element_handle.py +++ b/playwright/_impl/_element_handle.py @@ -56,57 +56,68 @@ def __init__( ) -> None: super().__init__(parent, type, guid, initializer) self._frame = cast("Frame", parent) - self._channel._set_timeout_calculator(self._frame._timeout) async def _createSelectorForTest(self, name: str) -> Optional[str]: - return await self._channel.send("createSelectorForTest", dict(name=name)) + return await self._channel.send( + "createSelectorForTest", self._frame._timeout, dict(name=name) + ) def as_element(self) -> Optional["ElementHandle"]: return self async def owner_frame(self) -> Optional["Frame"]: - return from_nullable_channel(await self._channel.send("ownerFrame")) + return from_nullable_channel( + await self._channel.send("ownerFrame", self._frame._timeout) + ) async def content_frame(self) -> Optional["Frame"]: - return from_nullable_channel(await self._channel.send("contentFrame")) + return from_nullable_channel( + await self._channel.send("contentFrame", self._frame._timeout) + ) async def get_attribute(self, name: str) -> Optional[str]: - return await self._channel.send("getAttribute", dict(name=name)) + return await self._channel.send( + "getAttribute", self._frame._timeout, dict(name=name) + ) async def text_content(self) -> Optional[str]: - return await self._channel.send("textContent") + return await self._channel.send("textContent", self._frame._timeout) async def inner_text(self) -> str: - return await self._channel.send("innerText") + return await self._channel.send("innerText", self._frame._timeout) async def inner_html(self) -> str: - return await self._channel.send("innerHTML") + return await self._channel.send("innerHTML", self._frame._timeout) async def is_checked(self) -> bool: - return await self._channel.send("isChecked") + return await self._channel.send("isChecked", self._frame._timeout) async def is_disabled(self) -> bool: - return await self._channel.send("isDisabled") + return await self._channel.send("isDisabled", self._frame._timeout) async def is_editable(self) -> bool: - return await self._channel.send("isEditable") + return await self._channel.send("isEditable", self._frame._timeout) async def is_enabled(self) -> bool: - return await self._channel.send("isEnabled") + return await self._channel.send("isEnabled", self._frame._timeout) async def is_hidden(self) -> bool: - return await self._channel.send("isHidden") + return await self._channel.send("isHidden", self._frame._timeout) async def is_visible(self) -> bool: - return await self._channel.send("isVisible") + return await self._channel.send("isVisible", self._frame._timeout) async def dispatch_event(self, type: str, eventInit: Dict = None) -> None: await self._channel.send( - "dispatchEvent", dict(type=type, eventInit=serialize_argument(eventInit)) + "dispatchEvent", + self._frame._timeout, + dict(type=type, eventInit=serialize_argument(eventInit)), ) async def scroll_into_view_if_needed(self, timeout: float = None) -> None: - await self._channel.send("scrollIntoViewIfNeeded", locals_to_params(locals())) + await self._channel.send( + "scrollIntoViewIfNeeded", self._frame._timeout, locals_to_params(locals()) + ) async def hover( self, @@ -117,7 +128,9 @@ async def hover( force: bool = None, trial: bool = None, ) -> None: - await self._channel.send("hover", locals_to_params(locals())) + await self._channel.send( + "hover", self._frame._timeout, locals_to_params(locals()) + ) async def click( self, @@ -131,7 +144,9 @@ async def click( noWaitAfter: bool = None, trial: bool = None, ) -> None: - await self._channel.send("click", locals_to_params(locals())) + await self._channel.send( + "click", self._frame._timeout, locals_to_params(locals()) + ) async def dblclick( self, @@ -144,7 +159,9 @@ async def dblclick( noWaitAfter: bool = None, trial: bool = None, ) -> None: - await self._channel.send("dblclick", locals_to_params(locals())) + await self._channel.send( + "dblclick", self._frame._timeout, locals_to_params(locals()) + ) async def select_option( self, @@ -163,7 +180,7 @@ async def select_option( **convert_select_option_values(value, index, label, element), ) ) - return await self._channel.send("selectOption", params) + return await self._channel.send("selectOption", self._frame._timeout, params) async def tap( self, @@ -174,7 +191,9 @@ async def tap( noWaitAfter: bool = None, trial: bool = None, ) -> None: - await self._channel.send("tap", locals_to_params(locals())) + await self._channel.send( + "tap", self._frame._timeout, locals_to_params(locals()) + ) async def fill( self, @@ -183,13 +202,19 @@ async def fill( noWaitAfter: bool = None, force: bool = None, ) -> None: - await self._channel.send("fill", locals_to_params(locals())) + await self._channel.send( + "fill", self._frame._timeout, locals_to_params(locals()) + ) async def select_text(self, force: bool = None, timeout: float = None) -> None: - await self._channel.send("selectText", locals_to_params(locals())) + await self._channel.send( + "selectText", self._frame._timeout, locals_to_params(locals()) + ) async def input_value(self, timeout: float = None) -> str: - return await self._channel.send("inputValue", locals_to_params(locals())) + return await self._channel.send( + "inputValue", self._frame._timeout, locals_to_params(locals()) + ) async def set_input_files( self, @@ -205,14 +230,15 @@ async def set_input_files( converted = await convert_input_files(files, frame.page.context) await self._channel.send( "setInputFiles", + self._frame._timeout, { - "timeout": self._frame._timeout(timeout), + "timeout": timeout, **converted, }, ) async def focus(self) -> None: - await self._channel.send("focus") + await self._channel.send("focus", self._frame._timeout) async def type( self, @@ -221,7 +247,9 @@ async def type( timeout: float = None, noWaitAfter: bool = None, ) -> None: - await self._channel.send("type", locals_to_params(locals())) + await self._channel.send( + "type", self._frame._timeout, locals_to_params(locals()) + ) async def press( self, @@ -230,7 +258,9 @@ async def press( timeout: float = None, noWaitAfter: bool = None, ) -> None: - await self._channel.send("press", locals_to_params(locals())) + await self._channel.send( + "press", self._frame._timeout, locals_to_params(locals()) + ) async def set_checked( self, @@ -264,7 +294,9 @@ async def check( noWaitAfter: bool = None, trial: bool = None, ) -> None: - await self._channel.send("check", locals_to_params(locals())) + await self._channel.send( + "check", self._frame._timeout, locals_to_params(locals()) + ) async def uncheck( self, @@ -274,10 +306,12 @@ async def uncheck( noWaitAfter: bool = None, trial: bool = None, ) -> None: - await self._channel.send("uncheck", locals_to_params(locals())) + await self._channel.send( + "uncheck", self._frame._timeout, locals_to_params(locals()) + ) async def bounding_box(self) -> Optional[FloatRect]: - return await self._channel.send("boundingBox") + return await self._channel.send("boundingBox", self._frame._timeout) async def screenshot( self, @@ -308,7 +342,9 @@ async def screenshot( params["mask"], ) ) - encoded_binary = await self._channel.send("screenshot", params) + encoded_binary = await self._channel.send( + "screenshot", self._frame._timeout, params + ) decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) @@ -317,14 +353,18 @@ async def screenshot( async def query_selector(self, selector: str) -> Optional["ElementHandle"]: return from_nullable_channel( - await self._channel.send("querySelector", dict(selector=selector)) + await self._channel.send( + "querySelector", self._frame._timeout, dict(selector=selector) + ) ) async def query_selector_all(self, selector: str) -> List["ElementHandle"]: return list( map( cast(Callable[[Any], Any], from_nullable_channel), - await self._channel.send("querySelectorAll", dict(selector=selector)), + await self._channel.send( + "querySelectorAll", self._frame._timeout, dict(selector=selector) + ), ) ) @@ -337,6 +377,7 @@ async def eval_on_selector( return parse_result( await self._channel.send( "evalOnSelector", + self._frame._timeout, dict( selector=selector, expression=expression, @@ -354,6 +395,7 @@ async def eval_on_selector_all( return parse_result( await self._channel.send( "evalOnSelectorAll", + self._frame._timeout, dict( selector=selector, expression=expression, @@ -369,7 +411,9 @@ async def wait_for_element_state( ], timeout: float = None, ) -> None: - await self._channel.send("waitForElementState", locals_to_params(locals())) + await self._channel.send( + "waitForElementState", self._frame._timeout, locals_to_params(locals()) + ) async def wait_for_selector( self, @@ -379,7 +423,9 @@ async def wait_for_selector( strict: bool = None, ) -> Optional["ElementHandle"]: return from_nullable_channel( - await self._channel.send("waitForSelector", locals_to_params(locals())) + await self._channel.send( + "waitForSelector", self._frame._timeout, locals_to_params(locals()) + ) ) diff --git a/playwright/_impl/_fetch.py b/playwright/_impl/_fetch.py index a0120e0cd..1aac14e92 100644 --- a/playwright/_impl/_fetch.py +++ b/playwright/_impl/_fetch.py @@ -91,7 +91,9 @@ async def new_context( ) context = cast( APIRequestContext, - from_channel(await self.playwright._channel.send("newRequest", params)), + from_channel( + await self.playwright._channel.send("newRequest", None, params) + ), ) context._timeout_settings.set_default_timeout(timeout) return context @@ -105,12 +107,13 @@ def __init__( self._tracing: Tracing = from_channel(initializer["tracing"]) self._close_reason: Optional[str] = None self._timeout_settings = TimeoutSettings(None) - self._channel._set_timeout_calculator(self._timeout_settings.timeout) async def dispose(self, reason: str = None) -> None: self._close_reason = reason try: - await self._channel.send("dispose", {"reason": reason}) + await self._channel.send( + "dispose", self._timeout_settings.timeout, {"reason": reason} + ) except Error as e: if is_target_closed_error(e): return @@ -408,6 +411,7 @@ async def _inner_fetch( response = await self._channel.send( "fetch", + self._timeout_settings.timeout, { "url": url, "params": object_to_array(params) if isinstance(params, dict) else None, @@ -432,7 +436,7 @@ async def storage_state( indexedDB: bool = None, ) -> StorageState: result = await self._channel.send_return_as_dict( - "storageState", {"indexedDB": indexedDB} + "storageState", self._timeout_settings.timeout, {"indexedDB": indexedDB} ) if path: await async_writefile(path, json.dumps(result)) @@ -487,6 +491,7 @@ async def body(self) -> bytes: result = await self._request._connection.wrap_api_call( lambda: self._request._channel.send_return_as_dict( "fetchResponseBody", + None, { "fetchUid": self._fetch_uid, }, @@ -512,6 +517,7 @@ async def json(self) -> Any: async def dispose(self) -> None: await self._request._channel.send( "disposeAPIResponse", + None, { "fetchUid": self._fetch_uid, }, @@ -524,6 +530,7 @@ def _fetch_uid(self) -> str: async def _fetch_log(self) -> List[str]: return await self._request._channel.send( "fetchLog", + None, { "fetchUid": self._fetch_uid, }, diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index fc3c4a54d..8e9dbfb9c 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -101,7 +101,6 @@ def __init__( "navigated", lambda params: self._on_frame_navigated(params), ) - self._channel._set_timeout_calculator(self._timeout) def __repr__(self) -> str: return f"" @@ -127,7 +126,9 @@ def _on_frame_navigated(self, event: FrameNavigatedEvent) -> None: self._page.emit("framenavigated", self) async def _query_count(self, selector: str) -> int: - return await self._channel.send("queryCount", {"selector": selector}) + return await self._channel.send( + "queryCount", self._timeout, {"selector": selector} + ) @property def page(self) -> "Page": @@ -145,7 +146,7 @@ async def goto( Optional[Response], from_nullable_channel( await self._channel.send( - "goto", self._locals_to_params_with_navigation_timeout(locals()) + "goto", self._navigation_timeout, locals_to_params(locals()) ) ), ) @@ -286,12 +287,13 @@ def _navigation_timeout(self, timeout: Optional[float]) -> float: return timeout_settings.navigation_timeout(timeout) async def frame_element(self) -> ElementHandle: - return from_channel(await self._channel.send("frameElement")) + return from_channel(await self._channel.send("frameElement", self._timeout)) async def evaluate(self, expression: str, arg: Serializable = None) -> Any: return parse_result( await self._channel.send( "evaluateExpression", + self._timeout, dict( expression=expression, arg=serialize_argument(arg), @@ -305,6 +307,7 @@ async def evaluate_handle( return from_channel( await self._channel.send( "evaluateExpressionHandle", + self._timeout, dict( expression=expression, arg=serialize_argument(arg), @@ -316,14 +319,18 @@ async def query_selector( self, selector: str, strict: bool = None ) -> Optional[ElementHandle]: return from_nullable_channel( - await self._channel.send("querySelector", locals_to_params(locals())) + await self._channel.send( + "querySelector", self._timeout, locals_to_params(locals()) + ) ) async def query_selector_all(self, selector: str) -> List[ElementHandle]: return list( map( from_channel, - await self._channel.send("querySelectorAll", dict(selector=selector)), + await self._channel.send( + "querySelectorAll", self._timeout, dict(selector=selector) + ), ) ) @@ -335,34 +342,48 @@ async def wait_for_selector( state: Literal["attached", "detached", "hidden", "visible"] = None, ) -> Optional[ElementHandle]: return from_nullable_channel( - await self._channel.send("waitForSelector", locals_to_params(locals())) + await self._channel.send( + "waitForSelector", self._timeout, locals_to_params(locals()) + ) ) async def is_checked( self, selector: str, strict: bool = None, timeout: float = None ) -> bool: - return await self._channel.send("isChecked", locals_to_params(locals())) + return await self._channel.send( + "isChecked", self._timeout, locals_to_params(locals()) + ) async def is_disabled( self, selector: str, strict: bool = None, timeout: float = None ) -> bool: - return await self._channel.send("isDisabled", locals_to_params(locals())) + return await self._channel.send( + "isDisabled", self._timeout, locals_to_params(locals()) + ) async def is_editable( self, selector: str, strict: bool = None, timeout: float = None ) -> bool: - return await self._channel.send("isEditable", locals_to_params(locals())) + return await self._channel.send( + "isEditable", self._timeout, locals_to_params(locals()) + ) async def is_enabled( self, selector: str, strict: bool = None, timeout: float = None ) -> bool: - return await self._channel.send("isEnabled", locals_to_params(locals())) + return await self._channel.send( + "isEnabled", self._timeout, locals_to_params(locals()) + ) async def is_hidden(self, selector: str, strict: bool = None) -> bool: - return await self._channel.send("isHidden", locals_to_params(locals())) + return await self._channel.send( + "isHidden", self._timeout, locals_to_params(locals()) + ) async def is_visible(self, selector: str, strict: bool = None) -> bool: - return await self._channel.send("isVisible", locals_to_params(locals())) + return await self._channel.send( + "isVisible", self._timeout, locals_to_params(locals()) + ) async def dispatch_event( self, @@ -374,6 +395,7 @@ async def dispatch_event( ) -> None: await self._channel.send( "dispatchEvent", + self._timeout, locals_to_params( dict( selector=selector, @@ -395,6 +417,7 @@ async def eval_on_selector( return parse_result( await self._channel.send( "evalOnSelector", + self._timeout, locals_to_params( dict( selector=selector, @@ -415,6 +438,7 @@ async def eval_on_selector_all( return parse_result( await self._channel.send( "evalOnSelectorAll", + self._timeout, dict( selector=selector, expression=expression, @@ -424,7 +448,7 @@ async def eval_on_selector_all( ) async def content(self) -> str: - return await self._channel.send("content") + return await self._channel.send("content", self._timeout) async def set_content( self, @@ -433,7 +457,7 @@ async def set_content( waitUntil: DocumentLoadState = None, ) -> None: await self._channel.send( - "setContent", self._locals_to_params_with_navigation_timeout(locals()) + "setContent", self._navigation_timeout, locals_to_params(locals()) ) @property @@ -468,7 +492,9 @@ async def add_script_tag( (await async_readfile(path)).decode(), path ) del params["path"] - return from_channel(await self._channel.send("addScriptTag", params)) + return from_channel( + await self._channel.send("addScriptTag", self._timeout, params) + ) async def add_style_tag( self, url: str = None, path: Union[str, Path] = None, content: str = None @@ -482,7 +508,9 @@ async def add_style_tag( + "*/" ) del params["path"] - return from_channel(await self._channel.send("addStyleTag", params)) + return from_channel( + await self._channel.send("addStyleTag", self._timeout, params) + ) async def click( self, @@ -498,7 +526,7 @@ async def click( strict: bool = None, trial: bool = None, ) -> None: - await self._channel.send("click", locals_to_params(locals())) + await self._channel.send("click", self._timeout, locals_to_params(locals())) async def dblclick( self, @@ -514,7 +542,7 @@ async def dblclick( trial: bool = None, ) -> None: await self._channel.send( - "dblclick", locals_to_params(locals()), title="Double click" + "dblclick", self._timeout, locals_to_params(locals()), title="Double click" ) async def tap( @@ -528,7 +556,7 @@ async def tap( strict: bool = None, trial: bool = None, ) -> None: - await self._channel.send("tap", locals_to_params(locals())) + await self._channel.send("tap", self._timeout, locals_to_params(locals())) async def fill( self, @@ -539,7 +567,7 @@ async def fill( strict: bool = None, force: bool = None, ) -> None: - await self._channel.send("fill", locals_to_params(locals())) + await self._channel.send("fill", self._timeout, locals_to_params(locals())) def locator( self, @@ -620,27 +648,35 @@ def frame_locator(self, selector: str) -> FrameLocator: async def focus( self, selector: str, strict: bool = None, timeout: float = None ) -> None: - await self._channel.send("focus", locals_to_params(locals())) + await self._channel.send("focus", self._timeout, locals_to_params(locals())) async def text_content( self, selector: str, strict: bool = None, timeout: float = None ) -> Optional[str]: - return await self._channel.send("textContent", locals_to_params(locals())) + return await self._channel.send( + "textContent", self._timeout, locals_to_params(locals()) + ) async def inner_text( self, selector: str, strict: bool = None, timeout: float = None ) -> str: - return await self._channel.send("innerText", locals_to_params(locals())) + return await self._channel.send( + "innerText", self._timeout, locals_to_params(locals()) + ) async def inner_html( self, selector: str, strict: bool = None, timeout: float = None ) -> str: - return await self._channel.send("innerHTML", locals_to_params(locals())) + return await self._channel.send( + "innerHTML", self._timeout, locals_to_params(locals()) + ) async def get_attribute( self, selector: str, name: str, strict: bool = None, timeout: float = None ) -> Optional[str]: - return await self._channel.send("getAttribute", locals_to_params(locals())) + return await self._channel.send( + "getAttribute", self._timeout, locals_to_params(locals()) + ) async def hover( self, @@ -653,7 +689,7 @@ async def hover( strict: bool = None, trial: bool = None, ) -> None: - await self._channel.send("hover", locals_to_params(locals())) + await self._channel.send("hover", self._timeout, locals_to_params(locals())) async def drag_and_drop( self, @@ -667,7 +703,9 @@ async def drag_and_drop( timeout: float = None, trial: bool = None, ) -> None: - await self._channel.send("dragAndDrop", locals_to_params(locals())) + await self._channel.send( + "dragAndDrop", self._timeout, locals_to_params(locals()) + ) async def select_option( self, @@ -690,7 +728,7 @@ async def select_option( **convert_select_option_values(value, index, label, element), ) ) - return await self._channel.send("selectOption", params) + return await self._channel.send("selectOption", self._timeout, params) async def input_value( self, @@ -698,7 +736,9 @@ async def input_value( strict: bool = None, timeout: float = None, ) -> str: - return await self._channel.send("inputValue", locals_to_params(locals())) + return await self._channel.send( + "inputValue", self._timeout, locals_to_params(locals()) + ) async def set_input_files( self, @@ -713,6 +753,7 @@ async def set_input_files( converted = await convert_input_files(files, self.page.context) await self._channel.send( "setInputFiles", + self._timeout, { "selector": selector, "strict": strict, @@ -730,7 +771,7 @@ async def type( timeout: float = None, noWaitAfter: bool = None, ) -> None: - await self._channel.send("type", locals_to_params(locals())) + await self._channel.send("type", self._timeout, locals_to_params(locals())) async def press( self, @@ -741,7 +782,7 @@ async def press( timeout: float = None, noWaitAfter: bool = None, ) -> None: - await self._channel.send("press", locals_to_params(locals())) + await self._channel.send("press", self._timeout, locals_to_params(locals())) async def check( self, @@ -753,7 +794,7 @@ async def check( strict: bool = None, trial: bool = None, ) -> None: - await self._channel.send("check", locals_to_params(locals())) + await self._channel.send("check", self._timeout, locals_to_params(locals())) async def uncheck( self, @@ -765,10 +806,12 @@ async def uncheck( strict: bool = None, trial: bool = None, ) -> None: - await self._channel.send("uncheck", locals_to_params(locals())) + await self._channel.send("uncheck", self._timeout, locals_to_params(locals())) async def wait_for_timeout(self, timeout: float) -> None: - await self._channel.send("waitForTimeout", locals_to_params(locals())) + await self._channel.send( + "waitForTimeout", self._timeout, locals_to_params(locals()) + ) async def wait_for_function( self, @@ -783,10 +826,12 @@ async def wait_for_function( params["arg"] = serialize_argument(arg) if polling is not None and polling != "raf": params["pollingInterval"] = polling - return from_channel(await self._channel.send("waitForFunction", params)) + return from_channel( + await self._channel.send("waitForFunction", self._timeout, params) + ) async def title(self) -> str: - return await self._channel.send("title") + return await self._channel.send("title", self._timeout) async def set_checked( self, @@ -819,9 +864,4 @@ async def set_checked( ) async def _highlight(self, selector: str) -> None: - await self._channel.send("highlight", {"selector": selector}) - - def _locals_to_params_with_navigation_timeout(self, args: Dict) -> Dict: - params = locals_to_params(args) - params["timeout"] = self._navigation_timeout(params.get("timeout")) - return params + await self._channel.send("highlight", self._timeout, {"selector": selector}) diff --git a/playwright/_impl/_har_router.py b/playwright/_impl/_har_router.py index 33ff37871..1fa1b0433 100644 --- a/playwright/_impl/_har_router.py +++ b/playwright/_impl/_har_router.py @@ -49,7 +49,7 @@ async def create( not_found_action: RouteFromHarNotFoundPolicy, url_matcher: Optional[URLMatch] = None, ) -> "HarRouter": - har_id = await local_utils._channel.send("harOpen", {"file": file}) + har_id = await local_utils._channel.send("harOpen", None, {"file": file}) return HarRouter( local_utils=local_utils, har_id=har_id, @@ -118,5 +118,5 @@ async def add_page_route(self, page: "Page") -> None: def dispose(self) -> None: asyncio.create_task( - self._local_utils._channel.send("harClose", {"harId": self._har_id}) + self._local_utils._channel.send("harClose", None, {"harId": self._har_id}) ) diff --git a/playwright/_impl/_input.py b/playwright/_impl/_input.py index 0e986ae8c..8a39242ee 100644 --- a/playwright/_impl/_input.py +++ b/playwright/_impl/_input.py @@ -23,19 +23,19 @@ def __init__(self, channel: Channel) -> None: self._dispatcher_fiber = channel._connection._dispatcher_fiber async def down(self, key: str) -> None: - await self._channel.send("keyboardDown", locals_to_params(locals())) + await self._channel.send("keyboardDown", None, locals_to_params(locals())) async def up(self, key: str) -> None: - await self._channel.send("keyboardUp", locals_to_params(locals())) + await self._channel.send("keyboardUp", None, locals_to_params(locals())) async def insert_text(self, text: str) -> None: - await self._channel.send("keyboardInsertText", locals_to_params(locals())) + await self._channel.send("keyboardInsertText", None, locals_to_params(locals())) async def type(self, text: str, delay: float = None) -> None: - await self._channel.send("keyboardType", locals_to_params(locals())) + await self._channel.send("keyboardType", None, locals_to_params(locals())) async def press(self, key: str, delay: float = None) -> None: - await self._channel.send("keyboardPress", locals_to_params(locals())) + await self._channel.send("keyboardPress", None, locals_to_params(locals())) class Mouse: @@ -45,21 +45,21 @@ def __init__(self, channel: Channel) -> None: self._dispatcher_fiber = channel._connection._dispatcher_fiber async def move(self, x: float, y: float, steps: int = None) -> None: - await self._channel.send("mouseMove", locals_to_params(locals())) + await self._channel.send("mouseMove", None, locals_to_params(locals())) async def down( self, button: MouseButton = None, clickCount: int = None, ) -> None: - await self._channel.send("mouseDown", locals_to_params(locals())) + await self._channel.send("mouseDown", None, locals_to_params(locals())) async def up( self, button: MouseButton = None, clickCount: int = None, ) -> None: - await self._channel.send("mouseUp", locals_to_params(locals())) + await self._channel.send("mouseUp", None, locals_to_params(locals())) async def _click( self, @@ -70,7 +70,9 @@ async def _click( clickCount: int = None, title: str = None, ) -> None: - await self._channel.send("mouseClick", locals_to_params(locals()), title=title) + await self._channel.send( + "mouseClick", None, locals_to_params(locals()), title=title + ) async def click( self, @@ -96,7 +98,7 @@ async def dblclick( ) async def wheel(self, deltaX: float, deltaY: float) -> None: - await self._channel.send("mouseWheel", locals_to_params(locals())) + await self._channel.send("mouseWheel", None, locals_to_params(locals())) class Touchscreen: @@ -106,4 +108,4 @@ def __init__(self, channel: Channel) -> None: self._dispatcher_fiber = channel._connection._dispatcher_fiber async def tap(self, x: float, y: float) -> None: - await self._channel.send("touchscreenTap", locals_to_params(locals())) + await self._channel.send("touchscreenTap", None, locals_to_params(locals())) diff --git a/playwright/_impl/_js_handle.py b/playwright/_impl/_js_handle.py index 0d0d7e2ef..84ef40d18 100644 --- a/playwright/_impl/_js_handle.py +++ b/playwright/_impl/_js_handle.py @@ -71,6 +71,7 @@ async def evaluate(self, expression: str, arg: Serializable = None) -> Any: return parse_result( await self._channel.send( "evaluateExpression", + None, dict( expression=expression, arg=serialize_argument(arg), @@ -84,6 +85,7 @@ async def evaluate_handle( return from_channel( await self._channel.send( "evaluateExpressionHandle", + None, dict( expression=expression, arg=serialize_argument(arg), @@ -93,13 +95,16 @@ async def evaluate_handle( async def get_property(self, propertyName: str) -> "JSHandle": return from_channel( - await self._channel.send("getProperty", dict(name=propertyName)) + await self._channel.send("getProperty", None, dict(name=propertyName)) ) async def get_properties(self) -> Dict[str, "JSHandle"]: return { prop["name"]: from_channel(prop["value"]) - for prop in await self._channel.send("getPropertyList") + for prop in await self._channel.send( + "getPropertyList", + None, + ) } def as_element(self) -> Optional["ElementHandle"]: @@ -107,13 +112,21 @@ def as_element(self) -> Optional["ElementHandle"]: async def dispose(self) -> None: try: - await self._channel.send("dispose") + await self._channel.send( + "dispose", + None, + ) except Exception as e: if not is_target_closed_error(e): raise e async def json_value(self) -> Any: - return parse_result(await self._channel.send("jsonValue")) + return parse_result( + await self._channel.send( + "jsonValue", + None, + ) + ) def serialize_value( diff --git a/playwright/_impl/_json_pipe.py b/playwright/_impl/_json_pipe.py index 3a6973baf..41973b8c7 100644 --- a/playwright/_impl/_json_pipe.py +++ b/playwright/_impl/_json_pipe.py @@ -36,7 +36,7 @@ def __init__( def request_stop(self) -> None: self._stop_requested = True - self._pipe_channel.send_no_reply("close", {}) + self._pipe_channel.send_no_reply("close", None, {}) def dispose(self) -> None: self.on_error_future.cancel() @@ -74,4 +74,4 @@ async def run(self) -> None: def send(self, message: Dict) -> None: if self._stop_requested: raise Error("Playwright connection closed") - self._pipe_channel.send_no_reply("send", {"message": message}) + self._pipe_channel.send_no_reply("send", None, {"message": message}) diff --git a/playwright/_impl/_local_utils.py b/playwright/_impl/_local_utils.py index 7172ee58a..c2d2d3fca 100644 --- a/playwright/_impl/_local_utils.py +++ b/playwright/_impl/_local_utils.py @@ -31,11 +31,11 @@ def __init__( } async def zip(self, params: Dict) -> None: - await self._channel.send("zip", params) + await self._channel.send("zip", None, params) async def har_open(self, file: str) -> None: params = locals_to_params(locals()) - await self._channel.send("harOpen", params) + await self._channel.send("harOpen", None, params) async def har_lookup( self, @@ -51,27 +51,28 @@ async def har_lookup( params["postData"] = base64.b64encode(params["postData"]).decode() return cast( HarLookupResult, - await self._channel.send_return_as_dict("harLookup", params), + await self._channel.send_return_as_dict("harLookup", None, params), ) async def har_close(self, harId: str) -> None: params = locals_to_params(locals()) - await self._channel.send("harClose", params) + await self._channel.send("harClose", None, params) async def har_unzip(self, zipFile: str, harFile: str) -> None: params = locals_to_params(locals()) - await self._channel.send("harUnzip", params) + await self._channel.send("harUnzip", None, params) async def tracing_started(self, tracesDir: Optional[str], traceName: str) -> str: params = locals_to_params(locals()) - return await self._channel.send("tracingStarted", params) + return await self._channel.send("tracingStarted", None, params) async def trace_discarded(self, stacks_id: str) -> None: - return await self._channel.send("traceDiscarded", {"stacksId": stacks_id}) + return await self._channel.send("traceDiscarded", None, {"stacksId": stacks_id}) def add_stack_to_tracing_no_reply(self, id: int, frames: List[StackFrame]) -> None: self._channel.send_no_reply( "addStackToTracingNoReply", + None, { "callData": { "stack": frames, diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index 9d190c453..5d39426b6 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -383,6 +383,7 @@ async def focus(self, timeout: float = None) -> None: async def blur(self, timeout: float = None) -> None: await self._frame._channel.send( "blur", + None, { "selector": self._selector, "strict": True, @@ -549,6 +550,7 @@ async def screenshot( async def aria_snapshot(self, timeout: float = None) -> str: return await self._frame._channel.send( "ariaSnapshot", + None, { "selector": self._selector, **locals_to_params(locals()), @@ -726,6 +728,7 @@ async def _expect( options["expectedValue"] = serialize_argument(options["expectedValue"]) result = await self._frame._channel.send_return_as_dict( "expect", + None, { "selector": self._selector, "expression": expression, diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index 748967dd8..616c75ec9 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -192,7 +192,10 @@ async def sizes(self) -> RequestSizes: response = await self.response() if not response: raise Error("Unable to fetch sizes for failed request") - return await response._channel.send("sizes") + return await response._channel.send( + "sizes", + None, + ) @property def post_data(self) -> Optional[str]: @@ -226,7 +229,12 @@ def post_data_buffer(self) -> Optional[bytes]: return None async def response(self) -> Optional["Response"]: - return from_nullable_channel(await self._channel.send("response")) + return from_nullable_channel( + await self._channel.send( + "response", + None, + ) + ) @property def frame(self) -> "Frame": @@ -291,7 +299,9 @@ async def _actual_headers(self) -> "RawHeaders": return RawHeaders(serialize_headers(override)) if not self._all_headers_future: self._all_headers_future = asyncio.Future() - headers = await self._channel.send("rawRequestHeaders", is_internal=True) + headers = await self._channel.send( + "rawRequestHeaders", None, is_internal=True + ) self._all_headers_future.set_result(RawHeaders(headers)) return await self._all_headers_future @@ -348,6 +358,7 @@ async def abort(self, errorCode: str = None) -> None: lambda: self._race_with_page_close( self._channel.send( "abort", + None, { "errorCode": errorCode, }, @@ -433,7 +444,7 @@ async def _inner_fulfill( headers["content-length"] = str(length) params["headers"] = serialize_headers(headers) - await self._race_with_page_close(self._channel.send("fulfill", params)) + await self._race_with_page_close(self._channel.send("fulfill", None, params)) async def _handle_route(self, callback: Callable) -> None: self._check_not_handled() @@ -499,6 +510,7 @@ async def _inner_continue(self, is_fallback: bool = False) -> None: await self._race_with_page_close( self._channel.send( "continue", + None, { "url": options.url, "method": options.method, @@ -518,7 +530,7 @@ async def _inner_continue(self, is_fallback: bool = False) -> None: async def _redirected_navigation_request(self, url: str) -> None: await self._handle_route( lambda: self._race_with_page_close( - self._channel.send("redirectNavigationRequest", {"url": url}) + self._channel.send("redirectNavigationRequest", None, {"url": url}) ) ) @@ -577,6 +589,7 @@ def close(self, code: int = None, reason: str = None) -> None: self._ws._loop, self._ws._channel.send( "closeServer", + None, { "code": code, "reason": reason, @@ -590,7 +603,7 @@ def send(self, message: Union[str, bytes]) -> None: _create_task_and_ignore_exception( self._ws._loop, self._ws._channel.send( - "sendToServer", {"message": message, "isBase64": False} + "sendToServer", None, {"message": message, "isBase64": False} ), ) else: @@ -598,6 +611,7 @@ def send(self, message: Union[str, bytes]) -> None: self._ws._loop, self._ws._channel.send( "sendToServer", + None, {"message": base64.b64encode(message).decode(), "isBase64": True}, ), ) @@ -633,7 +647,7 @@ def _channel_message_from_page(self, event: Dict) -> None: ) elif self._connected: _create_task_and_ignore_exception( - self._loop, self._channel.send("sendToServer", event) + self._loop, self._channel.send("sendToServer", None, event) ) def _channel_message_from_server(self, event: Dict) -> None: @@ -645,7 +659,7 @@ def _channel_message_from_server(self, event: Dict) -> None: ) else: _create_task_and_ignore_exception( - self._loop, self._channel.send("sendToPage", event) + self._loop, self._channel.send("sendToPage", None, event) ) def _channel_close_page(self, event: Dict) -> None: @@ -653,7 +667,7 @@ def _channel_close_page(self, event: Dict) -> None: self._on_page_close(event["code"], event["reason"]) else: _create_task_and_ignore_exception( - self._loop, self._channel.send("closeServer", event) + self._loop, self._channel.send("closeServer", None, event) ) def _channel_close_server(self, event: Dict) -> None: @@ -661,7 +675,7 @@ def _channel_close_server(self, event: Dict) -> None: self._on_server_close(event["code"], event["reason"]) else: _create_task_and_ignore_exception( - self._loop, self._channel.send("closePage", event) + self._loop, self._channel.send("closePage", None, event) ) @property @@ -671,7 +685,7 @@ def url(self) -> str: async def close(self, code: int = None, reason: str = None) -> None: try: await self._channel.send( - "closePage", {"code": code, "reason": reason, "wasClean": True} + "closePage", None, {"code": code, "reason": reason, "wasClean": True} ) except Exception: pass @@ -680,7 +694,12 @@ def connect_to_server(self) -> "WebSocketRoute": if self._connected: raise Error("Already connected to the server") self._connected = True - asyncio.create_task(self._channel.send("connect")) + asyncio.create_task( + self._channel.send( + "connect", + None, + ) + ) return cast("WebSocketRoute", self._server) def send(self, message: Union[str, bytes]) -> None: @@ -688,7 +707,7 @@ def send(self, message: Union[str, bytes]) -> None: _create_task_and_ignore_exception( self._loop, self._channel.send( - "sendToPage", {"message": message, "isBase64": False} + "sendToPage", None, {"message": message, "isBase64": False} ), ) else: @@ -696,6 +715,7 @@ def send(self, message: Union[str, bytes]) -> None: self._loop, self._channel.send( "sendToPage", + None, { "message": base64.b64encode(message).decode(), "isBase64": True, @@ -713,7 +733,10 @@ async def _after_handle(self) -> None: if self._connected: return # Ensure that websocket is "open" and can send messages without an actual server connection. - await self._channel.send("ensureOpened") + await self._channel.send( + "ensureOpened", + None, + ) class WebSocketRouteHandler: @@ -826,15 +849,27 @@ async def header_values(self, name: str) -> List[str]: async def _actual_headers(self) -> "RawHeaders": if not self._raw_headers_future: self._raw_headers_future = asyncio.Future() - headers = cast(HeadersArray, await self._channel.send("rawResponseHeaders")) + headers = cast( + HeadersArray, + await self._channel.send( + "rawResponseHeaders", + None, + ), + ) self._raw_headers_future.set_result(RawHeaders(headers)) return await self._raw_headers_future async def server_addr(self) -> Optional[RemoteAddr]: - return await self._channel.send("serverAddr") + return await self._channel.send( + "serverAddr", + None, + ) async def security_details(self) -> Optional[SecurityDetails]: - return await self._channel.send("securityDetails") + return await self._channel.send( + "securityDetails", + None, + ) async def finished(self) -> None: async def on_finished() -> None: @@ -853,7 +888,10 @@ async def on_finished() -> None: await on_finished_task async def body(self) -> bytes: - binary = await self._channel.send("body") + binary = await self._channel.send( + "body", + None, + ) return base64.b64decode(binary) async def text(self) -> str: diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 82b43a231..4e84b89ee 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -237,7 +237,6 @@ def __init__( self._channel.on( "worker", lambda params: self._on_worker(from_channel(params["worker"])) ) - self._channel._set_timeout_calculator(self._timeout_settings.timeout) self._closed_or_crashed_future: asyncio.Future = asyncio.Future() self.on( Page.Events.Close, @@ -520,12 +519,16 @@ async def expose_binding( ) self._bindings[name] = callback await self._channel.send( - "exposeBinding", dict(name=name, needsHandle=handle or False) + "exposeBinding", + self._timeout_settings.timeout, + dict(name=name, needsHandle=handle or False), ) async def set_extra_http_headers(self, headers: Dict[str, str]) -> None: await self._channel.send( - "setExtraHTTPHeaders", dict(headers=serialize_headers(headers)) + "setExtraHTTPHeaders", + self._timeout_settings.timeout, + dict(headers=serialize_headers(headers)), ) @property @@ -559,7 +562,9 @@ async def reload( ) -> Optional[Response]: return from_nullable_channel( await self._channel.send( - "reload", self._locals_to_params_with_navigation_timeout(locals()) + "reload", + self._timeout_settings.navigation_timeout, + locals_to_params(locals()), ) ) @@ -592,7 +597,9 @@ async def go_back( ) -> Optional[Response]: return from_nullable_channel( await self._channel.send( - "goBack", self._locals_to_params_with_navigation_timeout(locals()) + "goBack", + self._timeout_settings.navigation_timeout, + locals_to_params(locals()), ) ) @@ -603,12 +610,14 @@ async def go_forward( ) -> Optional[Response]: return from_nullable_channel( await self._channel.send( - "goForward", self._locals_to_params_with_navigation_timeout(locals()) + "goForward", + self._timeout_settings.navigation_timeout, + locals_to_params(locals()), ) ) async def request_gc(self) -> None: - await self._channel.send("requestGC") + await self._channel.send("requestGC", self._timeout_settings.timeout) async def emulate_media( self, @@ -637,18 +646,22 @@ async def emulate_media( params["contrast"] = ( "no-override" if params["contrast"] == "null" else contrast ) - await self._channel.send("emulateMedia", params) + await self._channel.send("emulateMedia", self._timeout_settings.timeout, params) async def set_viewport_size(self, viewportSize: ViewportSize) -> None: self._viewport_size = viewportSize - await self._channel.send("setViewportSize", locals_to_params(locals())) + await self._channel.send( + "setViewportSize", + self._timeout_settings.timeout, + locals_to_params(locals()), + ) @property def viewport_size(self) -> Optional[ViewportSize]: return self._viewport_size async def bring_to_front(self) -> None: - await self._channel.send("bringToFront") + await self._channel.send("bringToFront", self._timeout_settings.timeout) async def add_init_script( self, script: str = None, path: Union[str, Path] = None @@ -659,7 +672,9 @@ async def add_init_script( ) if not isinstance(script, str): raise Error("Either path or script parameter must be specified") - await self._channel.send("addInitScript", dict(source=script)) + await self._channel.send( + "addInitScript", self._timeout_settings.timeout, dict(source=script) + ) async def route( self, url: URLMatch, handler: RouteHandlerCallback, times: int = None @@ -757,7 +772,9 @@ async def route_from_har( async def _update_interception_patterns(self) -> None: patterns = RouteHandler.prepare_interception_patterns(self._routes) await self._channel.send( - "setNetworkInterceptionPatterns", {"patterns": patterns} + "setNetworkInterceptionPatterns", + self._timeout_settings.timeout, + {"patterns": patterns}, ) async def _update_web_socket_interception_patterns(self) -> None: @@ -765,7 +782,9 @@ async def _update_web_socket_interception_patterns(self) -> None: self._web_socket_routes ) await self._channel.send( - "setWebSocketInterceptionPatterns", {"patterns": patterns} + "setWebSocketInterceptionPatterns", + self._timeout_settings.timeout, + {"patterns": patterns}, ) async def screenshot( @@ -799,7 +818,9 @@ async def screenshot( params["mask"], ) ) - encoded_binary = await self._channel.send("screenshot", params) + encoded_binary = await self._channel.send( + "screenshot", self._timeout_settings.timeout, params + ) decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) @@ -813,7 +834,9 @@ async def close(self, runBeforeUnload: bool = None, reason: str = None) -> None: self._close_reason = reason self._close_was_called = True try: - await self._channel.send("close", locals_to_params(locals())) + await self._channel.send( + "close", self._timeout_settings.timeout, locals_to_params(locals()) + ) if self._owned_context: await self._owned_context.close() except Exception as e: @@ -1112,7 +1135,11 @@ async def pause(self) -> None: try: await asyncio.wait( [ - asyncio.create_task(self._browser_context._channel.send("pause")), + asyncio.create_task( + self._browser_context._channel.send( + "pause", self._timeout_settings.timeout + ) + ), self._closed_or_crashed_future, ], return_when=asyncio.FIRST_COMPLETED, @@ -1144,7 +1171,9 @@ async def pdf( params = locals_to_params(locals()) if "path" in params: del params["path"] - encoded_binary = await self._channel.send("pdf", params) + encoded_binary = await self._channel.send( + "pdf", self._timeout_settings.timeout, params + ) decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) @@ -1354,6 +1383,7 @@ async def add_locator_handler( return uid = await self._channel.send( "registerLocatorHandler", + self._timeout_settings.timeout, { "selector": locator._selector, "noWaitAfter": noWaitAfter, @@ -1394,7 +1424,9 @@ def _handler() -> None: try: await self._connection.wrap_api_call( lambda: self._channel.send( - "resolveLocatorHandlerNoReply", {"uid": uid, "remove": remove} + "resolveLocatorHandlerNoReply", + self._timeout_settings.timeout, + {"uid": uid, "remove": remove}, ), is_internal=True, ) @@ -1405,14 +1437,11 @@ async def remove_locator_handler(self, locator: "Locator") -> None: for uid, data in self._locator_handlers.copy().items(): if data.locator._equals(locator): del self._locator_handlers[uid] - self._channel.send_no_reply("unregisterLocatorHandler", {"uid": uid}) - - def _locals_to_params_with_navigation_timeout(self, args: Dict) -> Dict: - params = locals_to_params(args) - params["timeout"] = self._timeout_settings.navigation_timeout( - params.get("timeout") - ) - return params + self._channel.send_no_reply( + "unregisterLocatorHandler", + self._timeout_settings.timeout, + {"uid": uid}, + ) class Worker(ChannelOwner): @@ -1444,6 +1473,7 @@ async def evaluate(self, expression: str, arg: Serializable = None) -> Any: return parse_result( await self._channel.send( "evaluateExpression", + None, dict( expression=expression, arg=serialize_argument(arg), @@ -1457,6 +1487,7 @@ async def evaluate_handle( return from_channel( await self._channel.send( "evaluateExpressionHandle", + None, dict( expression=expression, arg=serialize_argument(arg), @@ -1482,12 +1513,14 @@ async def call(self, func: Callable) -> None: result = func(source, *func_args) if inspect.iscoroutine(result): result = await result - await self._channel.send("resolve", dict(result=serialize_argument(result))) + await self._channel.send( + "resolve", None, dict(result=serialize_argument(result)) + ) except Exception as e: tb = sys.exc_info()[2] asyncio.create_task( self._channel.send( - "reject", dict(error=dict(error=serialize_error(e, tb))) + "reject", None, dict(error=dict(error=serialize_error(e, tb))) ) ) diff --git a/playwright/_impl/_selectors.py b/playwright/_impl/_selectors.py index b0cf5afbb..e8e97a238 100644 --- a/playwright/_impl/_selectors.py +++ b/playwright/_impl/_selectors.py @@ -46,7 +46,7 @@ async def register( engine["contentScript"] = contentScript for context in self._contexts_for_selectors: await context._channel.send( - "registerSelectorEngine", {"selectorEngine": engine} + "registerSelectorEngine", None, {"selectorEngine": engine} ) self._selector_engines.append(engine) @@ -55,5 +55,5 @@ def set_test_id_attribute(self, attributeName: str) -> None: self._test_id_attribute_name = attributeName for context in self._contexts_for_selectors: context._channel.send_no_reply( - "setTestIdAttributeName", {"testIdAttributeName": attributeName} + "setTestIdAttributeName", None, {"testIdAttributeName": attributeName} ) diff --git a/playwright/_impl/_set_input_files_helpers.py b/playwright/_impl/_set_input_files_helpers.py index ababf5fab..f868886a3 100644 --- a/playwright/_impl/_set_input_files_helpers.py +++ b/playwright/_impl/_set_input_files_helpers.py @@ -84,6 +84,7 @@ async def convert_input_files( result = await context._connection.wrap_api_call( lambda: context._channel.send_return_as_dict( "createTempFiles", + None, { "rootDirName": ( os.path.basename(local_directory) diff --git a/playwright/_impl/_stream.py b/playwright/_impl/_stream.py index d27427589..04afa48e1 100644 --- a/playwright/_impl/_stream.py +++ b/playwright/_impl/_stream.py @@ -28,7 +28,7 @@ def __init__( async def save_as(self, path: Union[str, Path]) -> None: file = await self._loop.run_in_executor(None, lambda: open(path, "wb")) while True: - binary = await self._channel.send("read", {"size": 1024 * 1024}) + binary = await self._channel.send("read", None, {"size": 1024 * 1024}) if not binary: break await self._loop.run_in_executor( @@ -39,7 +39,7 @@ async def save_as(self, path: Union[str, Path]) -> None: async def read_all(self) -> bytes: binary = b"" while True: - chunk = await self._channel.send("read", {"size": 1024 * 1024}) + chunk = await self._channel.send("read", None, {"size": 1024 * 1024}) if not chunk: break binary += base64.b64decode(chunk) diff --git a/playwright/_impl/_tracing.py b/playwright/_impl/_tracing.py index e984bcbad..bbc6ec35e 100644 --- a/playwright/_impl/_tracing.py +++ b/playwright/_impl/_tracing.py @@ -42,15 +42,15 @@ async def start( params = locals_to_params(locals()) self._include_sources = bool(sources) - await self._channel.send("tracingStart", params) + await self._channel.send("tracingStart", None, params) trace_name = await self._channel.send( - "tracingStartChunk", {"title": title, "name": name} + "tracingStartChunk", None, {"title": title, "name": name} ) await self._start_collecting_stacks(trace_name) async def start_chunk(self, title: str = None, name: str = None) -> None: params = locals_to_params(locals()) - trace_name = await self._channel.send("tracingStartChunk", params) + trace_name = await self._channel.send("tracingStartChunk", None, params) await self._start_collecting_stacks(trace_name) async def _start_collecting_stacks(self, trace_name: str) -> None: @@ -66,14 +66,17 @@ async def stop_chunk(self, path: Union[pathlib.Path, str] = None) -> None: async def stop(self, path: Union[pathlib.Path, str] = None) -> None: await self._do_stop_chunk(path) - await self._channel.send("tracingStop") + await self._channel.send( + "tracingStop", + None, + ) async def _do_stop_chunk(self, file_path: Union[pathlib.Path, str] = None) -> None: self._reset_stack_counter() if not file_path: # Not interested in any artifacts - await self._channel.send("tracingStopChunk", {"mode": "discard"}) + await self._channel.send("tracingStopChunk", None, {"mode": "discard"}) if self._stacks_id: await self._connection.local_utils.trace_discarded(self._stacks_id) return @@ -82,7 +85,7 @@ async def _do_stop_chunk(self, file_path: Union[pathlib.Path, str] = None) -> No if is_local: result = await self._channel.send_return_as_dict( - "tracingStopChunk", {"mode": "entries"} + "tracingStopChunk", None, {"mode": "entries"} ) await self._connection.local_utils.zip( { @@ -97,6 +100,7 @@ async def _do_stop_chunk(self, file_path: Union[pathlib.Path, str] = None) -> No result = await self._channel.send_return_as_dict( "tracingStopChunk", + None, { "mode": "archive", }, @@ -133,7 +137,10 @@ def _reset_stack_counter(self) -> None: self._connection.set_is_tracing(False) async def group(self, name: str, location: TracingGroupLocation = None) -> None: - await self._channel.send("tracingGroup", locals_to_params(locals())) + await self._channel.send("tracingGroup", None, locals_to_params(locals())) async def group_end(self) -> None: - await self._channel.send("tracingGroupEnd") + await self._channel.send( + "tracingGroupEnd", + None, + ) diff --git a/playwright/_impl/_waiter.py b/playwright/_impl/_waiter.py index 7b0ad2cc6..f7ff4b6c1 100644 --- a/playwright/_impl/_waiter.py +++ b/playwright/_impl/_waiter.py @@ -38,6 +38,7 @@ def __init__(self, channel_owner: ChannelOwner, event: str) -> None: def _wait_for_event_info_before(self, wait_id: str, event: str) -> None: self._channel.send_no_reply( "waitForEventInfo", + None, { "info": { "waitId": wait_id, @@ -51,6 +52,7 @@ def _wait_for_event_info_after(self, wait_id: str, error: Exception = None) -> N self._channel._connection.wrap_api_call_sync( lambda: self._channel.send_no_reply( "waitForEventInfo", + None, { "info": { "waitId": wait_id, @@ -130,6 +132,7 @@ def log(self, message: str) -> None: self._channel._connection.wrap_api_call_sync( lambda: self._channel.send_no_reply( "waitForEventInfo", + None, { "info": { "waitId": self._wait_id, diff --git a/playwright/_impl/_writable_stream.py b/playwright/_impl/_writable_stream.py index 702adf153..7d5b7704b 100644 --- a/playwright/_impl/_writable_stream.py +++ b/playwright/_impl/_writable_stream.py @@ -37,6 +37,6 @@ async def copy(self, path: Union[str, Path]) -> None: if not data: break await self._channel.send( - "write", {"binary": base64.b64encode(data).decode()} + "write", None, {"binary": base64.b64encode(data).decode()} ) - await self._channel.send("close") + await self._channel.send("close", None) From 07834bad2b808ab7018965d9efd54de0cd03a94b Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Tue, 24 Jun 2025 11:10:45 -0700 Subject: [PATCH 2/8] Fix timeout not being passed around to certain send() calls --- playwright/_impl/_clock.py | 28 +++++++++++++++----- playwright/_impl/_connection.py | 2 +- playwright/_impl/_locator.py | 6 ++--- playwright/_impl/_selectors.py | 8 ++++-- playwright/_impl/_set_input_files_helpers.py | 2 +- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/playwright/_impl/_clock.py b/playwright/_impl/_clock.py index 3fc0dcde5..1e52cd54d 100644 --- a/playwright/_impl/_clock.py +++ b/playwright/_impl/_clock.py @@ -27,7 +27,9 @@ def __init__(self, browser_context: "BrowserContext") -> None: async def install(self, time: Union[float, str, datetime.datetime] = None) -> None: await self._browser_context._channel.send( - "clockInstall", None, parse_time(time) if time is not None else {} + "clockInstall", + self._browser_context._timeout_settings.timeout, + parse_time(time) if time is not None else {}, ) async def fast_forward( @@ -35,7 +37,9 @@ async def fast_forward( ticks: Union[int, str], ) -> None: await self._browser_context._channel.send( - "clockFastForward", None, parse_ticks(ticks) + "clockFastForward", + self._browser_context._timeout_settings.timeout, + parse_ticks(ticks), ) async def pause_at( @@ -43,20 +47,26 @@ async def pause_at( time: Union[float, str, datetime.datetime], ) -> None: await self._browser_context._channel.send( - "clockPauseAt", None, parse_time(time) + "clockPauseAt", + self._browser_context._timeout_settings.timeout, + parse_time(time), ) async def resume( self, ) -> None: - await self._browser_context._channel.send("clockResume", None) + await self._browser_context._channel.send( + "clockResume", self._browser_context._timeout_settings.timeout + ) async def run_for( self, ticks: Union[int, str], ) -> None: await self._browser_context._channel.send( - "clockRunFor", None, parse_ticks(ticks) + "clockRunFor", + self._browser_context._timeout_settings.timeout, + parse_ticks(ticks), ) async def set_fixed_time( @@ -64,7 +74,9 @@ async def set_fixed_time( time: Union[float, str, datetime.datetime], ) -> None: await self._browser_context._channel.send( - "clockSetFixedTime", None, parse_time(time) + "clockSetFixedTime", + self._browser_context._timeout_settings.timeout, + parse_time(time), ) async def set_system_time( @@ -72,7 +84,9 @@ async def set_system_time( time: Union[float, str, datetime.datetime], ) -> None: await self._browser_context._channel.send( - "clockSetSystemTime", None, parse_time(time) + "clockSetSystemTime", + self._browser_context._timeout_settings.timeout, + parse_time(time), ) diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index e01b3a212..166414a94 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -648,7 +648,7 @@ def _augment_params( ) -> Dict: if params is None: params = {} - if timeout_calculator is not None: + if timeout_calculator: params["timeout"] = timeout_calculator(params.get("timeout")) return _filter_none(params) diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index 5d39426b6..6fe075130 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -383,7 +383,7 @@ async def focus(self, timeout: float = None) -> None: async def blur(self, timeout: float = None) -> None: await self._frame._channel.send( "blur", - None, + self._frame._timeout, { "selector": self._selector, "strict": True, @@ -550,7 +550,7 @@ async def screenshot( async def aria_snapshot(self, timeout: float = None) -> str: return await self._frame._channel.send( "ariaSnapshot", - None, + self._frame._timeout, { "selector": self._selector, **locals_to_params(locals()), @@ -728,7 +728,7 @@ async def _expect( options["expectedValue"] = serialize_argument(options["expectedValue"]) result = await self._frame._channel.send_return_as_dict( "expect", - None, + self._frame._timeout, { "selector": self._selector, "expression": expression, diff --git a/playwright/_impl/_selectors.py b/playwright/_impl/_selectors.py index e8e97a238..4b7f51a71 100644 --- a/playwright/_impl/_selectors.py +++ b/playwright/_impl/_selectors.py @@ -46,7 +46,9 @@ async def register( engine["contentScript"] = contentScript for context in self._contexts_for_selectors: await context._channel.send( - "registerSelectorEngine", None, {"selectorEngine": engine} + "registerSelectorEngine", + context._timeout_settings.timeout, + {"selectorEngine": engine}, ) self._selector_engines.append(engine) @@ -55,5 +57,7 @@ def set_test_id_attribute(self, attributeName: str) -> None: self._test_id_attribute_name = attributeName for context in self._contexts_for_selectors: context._channel.send_no_reply( - "setTestIdAttributeName", None, {"testIdAttributeName": attributeName} + "setTestIdAttributeName", + context._timeout_settings.timeout, + {"testIdAttributeName": attributeName}, ) diff --git a/playwright/_impl/_set_input_files_helpers.py b/playwright/_impl/_set_input_files_helpers.py index f868886a3..a7e62cb52 100644 --- a/playwright/_impl/_set_input_files_helpers.py +++ b/playwright/_impl/_set_input_files_helpers.py @@ -84,7 +84,7 @@ async def convert_input_files( result = await context._connection.wrap_api_call( lambda: context._channel.send_return_as_dict( "createTempFiles", - None, + context._timeout_settings.timeout, { "rootDirName": ( os.path.basename(local_directory) From 68a832b0050699fd3a909bef69808e19b9acb157 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Tue, 24 Jun 2025 12:01:13 -0700 Subject: [PATCH 3/8] Disable none filtering on send_no_reply() --- playwright/_impl/_connection.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index 166414a94..a97de73d1 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -98,7 +98,10 @@ def send_no_reply( # No reply messages are used to e.g. waitForEventInfo(after). self._connection.wrap_api_call_sync( lambda: self._connection._send_message_to_server( - self._object, method, _augment_params(params, timeout_calculator), True + self._object, + method, + _augment_params(params, timeout_calculator, filter=False), + True, ), is_internal, title, @@ -645,12 +648,13 @@ def _extract_stack_trace_information_from_stack( def _augment_params( params: Optional[Dict], timeout_calculator: Optional[Callable[[Optional[float]], float]], + filter: bool = True, ) -> Dict: if params is None: params = {} if timeout_calculator: params["timeout"] = timeout_calculator(params.get("timeout")) - return _filter_none(params) + return _filter_none(params) if filter else params def _filter_none(d: Mapping) -> Dict: From 535debf4e0297bbb7f356cfdaec9d2f5757256a3 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 25 Jun 2025 06:23:35 -0700 Subject: [PATCH 4/8] Only set timeouts on necessary protocol calls --- playwright/_impl/_browser_type.py | 13 +++--- playwright/_impl/_clock.py | 16 +++---- playwright/_impl/_connection.py | 1 - playwright/_impl/_element_handle.py | 46 ++++++++------------ playwright/_impl/_fetch.py | 6 +-- playwright/_impl/_frame.py | 34 ++++++--------- playwright/_impl/_page.py | 42 +++++++----------- playwright/_impl/_selectors.py | 4 +- playwright/_impl/_set_input_files_helpers.py | 2 +- 9 files changed, 68 insertions(+), 96 deletions(-) diff --git a/playwright/_impl/_browser_type.py b/playwright/_impl/_browser_type.py index 050080fbe..93173160c 100644 --- a/playwright/_impl/_browser_type.py +++ b/playwright/_impl/_browser_type.py @@ -93,7 +93,12 @@ async def launch( params = locals_to_params(locals()) normalize_launch_params(params) browser = cast( - Browser, from_channel(await self._channel.send("launch", None, params)) + Browser, + from_channel( + await self._channel.send( + "launch", TimeoutSettings.launch_timeout, params + ) + ), ) browser._connect_to_browser_type( self, str(tracesDir) if tracesDir is not None else None @@ -159,7 +164,7 @@ async def launch_persistent_context( await self._prepare_browser_context_params(params) normalize_launch_params(params) result = await self._channel.send_return_as_dict( - "launchPersistentContext", None, params + "launchPersistentContext", TimeoutSettings.launch_timeout, params ) browser = cast( Browser, @@ -197,11 +202,10 @@ async def connect_over_cdp( headers: Dict[str, str] = None, ) -> Browser: params = locals_to_params(locals()) - params["timeout"] = TimeoutSettings.launch_timeout(timeout) if params.get("headers"): params["headers"] = serialize_headers(params["headers"]) response = await self._channel.send_return_as_dict( - "connectOverCDP", None, params + "connectOverCDP", TimeoutSettings.launch_timeout, params ) browser = cast(Browser, from_channel(response["browser"])) browser._connect_to_browser_type(self, None) @@ -358,4 +362,3 @@ def normalize_launch_params(params: Dict) -> None: params["downloadsPath"] = str(Path(params["downloadsPath"])) if "tracesDir" in params: params["tracesDir"] = str(Path(params["tracesDir"])) - params["timeout"] = TimeoutSettings.launch_timeout(params.get("timeout")) diff --git a/playwright/_impl/_clock.py b/playwright/_impl/_clock.py index 1e52cd54d..928536019 100644 --- a/playwright/_impl/_clock.py +++ b/playwright/_impl/_clock.py @@ -28,7 +28,7 @@ def __init__(self, browser_context: "BrowserContext") -> None: async def install(self, time: Union[float, str, datetime.datetime] = None) -> None: await self._browser_context._channel.send( "clockInstall", - self._browser_context._timeout_settings.timeout, + None, parse_time(time) if time is not None else {}, ) @@ -38,7 +38,7 @@ async def fast_forward( ) -> None: await self._browser_context._channel.send( "clockFastForward", - self._browser_context._timeout_settings.timeout, + None, parse_ticks(ticks), ) @@ -48,16 +48,14 @@ async def pause_at( ) -> None: await self._browser_context._channel.send( "clockPauseAt", - self._browser_context._timeout_settings.timeout, + None, parse_time(time), ) async def resume( self, ) -> None: - await self._browser_context._channel.send( - "clockResume", self._browser_context._timeout_settings.timeout - ) + await self._browser_context._channel.send("clockResume", None) async def run_for( self, @@ -65,7 +63,7 @@ async def run_for( ) -> None: await self._browser_context._channel.send( "clockRunFor", - self._browser_context._timeout_settings.timeout, + None, parse_ticks(ticks), ) @@ -75,7 +73,7 @@ async def set_fixed_time( ) -> None: await self._browser_context._channel.send( "clockSetFixedTime", - self._browser_context._timeout_settings.timeout, + None, parse_time(time), ) @@ -85,7 +83,7 @@ async def set_system_time( ) -> None: await self._browser_context._channel.send( "clockSetSystemTime", - self._browser_context._timeout_settings.timeout, + None, parse_time(time), ) diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index a97de73d1..ee95fe827 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -57,7 +57,6 @@ def __init__(self, connection: "Connection", object: "ChannelOwner") -> None: self._guid = object._guid self._object = object self.on("error", lambda exc: self._connection._on_event_listener_error(exc)) - self._timeout_calculator: TimeoutCalculator = None async def send( self, diff --git a/playwright/_impl/_element_handle.py b/playwright/_impl/_element_handle.py index ea03d3314..88f1a7358 100644 --- a/playwright/_impl/_element_handle.py +++ b/playwright/_impl/_element_handle.py @@ -66,51 +66,45 @@ def as_element(self) -> Optional["ElementHandle"]: return self async def owner_frame(self) -> Optional["Frame"]: - return from_nullable_channel( - await self._channel.send("ownerFrame", self._frame._timeout) - ) + return from_nullable_channel(await self._channel.send("ownerFrame", None)) async def content_frame(self) -> Optional["Frame"]: - return from_nullable_channel( - await self._channel.send("contentFrame", self._frame._timeout) - ) + return from_nullable_channel(await self._channel.send("contentFrame", None)) async def get_attribute(self, name: str) -> Optional[str]: - return await self._channel.send( - "getAttribute", self._frame._timeout, dict(name=name) - ) + return await self._channel.send("getAttribute", None, dict(name=name)) async def text_content(self) -> Optional[str]: - return await self._channel.send("textContent", self._frame._timeout) + return await self._channel.send("textContent", None) async def inner_text(self) -> str: - return await self._channel.send("innerText", self._frame._timeout) + return await self._channel.send("innerText", None) async def inner_html(self) -> str: - return await self._channel.send("innerHTML", self._frame._timeout) + return await self._channel.send("innerHTML", None) async def is_checked(self) -> bool: - return await self._channel.send("isChecked", self._frame._timeout) + return await self._channel.send("isChecked", None) async def is_disabled(self) -> bool: - return await self._channel.send("isDisabled", self._frame._timeout) + return await self._channel.send("isDisabled", None) async def is_editable(self) -> bool: - return await self._channel.send("isEditable", self._frame._timeout) + return await self._channel.send("isEditable", None) async def is_enabled(self) -> bool: - return await self._channel.send("isEnabled", self._frame._timeout) + return await self._channel.send("isEnabled", None) async def is_hidden(self) -> bool: - return await self._channel.send("isHidden", self._frame._timeout) + return await self._channel.send("isHidden", None) async def is_visible(self) -> bool: - return await self._channel.send("isVisible", self._frame._timeout) + return await self._channel.send("isVisible", None) async def dispatch_event(self, type: str, eventInit: Dict = None) -> None: await self._channel.send( "dispatchEvent", - self._frame._timeout, + None, dict(type=type, eventInit=serialize_argument(eventInit)), ) @@ -238,7 +232,7 @@ async def set_input_files( ) async def focus(self) -> None: - await self._channel.send("focus", self._frame._timeout) + await self._channel.send("focus", None) async def type( self, @@ -311,7 +305,7 @@ async def uncheck( ) async def bounding_box(self) -> Optional[FloatRect]: - return await self._channel.send("boundingBox", self._frame._timeout) + return await self._channel.send("boundingBox", None) async def screenshot( self, @@ -353,9 +347,7 @@ async def screenshot( async def query_selector(self, selector: str) -> Optional["ElementHandle"]: return from_nullable_channel( - await self._channel.send( - "querySelector", self._frame._timeout, dict(selector=selector) - ) + await self._channel.send("querySelector", None, dict(selector=selector)) ) async def query_selector_all(self, selector: str) -> List["ElementHandle"]: @@ -363,7 +355,7 @@ async def query_selector_all(self, selector: str) -> List["ElementHandle"]: map( cast(Callable[[Any], Any], from_nullable_channel), await self._channel.send( - "querySelectorAll", self._frame._timeout, dict(selector=selector) + "querySelectorAll", None, dict(selector=selector) ), ) ) @@ -377,7 +369,7 @@ async def eval_on_selector( return parse_result( await self._channel.send( "evalOnSelector", - self._frame._timeout, + None, dict( selector=selector, expression=expression, @@ -395,7 +387,7 @@ async def eval_on_selector_all( return parse_result( await self._channel.send( "evalOnSelectorAll", - self._frame._timeout, + None, dict( selector=selector, expression=expression, diff --git a/playwright/_impl/_fetch.py b/playwright/_impl/_fetch.py index 1aac14e92..e4174ea27 100644 --- a/playwright/_impl/_fetch.py +++ b/playwright/_impl/_fetch.py @@ -111,9 +111,7 @@ def __init__( async def dispose(self, reason: str = None) -> None: self._close_reason = reason try: - await self._channel.send( - "dispose", self._timeout_settings.timeout, {"reason": reason} - ) + await self._channel.send("dispose", None, {"reason": reason}) except Error as e: if is_target_closed_error(e): return @@ -436,7 +434,7 @@ async def storage_state( indexedDB: bool = None, ) -> StorageState: result = await self._channel.send_return_as_dict( - "storageState", self._timeout_settings.timeout, {"indexedDB": indexedDB} + "storageState", None, {"indexedDB": indexedDB} ) if path: await async_writefile(path, json.dumps(result)) diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index 8e9dbfb9c..a2f40e624 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -126,9 +126,7 @@ def _on_frame_navigated(self, event: FrameNavigatedEvent) -> None: self._page.emit("framenavigated", self) async def _query_count(self, selector: str) -> int: - return await self._channel.send( - "queryCount", self._timeout, {"selector": selector} - ) + return await self._channel.send("queryCount", None, {"selector": selector}) @property def page(self) -> "Page": @@ -287,13 +285,13 @@ def _navigation_timeout(self, timeout: Optional[float]) -> float: return timeout_settings.navigation_timeout(timeout) async def frame_element(self) -> ElementHandle: - return from_channel(await self._channel.send("frameElement", self._timeout)) + return from_channel(await self._channel.send("frameElement", None)) async def evaluate(self, expression: str, arg: Serializable = None) -> Any: return parse_result( await self._channel.send( "evaluateExpression", - self._timeout, + None, dict( expression=expression, arg=serialize_argument(arg), @@ -307,7 +305,7 @@ async def evaluate_handle( return from_channel( await self._channel.send( "evaluateExpressionHandle", - self._timeout, + None, dict( expression=expression, arg=serialize_argument(arg), @@ -319,9 +317,7 @@ async def query_selector( self, selector: str, strict: bool = None ) -> Optional[ElementHandle]: return from_nullable_channel( - await self._channel.send( - "querySelector", self._timeout, locals_to_params(locals()) - ) + await self._channel.send("querySelector", None, locals_to_params(locals())) ) async def query_selector_all(self, selector: str) -> List[ElementHandle]: @@ -329,7 +325,7 @@ async def query_selector_all(self, selector: str) -> List[ElementHandle]: map( from_channel, await self._channel.send( - "querySelectorAll", self._timeout, dict(selector=selector) + "querySelectorAll", None, dict(selector=selector) ), ) ) @@ -417,7 +413,7 @@ async def eval_on_selector( return parse_result( await self._channel.send( "evalOnSelector", - self._timeout, + None, locals_to_params( dict( selector=selector, @@ -438,7 +434,7 @@ async def eval_on_selector_all( return parse_result( await self._channel.send( "evalOnSelectorAll", - self._timeout, + None, dict( selector=selector, expression=expression, @@ -448,7 +444,7 @@ async def eval_on_selector_all( ) async def content(self) -> str: - return await self._channel.send("content", self._timeout) + return await self._channel.send("content", None) async def set_content( self, @@ -492,9 +488,7 @@ async def add_script_tag( (await async_readfile(path)).decode(), path ) del params["path"] - return from_channel( - await self._channel.send("addScriptTag", self._timeout, params) - ) + return from_channel(await self._channel.send("addScriptTag", None, params)) async def add_style_tag( self, url: str = None, path: Union[str, Path] = None, content: str = None @@ -508,9 +502,7 @@ async def add_style_tag( + "*/" ) del params["path"] - return from_channel( - await self._channel.send("addStyleTag", self._timeout, params) - ) + return from_channel(await self._channel.send("addStyleTag", None, params)) async def click( self, @@ -831,7 +823,7 @@ async def wait_for_function( ) async def title(self) -> str: - return await self._channel.send("title", self._timeout) + return await self._channel.send("title", None) async def set_checked( self, @@ -864,4 +856,4 @@ async def set_checked( ) async def _highlight(self, selector: str) -> None: - await self._channel.send("highlight", self._timeout, {"selector": selector}) + await self._channel.send("highlight", None, {"selector": selector}) diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 4e84b89ee..e5e85a2af 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -520,14 +520,14 @@ async def expose_binding( self._bindings[name] = callback await self._channel.send( "exposeBinding", - self._timeout_settings.timeout, + None, dict(name=name, needsHandle=handle or False), ) async def set_extra_http_headers(self, headers: Dict[str, str]) -> None: await self._channel.send( "setExtraHTTPHeaders", - self._timeout_settings.timeout, + None, dict(headers=serialize_headers(headers)), ) @@ -617,7 +617,7 @@ async def go_forward( ) async def request_gc(self) -> None: - await self._channel.send("requestGC", self._timeout_settings.timeout) + await self._channel.send("requestGC", None) async def emulate_media( self, @@ -646,13 +646,13 @@ async def emulate_media( params["contrast"] = ( "no-override" if params["contrast"] == "null" else contrast ) - await self._channel.send("emulateMedia", self._timeout_settings.timeout, params) + await self._channel.send("emulateMedia", None, params) async def set_viewport_size(self, viewportSize: ViewportSize) -> None: self._viewport_size = viewportSize await self._channel.send( "setViewportSize", - self._timeout_settings.timeout, + None, locals_to_params(locals()), ) @@ -661,7 +661,7 @@ def viewport_size(self) -> Optional[ViewportSize]: return self._viewport_size async def bring_to_front(self) -> None: - await self._channel.send("bringToFront", self._timeout_settings.timeout) + await self._channel.send("bringToFront", None) async def add_init_script( self, script: str = None, path: Union[str, Path] = None @@ -672,9 +672,7 @@ async def add_init_script( ) if not isinstance(script, str): raise Error("Either path or script parameter must be specified") - await self._channel.send( - "addInitScript", self._timeout_settings.timeout, dict(source=script) - ) + await self._channel.send("addInitScript", None, dict(source=script)) async def route( self, url: URLMatch, handler: RouteHandlerCallback, times: int = None @@ -773,7 +771,7 @@ async def _update_interception_patterns(self) -> None: patterns = RouteHandler.prepare_interception_patterns(self._routes) await self._channel.send( "setNetworkInterceptionPatterns", - self._timeout_settings.timeout, + None, {"patterns": patterns}, ) @@ -783,7 +781,7 @@ async def _update_web_socket_interception_patterns(self) -> None: ) await self._channel.send( "setWebSocketInterceptionPatterns", - self._timeout_settings.timeout, + None, {"patterns": patterns}, ) @@ -818,9 +816,7 @@ async def screenshot( params["mask"], ) ) - encoded_binary = await self._channel.send( - "screenshot", self._timeout_settings.timeout, params - ) + encoded_binary = await self._channel.send("screenshot", None, params) decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) @@ -834,9 +830,7 @@ async def close(self, runBeforeUnload: bool = None, reason: str = None) -> None: self._close_reason = reason self._close_was_called = True try: - await self._channel.send( - "close", self._timeout_settings.timeout, locals_to_params(locals()) - ) + await self._channel.send("close", None, locals_to_params(locals())) if self._owned_context: await self._owned_context.close() except Exception as e: @@ -1136,9 +1130,7 @@ async def pause(self) -> None: await asyncio.wait( [ asyncio.create_task( - self._browser_context._channel.send( - "pause", self._timeout_settings.timeout - ) + self._browser_context._channel.send("pause", None) ), self._closed_or_crashed_future, ], @@ -1171,9 +1163,7 @@ async def pdf( params = locals_to_params(locals()) if "path" in params: del params["path"] - encoded_binary = await self._channel.send( - "pdf", self._timeout_settings.timeout, params - ) + encoded_binary = await self._channel.send("pdf", None, params) decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) @@ -1383,7 +1373,7 @@ async def add_locator_handler( return uid = await self._channel.send( "registerLocatorHandler", - self._timeout_settings.timeout, + None, { "selector": locator._selector, "noWaitAfter": noWaitAfter, @@ -1425,7 +1415,7 @@ def _handler() -> None: await self._connection.wrap_api_call( lambda: self._channel.send( "resolveLocatorHandlerNoReply", - self._timeout_settings.timeout, + None, {"uid": uid, "remove": remove}, ), is_internal=True, @@ -1439,7 +1429,7 @@ async def remove_locator_handler(self, locator: "Locator") -> None: del self._locator_handlers[uid] self._channel.send_no_reply( "unregisterLocatorHandler", - self._timeout_settings.timeout, + None, {"uid": uid}, ) diff --git a/playwright/_impl/_selectors.py b/playwright/_impl/_selectors.py index 4b7f51a71..2a2e70974 100644 --- a/playwright/_impl/_selectors.py +++ b/playwright/_impl/_selectors.py @@ -47,7 +47,7 @@ async def register( for context in self._contexts_for_selectors: await context._channel.send( "registerSelectorEngine", - context._timeout_settings.timeout, + None, {"selectorEngine": engine}, ) self._selector_engines.append(engine) @@ -58,6 +58,6 @@ def set_test_id_attribute(self, attributeName: str) -> None: for context in self._contexts_for_selectors: context._channel.send_no_reply( "setTestIdAttributeName", - context._timeout_settings.timeout, + None, {"testIdAttributeName": attributeName}, ) diff --git a/playwright/_impl/_set_input_files_helpers.py b/playwright/_impl/_set_input_files_helpers.py index a7e62cb52..f868886a3 100644 --- a/playwright/_impl/_set_input_files_helpers.py +++ b/playwright/_impl/_set_input_files_helpers.py @@ -84,7 +84,7 @@ async def convert_input_files( result = await context._connection.wrap_api_call( lambda: context._channel.send_return_as_dict( "createTempFiles", - context._timeout_settings.timeout, + None, { "rootDirName": ( os.path.basename(local_directory) From 23c379268f524ad9ec0d9075f21fde64aafcad93 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 25 Jun 2025 06:30:21 -0700 Subject: [PATCH 5/8] Add missing screenshot timeout --- playwright/_impl/_page.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index e5e85a2af..20bba35db 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -816,7 +816,9 @@ async def screenshot( params["mask"], ) ) - encoded_binary = await self._channel.send("screenshot", None, params) + encoded_binary = await self._channel.send( + "screenshot", self._timeout_settings.timeout, params + ) decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) From ce672f215d5f32c2de2cf6bcf3c5fdd96f1b1233 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 25 Jun 2025 07:40:59 -0700 Subject: [PATCH 6/8] Fix strange connection filtering --- playwright/_impl/_connection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index ee95fe827..9e25fac8c 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -99,7 +99,7 @@ def send_no_reply( lambda: self._connection._send_message_to_server( self._object, method, - _augment_params(params, timeout_calculator, filter=False), + _augment_params(params, timeout_calculator), True, ), is_internal, @@ -647,13 +647,12 @@ def _extract_stack_trace_information_from_stack( def _augment_params( params: Optional[Dict], timeout_calculator: Optional[Callable[[Optional[float]], float]], - filter: bool = True, ) -> Dict: if params is None: params = {} if timeout_calculator: params["timeout"] = timeout_calculator(params.get("timeout")) - return _filter_none(params) if filter else params + return _filter_none(params) def _filter_none(d: Mapping) -> Dict: @@ -663,7 +662,8 @@ def _filter_none(d: Mapping) -> Dict: continue elif isinstance(v, dict): filtered_v = _filter_none(v) - if filtered_v: + if filtered_v is not None: + # Allow empty dicts/lists, but not None result[k] = filtered_v else: result[k] = v From 2f6bbf9704bbac0cc3888591f4c690c42aac8937 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 25 Jun 2025 08:13:22 -0700 Subject: [PATCH 7/8] Collapsed nested filtering --- playwright/_impl/_connection.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index 9e25fac8c..a837500b1 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -660,13 +660,7 @@ def _filter_none(d: Mapping) -> Dict: for k, v in d.items(): if v is None: continue - elif isinstance(v, dict): - filtered_v = _filter_none(v) - if filtered_v is not None: - # Allow empty dicts/lists, but not None - result[k] = filtered_v - else: - result[k] = v + result[k] = _filter_none(v) if isinstance(v, dict) else v return result From 43d771a1e01fcf04be8d687efef771464e1a987e Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 25 Jun 2025 08:17:46 -0700 Subject: [PATCH 8/8] Remove unnecessary waitForTimeout wrapper --- playwright/_impl/_frame.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index a2f40e624..c0646b680 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -801,9 +801,7 @@ async def uncheck( await self._channel.send("uncheck", self._timeout, locals_to_params(locals())) async def wait_for_timeout(self, timeout: float) -> None: - await self._channel.send( - "waitForTimeout", self._timeout, locals_to_params(locals()) - ) + await self._channel.send("waitForTimeout", None, locals_to_params(locals())) async def wait_for_function( self,