From 9947444576edc47bad87e7d6d9b0dc4c66ce60b7 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 15 Jul 2026 15:48:31 +0200 Subject: [PATCH 1/5] chore: roll to 1.62.0-alpha-1784109367000 Port the public API changes introduced upstream since 1.61 and adapt the client to the protocol-level timeout metadata field. - feat(routeFromHar): interceptAPIRequests option - feat(webauthn): credentials option on BrowserContext.storageState - feat(locator): Locator.wait_for_function() - feat(actions): scroll option to opt out of autoscroll - feat(screenshot): webp output format - adapt channel sends to send timeout as protocol-level metadata AbortSignal is intentionally not ported to Python. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3813c827-3dfc-44a4-9433-cae8c2919c95 --- DRIVER_VERSION | 2 +- NODE_VERSION | 2 +- README.md | 4 +- playwright/_impl/_browser_context.py | 10 +- playwright/_impl/_browser_type.py | 4 +- playwright/_impl/_connection.py | 26 +- playwright/_impl/_element_handle.py | 27 +- playwright/_impl/_frame.py | 13 + playwright/_impl/_har_router.py | 53 ++- playwright/_impl/_locator.py | 39 +- playwright/_impl/_page.py | 14 +- playwright/async_api/_generated.py | 340 ++++++++++++++++- playwright/sync_api/_generated.py | 344 +++++++++++++++++- scripts/expected_api_mismatch.txt | 4 +- .../test_browsercontext_storage_state.py | 21 ++ tests/async/test_har.py | 67 ++++ tests/async/test_locators.py | 83 +++++ tests/async/test_screenshot.py | 16 + .../sync/test_browsercontext_storage_state.py | 21 ++ tests/sync/test_har.py | 69 +++- tests/sync/test_locators.py | 80 ++++ 21 files changed, 1181 insertions(+), 58 deletions(-) diff --git a/DRIVER_VERSION b/DRIVER_VERSION index 9b083fc63..31850e34c 100644 --- a/DRIVER_VERSION +++ b/DRIVER_VERSION @@ -1 +1 @@ -1.61.1-beta-1782139630000 +1.62.0-alpha-1784109367000 diff --git a/NODE_VERSION b/NODE_VERSION index 1dd37d537..ca5c35005 100644 --- a/NODE_VERSION +++ b/NODE_VERSION @@ -1 +1 @@ -24.17.0 +24.18.0 diff --git a/README.md b/README.md index 50b9980b4..3b3db21d5 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 149.0.7827.55 | ✅ | ✅ | ✅ | +| Chromium 151.0.7922.19 | ✅ | ✅ | ✅ | | WebKit 26.5 | ✅ | ✅ | ✅ | -| Firefox 151.0 | ✅ | ✅ | ✅ | +| Firefox 152.0.4 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py index 3437879a8..84b25dfba 100644 --- a/playwright/_impl/_browser_context.py +++ b/playwright/_impl/_browser_context.py @@ -497,6 +497,7 @@ async def route_from_har( update: bool = None, updateContent: Literal["attach", "embed"] = None, updateMode: HarMode = None, + interceptAPIRequests: bool = None, ) -> None: if update: await self._tracing._record_into_har( @@ -515,6 +516,8 @@ async def route_from_har( ) self._har_routers.append(router) await router.add_context_route(self) + if interceptAPIRequests: + await router.add_api_request_route(self) async def _update_interception_patterns(self) -> None: patterns = RouteHandler.prepare_interception_patterns(self._routes) @@ -586,10 +589,13 @@ async def _inner_close() -> None: await self._closed_future async def storage_state( - self, path: Union[str, Path] = None, indexedDB: bool = None + self, + path: Union[str, Path] = None, + indexedDB: bool = None, + credentials: bool = None, ) -> StorageState: result = await self._channel.send_return_as_dict( - "storageState", None, {"indexedDB": indexedDB} + "storageState", None, {"indexedDB": indexedDB, "credentials": credentials} ) if path: await async_writefile(path, json.dumps(result)) diff --git a/playwright/_impl/_browser_type.py b/playwright/_impl/_browser_type.py index 5f436d7fb..880f546ef 100644 --- a/playwright/_impl/_browser_type.py +++ b/playwright/_impl/_browser_type.py @@ -226,12 +226,12 @@ async def connect( pipe_channel = ( await local_utils._channel.send_return_as_dict( "connect", - None, + lambda t: t if t is not None else 0, { "endpoint": endpoint, "headers": headers, "slowMo": slowMo, - "timeout": timeout if timeout is not None else 0, + "timeout": timeout, "exposeNetwork": exposeNetwork, }, ) diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index dc58bdaad..c1b0a4eb4 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -28,6 +28,7 @@ List, Mapping, Optional, + Tuple, TypedDict, Union, cast, @@ -95,12 +96,14 @@ def send_no_reply( title: str = None, ) -> None: # No reply messages are used to e.g. __waitInfo__(after). + augmented_params, timeout = _augment_params(params, timeout_calculator) self._connection.wrap_api_call_sync( lambda: self._connection._send_message_to_server( self._object, method, - _augment_params(params, timeout_calculator), + augmented_params, True, + timeout, ), is_internal, title, @@ -117,8 +120,9 @@ async def _inner_send( error = self._connection._error self._connection._error = None raise error + augmented_params, timeout = _augment_params(params, timeout_calculator) callback = self._connection._send_message_to_server( - self._object, method, _augment_params(params, timeout_calculator) + self._object, method, augmented_params, timeout=timeout ) done, _ = await asyncio.wait( { @@ -352,7 +356,12 @@ def set_is_tracing(self, is_tracing: bool) -> None: self._tracing_count -= 1 def _send_message_to_server( - self, object: ChannelOwner, method: str, params: Dict, no_reply: bool = False + self, + object: ChannelOwner, + method: str, + params: Dict, + no_reply: bool = False, + timeout: Optional[float] = None, ) -> ProtocolCallback: if self._closed_error: raise self._closed_error @@ -390,6 +399,8 @@ def _send_message_to_server( title = stack_trace_information["title"] if title: metadata["title"] = title + if timeout is not None: + metadata["timeout"] = timeout # type: ignore message = { "id": id, "guid": object._guid, @@ -652,12 +663,15 @@ def _extract_stack_trace_information_from_stack( def _augment_params( params: Optional[Dict], timeout_calculator: Optional[Callable[[Optional[float]], float]], -) -> Dict: +) -> Tuple[Dict, Optional[float]]: if params is None: params = {} + timeout: Optional[float] = None if timeout_calculator: - params["timeout"] = timeout_calculator(params.get("timeout")) - return _filter_none(params) + timeout = timeout_calculator(params.get("timeout")) + # The timeout is sent as a protocol-level metadata field, never in params. + params = {key: value for key, value in params.items() if key != "timeout"} + return _filter_none(params), timeout def _filter_none(d: Mapping) -> Dict: diff --git a/playwright/_impl/_element_handle.py b/playwright/_impl/_element_handle.py index 3854669d0..f92369b17 100644 --- a/playwright/_impl/_element_handle.py +++ b/playwright/_impl/_element_handle.py @@ -13,7 +13,6 @@ # limitations under the License. import base64 -import mimetypes from pathlib import Path from typing import ( TYPE_CHECKING, @@ -122,6 +121,7 @@ async def hover( noWaitAfter: bool = None, force: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send( "hover", self._frame._timeout, locals_to_params(locals()) @@ -139,6 +139,7 @@ async def click( noWaitAfter: bool = None, trial: bool = None, steps: int = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send( "click", self._frame._timeout, locals_to_params(locals()) @@ -155,6 +156,7 @@ async def dblclick( noWaitAfter: bool = None, trial: bool = None, steps: int = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send( "dblclick", self._frame._timeout, locals_to_params(locals()) @@ -187,6 +189,7 @@ async def tap( force: bool = None, noWaitAfter: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send( "tap", self._frame._timeout, locals_to_params(locals()) @@ -267,6 +270,7 @@ async def set_checked( force: bool = None, noWaitAfter: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: if checked: await self.check( @@ -274,6 +278,7 @@ async def set_checked( timeout=timeout, force=force, trial=trial, + scroll=scroll, ) else: await self.uncheck( @@ -281,6 +286,7 @@ async def set_checked( timeout=timeout, force=force, trial=trial, + scroll=scroll, ) async def check( @@ -290,6 +296,7 @@ async def check( force: bool = None, noWaitAfter: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send( "check", self._frame._timeout, locals_to_params(locals()) @@ -302,6 +309,7 @@ async def uncheck( force: bool = None, noWaitAfter: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send( "uncheck", self._frame._timeout, locals_to_params(locals()) @@ -313,7 +321,7 @@ async def bounding_box(self) -> Optional[FloatRect]: async def screenshot( self, timeout: float = None, - type: Literal["jpeg", "png"] = None, + type: Literal["jpeg", "png", "webp"] = None, path: Union[str, Path] = None, quality: int = None, omitBackground: bool = None, @@ -457,10 +465,15 @@ def convert_select_option_values( return dict(options=options, elements=elements) -def determine_screenshot_type(path: Union[str, Path]) -> Literal["jpeg", "png"]: - mime_type, _ = mimetypes.guess_type(path) - if mime_type == "image/png": +def determine_screenshot_type(path: Union[str, Path]) -> Literal["jpeg", "png", "webp"]: + # Detect by file extension rather than mimetypes.guess_type, whose result is + # OS-dependent (e.g. Windows does not register image/webp), mirroring + # upstream's getMimeTypeForPath extension map. + suffix = Path(path).suffix.lower() + if suffix == ".png": return "png" - if mime_type == "image/jpeg": + if suffix in (".jpeg", ".jpg"): return "jpeg" - raise Error(f'Unsupported screenshot mime type for path "{path}": {mime_type}') + if suffix == ".webp": + return "webp" + raise Error(f'path: unsupported mime type "{suffix}"') diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index 3bf5992a5..faa1674fd 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -570,6 +570,7 @@ async def click( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._click(**locals_to_params(locals())) @@ -587,6 +588,7 @@ async def _click( strict: bool = None, trial: bool = None, steps: int = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send("click", self._timeout, locals_to_params(locals())) @@ -602,6 +604,7 @@ async def dblclick( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send( "dblclick", self._timeout, locals_to_params(locals()), title="Double click" @@ -617,6 +620,7 @@ async def tap( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send("tap", self._timeout, locals_to_params(locals())) @@ -688,6 +692,7 @@ def get_by_role( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, + busy: bool = None, ) -> "Locator": return self.locator( get_by_role_selector( @@ -702,6 +707,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -764,6 +770,7 @@ async def hover( force: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send("hover", self._timeout, locals_to_params(locals())) @@ -779,6 +786,7 @@ async def drag_and_drop( timeout: float = None, trial: bool = None, steps: int = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send( "dragAndDrop", self._timeout, locals_to_params(locals()) @@ -897,6 +905,7 @@ async def check( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send("check", self._timeout, locals_to_params(locals())) @@ -909,6 +918,7 @@ async def uncheck( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: await self._channel.send("uncheck", self._timeout, locals_to_params(locals())) @@ -945,6 +955,7 @@ async def set_checked( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: if checked: await self.check( @@ -954,6 +965,7 @@ async def set_checked( force=force, strict=strict, trial=trial, + scroll=scroll, ) else: await self.uncheck( @@ -963,6 +975,7 @@ async def set_checked( force=force, strict=strict, trial=trial, + scroll=scroll, ) async def _highlight(self, selector: str, style: str = None) -> None: diff --git a/playwright/_impl/_har_router.py b/playwright/_impl/_har_router.py index 1fa1b0433..bc239d445 100644 --- a/playwright/_impl/_har_router.py +++ b/playwright/_impl/_har_router.py @@ -13,13 +13,15 @@ # limitations under the License. import asyncio import base64 -from typing import TYPE_CHECKING, Optional, cast +import re +from typing import TYPE_CHECKING, Coroutine, List, Optional, cast from playwright._impl._api_structures import HeadersArray from playwright._impl._helper import ( HarLookupResult, RouteFromHarNotFoundPolicy, URLMatch, + escape_regex_flags, ) from playwright._impl._local_utils import LocalUtils @@ -41,6 +43,7 @@ def __init__( self._har_id: str = har_id self._not_found_action: RouteFromHarNotFoundPolicy = not_found_action self._options_url_match: Optional[URLMatch] = url_matcher + self._api_request_registrations: List["_APIRequestRegistration"] = [] @staticmethod async def create( @@ -116,7 +119,53 @@ async def add_page_route(self, page: "Page") -> None: handler=lambda route, _: asyncio.create_task(self._handle(route)), ) + async def add_api_request_route(self, context: "BrowserContext") -> None: + url_match = self._options_url_match + params: dict = { + "harId": self._har_id, + "notFound": self._not_found_action, + } + if isinstance(url_match, str): + params["urlGlob"] = url_match + elif isinstance(url_match, re.Pattern): + params["urlRegexSource"] = url_match.pattern + params["urlRegexFlags"] = escape_regex_flags(url_match) + result = await context._channel.send_return_as_dict( + "routeAPIRequestsFromHar", None, params + ) + self._api_request_registrations.append( + _APIRequestRegistration(context, result["registrationId"]) + ) + def dispose(self) -> None: + for registration in self._api_request_registrations: + asyncio.create_task( + self._swallow( + registration.context._channel.send( + "unrouteAPIRequestsFromHar", + None, + {"registrationId": registration.registration_id}, + ) + ) + ) + self._api_request_registrations = [] asyncio.create_task( - self._local_utils._channel.send("harClose", None, {"harId": self._har_id}) + self._swallow( + self._local_utils._channel.send( + "harClose", None, {"harId": self._har_id} + ) + ) ) + + @staticmethod + async def _swallow(coro: Coroutine) -> None: + try: + await coro + except Exception: + pass + + +class _APIRequestRegistration: + def __init__(self, context: "BrowserContext", registration_id: str) -> None: + self.context = context + self.registration_id = registration_id diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index c76b248ce..c78db38a8 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -49,7 +49,7 @@ monotonic_time, to_impl, ) -from playwright._impl._js_handle import Serializable +from playwright._impl._js_handle import Serializable, serialize_argument from playwright._impl._str_utils import ( escape_for_attribute_selector, escape_for_text_selector, @@ -142,6 +142,7 @@ async def check( force: bool = None, noWaitAfter: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: params = locals_to_params(locals()) return await self._frame.check(self._selector, strict=True, **params) @@ -158,6 +159,7 @@ async def click( noWaitAfter: bool = None, trial: bool = None, steps: int = None, + scroll: Literal["auto", "none"] = None, ) -> None: params = locals_to_params(locals()) return await self._frame._click(self._selector, strict=True, **params) @@ -173,6 +175,7 @@ async def dblclick( noWaitAfter: bool = None, trial: bool = None, steps: int = None, + scroll: Literal["auto", "none"] = None, ) -> None: params = locals_to_params(locals()) return await self._frame.dblclick(self._selector, strict=True, **params) @@ -281,6 +284,7 @@ def get_by_role( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, + busy: bool = None, ) -> "Locator": return self.locator( get_by_role_selector( @@ -295,6 +299,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -435,6 +440,7 @@ async def drag_to( sourcePosition: Position = None, targetPosition: Position = None, steps: int = None, + scroll: Literal["auto", "none"] = None, ) -> None: params = locals_to_params(locals()) del params["target"] @@ -472,6 +478,7 @@ async def hover( noWaitAfter: bool = None, force: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: params = locals_to_params(locals()) return await self._frame.hover( @@ -563,7 +570,7 @@ async def press( async def screenshot( self, timeout: float = None, - type: Literal["jpeg", "png"] = None, + type: Literal["jpeg", "png", "webp"] = None, path: Union[str, pathlib.Path] = None, quality: int = None, omitBackground: bool = None, @@ -665,6 +672,7 @@ async def tap( force: bool = None, noWaitAfter: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: params = locals_to_params(locals()) return await self._frame.tap( @@ -711,6 +719,7 @@ async def uncheck( force: bool = None, noWaitAfter: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: params = locals_to_params(locals()) return await self._frame.uncheck( @@ -742,6 +751,24 @@ async def wait_for( self._selector, strict=True, timeout=timeout, state=state ) + async def wait_for_function( + self, + expression: str, + arg: Serializable = None, + timeout: float = None, + ) -> None: + await self._frame._channel.send( + "waitForFunction", + self._frame._timeout, + { + "selector": self._selector, + "strict": True, + "expression": expression, + "arg": serialize_argument(arg), + "timeout": timeout, + }, + ) + async def set_checked( self, checked: bool, @@ -750,6 +777,7 @@ async def set_checked( force: bool = None, noWaitAfter: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: if checked: await self.check( @@ -757,6 +785,7 @@ async def set_checked( timeout=timeout, force=force, trial=trial, + scroll=scroll, ) else: await self.uncheck( @@ -764,6 +793,7 @@ async def set_checked( timeout=timeout, force=force, trial=trial, + scroll=scroll, ) async def _expect( @@ -845,6 +875,7 @@ def get_by_role( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, + busy: bool = None, ) -> "Locator": return self.locator( get_by_role_selector( @@ -859,6 +890,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -962,6 +994,7 @@ def get_by_role_selector( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, + busy: bool = None, ) -> str: props: List[Tuple[str, str]] = [] if checked is not None: @@ -992,5 +1025,7 @@ def get_by_role_selector( ) if pressed is not None: props.append(("pressed", bool_to_js_bool(pressed))) + if busy is not None: + props.append(("busy", bool_to_js_bool(busy))) props_str = "".join([f"[{t[0]}={t[1]}]" for t in props]) return f"internal:role={role}{props_str}" diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 313c1c841..470c102fe 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -795,7 +795,7 @@ async def _update_web_socket_interception_patterns(self) -> None: async def screenshot( self, timeout: float = None, - type: Literal["jpeg", "png"] = None, + type: Literal["jpeg", "png", "webp"] = None, path: Union[str, Path] = None, quality: int = None, omitBackground: bool = None, @@ -882,6 +882,7 @@ async def click( noWaitAfter: bool = None, trial: bool = None, strict: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: return await self._main_frame._click(**locals_to_params(locals())) @@ -897,6 +898,7 @@ async def dblclick( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: return await self._main_frame.dblclick(**locals_to_params(locals())) @@ -910,6 +912,7 @@ async def tap( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: return await self._main_frame.tap(**locals_to_params(locals())) @@ -968,6 +971,7 @@ def get_by_role( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, + busy: bool = None, ) -> "Locator": return self._main_frame.get_by_role( role, @@ -981,6 +985,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator": @@ -1034,6 +1039,7 @@ async def hover( force: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: return await self._main_frame.hover(**locals_to_params(locals())) @@ -1049,6 +1055,7 @@ async def drag_and_drop( strict: bool = None, trial: bool = None, steps: int = None, + scroll: Literal["auto", "none"] = None, ) -> None: return await self._main_frame.drag_and_drop(**locals_to_params(locals())) @@ -1116,6 +1123,7 @@ async def check( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: return await self._main_frame.check(**locals_to_params(locals())) @@ -1128,6 +1136,7 @@ async def uncheck( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: return await self._main_frame.uncheck(**locals_to_params(locals())) @@ -1376,6 +1385,7 @@ async def set_checked( noWaitAfter: bool = None, strict: bool = None, trial: bool = None, + scroll: Literal["auto", "none"] = None, ) -> None: if checked: await self.check( @@ -1385,6 +1395,7 @@ async def set_checked( force=force, strict=strict, trial=trial, + scroll=scroll, ) else: await self.uncheck( @@ -1394,6 +1405,7 @@ async def set_checked( force=force, strict=strict, trial=trial, + scroll=scroll, ) async def add_locator_handler( diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 6dfd634a3..b99e9f2e8 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -2221,6 +2221,7 @@ async def hover( no_wait_after: typing.Optional[bool] = None, force: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.hover @@ -2254,6 +2255,11 @@ async def hover( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2264,6 +2270,7 @@ async def hover( noWaitAfter=no_wait_after, force=force, trial=trial, + scroll=scroll, ) ) @@ -2282,6 +2289,7 @@ async def click( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.click @@ -2327,6 +2335,11 @@ async def click( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2341,6 +2354,7 @@ async def click( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) @@ -2358,6 +2372,7 @@ async def dblclick( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.dblclick @@ -2400,6 +2415,11 @@ async def dblclick( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2413,6 +2433,7 @@ async def dblclick( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) @@ -2503,6 +2524,7 @@ async def tap( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.tap @@ -2538,6 +2560,11 @@ async def tap( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2548,6 +2575,7 @@ async def tap( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -2640,8 +2668,7 @@ async def input_value( Parameters ---------- timeout : Union[float, None] - Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can - be changed by using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. + Deprecated: This option is ignored. The value is returned immediately. Returns ------- @@ -2809,6 +2836,7 @@ async def set_checked( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.set_checked @@ -2842,6 +2870,11 @@ async def set_checked( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2852,6 +2885,7 @@ async def set_checked( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -2863,6 +2897,7 @@ async def check( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.check @@ -2895,6 +2930,11 @@ async def check( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2904,6 +2944,7 @@ async def check( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -2915,6 +2956,7 @@ async def uncheck( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.uncheck @@ -2947,6 +2989,11 @@ async def uncheck( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2956,6 +3003,7 @@ async def uncheck( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -2993,7 +3041,7 @@ async def screenshot( self, *, timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, - type: typing.Optional[Literal["jpeg", "png"]] = None, + type: typing.Optional[Literal["jpeg", "png", "webp"]] = None, path: typing.Optional[typing.Union[pathlib.Path, str]] = None, quality: typing.Optional[int] = None, omit_background: typing.Optional[bool] = None, @@ -3020,14 +3068,15 @@ async def screenshot( timeout : Union[float, None] Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. - type : Union["jpeg", "png", None] + type : Union["jpeg", "png", "webp", None] Specify screenshot type, defaults to `png`. path : Union[pathlib.Path, str, None] The file path to save the image to. The screenshot type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to the current working directory. If no path is provided, the image won't be saved to the disk. quality : Union[int, None] - The quality of the image, between 0-100. Not applicable to `png` images. + The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`. For + `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression. omit_background : Union[bool, None] Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. Defaults to `false`. @@ -4460,6 +4509,7 @@ async def click( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.click @@ -4510,6 +4560,11 @@ async def click( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -4525,6 +4580,7 @@ async def click( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -4543,6 +4599,7 @@ async def dblclick( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.dblclick @@ -4591,6 +4648,11 @@ async def dblclick( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -4605,6 +4667,7 @@ async def dblclick( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -4621,6 +4684,7 @@ async def tap( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.tap @@ -4664,6 +4728,11 @@ async def tap( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -4676,6 +4745,7 @@ async def tap( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -5010,6 +5080,7 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, + busy: typing.Optional[bool] = None, ) -> "Locator": """Frame.get_by_role @@ -5099,6 +5170,10 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + busy : Union[bool, None] + An attribute that is usually set by `aria-busy`. + + Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -5118,6 +5193,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -5476,6 +5552,7 @@ async def hover( force: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.hover @@ -5517,6 +5594,11 @@ async def hover( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -5529,6 +5611,7 @@ async def hover( force=force, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -5545,6 +5628,7 @@ async def drag_and_drop( timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.drag_and_drop @@ -5579,6 +5663,11 @@ async def drag_and_drop( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between the `mousedown` and `mouseup` of the drag. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -5593,6 +5682,7 @@ async def drag_and_drop( timeout=to_milliseconds(timeout), trial=trial, steps=steps, + scroll=scroll, ) ) @@ -5896,6 +5986,7 @@ async def check( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.check @@ -5934,6 +6025,11 @@ async def check( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -5945,6 +6041,7 @@ async def check( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -5958,6 +6055,7 @@ async def uncheck( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.uncheck @@ -5996,6 +6094,11 @@ async def uncheck( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -6007,6 +6110,7 @@ async def uncheck( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -6124,6 +6228,7 @@ async def set_checked( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.set_checked @@ -6164,6 +6269,11 @@ async def set_checked( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -6176,6 +6286,7 @@ async def set_checked( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -6509,6 +6620,7 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, + busy: typing.Optional[bool] = None, ) -> "Locator": """FrameLocator.get_by_role @@ -6598,6 +6710,10 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + busy : Union[bool, None] + An attribute that is usually set by `aria-busy`. + + Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -6617,6 +6733,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -10648,7 +10765,7 @@ async def screenshot( self, *, timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, - type: typing.Optional[Literal["jpeg", "png"]] = None, + type: typing.Optional[Literal["jpeg", "png", "webp"]] = None, path: typing.Optional[typing.Union[pathlib.Path, str]] = None, quality: typing.Optional[int] = None, omit_background: typing.Optional[bool] = None, @@ -10670,14 +10787,15 @@ async def screenshot( timeout : Union[float, None] Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. - type : Union["jpeg", "png", None] + type : Union["jpeg", "png", "webp", None] Specify screenshot type, defaults to `png`. path : Union[pathlib.Path, str, None] The file path to save the image to. The screenshot type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to the current working directory. If no path is provided, the image won't be saved to the disk. quality : Union[int, None] - The quality of the image, between 0-100. Not applicable to `png` images. + The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`. For + `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression. omit_background : Union[bool, None] Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. Defaults to `false`. @@ -10846,6 +10964,7 @@ async def click( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.click @@ -10896,6 +11015,11 @@ async def click( strict : Union[bool, None] When true, the call requires selector to resolve to a single element. If given selector resolves to more than one element, the call throws an exception. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -10911,6 +11035,7 @@ async def click( noWaitAfter=no_wait_after, trial=trial, strict=strict, + scroll=scroll, ) ) @@ -10929,6 +11054,7 @@ async def dblclick( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.dblclick @@ -10976,6 +11102,11 @@ async def dblclick( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -10990,6 +11121,7 @@ async def dblclick( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -11006,6 +11138,7 @@ async def tap( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.tap @@ -11049,6 +11182,11 @@ async def tap( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -11061,6 +11199,7 @@ async def tap( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -11393,6 +11532,7 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, + busy: typing.Optional[bool] = None, ) -> "Locator": """Page.get_by_role @@ -11482,6 +11622,10 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + busy : Union[bool, None] + An attribute that is usually set by `aria-busy`. + + Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -11501,6 +11645,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -11859,6 +12004,7 @@ async def hover( force: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.hover @@ -11900,6 +12046,11 @@ async def hover( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -11912,6 +12063,7 @@ async def hover( force=force, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -11928,6 +12080,7 @@ async def drag_and_drop( strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.drag_and_drop @@ -11978,6 +12131,11 @@ async def drag_and_drop( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between the `mousedown` and `mouseup` of the drag. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -11992,6 +12150,7 @@ async def drag_and_drop( strict=strict, trial=trial, steps=steps, + scroll=scroll, ) ) @@ -12313,6 +12472,7 @@ async def check( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.check @@ -12351,6 +12511,11 @@ async def check( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -12362,6 +12527,7 @@ async def check( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -12375,6 +12541,7 @@ async def uncheck( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.uncheck @@ -12413,6 +12580,11 @@ async def uncheck( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -12424,6 +12596,7 @@ async def uncheck( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -13299,6 +13472,7 @@ async def set_checked( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.set_checked @@ -13339,6 +13513,11 @@ async def set_checked( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -13351,6 +13530,7 @@ async def set_checked( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -14930,6 +15110,7 @@ async def route_from_har( update: typing.Optional[bool] = None, update_content: typing.Optional[Literal["attach", "embed"]] = None, update_mode: typing.Optional[Literal["full", "minimal"]] = None, + intercept_api_requests: typing.Optional[bool] = None, ) -> None: """BrowserContext.route_from_har @@ -14963,6 +15144,10 @@ async def route_from_har( When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to `minimal`. + intercept_api_requests : Union[bool, None] + If set to `true`, requests made via `APIRequestContext` (such as `browser_context.request` or + `page.request`) are also served from the HAR file. By default these requests are sent to the network, + matching the behavior prior to v1.62. Defaults to `false` for backward compatibility. """ return mapping.from_maybe_impl( @@ -14973,6 +15158,7 @@ async def route_from_har( update=update, updateContent=update_content, updateMode=update_mode, + interceptAPIRequests=intercept_api_requests, ) ) @@ -15213,11 +15399,12 @@ async def storage_state( *, path: typing.Optional[typing.Union[pathlib.Path, str]] = None, indexed_db: typing.Optional[bool] = None, + credentials: typing.Optional[bool] = None, ) -> StorageState: """BrowserContext.storage_state - Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB - snapshot. + Returns storage state for this browser context, contains current cookies, local storage snapshot, IndexedDB + snapshot and virtual WebAuthn credentials. Parameters ---------- @@ -15228,6 +15415,12 @@ async def storage_state( Set to `true` to include [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) in the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase Authentication, enable this. + credentials : Union[bool, None] + Set to `true` to include the context's virtual WebAuthn `browser_context.credentials` (passkeys) in the + storage state snapshot. The captured credentials carry their private keys, so they can be re-seeded into a later + context via the `storageState` option or `browser_context.set_storage_state()`. Note that restoring the + storage state that contains credentials will automatically install the virtual WebAuthn authenticator (see + `credentials.install()`), and prevent all real authenticators from working in this context. Returns ------- @@ -15235,7 +15428,9 @@ async def storage_state( """ return mapping.from_impl( - await self._impl_obj.storage_state(path=path, indexedDB=indexed_db) + await self._impl_obj.storage_state( + path=path, indexedDB=indexed_db, credentials=credentials + ) ) async def set_storage_state( @@ -15243,7 +15438,9 @@ async def set_storage_state( ) -> None: """BrowserContext.set_storage_state - Clears the existing cookies, local storage and IndexedDB entries for all origins and sets the new storage state. + Clears the existing cookies, local storage, IndexedDB entries and virtual WebAuthn credentials, and sets the new + storage state. When the storage state contains credentials, the virtual WebAuthn authenticator is installed + (equivalent to `credentials.install()`), preventing all real authenticators from working in this context. **Usage** @@ -16920,6 +17117,10 @@ async def connect_over_cdp( `browser_type.connect()`. If you are experiencing issues or attempting to use advanced functionality, you probably want to use `browser_type.connect()`. + **NOTE** Playwright maintains a curated list of arguments for launching the browser. If you launch the browser + without Playwright and do not pass the exact same arguments, some of Playwright functionality may be broken upon + connecting to the browser. + **Usage** ```py @@ -17550,6 +17751,7 @@ async def check( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.check @@ -17592,6 +17794,11 @@ async def check( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -17601,6 +17808,7 @@ async def check( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -17619,6 +17827,7 @@ async def click( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.click @@ -17686,6 +17895,11 @@ async def click( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -17700,6 +17914,7 @@ async def click( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) @@ -17717,6 +17932,7 @@ async def dblclick( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.dblclick @@ -17765,6 +17981,11 @@ async def dblclick( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -17778,6 +17999,7 @@ async def dblclick( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) @@ -18362,6 +18584,7 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, + busy: typing.Optional[bool] = None, ) -> "Locator": """Locator.get_by_role @@ -18451,6 +18674,10 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + busy : Union[bool, None] + An attribute that is usually set by `aria-busy`. + + Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -18470,6 +18697,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -18954,6 +19182,7 @@ async def drag_to( source_position: typing.Optional[Position] = None, target_position: typing.Optional[Position] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.drag_to @@ -19003,6 +19232,11 @@ async def drag_to( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between the `mousedown` and `mouseup` of the drag. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -19015,6 +19249,7 @@ async def drag_to( sourcePosition=source_position, targetPosition=target_position, steps=steps, + scroll=scroll, ) ) @@ -19106,6 +19341,7 @@ async def hover( no_wait_after: typing.Optional[bool] = None, force: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.hover @@ -19151,6 +19387,11 @@ async def hover( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -19161,6 +19402,7 @@ async def hover( noWaitAfter=no_wait_after, force=force, trial=trial, + scroll=scroll, ) ) @@ -19524,7 +19766,7 @@ async def screenshot( self, *, timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, - type: typing.Optional[Literal["jpeg", "png"]] = None, + type: typing.Optional[Literal["jpeg", "png", "webp"]] = None, path: typing.Optional[typing.Union[pathlib.Path, str]] = None, quality: typing.Optional[int] = None, omit_background: typing.Optional[bool] = None, @@ -19567,14 +19809,15 @@ async def screenshot( timeout : Union[float, None] Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. - type : Union["jpeg", "png", None] + type : Union["jpeg", "png", "webp", None] Specify screenshot type, defaults to `png`. path : Union[pathlib.Path, str, None] The file path to save the image to. The screenshot type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to the current working directory. If no path is provided, the image won't be saved to the disk. quality : Union[int, None] - The quality of the image, between 0-100. Not applicable to `png` images. + The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`. For + `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression. omit_background : Union[bool, None] Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. Defaults to `false`. @@ -19945,6 +20188,7 @@ async def tap( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.tap @@ -19987,6 +20231,11 @@ async def tap( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -19997,6 +20246,7 @@ async def tap( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -20131,6 +20381,7 @@ async def uncheck( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.uncheck @@ -20173,6 +20424,11 @@ async def uncheck( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -20182,6 +20438,7 @@ async def uncheck( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -20268,6 +20525,52 @@ async def wait_for( await self._impl_obj.wait_for(timeout=to_milliseconds(timeout), state=state) ) + async def wait_for_function( + self, + expression: str, + *, + arg: typing.Optional[typing.Any] = None, + timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, + ) -> None: + """Locator.wait_for_function + + Returns when `expression` returns a truthy value, called with the matching element as a first argument, and `arg` + as a second argument. + + This is a generic way to wait for an element to reach a custom condition without asserting it. The locator is + re-resolved on each retry, so it tolerates the element being re-rendered while waiting. + + If `expression` returns a [Promise], this method will wait for the promise to resolve before checking its value. + + If `expression` throws or rejects, this method throws. + + **Usage** + + Wait for an attribute to appear: + + Passing argument to `expression`: + + Parameters + ---------- + expression : str + JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the + function is automatically invoked. + arg : Union[Any, None] + Optional argument to pass to `expression`. + timeout : Union[float, None] + Maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The + default value can be changed by using the `browser_context.set_default_timeout()` or + `page.set_default_timeout()` methods. + """ + + return mapping.from_maybe_impl( + await self._impl_obj.wait_for_function( + expression=expression, + arg=mapping.to_impl(arg), + timeout=to_milliseconds(timeout), + ) + ) + async def set_checked( self, checked: bool, @@ -20277,6 +20580,7 @@ async def set_checked( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.set_checked @@ -20320,6 +20624,11 @@ async def set_checked( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -20330,6 +20639,7 @@ async def set_checked( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index f9d9287ae..2b904a4a6 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -2229,6 +2229,7 @@ def hover( no_wait_after: typing.Optional[bool] = None, force: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.hover @@ -2262,6 +2263,11 @@ def hover( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2273,6 +2279,7 @@ def hover( noWaitAfter=no_wait_after, force=force, trial=trial, + scroll=scroll, ) ) ) @@ -2292,6 +2299,7 @@ def click( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.click @@ -2337,6 +2345,11 @@ def click( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2352,6 +2365,7 @@ def click( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -2370,6 +2384,7 @@ def dblclick( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.dblclick @@ -2412,6 +2427,11 @@ def dblclick( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2426,6 +2446,7 @@ def dblclick( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -2519,6 +2540,7 @@ def tap( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.tap @@ -2554,6 +2576,11 @@ def tap( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2565,6 +2592,7 @@ def tap( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -2662,8 +2690,7 @@ def input_value( Parameters ---------- timeout : Union[float, None] - Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can - be changed by using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. + Deprecated: This option is ignored. The value is returned immediately. Returns ------- @@ -2837,6 +2864,7 @@ def set_checked( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.set_checked @@ -2870,6 +2898,11 @@ def set_checked( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2881,6 +2914,7 @@ def set_checked( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -2893,6 +2927,7 @@ def check( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.check @@ -2925,6 +2960,11 @@ def check( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2935,6 +2975,7 @@ def check( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -2947,6 +2988,7 @@ def uncheck( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """ElementHandle.uncheck @@ -2979,6 +3021,11 @@ def uncheck( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -2989,6 +3036,7 @@ def uncheck( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -3027,7 +3075,7 @@ def screenshot( self, *, timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, - type: typing.Optional[Literal["jpeg", "png"]] = None, + type: typing.Optional[Literal["jpeg", "png", "webp"]] = None, path: typing.Optional[typing.Union[pathlib.Path, str]] = None, quality: typing.Optional[int] = None, omit_background: typing.Optional[bool] = None, @@ -3054,14 +3102,15 @@ def screenshot( timeout : Union[float, None] Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. - type : Union["jpeg", "png", None] + type : Union["jpeg", "png", "webp", None] Specify screenshot type, defaults to `png`. path : Union[pathlib.Path, str, None] The file path to save the image to. The screenshot type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to the current working directory. If no path is provided, the image won't be saved to the disk. quality : Union[int, None] - The quality of the image, between 0-100. Not applicable to `png` images. + The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`. For + `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression. omit_background : Union[bool, None] Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. Defaults to `false`. @@ -4531,6 +4580,7 @@ def click( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.click @@ -4581,6 +4631,11 @@ def click( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -4597,6 +4652,7 @@ def click( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -4616,6 +4672,7 @@ def dblclick( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.dblclick @@ -4664,6 +4721,11 @@ def dblclick( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -4679,6 +4741,7 @@ def dblclick( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -4696,6 +4759,7 @@ def tap( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.tap @@ -4739,6 +4803,11 @@ def tap( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -4752,6 +4821,7 @@ def tap( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -5089,6 +5159,7 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, + busy: typing.Optional[bool] = None, ) -> "Locator": """Frame.get_by_role @@ -5178,6 +5249,10 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + busy : Union[bool, None] + An attribute that is usually set by `aria-busy`. + + Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -5197,6 +5272,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -5565,6 +5641,7 @@ def hover( force: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.hover @@ -5606,6 +5683,11 @@ def hover( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -5619,6 +5701,7 @@ def hover( force=force, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -5636,6 +5719,7 @@ def drag_and_drop( timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.drag_and_drop @@ -5670,6 +5754,11 @@ def drag_and_drop( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between the `mousedown` and `mouseup` of the drag. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -5685,6 +5774,7 @@ def drag_and_drop( timeout=to_milliseconds(timeout), trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -5999,6 +6089,7 @@ def check( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.check @@ -6037,6 +6128,11 @@ def check( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -6049,6 +6145,7 @@ def check( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -6063,6 +6160,7 @@ def uncheck( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.uncheck @@ -6101,6 +6199,11 @@ def uncheck( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -6113,6 +6216,7 @@ def uncheck( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -6232,6 +6336,7 @@ def set_checked( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Frame.set_checked @@ -6272,6 +6377,11 @@ def set_checked( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -6285,6 +6395,7 @@ def set_checked( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -6619,6 +6730,7 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, + busy: typing.Optional[bool] = None, ) -> "Locator": """FrameLocator.get_by_role @@ -6708,6 +6820,10 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + busy : Union[bool, None] + An attribute that is usually set by `aria-busy`. + + Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -6727,6 +6843,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -10678,7 +10795,7 @@ def screenshot( self, *, timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, - type: typing.Optional[Literal["jpeg", "png"]] = None, + type: typing.Optional[Literal["jpeg", "png", "webp"]] = None, path: typing.Optional[typing.Union[pathlib.Path, str]] = None, quality: typing.Optional[int] = None, omit_background: typing.Optional[bool] = None, @@ -10700,14 +10817,15 @@ def screenshot( timeout : Union[float, None] Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. - type : Union["jpeg", "png", None] + type : Union["jpeg", "png", "webp", None] Specify screenshot type, defaults to `png`. path : Union[pathlib.Path, str, None] The file path to save the image to. The screenshot type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to the current working directory. If no path is provided, the image won't be saved to the disk. quality : Union[int, None] - The quality of the image, between 0-100. Not applicable to `png` images. + The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`. For + `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression. omit_background : Union[bool, None] Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. Defaults to `false`. @@ -10885,6 +11003,7 @@ def click( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.click @@ -10935,6 +11054,11 @@ def click( strict : Union[bool, None] When true, the call requires selector to resolve to a single element. If given selector resolves to more than one element, the call throws an exception. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -10951,6 +11075,7 @@ def click( noWaitAfter=no_wait_after, trial=trial, strict=strict, + scroll=scroll, ) ) ) @@ -10970,6 +11095,7 @@ def dblclick( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.dblclick @@ -11017,6 +11143,11 @@ def dblclick( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -11032,6 +11163,7 @@ def dblclick( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -11049,6 +11181,7 @@ def tap( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.tap @@ -11092,6 +11225,11 @@ def tap( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -11105,6 +11243,7 @@ def tap( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -11440,6 +11579,7 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, + busy: typing.Optional[bool] = None, ) -> "Locator": """Page.get_by_role @@ -11529,6 +11669,10 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + busy : Union[bool, None] + An attribute that is usually set by `aria-busy`. + + Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -11548,6 +11692,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -11916,6 +12061,7 @@ def hover( force: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.hover @@ -11957,6 +12103,11 @@ def hover( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -11970,6 +12121,7 @@ def hover( force=force, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -11987,6 +12139,7 @@ def drag_and_drop( strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.drag_and_drop @@ -12037,6 +12190,11 @@ def drag_and_drop( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between the `mousedown` and `mouseup` of the drag. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -12052,6 +12210,7 @@ def drag_and_drop( strict=strict, trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -12384,6 +12543,7 @@ def check( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.check @@ -12422,6 +12582,11 @@ def check( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -12434,6 +12599,7 @@ def check( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -12448,6 +12614,7 @@ def uncheck( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.uncheck @@ -12486,6 +12653,11 @@ def uncheck( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -12498,6 +12670,7 @@ def uncheck( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -13373,6 +13546,7 @@ def set_checked( no_wait_after: typing.Optional[bool] = None, strict: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Page.set_checked @@ -13413,6 +13587,11 @@ def set_checked( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -13426,6 +13605,7 @@ def set_checked( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -14931,6 +15111,7 @@ def route_from_har( update: typing.Optional[bool] = None, update_content: typing.Optional[Literal["attach", "embed"]] = None, update_mode: typing.Optional[Literal["full", "minimal"]] = None, + intercept_api_requests: typing.Optional[bool] = None, ) -> None: """BrowserContext.route_from_har @@ -14964,6 +15145,10 @@ def route_from_har( When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to `minimal`. + intercept_api_requests : Union[bool, None] + If set to `true`, requests made via `APIRequestContext` (such as `browser_context.request` or + `page.request`) are also served from the HAR file. By default these requests are sent to the network, + matching the behavior prior to v1.62. Defaults to `false` for backward compatibility. """ return mapping.from_maybe_impl( @@ -14975,6 +15160,7 @@ def route_from_har( update=update, updateContent=update_content, updateMode=update_mode, + interceptAPIRequests=intercept_api_requests, ) ) ) @@ -15216,11 +15402,12 @@ def storage_state( *, path: typing.Optional[typing.Union[pathlib.Path, str]] = None, indexed_db: typing.Optional[bool] = None, + credentials: typing.Optional[bool] = None, ) -> StorageState: """BrowserContext.storage_state - Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB - snapshot. + Returns storage state for this browser context, contains current cookies, local storage snapshot, IndexedDB + snapshot and virtual WebAuthn credentials. Parameters ---------- @@ -15231,6 +15418,12 @@ def storage_state( Set to `true` to include [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) in the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase Authentication, enable this. + credentials : Union[bool, None] + Set to `true` to include the context's virtual WebAuthn `browser_context.credentials` (passkeys) in the + storage state snapshot. The captured credentials carry their private keys, so they can be re-seeded into a later + context via the `storageState` option or `browser_context.set_storage_state()`. Note that restoring the + storage state that contains credentials will automatically install the virtual WebAuthn authenticator (see + `credentials.install()`), and prevent all real authenticators from working in this context. Returns ------- @@ -15238,7 +15431,11 @@ def storage_state( """ return mapping.from_impl( - self._sync(self._impl_obj.storage_state(path=path, indexedDB=indexed_db)) + self._sync( + self._impl_obj.storage_state( + path=path, indexedDB=indexed_db, credentials=credentials + ) + ) ) def set_storage_state( @@ -15246,7 +15443,9 @@ def set_storage_state( ) -> None: """BrowserContext.set_storage_state - Clears the existing cookies, local storage and IndexedDB entries for all origins and sets the new storage state. + Clears the existing cookies, local storage, IndexedDB entries and virtual WebAuthn credentials, and sets the new + storage state. When the storage state contains credentials, the virtual WebAuthn authenticator is installed + (equivalent to `credentials.install()`), preventing all real authenticators from working in this context. **Usage** @@ -16893,6 +17092,10 @@ def connect_over_cdp( `browser_type.connect()`. If you are experiencing issues or attempting to use advanced functionality, you probably want to use `browser_type.connect()`. + **NOTE** Playwright maintains a curated list of arguments for launching the browser. If you launch the browser + without Playwright and do not pass the exact same arguments, some of Playwright functionality may be broken upon + connecting to the browser. + **Usage** ```py @@ -17528,6 +17731,7 @@ def check( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.check @@ -17570,6 +17774,11 @@ def check( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -17580,6 +17789,7 @@ def check( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -17599,6 +17809,7 @@ def click( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.click @@ -17666,6 +17877,11 @@ def click( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -17681,6 +17897,7 @@ def click( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -17699,6 +17916,7 @@ def dblclick( no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.dblclick @@ -17747,6 +17965,11 @@ def dblclick( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between Playwright's current cursor position and the provided destination. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -17761,6 +17984,7 @@ def dblclick( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -18360,6 +18584,7 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, + busy: typing.Optional[bool] = None, ) -> "Locator": """Locator.get_by_role @@ -18449,6 +18674,10 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). + busy : Union[bool, None] + An attribute that is usually set by `aria-busy`. + + Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -18468,6 +18697,7 @@ def get_by_role( selected=selected, exact=exact, description=description, + busy=busy, ) ) @@ -18951,6 +19181,7 @@ def drag_to( source_position: typing.Optional[Position] = None, target_position: typing.Optional[Position] = None, steps: typing.Optional[int] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.drag_to @@ -19000,6 +19231,11 @@ def drag_to( steps : Union[int, None] Defaults to 1. Sends `n` interpolated `mousemove` events to represent travel between the `mousedown` and `mouseup` of the drag. When set to 1, emits a single `mousemove` event at the destination location. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -19013,6 +19249,7 @@ def drag_to( sourcePosition=source_position, targetPosition=target_position, steps=steps, + scroll=scroll, ) ) ) @@ -19109,6 +19346,7 @@ def hover( no_wait_after: typing.Optional[bool] = None, force: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.hover @@ -19154,6 +19392,11 @@ def hover( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -19165,6 +19408,7 @@ def hover( noWaitAfter=no_wait_after, force=force, trial=trial, + scroll=scroll, ) ) ) @@ -19531,7 +19775,7 @@ def screenshot( self, *, timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, - type: typing.Optional[Literal["jpeg", "png"]] = None, + type: typing.Optional[Literal["jpeg", "png", "webp"]] = None, path: typing.Optional[typing.Union[pathlib.Path, str]] = None, quality: typing.Optional[int] = None, omit_background: typing.Optional[bool] = None, @@ -19574,14 +19818,15 @@ def screenshot( timeout : Union[float, None] Maximum time in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the `browser_context.set_default_timeout()` or `page.set_default_timeout()` methods. - type : Union["jpeg", "png", None] + type : Union["jpeg", "png", "webp", None] Specify screenshot type, defaults to `png`. path : Union[pathlib.Path, str, None] The file path to save the image to. The screenshot type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to the current working directory. If no path is provided, the image won't be saved to the disk. quality : Union[int, None] - The quality of the image, between 0-100. Not applicable to `png` images. + The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`. For + `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression. omit_background : Union[bool, None] Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. Defaults to `false`. @@ -19967,6 +20212,7 @@ def tap( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.tap @@ -20009,6 +20255,11 @@ def tap( to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -20020,6 +20271,7 @@ def tap( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -20159,6 +20411,7 @@ def uncheck( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.uncheck @@ -20201,6 +20454,11 @@ def uncheck( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -20211,6 +20469,7 @@ def uncheck( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -20300,6 +20559,54 @@ def wait_for( ) ) + def wait_for_function( + self, + expression: str, + *, + arg: typing.Optional[typing.Any] = None, + timeout: typing.Optional[typing.Union[float, datetime.timedelta]] = None, + ) -> None: + """Locator.wait_for_function + + Returns when `expression` returns a truthy value, called with the matching element as a first argument, and `arg` + as a second argument. + + This is a generic way to wait for an element to reach a custom condition without asserting it. The locator is + re-resolved on each retry, so it tolerates the element being re-rendered while waiting. + + If `expression` returns a [Promise], this method will wait for the promise to resolve before checking its value. + + If `expression` throws or rejects, this method throws. + + **Usage** + + Wait for an attribute to appear: + + Passing argument to `expression`: + + Parameters + ---------- + expression : str + JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the + function is automatically invoked. + arg : Union[Any, None] + Optional argument to pass to `expression`. + timeout : Union[float, None] + Maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The + default value can be changed by using the `browser_context.set_default_timeout()` or + `page.set_default_timeout()` methods. + """ + + return mapping.from_maybe_impl( + self._sync( + self._impl_obj.wait_for_function( + expression=expression, + arg=mapping.to_impl(arg), + timeout=to_milliseconds(timeout), + ) + ) + ) + def set_checked( self, checked: bool, @@ -20309,6 +20616,7 @@ def set_checked( force: typing.Optional[bool] = None, no_wait_after: typing.Optional[bool] = None, trial: typing.Optional[bool] = None, + scroll: typing.Optional[Literal["auto", "none"]] = None, ) -> None: """Locator.set_checked @@ -20352,6 +20660,11 @@ def set_checked( trial : Union[bool, None] When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. + scroll : Union["auto", "none", None] + Controls whether Playwright scrolls the element into view before performing the action. Defaults to `"auto"`, which + scrolls the element into view when necessary, including scrolling nested scrollable containers. When set to + `"none"`, Playwright does not scroll the element and the action fails if the element is not already in the + viewport. This is useful to assert that an element is reachable by the user without additional scrolling. """ return mapping.from_maybe_impl( @@ -20363,6 +20676,7 @@ def set_checked( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) diff --git a/scripts/expected_api_mismatch.txt b/scripts/expected_api_mismatch.txt index d9bcc41a0..685f81f8b 100644 --- a/scripts/expected_api_mismatch.txt +++ b/scripts/expected_api_mismatch.txt @@ -21,6 +21,8 @@ Parameter type mismatch in Page.route_web_socket(handler=): documented as Callab Parameter type mismatch in WebSocketRoute.on_close(handler=): documented as Callable[[Union[int, undefined]], Union[Any, Any]], code has Callable[[Union[int, None], Union[str, None]], Any] Parameter type mismatch in WebSocketRoute.on_message(handler=): documented as Callable[[str], Union[Any, Any]], code has Callable[[Union[bytes, str]], Any] -# Async API additionally accepts an `async def` predicate. +# Async API additionally accepts an `async def` predicate. (Sync collapses the +# Awaitable union, so the sync generation pass reports these as "No longer +# there" — that is expected; async generation still needs them.) Parameter type mismatch in Page.expect_request(url_or_predicate=): documented as Union[Callable[[Request], bool], Pattern[str], str], code has Union[Callable[[Request], Union[bool, typing.Awaitable[bool]]], Pattern[str], str] Parameter type mismatch in Page.expect_response(url_or_predicate=): documented as Union[Callable[[Response], bool], Pattern[str], str], code has Union[Callable[[Response], Union[bool, typing.Awaitable[bool]]], Pattern[str], str] diff --git a/tests/async/test_browsercontext_storage_state.py b/tests/async/test_browsercontext_storage_state.py index 02fb0e2b0..a2b20de19 100644 --- a/tests/async/test_browsercontext_storage_state.py +++ b/tests/async/test_browsercontext_storage_state.py @@ -219,3 +219,24 @@ async def test_should_serialise_indexed_db(page: Page, server: Server) -> None: } ], } + + +async def test_should_round_trip_webauthn_credentials_with_storage_state( + browser: Browser, +) -> None: + # Ported from upstream browsercontext-storage-state.spec.ts. + context = await browser.new_context() + credential = await context.credentials.create(rp_id="localhost") + + # Credentials are opt-in, omitted by default. + assert await context.storage_state() == {"cookies": [], "origins": []} + + state = await context.storage_state(credentials=True) + assert state == {"cookies": [], "origins": [], "credentials": [credential]} + + # A fresh context seeded from the storage state holds the same credential. + context2 = await browser.new_context(storage_state=state) + assert await context2.credentials.get() == [credential] + assert await context2.storage_state(credentials=True) == state + await context.close() + await context2.close() diff --git a/tests/async/test_har.py b/tests/async/test_har.py index 0ea5ee054..faaeb9cb7 100644 --- a/tests/async/test_har.py +++ b/tests/async/test_har.py @@ -804,3 +804,70 @@ async def _timeout() -> str: ) assert next(iter(done)).result() == "timeout" eval_task.cancel() + + +async def test_should_fulfill_api_request_context_requests_from_har_when_intercepting( + browser: Browser, server: Server, tmp_path: Path +) -> None: + # Ported from upstream browsercontext-har.spec.ts interceptAPIRequests. + def _live(req: TestServerRequest) -> None: + req.setHeader("content-type", "application/json") + req.write(json.dumps({"hello": "live"}).encode()) + req.finish() + + server.set_route("/api/data", _live) + + har_path = tmp_path / "api.har" + context1 = await browser.new_context() + await context1.route_from_har(har_path, update=True) + page1 = await context1.new_page() + await page1.goto(server.EMPTY_PAGE) + recorded = await page1.request.get(server.PREFIX + "/api/data") + assert await recorded.json() == {"hello": "live"} + await context1.close() + + # Now stop serving from the network - the request must come from the HAR. + def _not_from_har(req: TestServerRequest) -> None: + req.write(b"NOT_FROM_HAR") + req.finish() + + server.set_route("/api/data", _not_from_har) + context2 = await browser.new_context() + await context2.route_from_har(har_path, intercept_api_requests=True) + page2 = await context2.new_page() + replayed = await page2.request.get(server.PREFIX + "/api/data") + assert await replayed.json() == {"hello": "live"} + await context2.close() + + +async def test_should_not_intercept_api_request_context_requests_by_default( + browser: Browser, server: Server, tmp_path: Path +) -> None: + def _live(req: TestServerRequest) -> None: + req.setHeader("content-type", "application/json") + req.write(json.dumps({"hello": "live"}).encode()) + req.finish() + + server.set_route("/api/data", _live) + + har_path = tmp_path / "api.har" + context1 = await browser.new_context() + await context1.route_from_har(har_path, update=True) + page1 = await context1.new_page() + await page1.goto(server.EMPTY_PAGE) + await page1.request.get(server.PREFIX + "/api/data") + await context1.close() + + # Without the option, the live network is hit. + def _fresh(req: TestServerRequest) -> None: + req.setHeader("content-type", "application/json") + req.write(json.dumps({"hello": "fresh"}).encode()) + req.finish() + + server.set_route("/api/data", _fresh) + context2 = await browser.new_context() + await context2.route_from_har(har_path, not_found="fallback") + page2 = await context2.new_page() + replayed = await page2.request.get(server.PREFIX + "/api/data") + assert await replayed.json() == {"hello": "fresh"} + await context2.close() diff --git a/tests/async/test_locators.py b/tests/async/test_locators.py index d43a81c52..10e37ea32 100644 --- a/tests/async/test_locators.py +++ b/tests/async/test_locators.py @@ -1358,3 +1358,86 @@ async def test_get_by_role_with_description_whitespace_normalization( assert await page.get_by_role( "alert", description=" doc-2025.pdf \n was uploaded " ).evaluate_all("els => els.map(e => e.textContent)") == ["Alert"] + + +async def test_get_by_role_with_busy(page: Page) -> None: + # Ported from upstream tests/page/selectors-role.spec.ts. + await page.set_content( + """ +
Hi
+
Hello
+
Bye
+ + + """ + ) + + async def outer_htmls(locator: Locator) -> list: + return await locator.evaluate_all("els => els.map(e => e.outerHTML)") + + assert await outer_htmls(page.get_by_role("cell", busy=True)) == [ + '
Hello
' + ] + assert await outer_htmls(page.get_by_role("cell", busy=False)) == [ + '
Hi
', + '
Bye
', + ] + # aria-busy is a global ARIA state and should work for any role. + assert await outer_htmls(page.get_by_role("button", busy=True)) == [ + '' + ] + assert await outer_htmls(page.get_by_role("button", busy=False)) == [ + "" + ] + + +async def test_should_not_scroll_when_scroll_is_none(page: Page) -> None: + # Ported from upstream tests/page/page-click-scroll.spec.ts. + await page.set_content( + """ +
+ + """ + ) + with pytest.raises(Error) as exc_info: + await page.locator("button").click(scroll="none", timeout=2000) + assert "element is outside of the viewport" in exc_info.value.message + assert not await page.evaluate("window._clicked") + assert await page.evaluate("() => window.scrollY") == 0 + + +async def test_should_click_in_viewport_element_when_scroll_is_none(page: Page) -> None: + await page.set_content( + """ + +
+ """ + ) + await page.locator("button").click(scroll="none", timeout=2000) + assert await page.evaluate("window._clicked") is True + assert await page.evaluate("() => window.scrollY") == 0 + + +async def test_locator_wait_for_function(page: Page) -> None: + await page.set_content("
0
") + locator = page.locator("#target") + await page.evaluate( + """() => { + setTimeout(() => { + document.querySelector('#target').textContent = '5'; + }, 100); + }""" + ) + await locator.wait_for_function( + "(node) => node.textContent === '5'", + ) + assert await locator.text_content() == "5" + + +async def test_locator_wait_for_function_with_arg(page: Page) -> None: + await page.set_content("
hello
") + locator = page.locator("#target") + await locator.wait_for_function( + "(node, arg) => node.textContent === arg", + arg="hello", + ) diff --git a/tests/async/test_screenshot.py b/tests/async/test_screenshot.py index 36149225f..0a4a20f7a 100644 --- a/tests/async/test_screenshot.py +++ b/tests/async/test_screenshot.py @@ -77,3 +77,19 @@ async def test_should_screenshot_with_type_argument(page: Page, tmp_path: Path) output_png_with_jpeg_extension = tmp_path / "bar_png.jpeg" await page.screenshot(path=output_png_with_jpeg_extension, type="png") assert_image_file_format(output_png_with_jpeg_extension, "PNG") + + +async def test_should_screenshot_with_webp_type_argument( + page: Page, tmp_path: Path +) -> None: + output_webp_with_png_extension = tmp_path / "foo_webp.png" + await page.screenshot(path=output_webp_with_png_extension, type="webp") + assert_image_file_format(output_webp_with_png_extension, "WEBP") + + +async def test_should_infer_webp_screenshot_type_from_path( + page: Page, tmp_path: Path +) -> None: + output_webp_file = tmp_path / "foo.webp" + await page.screenshot(path=output_webp_file) + assert_image_file_format(output_webp_file, "WEBP") diff --git a/tests/sync/test_browsercontext_storage_state.py b/tests/sync/test_browsercontext_storage_state.py index 6850de8a1..15b019f54 100644 --- a/tests/sync/test_browsercontext_storage_state.py +++ b/tests/sync/test_browsercontext_storage_state.py @@ -172,3 +172,24 @@ def test_should_serialise_indexed_db(page: Page, server: Server) -> None: } ], } + + +def test_should_round_trip_webauthn_credentials_with_storage_state( + browser: Browser, +) -> None: + # Ported from upstream browsercontext-storage-state.spec.ts. + context = browser.new_context() + credential = context.credentials.create(rp_id="localhost") + + # Credentials are opt-in, omitted by default. + assert context.storage_state() == {"cookies": [], "origins": []} + + state = context.storage_state(credentials=True) + assert state == {"cookies": [], "origins": [], "credentials": [credential]} + + # A fresh context seeded from the storage state holds the same credential. + context2 = browser.new_context(storage_state=state) + assert context2.credentials.get() == [credential] + assert context2.storage_state(credentials=True) == state + context.close() + context2.close() diff --git a/tests/sync/test_har.py b/tests/sync/test_har.py index 6ac848b8a..45c3f1547 100644 --- a/tests/sync/test_har.py +++ b/tests/sync/test_har.py @@ -22,7 +22,7 @@ import pytest from playwright.sync_api import Browser, BrowserContext, Error, Page, Route, expect -from tests.server import Server +from tests.server import Server, TestServerRequest def test_should_work(browser: Browser, server: Server, tmp_path: Path) -> None: @@ -634,3 +634,70 @@ def test_should_update_extracted_har_zip_for_page( assert "hello, world!" in page_2.content() expect(page_2.locator("body")).to_have_css("background-color", "rgb(255, 192, 203)") context_2.close() + + +def test_should_fulfill_api_request_context_requests_from_har_when_intercepting( + browser: Browser, server: Server, tmp_path: Path +) -> None: + # Ported from upstream browsercontext-har.spec.ts interceptAPIRequests. + def _live(req: TestServerRequest) -> None: + req.setHeader("content-type", "application/json") + req.write(json.dumps({"hello": "live"}).encode()) + req.finish() + + server.set_route("/api/data", _live) + + har_path = tmp_path / "api.har" + context1 = browser.new_context() + context1.route_from_har(har_path, update=True) + page1 = context1.new_page() + page1.goto(server.EMPTY_PAGE) + recorded = page1.request.get(server.PREFIX + "/api/data") + assert recorded.json() == {"hello": "live"} + context1.close() + + # Now stop serving from the network - the request must come from the HAR. + def _not_from_har(req: TestServerRequest) -> None: + req.write(b"NOT_FROM_HAR") + req.finish() + + server.set_route("/api/data", _not_from_har) + context2 = browser.new_context() + context2.route_from_har(har_path, intercept_api_requests=True) + page2 = context2.new_page() + replayed = page2.request.get(server.PREFIX + "/api/data") + assert replayed.json() == {"hello": "live"} + context2.close() + + +def test_should_not_intercept_api_request_context_requests_by_default( + browser: Browser, server: Server, tmp_path: Path +) -> None: + def _live(req: TestServerRequest) -> None: + req.setHeader("content-type", "application/json") + req.write(json.dumps({"hello": "live"}).encode()) + req.finish() + + server.set_route("/api/data", _live) + + har_path = tmp_path / "api.har" + context1 = browser.new_context() + context1.route_from_har(har_path, update=True) + page1 = context1.new_page() + page1.goto(server.EMPTY_PAGE) + page1.request.get(server.PREFIX + "/api/data") + context1.close() + + # Without the option, the live network is hit. + def _fresh(req: TestServerRequest) -> None: + req.setHeader("content-type", "application/json") + req.write(json.dumps({"hello": "fresh"}).encode()) + req.finish() + + server.set_route("/api/data", _fresh) + context2 = browser.new_context() + context2.route_from_har(har_path, not_found="fallback") + page2 = context2.new_page() + replayed = page2.request.get(server.PREFIX + "/api/data") + assert replayed.json() == {"hello": "fresh"} + context2.close() diff --git a/tests/sync/test_locators.py b/tests/sync/test_locators.py index 6201d64db..d9914b011 100644 --- a/tests/sync/test_locators.py +++ b/tests/sync/test_locators.py @@ -1105,3 +1105,83 @@ def test_get_by_role_with_description_whitespace_normalization(page: Page) -> No assert page.get_by_role( "alert", description=" doc-2025.pdf \n was uploaded " ).evaluate_all("els => els.map(e => e.textContent)") == ["Alert"] + + +def test_get_by_role_with_busy(page: Page) -> None: + # Ported from upstream tests/page/selectors-role.spec.ts. + page.set_content( + """ +
Hi
+
Hello
+
Bye
+ + + """ + ) + + def outer_htmls(locator: Locator) -> list: + return locator.evaluate_all("els => els.map(e => e.outerHTML)") + + assert outer_htmls(page.get_by_role("cell", busy=True)) == [ + '
Hello
' + ] + assert outer_htmls(page.get_by_role("cell", busy=False)) == [ + '
Hi
', + '
Bye
', + ] + assert outer_htmls(page.get_by_role("button", busy=True)) == [ + '' + ] + assert outer_htmls(page.get_by_role("button", busy=False)) == [ + "" + ] + + +def test_should_not_scroll_when_scroll_is_none(page: Page) -> None: + # Ported from upstream tests/page/page-click-scroll.spec.ts. + page.set_content( + """ +
+ + """ + ) + with pytest.raises(Error) as exc_info: + page.locator("button").click(scroll="none", timeout=2000) + assert "element is outside of the viewport" in exc_info.value.message + assert not page.evaluate("window._clicked") + assert page.evaluate("() => window.scrollY") == 0 + + +def test_should_click_in_viewport_element_when_scroll_is_none(page: Page) -> None: + page.set_content( + """ + +
+ """ + ) + page.locator("button").click(scroll="none", timeout=2000) + assert page.evaluate("window._clicked") is True + assert page.evaluate("() => window.scrollY") == 0 + + +def test_locator_wait_for_function(page: Page) -> None: + page.set_content("
0
") + locator = page.locator("#target") + page.evaluate( + """() => { + setTimeout(() => { + document.querySelector('#target').textContent = '5'; + }, 100); + }""" + ) + locator.wait_for_function("(node) => node.textContent === '5'") + assert locator.text_content() == "5" + + +def test_locator_wait_for_function_with_arg(page: Page) -> None: + page.set_content("
hello
") + locator = page.locator("#target") + locator.wait_for_function( + "(node, arg) => node.textContent === arg", + arg="hello", + ) From a3eb4ba572847dc44a077efa0c2644ae2429eb40 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 15 Jul 2026 17:35:05 +0200 Subject: [PATCH 2/5] fix(client): align with 1.62 protocol behavior Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3813c827-3dfc-44a4-9433-cae8c2919c95 --- playwright/_impl/_browser_context.py | 10 +++--- playwright/_impl/_browser_type.py | 6 ++-- playwright/_impl/_connection.py | 18 +++++------ playwright/_impl/_element_handle.py | 3 +- playwright/_impl/_har_router.py | 48 +++++++++------------------- playwright/_impl/_helper.py | 12 +++++++ playwright/_impl/_network.py | 36 ++++++++------------- playwright/_impl/_page.py | 13 +++++--- 8 files changed, 69 insertions(+), 77 deletions(-) diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py index 84b25dfba..415668899 100644 --- a/playwright/_impl/_browser_context.py +++ b/playwright/_impl/_browser_context.py @@ -66,6 +66,7 @@ WebSocketRouteHandlerCallback, async_readfile, async_writefile, + create_task_and_ignore_exception, locals_to_params, parse_error, to_impl, @@ -260,10 +261,11 @@ async def _on_route(self, route: Route) -> None: handled = await route_handler.handle(route) finally: if len(self._routes) == 0: - asyncio.create_task( + create_task_and_ignore_exception( + self._loop, self._connection.wrap_api_call( lambda: self._update_interception_patterns(), True - ) + ), ) if handled: return @@ -691,9 +693,9 @@ def _on_dialog(self, dialog: Dialog) -> None: # a) removing "dialog" listener subscription (client->server) # b) actual "dialog" event (server->client) if dialog.type == "beforeunload": - asyncio.create_task(dialog.accept()) + create_task_and_ignore_exception(self._loop, dialog.accept()) else: - asyncio.create_task(dialog.dismiss()) + create_task_and_ignore_exception(self._loop, dialog.dismiss()) def _on_page_error( self, error: Error, page: Optional[Page], location: WebErrorLocation diff --git a/playwright/_impl/_browser_type.py b/playwright/_impl/_browser_type.py index 880f546ef..f7b2f239d 100644 --- a/playwright/_impl/_browser_type.py +++ b/playwright/_impl/_browser_type.py @@ -220,13 +220,15 @@ async def connect( ) -> Browser: if slowMo is None: slowMo = 0 + if timeout is None: + timeout = 0 headers = {**(headers if headers else {}), "x-playwright-browser": self.name} local_utils = self._connection.local_utils pipe_channel = ( await local_utils._channel.send_return_as_dict( "connect", - lambda t: t if t is not None else 0, + lambda t: t or 0, { "endpoint": endpoint, "headers": headers, @@ -272,7 +274,7 @@ def handle_transport_close(reason: Optional[str]) -> None: playwright_future = connection.playwright_future timeout_future = throw_on_timeout( - timeout if timeout is not None else PLAYWRIGHT_MAX_DEADLINE, + timeout if timeout else PLAYWRIGHT_MAX_DEADLINE, Error("Connection timed out"), ) done, pending = await asyncio.wait( diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index c1b0a4eb4..1f4dd8ee6 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -102,8 +102,8 @@ def send_no_reply( self._object, method, augmented_params, - True, timeout, + True, ), is_internal, title, @@ -122,7 +122,7 @@ async def _inner_send( raise error augmented_params, timeout = _augment_params(params, timeout_calculator) callback = self._connection._send_message_to_server( - self._object, method, augmented_params, timeout=timeout + self._object, method, augmented_params, timeout ) done, _ = await asyncio.wait( { @@ -360,8 +360,8 @@ def _send_message_to_server( object: ChannelOwner, method: str, params: Dict, + timeout: float, no_reply: bool = False, - timeout: Optional[float] = None, ) -> ProtocolCallback: if self._closed_error: raise self._closed_error @@ -393,14 +393,13 @@ def _send_message_to_server( "wallTime": int(datetime.datetime.now().timestamp() * 1000), "apiName": stack_trace_information["apiName"], "internal": not stack_trace_information["apiName"], + "timeout": timeout, } if location: metadata["location"] = location # type: ignore title = stack_trace_information["title"] if title: metadata["title"] = title - if timeout is not None: - metadata["timeout"] = timeout # type: ignore message = { "id": id, "guid": object._guid, @@ -663,14 +662,13 @@ def _extract_stack_trace_information_from_stack( def _augment_params( params: Optional[Dict], timeout_calculator: Optional[Callable[[Optional[float]], float]], -) -> Tuple[Dict, Optional[float]]: +) -> Tuple[Dict, float]: if params is None: params = {} - timeout: Optional[float] = None + timeout_param = params.pop("timeout", None) + timeout: float = 0 if timeout_calculator: - timeout = timeout_calculator(params.get("timeout")) - # The timeout is sent as a protocol-level metadata field, never in params. - params = {key: value for key, value in params.items() if key != "timeout"} + timeout = timeout_calculator(timeout_param) return _filter_none(params), timeout diff --git a/playwright/_impl/_element_handle.py b/playwright/_impl/_element_handle.py index f92369b17..2284b96e3 100644 --- a/playwright/_impl/_element_handle.py +++ b/playwright/_impl/_element_handle.py @@ -468,7 +468,8 @@ def convert_select_option_values( def determine_screenshot_type(path: Union[str, Path]) -> Literal["jpeg", "png", "webp"]: # Detect by file extension rather than mimetypes.guess_type, whose result is # OS-dependent (e.g. Windows does not register image/webp), mirroring - # upstream's getMimeTypeForPath extension map. + # upstream's getMimeTypeForPath extension map: + # https://github.com/microsoft/playwright/blob/e0e814deed7b0a4c4d2bdf98481e6be7419cda16/packages/isomorphic/mimeType.ts#L28 suffix = Path(path).suffix.lower() if suffix == ".png": return "png" diff --git a/playwright/_impl/_har_router.py b/playwright/_impl/_har_router.py index bc239d445..a9eaad03e 100644 --- a/playwright/_impl/_har_router.py +++ b/playwright/_impl/_har_router.py @@ -14,13 +14,14 @@ import asyncio import base64 import re -from typing import TYPE_CHECKING, Coroutine, List, Optional, cast +from typing import TYPE_CHECKING, List, Optional, Tuple, cast from playwright._impl._api_structures import HeadersArray from playwright._impl._helper import ( HarLookupResult, RouteFromHarNotFoundPolicy, URLMatch, + create_task_and_ignore_exception, escape_regex_flags, ) from playwright._impl._local_utils import LocalUtils @@ -43,7 +44,7 @@ def __init__( self._har_id: str = har_id self._not_found_action: RouteFromHarNotFoundPolicy = not_found_action self._options_url_match: Optional[URLMatch] = url_matcher - self._api_request_registrations: List["_APIRequestRegistration"] = [] + self._api_request_registrations: List[Tuple["BrowserContext", str]] = [] @staticmethod async def create( @@ -133,39 +134,20 @@ async def add_api_request_route(self, context: "BrowserContext") -> None: result = await context._channel.send_return_as_dict( "routeAPIRequestsFromHar", None, params ) - self._api_request_registrations.append( - _APIRequestRegistration(context, result["registrationId"]) - ) + self._api_request_registrations.append((context, result["registrationId"])) def dispose(self) -> None: - for registration in self._api_request_registrations: - asyncio.create_task( - self._swallow( - registration.context._channel.send( - "unrouteAPIRequestsFromHar", - None, - {"registrationId": registration.registration_id}, - ) - ) + for context, registration_id in self._api_request_registrations: + create_task_and_ignore_exception( + context._loop, + context._channel.send( + "unrouteAPIRequestsFromHar", + None, + {"registrationId": registration_id}, + ), ) self._api_request_registrations = [] - asyncio.create_task( - self._swallow( - self._local_utils._channel.send( - "harClose", None, {"harId": self._har_id} - ) - ) + create_task_and_ignore_exception( + self._local_utils._loop, + self._local_utils._channel.send("harClose", None, {"harId": self._har_id}), ) - - @staticmethod - async def _swallow(coro: Coroutine) -> None: - try: - await coro - except Exception: - pass - - -class _APIRequestRegistration: - def __init__(self, context: "BrowserContext", registration_id: str) -> None: - self.context = context - self.registration_id = registration_id diff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py index 4ac759c2f..b1b7b7c9f 100644 --- a/playwright/_impl/_helper.py +++ b/playwright/_impl/_helper.py @@ -391,6 +391,18 @@ def monotonic_time() -> int: return math.floor(time.monotonic() * 1000) +def create_task_and_ignore_exception( + loop: asyncio.AbstractEventLoop, coro: Awaitable[Any] +) -> None: + async def _ignore_exception() -> None: + try: + await coro + except Exception: + pass + + loop.create_task(_ignore_exception()) + + class RouteHandlerInvocation: complete: "asyncio.Future" route: "Route" diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index 852b5fac7..c8df96e35 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -56,6 +56,7 @@ URLMatch, WebSocketRouteHandlerCallback, async_readfile, + create_task_and_ignore_exception, locals_to_params, url_matches, ) @@ -565,18 +566,6 @@ async def _race_with_page_close(self, future: Coroutine) -> None: await asyncio.gather(fut, return_exceptions=True) -def _create_task_and_ignore_exception( - loop: asyncio.AbstractEventLoop, coro: Coroutine -) -> None: - async def _ignore_exception() -> None: - try: - await coro - except Exception: - pass - - loop.create_task(_ignore_exception()) - - class ServerWebSocketRoute: def __init__(self, ws: "WebSocketRoute"): self._ws = ws @@ -601,7 +590,7 @@ def protocols(self) -> List[str]: return list(self._ws._initializer.get("protocols", [])) def close(self, code: int = None, reason: str = None) -> None: - _create_task_and_ignore_exception( + create_task_and_ignore_exception( self._ws._loop, self._ws._channel.send( "closeServer", @@ -616,14 +605,14 @@ def close(self, code: int = None, reason: str = None) -> None: def send(self, message: Union[str, bytes]) -> None: if isinstance(message, str): - _create_task_and_ignore_exception( + create_task_and_ignore_exception( self._ws._loop, self._ws._channel.send( "sendToServer", None, {"message": message, "isBase64": False} ), ) else: - _create_task_and_ignore_exception( + create_task_and_ignore_exception( self._ws._loop, self._ws._channel.send( "sendToServer", @@ -662,7 +651,7 @@ def _channel_message_from_page(self, event: Dict) -> None: else event["message"] ) elif self._connected: - _create_task_and_ignore_exception( + create_task_and_ignore_exception( self._loop, self._channel.send("sendToServer", None, event) ) @@ -674,7 +663,7 @@ def _channel_message_from_server(self, event: Dict) -> None: else event["message"] ) else: - _create_task_and_ignore_exception( + create_task_and_ignore_exception( self._loop, self._channel.send("sendToPage", None, event) ) @@ -682,7 +671,7 @@ def _channel_close_page(self, event: Dict) -> None: if self._on_page_close: self._on_page_close(event["code"], event["reason"]) else: - _create_task_and_ignore_exception( + create_task_and_ignore_exception( self._loop, self._channel.send("closeServer", None, event) ) @@ -690,7 +679,7 @@ def _channel_close_server(self, event: Dict) -> None: if self._on_server_close: self._on_server_close(event["code"], event["reason"]) else: - _create_task_and_ignore_exception( + create_task_and_ignore_exception( self._loop, self._channel.send("closePage", None, event) ) @@ -714,24 +703,25 @@ def connect_to_server(self) -> "WebSocketRoute": if self._connected: raise Error("Already connected to the server") self._connected = True - asyncio.create_task( + create_task_and_ignore_exception( + self._loop, self._channel.send( "connect", None, - ) + ), ) return cast("WebSocketRoute", self._server) def send(self, message: Union[str, bytes]) -> None: if isinstance(message, str): - _create_task_and_ignore_exception( + create_task_and_ignore_exception( self._loop, self._channel.send( "sendToPage", None, {"message": message, "isBase64": False} ), ) else: - _create_task_and_ignore_exception( + create_task_and_ignore_exception( self._loop, self._channel.send( "sendToPage", diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 470c102fe..b223cff9d 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -78,6 +78,7 @@ WebSocketRouteHandlerCallback, async_readfile, async_writefile, + create_task_and_ignore_exception, locals_to_params, make_dirs_for_file, parse_error, @@ -1615,15 +1616,19 @@ async def call(self, func: Callable) -> None: result = func(source, *func_args) if inspect.iscoroutine(result): result = await result - await self._channel.send( - "resolve", None, dict(result=serialize_argument(result)) + create_task_and_ignore_exception( + self._loop, + self._channel.send( + "resolve", None, dict(result=serialize_argument(result)) + ), ) except Exception as e: tb = sys.exc_info()[2] - asyncio.create_task( + create_task_and_ignore_exception( + self._loop, self._channel.send( "reject", None, dict(error=dict(error=serialize_error(e, tb))) - ) + ), ) From 325102c733902e0bf688eb20c3d9550d4c57daf8 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 16 Jul 2026 09:33:54 +0200 Subject: [PATCH 3/5] chore: roll to 1.62.0-alpha-2026-07-16 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3813c827-3dfc-44a4-9433-cae8c2919c95 --- DRIVER_VERSION | 2 +- playwright/_impl/_fetch.py | 19 +++++++++++ playwright/_impl/_frame.py | 2 -- playwright/_impl/_locator.py | 7 ---- playwright/_impl/_page.py | 2 -- playwright/async_api/_generated.py | 39 +++++++++-------------- playwright/sync_api/_generated.py | 39 +++++++++-------------- tests/async/test_fetch_browser_context.py | 23 ++++++++++++- tests/async/test_har.py | 11 +++++++ tests/async/test_locators.py | 31 ------------------ tests/sync/test_fetch_browser_context.py | 23 ++++++++++++- tests/sync/test_har.py | 11 +++++++ tests/sync/test_locators.py | 30 ----------------- 13 files changed, 116 insertions(+), 123 deletions(-) diff --git a/DRIVER_VERSION b/DRIVER_VERSION index 31850e34c..2d524386a 100644 --- a/DRIVER_VERSION +++ b/DRIVER_VERSION @@ -1 +1 @@ -1.62.0-alpha-1784109367000 +1.62.0-alpha-2026-07-16 diff --git a/playwright/_impl/_fetch.py b/playwright/_impl/_fetch.py index c5cd09340..27174440c 100644 --- a/playwright/_impl/_fetch.py +++ b/playwright/_impl/_fetch.py @@ -29,6 +29,7 @@ HttpCredentials, ProxySettings, RemoteAddr, + ResourceTiming, SecurityDetails, ServerFilePayload, StorageState, @@ -531,6 +532,24 @@ def headers(self) -> Headers: def headers_array(self) -> network.HeadersArray: return self._headers.headers_array() + @property + def timing(self) -> ResourceTiming: + return cast( + ResourceTiming, + { + "startTime": -1, + "domainLookupStart": -1, + "domainLookupEnd": -1, + "connectStart": -1, + "secureConnectionStart": -1, + "connectEnd": -1, + "requestStart": -1, + "responseStart": -1, + **self._initializer.get("timing", {}), + "responseEnd": self._initializer.get("responseEndTiming", -1), + }, + ) + async def body(self) -> bytes: try: result = await self._request._connection.wrap_api_call( diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index faa1674fd..12f24f654 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -692,7 +692,6 @@ def get_by_role( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, - busy: bool = None, ) -> "Locator": return self.locator( get_by_role_selector( @@ -707,7 +706,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index c78db38a8..e4eb561cd 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -284,7 +284,6 @@ def get_by_role( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, - busy: bool = None, ) -> "Locator": return self.locator( get_by_role_selector( @@ -299,7 +298,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -875,7 +873,6 @@ def get_by_role( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, - busy: bool = None, ) -> "Locator": return self.locator( get_by_role_selector( @@ -890,7 +887,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -994,7 +990,6 @@ def get_by_role_selector( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, - busy: bool = None, ) -> str: props: List[Tuple[str, str]] = [] if checked is not None: @@ -1025,7 +1020,5 @@ def get_by_role_selector( ) if pressed is not None: props.append(("pressed", bool_to_js_bool(pressed))) - if busy is not None: - props.append(("busy", bool_to_js_bool(busy))) props_str = "".join([f"[{t[0]}={t[1]}]" for t in props]) return f"internal:role={role}{props_str}" diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index b223cff9d..533a46178 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -972,7 +972,6 @@ def get_by_role( selected: bool = None, exact: bool = None, description: Union[str, Pattern[str]] = None, - busy: bool = None, ) -> "Locator": return self._main_frame.get_by_role( role, @@ -986,7 +985,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator": diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index b99e9f2e8..b74711c94 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -5080,7 +5080,6 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, - busy: typing.Optional[bool] = None, ) -> "Locator": """Frame.get_by_role @@ -5170,10 +5169,6 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). - busy : Union[bool, None] - An attribute that is usually set by `aria-busy`. - - Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -5193,7 +5188,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -6620,7 +6614,6 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, - busy: typing.Optional[bool] = None, ) -> "Locator": """FrameLocator.get_by_role @@ -6710,10 +6703,6 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). - busy : Union[bool, None] - An attribute that is usually set by `aria-busy`. - - Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -6733,7 +6722,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -11532,7 +11520,6 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, - busy: typing.Optional[bool] = None, ) -> "Locator": """Page.get_by_role @@ -11622,10 +11609,6 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). - busy : Union[bool, None] - An attribute that is usually set by `aria-busy`. - - Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -11645,7 +11628,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -18584,7 +18566,6 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, - busy: typing.Optional[bool] = None, ) -> "Locator": """Locator.get_by_role @@ -18674,10 +18655,6 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). - busy : Union[bool, None] - An attribute that is usually set by `aria-busy`. - - Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -18697,7 +18674,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -20744,6 +20720,21 @@ def headers_array(self) -> typing.List[NameValue]: """ return mapping.from_impl_list(self._impl_obj.headers_array) + @property + def timing(self) -> ResourceTiming: + """APIResponse.timing + + Returns resource timing information for given response. For redirected requests, returns the information for the + last request in the redirect chain. When the response is served [from the HAR file](https://playwright.dev/python/docs/mock#replaying-from-har), + timing information is not available and all the values are -1. Find more information at + [Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming). + + Returns + ------- + {startTime: float, domainLookupStart: float, domainLookupEnd: float, connectStart: float, secureConnectionStart: float, connectEnd: float, requestStart: float, responseStart: float, responseEnd: float} + """ + return mapping.from_impl(self._impl_obj.timing) + async def body(self) -> bytes: """APIResponse.body diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index 2b904a4a6..d9077f0d8 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -5159,7 +5159,6 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, - busy: typing.Optional[bool] = None, ) -> "Locator": """Frame.get_by_role @@ -5249,10 +5248,6 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). - busy : Union[bool, None] - An attribute that is usually set by `aria-busy`. - - Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -5272,7 +5267,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -6730,7 +6724,6 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, - busy: typing.Optional[bool] = None, ) -> "Locator": """FrameLocator.get_by_role @@ -6820,10 +6813,6 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). - busy : Union[bool, None] - An attribute that is usually set by `aria-busy`. - - Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -6843,7 +6832,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -11579,7 +11567,6 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, - busy: typing.Optional[bool] = None, ) -> "Locator": """Page.get_by_role @@ -11669,10 +11656,6 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). - busy : Union[bool, None] - An attribute that is usually set by `aria-busy`. - - Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -11692,7 +11675,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -18584,7 +18566,6 @@ def get_by_role( selected: typing.Optional[bool] = None, exact: typing.Optional[bool] = None, description: typing.Optional[typing.Union[typing.Pattern[str], str]] = None, - busy: typing.Optional[bool] = None, ) -> "Locator": """Locator.get_by_role @@ -18674,10 +18655,6 @@ def get_by_role( default, matching is case-insensitive and searches for a substring, use `exact` to control this behavior. Learn more about [accessible description](https://w3c.github.io/accname/#dfn-accessible-description). - busy : Union[bool, None] - An attribute that is usually set by `aria-busy`. - - Learn more about [`aria-busy`](https://www.w3.org/TR/wai-aria-1.2/#aria-busy). Returns ------- @@ -18697,7 +18674,6 @@ def get_by_role( selected=selected, exact=exact, description=description, - busy=busy, ) ) @@ -20784,6 +20760,21 @@ def headers_array(self) -> typing.List[NameValue]: """ return mapping.from_impl_list(self._impl_obj.headers_array) + @property + def timing(self) -> ResourceTiming: + """APIResponse.timing + + Returns resource timing information for given response. For redirected requests, returns the information for the + last request in the redirect chain. When the response is served [from the HAR file](https://playwright.dev/python/docs/mock#replaying-from-har), + timing information is not available and all the values are -1. Find more information at + [Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming). + + Returns + ------- + {startTime: float, domainLookupStart: float, domainLookupEnd: float, connectStart: float, secureConnectionStart: float, connectEnd: float, requestStart: float, responseStart: float, responseEnd: float} + """ + return mapping.from_impl(self._impl_obj.timing) + def body(self) -> bytes: """APIResponse.body diff --git a/tests/async/test_fetch_browser_context.py b/tests/async/test_fetch_browser_context.py index f61f12eb0..35f39128d 100644 --- a/tests/async/test_fetch_browser_context.py +++ b/tests/async/test_fetch_browser_context.py @@ -15,6 +15,7 @@ import asyncio import base64 import json +import time from pathlib import Path from typing import Any, Callable, cast from urllib.parse import parse_qs @@ -29,7 +30,7 @@ FormData, Page, ) -from tests.server import Server, TestServerRequest +from tests.server import HTTPServer, Server, TestServerRequest from tests.utils import must @@ -61,6 +62,26 @@ async def test_fetch_should_work(context: BrowserContext, server: Server) -> Non assert await response.text() == '{"foo": "bar"}\n' +async def test_should_return_timing(context: BrowserContext) -> None: + fresh_server = HTTPServer() + fresh_server.start() + try: + response = await context.request.get(fresh_server.EMPTY_PAGE) + timing = response.timing + assert abs(timing["startTime"] - time.time() * 1000) < 10000 + assert timing["domainLookupStart"] == 0 + assert timing["domainLookupEnd"] >= timing["domainLookupStart"] + assert timing["connectStart"] == timing["domainLookupEnd"] + assert timing["secureConnectionStart"] == -1 + assert timing["connectEnd"] >= timing["connectStart"] + assert timing["requestStart"] == timing["connectEnd"] + assert timing["responseStart"] >= timing["requestStart"] + assert timing["responseEnd"] >= timing["responseStart"] + assert timing["responseEnd"] < 60000 + finally: + fresh_server.stop() + + async def test_should_throw_on_network_error( context: BrowserContext, server: Server ) -> None: diff --git a/tests/async/test_har.py b/tests/async/test_har.py index faaeb9cb7..36393b8c5 100644 --- a/tests/async/test_har.py +++ b/tests/async/test_har.py @@ -837,6 +837,17 @@ def _not_from_har(req: TestServerRequest) -> None: page2 = await context2.new_page() replayed = await page2.request.get(server.PREFIX + "/api/data") assert await replayed.json() == {"hello": "live"} + assert replayed.timing == { + "startTime": -1, + "domainLookupStart": -1, + "domainLookupEnd": -1, + "connectStart": -1, + "secureConnectionStart": -1, + "connectEnd": -1, + "requestStart": -1, + "responseStart": -1, + "responseEnd": -1, + } await context2.close() diff --git a/tests/async/test_locators.py b/tests/async/test_locators.py index 10e37ea32..878d364d6 100644 --- a/tests/async/test_locators.py +++ b/tests/async/test_locators.py @@ -1360,37 +1360,6 @@ async def test_get_by_role_with_description_whitespace_normalization( ).evaluate_all("els => els.map(e => e.textContent)") == ["Alert"] -async def test_get_by_role_with_busy(page: Page) -> None: - # Ported from upstream tests/page/selectors-role.spec.ts. - await page.set_content( - """ -
Hi
-
Hello
-
Bye
- - - """ - ) - - async def outer_htmls(locator: Locator) -> list: - return await locator.evaluate_all("els => els.map(e => e.outerHTML)") - - assert await outer_htmls(page.get_by_role("cell", busy=True)) == [ - '
Hello
' - ] - assert await outer_htmls(page.get_by_role("cell", busy=False)) == [ - '
Hi
', - '
Bye
', - ] - # aria-busy is a global ARIA state and should work for any role. - assert await outer_htmls(page.get_by_role("button", busy=True)) == [ - '' - ] - assert await outer_htmls(page.get_by_role("button", busy=False)) == [ - "" - ] - - async def test_should_not_scroll_when_scroll_is_none(page: Page) -> None: # Ported from upstream tests/page/page-click-scroll.spec.ts. await page.set_content( diff --git a/tests/sync/test_fetch_browser_context.py b/tests/sync/test_fetch_browser_context.py index affca0e0d..c708da33d 100644 --- a/tests/sync/test_fetch_browser_context.py +++ b/tests/sync/test_fetch_browser_context.py @@ -13,6 +13,7 @@ # limitations under the License. import json +import time from pathlib import Path from typing import Any, Dict, List, cast from urllib.parse import parse_qs @@ -26,7 +27,7 @@ FormData, Page, ) -from tests.server import Server +from tests.server import HTTPServer, Server from tests.utils import must @@ -58,6 +59,26 @@ def test_fetch_should_work(context: BrowserContext, server: Server) -> None: assert response.text() == '{"foo": "bar"}\n' +def test_should_return_timing(context: BrowserContext) -> None: + fresh_server = HTTPServer() + fresh_server.start() + try: + response = context.request.get(fresh_server.EMPTY_PAGE) + timing = response.timing + assert abs(timing["startTime"] - time.time() * 1000) < 10000 + assert timing["domainLookupStart"] == 0 + assert timing["domainLookupEnd"] >= timing["domainLookupStart"] + assert timing["connectStart"] == timing["domainLookupEnd"] + assert timing["secureConnectionStart"] == -1 + assert timing["connectEnd"] >= timing["connectStart"] + assert timing["requestStart"] == timing["connectEnd"] + assert timing["responseStart"] >= timing["requestStart"] + assert timing["responseEnd"] >= timing["responseStart"] + assert timing["responseEnd"] < 60000 + finally: + fresh_server.stop() + + def test_should_throw_on_network_error(context: BrowserContext, server: Server) -> None: server.set_route("/test", lambda request: request.loseConnection()) with pytest.raises(Error, match="socket hang up"): diff --git a/tests/sync/test_har.py b/tests/sync/test_har.py index 45c3f1547..ad165711c 100644 --- a/tests/sync/test_har.py +++ b/tests/sync/test_har.py @@ -667,6 +667,17 @@ def _not_from_har(req: TestServerRequest) -> None: page2 = context2.new_page() replayed = page2.request.get(server.PREFIX + "/api/data") assert replayed.json() == {"hello": "live"} + assert replayed.timing == { + "startTime": -1, + "domainLookupStart": -1, + "domainLookupEnd": -1, + "connectStart": -1, + "secureConnectionStart": -1, + "connectEnd": -1, + "requestStart": -1, + "responseStart": -1, + "responseEnd": -1, + } context2.close() diff --git a/tests/sync/test_locators.py b/tests/sync/test_locators.py index d9914b011..34493bbaf 100644 --- a/tests/sync/test_locators.py +++ b/tests/sync/test_locators.py @@ -1107,36 +1107,6 @@ def test_get_by_role_with_description_whitespace_normalization(page: Page) -> No ).evaluate_all("els => els.map(e => e.textContent)") == ["Alert"] -def test_get_by_role_with_busy(page: Page) -> None: - # Ported from upstream tests/page/selectors-role.spec.ts. - page.set_content( - """ -
Hi
-
Hello
-
Bye
- - - """ - ) - - def outer_htmls(locator: Locator) -> list: - return locator.evaluate_all("els => els.map(e => e.outerHTML)") - - assert outer_htmls(page.get_by_role("cell", busy=True)) == [ - '
Hello
' - ] - assert outer_htmls(page.get_by_role("cell", busy=False)) == [ - '
Hi
', - '
Bye
', - ] - assert outer_htmls(page.get_by_role("button", busy=True)) == [ - '' - ] - assert outer_htmls(page.get_by_role("button", busy=False)) == [ - "" - ] - - def test_should_not_scroll_when_scroll_is_none(page: Page) -> None: # Ported from upstream tests/page/page-click-scroll.spec.ts. page.set_content( From c6871b5fc8cf750a5140d243abfafdf720415896 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 16 Jul 2026 11:13:20 +0200 Subject: [PATCH 4/5] test(navigation): mirror upstream Firefox skip Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3813c827-3dfc-44a4-9433-cae8c2919c95 --- tests/async/test_navigation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/async/test_navigation.py b/tests/async/test_navigation.py index a74d67533..505940dbe 100644 --- a/tests/async/test_navigation.py +++ b/tests/async/test_navigation.py @@ -989,6 +989,9 @@ async def test_frame_goto_should_reject_when_frame_detaches( assert "frame was detached" in exc_info.value.message.lower() +@pytest.mark.skip_browser( + "firefox" +) # script.js is requested before navigationCommitted arrives async def test_frame_goto_should_continue_after_client_redirect( page: Page, server: Server ) -> None: From 9458a18faf209e04f0e8893c43faef4957d01b58 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 17 Jul 2026 10:07:10 +0200 Subject: [PATCH 5/5] chore(connection): add send_may_fail helper Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3813c827-3dfc-44a4-9433-cae8c2919c95 --- playwright/_impl/_connection.py | 20 +++++++- playwright/_impl/_har_router.py | 17 +++---- playwright/_impl/_network.py | 86 +++++++++++---------------------- playwright/_impl/_page.py | 15 ++---- 4 files changed, 57 insertions(+), 81 deletions(-) diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index 1f4dd8ee6..40ac78449 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -41,7 +41,12 @@ import playwright._impl._impl_to_api_mapping from playwright._impl._errors import TargetClosedError, rewrite_error from playwright._impl._greenlets import EventGreenlet -from playwright._impl._helper import Error, ParsedMessagePayload, parse_error +from playwright._impl._helper import ( + Error, + ParsedMessagePayload, + create_task_and_ignore_exception, + parse_error, +) from playwright._impl._transport import Transport if TYPE_CHECKING: @@ -73,6 +78,19 @@ async def send( title, ) + def send_may_fail( + self, + method: str, + timeout_calculator: TimeoutCalculator, + params: Dict = None, + is_internal: bool = False, + title: str = None, + ) -> None: + create_task_and_ignore_exception( + self._connection._loop, + self.send(method, timeout_calculator, params, is_internal, title), + ) + async def send_return_as_dict( self, method: str, diff --git a/playwright/_impl/_har_router.py b/playwright/_impl/_har_router.py index a9eaad03e..574697326 100644 --- a/playwright/_impl/_har_router.py +++ b/playwright/_impl/_har_router.py @@ -21,7 +21,6 @@ HarLookupResult, RouteFromHarNotFoundPolicy, URLMatch, - create_task_and_ignore_exception, escape_regex_flags, ) from playwright._impl._local_utils import LocalUtils @@ -138,16 +137,12 @@ async def add_api_request_route(self, context: "BrowserContext") -> None: def dispose(self) -> None: for context, registration_id in self._api_request_registrations: - create_task_and_ignore_exception( - context._loop, - context._channel.send( - "unrouteAPIRequestsFromHar", - None, - {"registrationId": registration_id}, - ), + context._channel.send_may_fail( + "unrouteAPIRequestsFromHar", + None, + {"registrationId": registration_id}, ) self._api_request_registrations = [] - create_task_and_ignore_exception( - self._local_utils._loop, - self._local_utils._channel.send("harClose", None, {"harId": self._har_id}), + self._local_utils._channel.send_may_fail( + "harClose", None, {"harId": self._har_id} ) diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index c8df96e35..4de787567 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -56,7 +56,6 @@ URLMatch, WebSocketRouteHandlerCallback, async_readfile, - create_task_and_ignore_exception, locals_to_params, url_matches, ) @@ -590,35 +589,26 @@ def protocols(self) -> List[str]: return list(self._ws._initializer.get("protocols", [])) def close(self, code: int = None, reason: str = None) -> None: - create_task_and_ignore_exception( - self._ws._loop, - self._ws._channel.send( - "closeServer", - None, - { - "code": code, - "reason": reason, - "wasClean": True, - }, - ), + self._ws._channel.send_may_fail( + "closeServer", + None, + { + "code": code, + "reason": reason, + "wasClean": True, + }, ) def send(self, message: Union[str, bytes]) -> None: if isinstance(message, str): - create_task_and_ignore_exception( - self._ws._loop, - self._ws._channel.send( - "sendToServer", None, {"message": message, "isBase64": False} - ), + self._ws._channel.send_may_fail( + "sendToServer", None, {"message": message, "isBase64": False} ) else: - create_task_and_ignore_exception( - self._ws._loop, - self._ws._channel.send( - "sendToServer", - None, - {"message": base64.b64encode(message).decode(), "isBase64": True}, - ), + self._ws._channel.send_may_fail( + "sendToServer", + None, + {"message": base64.b64encode(message).decode(), "isBase64": True}, ) @@ -651,9 +641,7 @@ def _channel_message_from_page(self, event: Dict) -> None: else event["message"] ) elif self._connected: - create_task_and_ignore_exception( - self._loop, self._channel.send("sendToServer", None, event) - ) + self._channel.send_may_fail("sendToServer", None, event) def _channel_message_from_server(self, event: Dict) -> None: if self._on_server_message: @@ -663,25 +651,19 @@ def _channel_message_from_server(self, event: Dict) -> None: else event["message"] ) else: - create_task_and_ignore_exception( - self._loop, self._channel.send("sendToPage", None, event) - ) + self._channel.send_may_fail("sendToPage", None, event) def _channel_close_page(self, event: Dict) -> None: if self._on_page_close: self._on_page_close(event["code"], event["reason"]) else: - create_task_and_ignore_exception( - self._loop, self._channel.send("closeServer", None, event) - ) + self._channel.send_may_fail("closeServer", None, event) def _channel_close_server(self, event: Dict) -> None: if self._on_server_close: self._on_server_close(event["code"], event["reason"]) else: - create_task_and_ignore_exception( - self._loop, self._channel.send("closePage", None, event) - ) + self._channel.send_may_fail("closePage", None, event) @property def url(self) -> str: @@ -703,34 +685,22 @@ def connect_to_server(self) -> "WebSocketRoute": if self._connected: raise Error("Already connected to the server") self._connected = True - create_task_and_ignore_exception( - self._loop, - self._channel.send( - "connect", - None, - ), - ) + self._channel.send_may_fail("connect", None) return cast("WebSocketRoute", self._server) def send(self, message: Union[str, bytes]) -> None: if isinstance(message, str): - create_task_and_ignore_exception( - self._loop, - self._channel.send( - "sendToPage", None, {"message": message, "isBase64": False} - ), + self._channel.send_may_fail( + "sendToPage", None, {"message": message, "isBase64": False} ) else: - create_task_and_ignore_exception( - self._loop, - self._channel.send( - "sendToPage", - None, - { - "message": base64.b64encode(message).decode(), - "isBase64": True, - }, - ), + self._channel.send_may_fail( + "sendToPage", + None, + { + "message": base64.b64encode(message).decode(), + "isBase64": True, + }, ) def on_message(self, handler: Callable[[Union[str, bytes]], Any]) -> None: diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 533a46178..4da778a11 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -78,7 +78,6 @@ WebSocketRouteHandlerCallback, async_readfile, async_writefile, - create_task_and_ignore_exception, locals_to_params, make_dirs_for_file, parse_error, @@ -1614,19 +1613,13 @@ async def call(self, func: Callable) -> None: result = func(source, *func_args) if inspect.iscoroutine(result): result = await result - create_task_and_ignore_exception( - self._loop, - self._channel.send( - "resolve", None, dict(result=serialize_argument(result)) - ), + self._channel.send_may_fail( + "resolve", None, dict(result=serialize_argument(result)) ) except Exception as e: tb = sys.exc_info()[2] - create_task_and_ignore_exception( - self._loop, - self._channel.send( - "reject", None, dict(error=dict(error=serialize_error(e, tb))) - ), + self._channel.send_may_fail( + "reject", None, dict(error=dict(error=serialize_error(e, tb))) )