diff --git a/DRIVER_VERSION b/DRIVER_VERSION index 9b083fc63..2d524386a 100644 --- a/DRIVER_VERSION +++ b/DRIVER_VERSION @@ -1 +1 @@ -1.61.1-beta-1782139630000 +1.62.0-alpha-2026-07-16 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..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 @@ -497,6 +499,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 +518,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 +591,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)) @@ -685,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 5f436d7fb..f7b2f239d 100644 --- a/playwright/_impl/_browser_type.py +++ b/playwright/_impl/_browser_type.py @@ -220,18 +220,20 @@ 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", - None, + lambda t: t or 0, { "endpoint": endpoint, "headers": headers, "slowMo": slowMo, - "timeout": timeout if timeout is not None else 0, + "timeout": timeout, "exposeNetwork": exposeNetwork, }, ) @@ -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 dc58bdaad..40ac78449 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -28,6 +28,7 @@ List, Mapping, Optional, + Tuple, TypedDict, Union, cast, @@ -40,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: @@ -72,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, @@ -95,11 +114,13 @@ 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, + timeout, True, ), is_internal, @@ -117,8 +138,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 ) done, _ = await asyncio.wait( { @@ -352,7 +374,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, + timeout: float, + no_reply: bool = False, ) -> ProtocolCallback: if self._closed_error: raise self._closed_error @@ -384,6 +411,7 @@ 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 @@ -652,12 +680,14 @@ def _extract_stack_trace_information_from_stack( def _augment_params( params: Optional[Dict], timeout_calculator: Optional[Callable[[Optional[float]], float]], -) -> Dict: +) -> Tuple[Dict, float]: if params is None: params = {} + timeout_param = params.pop("timeout", None) + timeout: float = 0 if timeout_calculator: - params["timeout"] = timeout_calculator(params.get("timeout")) - return _filter_none(params) + timeout = timeout_calculator(timeout_param) + 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..2284b96e3 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,16 @@ 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: + # https://github.com/microsoft/playwright/blob/e0e814deed7b0a4c4d2bdf98481e6be7419cda16/packages/isomorphic/mimeType.ts#L28 + 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/_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 3bf5992a5..12f24f654 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())) @@ -764,6 +768,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 +784,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 +903,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 +916,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 +953,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 +963,7 @@ async def set_checked( force=force, strict=strict, trial=trial, + scroll=scroll, ) else: await self.uncheck( @@ -963,6 +973,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..574697326 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, List, Optional, Tuple, 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[Tuple["BrowserContext", str]] = [] @staticmethod async def create( @@ -116,7 +119,30 @@ 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((context, result["registrationId"])) + def dispose(self) -> None: - asyncio.create_task( - self._local_utils._channel.send("harClose", None, {"harId": self._har_id}) + for context, registration_id in self._api_request_registrations: + context._channel.send_may_fail( + "unrouteAPIRequestsFromHar", + None, + {"registrationId": registration_id}, + ) + self._api_request_registrations = [] + self._local_utils._channel.send_may_fail( + "harClose", None, {"harId": self._har_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/_locator.py b/playwright/_impl/_locator.py index c76b248ce..e4eb561cd 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) @@ -435,6 +438,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 +476,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 +568,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 +670,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 +717,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 +749,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 +775,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 +783,7 @@ async def set_checked( timeout=timeout, force=force, trial=trial, + scroll=scroll, ) else: await self.uncheck( @@ -764,6 +791,7 @@ async def set_checked( timeout=timeout, force=force, trial=trial, + scroll=scroll, ) async def _expect( diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index 852b5fac7..4de787567 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -565,18 +565,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,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}, ) @@ -662,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: @@ -674,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: @@ -714,33 +685,22 @@ def connect_to_server(self) -> "WebSocketRoute": if self._connected: raise Error("Already connected to the server") self._connected = True - asyncio.create_task( - self._channel.send( - "connect", - 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 313c1c841..4da778a11 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())) @@ -1034,6 +1037,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 +1053,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 +1121,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 +1134,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 +1383,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 +1393,7 @@ async def set_checked( force=force, strict=strict, trial=trial, + scroll=scroll, ) else: await self.uncheck( @@ -1394,6 +1403,7 @@ async def set_checked( force=force, strict=strict, trial=trial, + scroll=scroll, ) async def add_locator_handler( @@ -1603,15 +1613,13 @@ async def call(self, func: Callable) -> None: result = func(source, *func_args) if inspect.iscoroutine(result): result = await result - await self._channel.send( + self._channel.send_may_fail( "resolve", None, dict(result=serialize_argument(result)) ) except Exception as e: tb = sys.exc_info()[2] - asyncio.create_task( - 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))) ) diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 6dfd634a3..b74711c94 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, ) ) @@ -5476,6 +5546,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 +5588,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 +5605,7 @@ async def hover( force=force, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -5545,6 +5622,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 +5657,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 +5676,7 @@ async def drag_and_drop( timeout=to_milliseconds(timeout), trial=trial, steps=steps, + scroll=scroll, ) ) @@ -5896,6 +5980,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 +6019,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 +6035,7 @@ async def check( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -5958,6 +6049,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 +6088,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 +6104,7 @@ async def uncheck( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -6124,6 +6222,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 +6263,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 +6280,7 @@ async def set_checked( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -10648,7 +10753,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 +10775,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 +10952,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 +11003,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 +11023,7 @@ async def click( noWaitAfter=no_wait_after, trial=trial, strict=strict, + scroll=scroll, ) ) @@ -10929,6 +11042,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 +11090,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 +11109,7 @@ async def dblclick( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -11006,6 +11126,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 +11170,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 +11187,7 @@ async def tap( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -11859,6 +11986,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 +12028,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 +12045,7 @@ async def hover( force=force, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -11928,6 +12062,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 +12113,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 +12132,7 @@ async def drag_and_drop( strict=strict, trial=trial, steps=steps, + scroll=scroll, ) ) @@ -12313,6 +12454,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 +12493,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 +12509,7 @@ async def check( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -12375,6 +12523,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 +12562,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 +12578,7 @@ async def uncheck( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -13299,6 +13454,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 +13495,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 +13512,7 @@ async def set_checked( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) @@ -14930,6 +15092,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 +15126,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 +15140,7 @@ async def route_from_har( update=update, updateContent=update_content, updateMode=update_mode, + interceptAPIRequests=intercept_api_requests, ) ) @@ -15213,11 +15381,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 +15397,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 +15410,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 +15420,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 +17099,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 +17733,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 +17776,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 +17790,7 @@ async def check( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -17619,6 +17809,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 +17877,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 +17896,7 @@ async def click( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) @@ -17717,6 +17914,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 +17963,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 +17981,7 @@ async def dblclick( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) @@ -18954,6 +19158,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 +19208,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 +19225,7 @@ async def drag_to( sourcePosition=source_position, targetPosition=target_position, steps=steps, + scroll=scroll, ) ) @@ -19106,6 +19317,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 +19363,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 +19378,7 @@ async def hover( noWaitAfter=no_wait_after, force=force, trial=trial, + scroll=scroll, ) ) @@ -19524,7 +19742,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 +19785,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 +20164,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 +20207,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 +20222,7 @@ async def tap( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -20131,6 +20357,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 +20400,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 +20414,7 @@ async def uncheck( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -20268,6 +20501,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 +20556,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 +20600,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 +20615,7 @@ async def set_checked( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) @@ -20434,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 f9d9287ae..d9077f0d8 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, ) ) ) @@ -5565,6 +5635,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 +5677,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 +5695,7 @@ def hover( force=force, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -5636,6 +5713,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 +5748,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 +5768,7 @@ def drag_and_drop( timeout=to_milliseconds(timeout), trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -5999,6 +6083,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 +6122,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 +6139,7 @@ def check( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -6063,6 +6154,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 +6193,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 +6210,7 @@ def uncheck( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -6232,6 +6330,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 +6371,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 +6389,7 @@ def set_checked( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -10678,7 +10783,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 +10805,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 +10991,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 +11042,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 +11063,7 @@ def click( noWaitAfter=no_wait_after, trial=trial, strict=strict, + scroll=scroll, ) ) ) @@ -10970,6 +11083,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 +11131,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 +11151,7 @@ def dblclick( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -11049,6 +11169,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 +11213,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 +11231,7 @@ def tap( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -11916,6 +12043,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 +12085,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 +12103,7 @@ def hover( force=force, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -11987,6 +12121,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 +12172,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 +12192,7 @@ def drag_and_drop( strict=strict, trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -12384,6 +12525,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 +12564,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 +12581,7 @@ def check( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -12448,6 +12596,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 +12635,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 +12652,7 @@ def uncheck( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -13373,6 +13528,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 +13569,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 +13587,7 @@ def set_checked( noWaitAfter=no_wait_after, strict=strict, trial=trial, + scroll=scroll, ) ) ) @@ -14931,6 +15093,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 +15127,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 +15142,7 @@ def route_from_har( update=update, updateContent=update_content, updateMode=update_mode, + interceptAPIRequests=intercept_api_requests, ) ) ) @@ -15216,11 +15384,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 +15400,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 +15413,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 +15425,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 +17074,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 +17713,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 +17756,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 +17771,7 @@ def check( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -17599,6 +17791,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 +17859,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 +17879,7 @@ def click( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -17699,6 +17898,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 +17947,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 +17966,7 @@ def dblclick( noWaitAfter=no_wait_after, trial=trial, steps=steps, + scroll=scroll, ) ) ) @@ -18951,6 +19157,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 +19207,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 +19225,7 @@ def drag_to( sourcePosition=source_position, targetPosition=target_position, steps=steps, + scroll=scroll, ) ) ) @@ -19109,6 +19322,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 +19368,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 +19384,7 @@ def hover( noWaitAfter=no_wait_after, force=force, trial=trial, + scroll=scroll, ) ) ) @@ -19531,7 +19751,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 +19794,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 +20188,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 +20231,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 +20247,7 @@ def tap( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -20159,6 +20387,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 +20430,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 +20445,7 @@ def uncheck( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -20300,6 +20535,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 +20592,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 +20636,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 +20652,7 @@ def set_checked( force=force, noWaitAfter=no_wait_after, trial=trial, + scroll=scroll, ) ) ) @@ -20470,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/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_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 0ea5ee054..36393b8c5 100644 --- a/tests/async/test_har.py +++ b/tests/async/test_har.py @@ -804,3 +804,81 @@ 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"} + assert replayed.timing == { + "startTime": -1, + "domainLookupStart": -1, + "domainLookupEnd": -1, + "connectStart": -1, + "secureConnectionStart": -1, + "connectEnd": -1, + "requestStart": -1, + "responseStart": -1, + "responseEnd": -1, + } + 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..878d364d6 100644 --- a/tests/async/test_locators.py +++ b/tests/async/test_locators.py @@ -1358,3 +1358,55 @@ 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_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_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: 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_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 6ac848b8a..ad165711c 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,81 @@ 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"} + assert replayed.timing == { + "startTime": -1, + "domainLookupStart": -1, + "domainLookupEnd": -1, + "connectStart": -1, + "secureConnectionStart": -1, + "connectEnd": -1, + "requestStart": -1, + "responseStart": -1, + "responseEnd": -1, + } + 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..34493bbaf 100644 --- a/tests/sync/test_locators.py +++ b/tests/sync/test_locators.py @@ -1105,3 +1105,53 @@ 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_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", + )