From 6157d89ed543719a7e149f1debf0d0fd1de73a1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 11:06:59 +0200 Subject: [PATCH 01/45] build(deps): bump typing-extensions from 4.13.2 to 4.14.1 (#2912) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a6b31fd67..14dc0500a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,5 @@ greenlet==3.2.3 # via playwright (pyproject.toml) pyee==13.0.0 # via playwright (pyproject.toml) -typing-extensions==4.13.2 +typing-extensions==4.14.1 # via pyee From 8cd92566bc6724d54216c4aa333546c3906c81b4 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 10 Jul 2025 13:11:08 +0200 Subject: [PATCH 02/45] fix: Make context manager __exit__/__aexit__ signatures compatible with typing protocols (#2915) --- playwright/_impl/_async_base.py | 6 +++--- playwright/_impl/_sync_base.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/playwright/_impl/_async_base.py b/playwright/_impl/_async_base.py index b06994a65..db7e5d005 100644 --- a/playwright/_impl/_async_base.py +++ b/playwright/_impl/_async_base.py @@ -96,9 +96,9 @@ async def __aenter__(self: Self) -> Self: async def __aexit__( self, - exc_type: Type[BaseException], - exc_val: BaseException, - traceback: TracebackType, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + traceback: Optional[TracebackType], ) -> None: await self.close() diff --git a/playwright/_impl/_sync_base.py b/playwright/_impl/_sync_base.py index e6fac9750..3fef433b5 100644 --- a/playwright/_impl/_sync_base.py +++ b/playwright/_impl/_sync_base.py @@ -142,9 +142,9 @@ def __enter__(self: Self) -> Self: def __exit__( self, - exc_type: Type[BaseException], - exc_val: BaseException, - _traceback: TracebackType, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + _traceback: Optional[TracebackType], ) -> None: self.close() From 3f957528af49f6c5314b0fdb5317e15eeb87bb5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:07:35 +0200 Subject: [PATCH 03/45] build(deps): bump pytest from 8.4.0 to 8.4.1 (#2897) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index afe7e4bb8..e19488956 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -8,7 +8,7 @@ Pillow==11.2.1 pixelmatch==0.3.0 pre-commit==3.5.0 pyOpenSSL==25.1.0 -pytest==8.4.0 +pytest==8.4.1 pytest-asyncio==1.0.0 pytest-cov==6.2.1 pytest-repeat==0.9.4 From 6af4fb2ac5951576b8a47ce553aacf84f2ef5561 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 11 Jul 2025 08:06:15 -0700 Subject: [PATCH 04/45] chore: roll to 1.54.0 (#2913) --- README.md | 6 +-- playwright/_impl/_api_structures.py | 15 +++++- playwright/_impl/_assertions.py | 26 ++++++++- playwright/_impl/_browser_context.py | 11 ++-- playwright/_impl/_console_message.py | 23 +++++++- playwright/_impl/_frame.py | 52 +++++++++++++++--- playwright/_impl/_helper.py | 10 ++++ playwright/_impl/_locator.py | 21 ++------ playwright/_impl/_network.py | 11 ++-- playwright/_impl/_page.py | 31 +++++------ playwright/_impl/_selectors.py | 4 ++ playwright/async_api/__init__.py | 2 + playwright/async_api/_generated.py | 37 +++++++++---- playwright/sync_api/__init__.py | 2 + playwright/sync_api/_generated.py | 37 +++++++++---- setup.py | 2 +- tests/async/conftest.py | 2 +- tests/async/test_browsercontext.py | 4 +- tests/async/test_chromium_tracing.py | 18 +++++-- tests/async/test_fetch_global.py | 2 +- tests/async/test_geolocation.py | 4 +- tests/async/test_page_add_locator_handler.py | 2 +- tests/async/test_page_request_intercept.py | 2 +- tests/async/test_page_route.py | 12 +++++ tests/async/test_selectors_misc.py | 28 ++++++++++ tests/async/test_tracing.py | 2 + tests/async/test_unroute_behavior.py | 56 ++++++++++++++++++++ tests/sync/conftest.py | 2 +- tests/sync/test_fetch_global.py | 2 +- tests/sync/test_page_add_locator_handler.py | 2 +- tests/sync/test_tracing.py | 2 + 31 files changed, 338 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 9577b82e8..fa9e246a9 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 138.0.7204.23 | ✅ | ✅ | ✅ | -| WebKit 18.5 | ✅ | ✅ | ✅ | -| Firefox 139.0 | ✅ | ✅ | ✅ | +| Chromium 139.0.7258.5 | ✅ | ✅ | ✅ | +| WebKit 26.0 | ✅ | ✅ | ✅ | +| Firefox 140.0.2 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/_api_structures.py b/playwright/_impl/_api_structures.py index 3b639486a..0afa0d02e 100644 --- a/playwright/_impl/_api_structures.py +++ b/playwright/_impl/_api_structures.py @@ -32,6 +32,18 @@ class Cookie(TypedDict, total=False): httpOnly: bool secure: bool sameSite: Literal["Lax", "None", "Strict"] + partitionKey: Optional[str] + + +class StorageStateCookie(TypedDict, total=False): + name: str + value: str + domain: str + path: str + expires: float + httpOnly: bool + secure: bool + sameSite: Literal["Lax", "None", "Strict"] # TODO: We are waiting for PEP705 so SetCookieParam can be readonly and matches Cookie. @@ -45,6 +57,7 @@ class SetCookieParam(TypedDict, total=False): httpOnly: Optional[bool] secure: Optional[bool] sameSite: Optional[Literal["Lax", "None", "Strict"]] + partitionKey: Optional[str] class FloatRect(TypedDict): @@ -97,7 +110,7 @@ class ProxySettings(TypedDict, total=False): class StorageState(TypedDict, total=False): - cookies: List[Cookie] + cookies: List[StorageStateCookie] origins: List[OriginState] diff --git a/playwright/_impl/_assertions.py b/playwright/_impl/_assertions.py index 6e0161b7c..3aadbf5fe 100644 --- a/playwright/_impl/_assertions.py +++ b/playwright/_impl/_assertions.py @@ -20,6 +20,7 @@ AriaRole, ExpectedTextValue, FrameExpectOptions, + FrameExpectResult, ) from playwright._impl._connection import format_call_log from playwright._impl._errors import Error @@ -45,6 +46,13 @@ def __init__( self._is_not = is_not self._custom_message = message + async def _call_expect( + self, expression: str, expect_options: FrameExpectOptions, title: Optional[str] + ) -> FrameExpectResult: + raise NotImplementedError( + "_call_expect must be implemented in a derived class." + ) + async def _expect_impl( self, expression: str, @@ -61,7 +69,7 @@ async def _expect_impl( message = message.replace("expected to", "expected not to") if "useInnerText" in expect_options and expect_options["useInnerText"] is None: del expect_options["useInnerText"] - result = await self._actual_locator._expect(expression, expect_options, title) + result = await self._call_expect(expression, expect_options, title) if result["matches"] == self._is_not: actual = result.get("received") if self._custom_message: @@ -88,6 +96,14 @@ def __init__( super().__init__(page.locator(":root"), timeout, is_not, message) self._actual_page = page + async def _call_expect( + self, expression: str, expect_options: FrameExpectOptions, title: Optional[str] + ) -> FrameExpectResult: + __tracebackhide__ = True + return await self._actual_page.main_frame._expect( + None, expression, expect_options, title + ) + @property def _not(self) -> "PageAssertions": return PageAssertions( @@ -122,7 +138,7 @@ async def to_have_url( ignoreCase: bool = None, ) -> None: __tracebackhide__ = True - base_url = self._actual_page.context._options.get("baseURL") + base_url = self._actual_page.context._base_url if isinstance(urlOrRegExp, str) and base_url: urlOrRegExp = urljoin(base_url, urlOrRegExp) expected_text = to_expected_text_values([urlOrRegExp], ignoreCase=ignoreCase) @@ -155,6 +171,12 @@ def __init__( super().__init__(locator, timeout, is_not, message) self._actual_locator = locator + async def _call_expect( + self, expression: str, expect_options: FrameExpectOptions, title: Optional[str] + ) -> FrameExpectResult: + __tracebackhide__ = True + return await self._actual_locator._expect(expression, expect_options, title) + @property def _not(self) -> "LocatorAssertions": return LocatorAssertions( diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py index 60b60c46e..391e61ec6 100644 --- a/playwright/_impl/_browser_context.py +++ b/playwright/_impl/_browser_context.py @@ -119,6 +119,8 @@ def __init__( self._options: Dict[str, Any] = initializer["options"] self._background_pages: Set[Page] = set() self._service_workers: Set[Worker] = set() + self._base_url: Optional[str] = self._options.get("baseURL") + self._videos_dir: Optional[str] = self._options.get("recordVideo") self._tracing = cast(Tracing, from_channel(initializer["tracing"])) self._har_recorders: Dict[str, HarRecordingMetadata] = {} self._request: APIRequestContext = from_channel(initializer["requestContext"]) @@ -424,7 +426,7 @@ async def route( self._routes.insert( 0, RouteHandler( - self._options.get("baseURL"), + self._base_url, url, handler, True if self._dispatcher_fiber else False, @@ -452,17 +454,16 @@ async def _unroute_internal( behavior: Literal["default", "ignoreErrors", "wait"] = None, ) -> None: self._routes = remaining + if behavior is not None and behavior != "default": + await asyncio.gather(*map(lambda router: router.stop(behavior), removed)) # type: ignore await self._update_interception_patterns() - if behavior is None or behavior == "default": - return - await asyncio.gather(*map(lambda router: router.stop(behavior), removed)) # type: ignore async def route_web_socket( self, url: URLMatch, handler: WebSocketRouteHandlerCallback ) -> None: self._web_socket_routes.insert( 0, - WebSocketRouteHandler(self._options.get("baseURL"), url, handler), + WebSocketRouteHandler(self._base_url, url, handler), ) await self._update_web_socket_interception_patterns() diff --git a/playwright/_impl/_console_message.py b/playwright/_impl/_console_message.py index ba8fc0a38..53c0dee95 100644 --- a/playwright/_impl/_console_message.py +++ b/playwright/_impl/_console_message.py @@ -13,7 +13,7 @@ # limitations under the License. from asyncio import AbstractEventLoop -from typing import TYPE_CHECKING, Any, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union from playwright._impl._api_structures import SourceLocation from playwright._impl._connection import from_channel, from_nullable_channel @@ -39,7 +39,26 @@ def __str__(self) -> str: return self.text @property - def type(self) -> str: + def type(self) -> Union[ + Literal["assert"], + Literal["clear"], + Literal["count"], + Literal["debug"], + Literal["dir"], + Literal["dirxml"], + Literal["endGroup"], + Literal["error"], + Literal["info"], + Literal["log"], + Literal["profile"], + Literal["profileEnd"], + Literal["startGroup"], + Literal["startGroupCollapsed"], + Literal["table"], + Literal["timeEnd"], + Literal["trace"], + Literal["warning"], + ]: return self._event["type"] @property diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index c0646b680..fe19a576d 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -30,7 +30,13 @@ from pyee import EventEmitter -from playwright._impl._api_structures import AriaRole, FilePayload, Position +from playwright._impl._api_structures import ( + AriaRole, + FilePayload, + FrameExpectOptions, + FrameExpectResult, + Position, +) from playwright._impl._connection import ( ChannelOwner, from_channel, @@ -56,6 +62,7 @@ Serializable, add_source_url_to_script, parse_result, + parse_value, serialize_argument, ) from playwright._impl._locator import ( @@ -170,6 +177,29 @@ def _setup_navigation_waiter(self, wait_name: str, timeout: float = None) -> Wai waiter.reject_on_timeout(timeout, f"Timeout {timeout}ms exceeded.") return waiter + async def _expect( + self, + selector: Optional[str], + expression: str, + options: FrameExpectOptions, + title: str = None, + ) -> FrameExpectResult: + if "expectedValue" in options: + options["expectedValue"] = serialize_argument(options["expectedValue"]) + result = await self._channel.send_return_as_dict( + "expect", + self._timeout, + { + "selector": selector, + "expression": expression, + **options, + }, + title=title, + ) + if result.get("received"): + result["received"] = parse_value(result["received"]) + return result + def expect_navigation( self, url: URLMatch = None, @@ -194,7 +224,7 @@ def predicate(event: Any) -> bool: return True waiter.log(f' navigated to "{event["url"]}"') return url_matches( - cast("Page", self._page)._browser_context._options.get("baseURL"), + cast("Page", self._page)._browser_context._base_url, event["url"], url, ) @@ -227,9 +257,7 @@ async def wait_for_url( timeout: float = None, ) -> None: assert self._page - if url_matches( - self._page._browser_context._options.get("baseURL"), self.url, url - ): + if url_matches(self._page._browser_context._base_url, self.url, url): await self._wait_for_load_state_impl(state=waitUntil, timeout=timeout) return async with self.expect_navigation( @@ -558,6 +586,18 @@ async def fill( noWaitAfter: bool = None, strict: bool = None, force: bool = None, + ) -> None: + await self._fill(**locals_to_params(locals())) + + async def _fill( + self, + selector: str, + value: str, + timeout: float = None, + noWaitAfter: bool = None, + strict: bool = None, + force: bool = None, + title: str = None, ) -> None: await self._channel.send("fill", self._timeout, locals_to_params(locals())) @@ -801,7 +841,7 @@ async def uncheck( await self._channel.send("uncheck", self._timeout, locals_to_params(locals())) async def wait_for_timeout(self, timeout: float) -> None: - await self._channel.send("waitForTimeout", None, locals_to_params(locals())) + await self._channel.send("waitForTimeout", None, {"waitTimeout": timeout}) async def wait_for_function( self, diff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py index 67a096dc5..66e59c65f 100644 --- a/playwright/_impl/_helper.py +++ b/playwright/_impl/_helper.py @@ -189,6 +189,16 @@ def map_token(original: str, replacement: str) -> str: # Escaped `\\?` behaves the same as `?` in our glob patterns. match = match.replace(r"\\?", "?") + # Special case about: URLs as they are not relative to base_url + if ( + match.startswith("about:") + or match.startswith("data:") + or match.startswith("chrome:") + or match.startswith("edge:") + or match.startswith("file:") + ): + # about: and data: URLs are not relative to base_url, so we return them as is. + return match # Glob symbols may be escaped in the URL and some of them such as ? affect resolution, # so we replace them with safe components first. processed_parts = [] diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index a1ea180ed..a65b68266 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -47,7 +47,7 @@ monotonic_time, to_impl, ) -from playwright._impl._js_handle import Serializable, parse_value, serialize_argument +from playwright._impl._js_handle import Serializable from playwright._impl._str_utils import ( escape_for_attribute_selector, escape_for_text_selector, @@ -217,7 +217,8 @@ async def clear( noWaitAfter: bool = None, force: bool = None, ) -> None: - await self.fill("", timeout=timeout, force=force) + params = locals_to_params(locals()) + await self._frame._fill(self._selector, value="", title="Clear", **params) def locator( self, @@ -722,21 +723,7 @@ async def _expect( options: FrameExpectOptions, title: str = None, ) -> FrameExpectResult: - if "expectedValue" in options: - options["expectedValue"] = serialize_argument(options["expectedValue"]) - result = await self._frame._channel.send_return_as_dict( - "expect", - self._frame._timeout, - { - "selector": self._selector, - "expression": expression, - **options, - }, - title=title, - ) - if result.get("received"): - result["received"] = parse_value(result["received"]) - return result + return await self._frame._expect(self._selector, expression, options, title) async def highlight(self) -> None: await self._frame._highlight(self._selector) diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index 616c75ec9..a999ce73c 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -733,10 +733,13 @@ async def _after_handle(self) -> None: if self._connected: return # Ensure that websocket is "open" and can send messages without an actual server connection. - await self._channel.send( - "ensureOpened", - None, - ) + try: + await self._channel.send( + "ensureOpened", + None, + ) + except Exception: + pass class WebSocketRouteHandler: diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 55ee44df2..a0fa4eec2 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -388,9 +388,7 @@ def frame(self, name: str = None, url: URLMatch = None) -> Optional[Frame]: for frame in self._frames: if name and frame.name == name: return frame - if url and url_matches( - self._browser_context._options.get("baseURL"), frame.url, url - ): + if url and url_matches(self._browser_context._base_url, frame.url, url): return frame return None @@ -682,7 +680,7 @@ async def route( self._routes.insert( 0, RouteHandler( - self._browser_context._options.get("baseURL"), + self._browser_context._base_url, url, handler, True if self._dispatcher_fiber else False, @@ -710,24 +708,21 @@ async def _unroute_internal( behavior: Literal["default", "ignoreErrors", "wait"] = None, ) -> None: self._routes = remaining - await self._update_interception_patterns() - if behavior is None or behavior == "default": - return - await asyncio.gather( - *map( - lambda route: route.stop(behavior), # type: ignore - removed, + if behavior is not None and behavior != "default": + await asyncio.gather( + *map( + lambda route: route.stop(behavior), # type: ignore + removed, + ) ) - ) + await self._update_interception_patterns() async def route_web_socket( self, url: URLMatch, handler: WebSocketRouteHandlerCallback ) -> None: self._web_socket_routes.insert( 0, - WebSocketRouteHandler( - self._browser_context._options.get("baseURL"), url, handler - ), + WebSocketRouteHandler(self._browser_context._base_url, url, handler), ) await self._update_web_socket_interception_patterns() @@ -1186,7 +1181,7 @@ def video( # Note: we are creating Video object lazily, because we do not know # BrowserContextOptions when constructing the page - it is assigned # too late during launchPersistentContext. - if not self._browser_context._options.get("recordVideo"): + if not self._browser_context._videos_dir: return None return self._force_video() @@ -1273,7 +1268,7 @@ def expect_request( def my_predicate(request: Request) -> bool: if not callable(urlOrPredicate): return url_matches( - self._browser_context._options.get("baseURL"), + self._browser_context._base_url, request.url, urlOrPredicate, ) @@ -1305,7 +1300,7 @@ def expect_response( def my_predicate(request: Response) -> bool: if not callable(urlOrPredicate): return url_matches( - self._browser_context._options.get("baseURL"), + self._browser_context._base_url, request.url, urlOrPredicate, ) diff --git a/playwright/_impl/_selectors.py b/playwright/_impl/_selectors.py index 2a2e70974..c3bac78e5 100644 --- a/playwright/_impl/_selectors.py +++ b/playwright/_impl/_selectors.py @@ -37,6 +37,10 @@ async def register( path: Union[str, Path] = None, contentScript: bool = None, ) -> None: + if any(engine for engine in self._selector_engines if engine["name"] == name): + raise Error( + f'Selectors.register: "{name}" selector engine has been already registered' + ) if not script and not path: raise Error("Either source or path should be specified") if path: diff --git a/playwright/async_api/__init__.py b/playwright/async_api/__init__.py index be918f53c..257ac2022 100644 --- a/playwright/async_api/__init__.py +++ b/playwright/async_api/__init__.py @@ -79,6 +79,7 @@ ResourceTiming = playwright._impl._api_structures.ResourceTiming SourceLocation = playwright._impl._api_structures.SourceLocation StorageState = playwright._impl._api_structures.StorageState +StorageStateCookie = playwright._impl._api_structures.StorageStateCookie ViewportSize = playwright._impl._api_structures.ViewportSize Error = playwright._impl._errors.Error @@ -187,6 +188,7 @@ def __call__( "Selectors", "SourceLocation", "StorageState", + "StorageStateCookie", "TimeoutError", "Touchscreen", "Video", diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 5f0af8bf0..bedf233de 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -6975,16 +6975,33 @@ async def set_system_time( class ConsoleMessage(AsyncBase): @property - def type(self) -> str: + def type( + self, + ) -> typing.Union[ + Literal["assert"], + Literal["clear"], + Literal["count"], + Literal["debug"], + Literal["dir"], + Literal["dirxml"], + Literal["endGroup"], + Literal["error"], + Literal["info"], + Literal["log"], + Literal["profile"], + Literal["profileEnd"], + Literal["startGroup"], + Literal["startGroupCollapsed"], + Literal["table"], + Literal["timeEnd"], + Literal["trace"], + Literal["warning"], + ]: """ConsoleMessage.type - One of the following values: `'log'`, `'debug'`, `'info'`, `'error'`, `'warning'`, `'dir'`, `'dirxml'`, `'table'`, - `'trace'`, `'clear'`, `'startGroup'`, `'startGroupCollapsed'`, `'endGroup'`, `'assert'`, `'profile'`, - `'profileEnd'`, `'count'`, `'timeEnd'`. - Returns ------- - str + Union["assert", "clear", "count", "debug", "dir", "dirxml", "endGroup", "error", "info", "log", "profile", "profileEnd", "startGroup", "startGroupCollapsed", "table", "timeEnd", "trace", "warning"] """ return mapping.from_maybe_impl(self._impl_obj.type) @@ -12649,7 +12666,8 @@ def pages(self) -> typing.List["Page"]: def browser(self) -> typing.Optional["Browser"]: """BrowserContext.browser - Returns the browser instance of the context. If it was launched as a persistent context null gets returned. + Gets the browser instance that owns the context. Returns `null` if the context is created outside of normal + browser, e.g. Android or Electron. Returns ------- @@ -12789,7 +12807,7 @@ async def cookies( Returns ------- - List[{name: str, value: str, domain: str, path: str, expires: float, httpOnly: bool, secure: bool, sameSite: Union["Lax", "None", "Strict"]}] + List[{name: str, value: str, domain: str, path: str, expires: float, httpOnly: bool, secure: bool, sameSite: Union["Lax", "None", "Strict"], partitionKey: Union[str, None]}] """ return mapping.from_impl_list( @@ -12810,7 +12828,7 @@ async def add_cookies(self, cookies: typing.Sequence[SetCookieParam]) -> None: Parameters ---------- - cookies : Sequence[{name: str, value: str, url: Union[str, None], domain: Union[str, None], path: Union[str, None], expires: Union[float, None], httpOnly: Union[bool, None], secure: Union[bool, None], sameSite: Union["Lax", "None", "Strict", None]}] + cookies : Sequence[{name: str, value: str, url: Union[str, None], domain: Union[str, None], path: Union[str, None], expires: Union[float, None], httpOnly: Union[bool, None], secure: Union[bool, None], sameSite: Union["Lax", "None", "Strict", None], partitionKey: Union[str, None]}] """ return mapping.from_maybe_impl( @@ -12884,6 +12902,7 @@ async def grant_permissions( - `'notifications'` - `'payment-handler'` - `'storage-access'` + - `'local-fonts'` origin : Union[str, None] The [origin] to grant permissions to, e.g. "https://example.com". """ diff --git a/playwright/sync_api/__init__.py b/playwright/sync_api/__init__.py index 136433982..e901cadbf 100644 --- a/playwright/sync_api/__init__.py +++ b/playwright/sync_api/__init__.py @@ -79,6 +79,7 @@ ResourceTiming = playwright._impl._api_structures.ResourceTiming SourceLocation = playwright._impl._api_structures.SourceLocation StorageState = playwright._impl._api_structures.StorageState +StorageStateCookie = playwright._impl._api_structures.StorageStateCookie ViewportSize = playwright._impl._api_structures.ViewportSize Error = playwright._impl._errors.Error @@ -186,6 +187,7 @@ def __call__( "Selectors", "SourceLocation", "StorageState", + "StorageStateCookie", "sync_playwright", "TimeoutError", "Touchscreen", diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index 763df6de3..8f4b60764 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -7083,16 +7083,33 @@ def set_system_time( class ConsoleMessage(SyncBase): @property - def type(self) -> str: + def type( + self, + ) -> typing.Union[ + Literal["assert"], + Literal["clear"], + Literal["count"], + Literal["debug"], + Literal["dir"], + Literal["dirxml"], + Literal["endGroup"], + Literal["error"], + Literal["info"], + Literal["log"], + Literal["profile"], + Literal["profileEnd"], + Literal["startGroup"], + Literal["startGroupCollapsed"], + Literal["table"], + Literal["timeEnd"], + Literal["trace"], + Literal["warning"], + ]: """ConsoleMessage.type - One of the following values: `'log'`, `'debug'`, `'info'`, `'error'`, `'warning'`, `'dir'`, `'dirxml'`, `'table'`, - `'trace'`, `'clear'`, `'startGroup'`, `'startGroupCollapsed'`, `'endGroup'`, `'assert'`, `'profile'`, - `'profileEnd'`, `'count'`, `'timeEnd'`. - Returns ------- - str + Union["assert", "clear", "count", "debug", "dir", "dirxml", "endGroup", "error", "info", "log", "profile", "profileEnd", "startGroup", "startGroupCollapsed", "table", "timeEnd", "trace", "warning"] """ return mapping.from_maybe_impl(self._impl_obj.type) @@ -12671,7 +12688,8 @@ def pages(self) -> typing.List["Page"]: def browser(self) -> typing.Optional["Browser"]: """BrowserContext.browser - Returns the browser instance of the context. If it was launched as a persistent context null gets returned. + Gets the browser instance that owns the context. Returns `null` if the context is created outside of normal + browser, e.g. Android or Electron. Returns ------- @@ -12811,7 +12829,7 @@ def cookies( Returns ------- - List[{name: str, value: str, domain: str, path: str, expires: float, httpOnly: bool, secure: bool, sameSite: Union["Lax", "None", "Strict"]}] + List[{name: str, value: str, domain: str, path: str, expires: float, httpOnly: bool, secure: bool, sameSite: Union["Lax", "None", "Strict"], partitionKey: Union[str, None]}] """ return mapping.from_impl_list( @@ -12832,7 +12850,7 @@ def add_cookies(self, cookies: typing.Sequence[SetCookieParam]) -> None: Parameters ---------- - cookies : Sequence[{name: str, value: str, url: Union[str, None], domain: Union[str, None], path: Union[str, None], expires: Union[float, None], httpOnly: Union[bool, None], secure: Union[bool, None], sameSite: Union["Lax", "None", "Strict", None]}] + cookies : Sequence[{name: str, value: str, url: Union[str, None], domain: Union[str, None], path: Union[str, None], expires: Union[float, None], httpOnly: Union[bool, None], secure: Union[bool, None], sameSite: Union["Lax", "None", "Strict", None], partitionKey: Union[str, None]}] """ return mapping.from_maybe_impl( @@ -12908,6 +12926,7 @@ def grant_permissions( - `'notifications'` - `'payment-handler'` - `'storage-access'` + - `'local-fonts'` origin : Union[str, None] The [origin] to grant permissions to, e.g. "https://example.com". """ diff --git a/setup.py b/setup.py index fd590167f..c4a75870a 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ import zipfile from typing import Dict -driver_version = "1.53.1" +driver_version = "1.54.0" base_wheel_bundles = [ { diff --git a/tests/async/conftest.py b/tests/async/conftest.py index f2e06d56e..5dff9794f 100644 --- a/tests/async/conftest.py +++ b/tests/async/conftest.py @@ -153,7 +153,7 @@ def action_titles(self) -> Locator: @property def stack_frames(self) -> Locator: - return self.page.get_by_test_id("stack-trace-list").locator(".list-view-entry") + return self.page.get_by_role("list", name="Stack Trace").get_by_role("listitem") async def select_action(self, title: str, ordinal: int = 0) -> None: await self.page.locator(".action-title", has_text=title).nth(ordinal).click() diff --git a/tests/async/test_browsercontext.py b/tests/async/test_browsercontext.py index 37c812f57..ba53b6f95 100644 --- a/tests/async/test_browsercontext.py +++ b/tests/async/test_browsercontext.py @@ -118,9 +118,9 @@ async def test_page_event_should_propagate_default_viewport_to_the_page( async def test_page_event_should_respect_device_scale_factor(browser: Browser) -> None: - context = await browser.new_context(device_scale_factor=3) + context = await browser.new_context(device_scale_factor=3.5) page = await context.new_page() - assert await page.evaluate("window.devicePixelRatio") == 3 + assert await page.evaluate("window.devicePixelRatio") == 3.5 await context.close() diff --git a/tests/async/test_chromium_tracing.py b/tests/async/test_chromium_tracing.py index 23608e009..fd065efde 100644 --- a/tests/async/test_chromium_tracing.py +++ b/tests/async/test_chromium_tracing.py @@ -44,6 +44,13 @@ async def test_should_create_directories_as_needed( assert os.path.getsize(output_file) > 0 +async def rafraf(target: Page, count: int = 1) -> None: + for _ in range(count): + await target.evaluate( + "async () => await new Promise(f => window.requestAnimationFrame(() => window.requestAnimationFrame(f)));" + ) + + @pytest.mark.only_browser("chromium") async def test_should_run_with_custom_categories_if_provided( browser: Browser, page: Page, tmp_path: Path @@ -51,16 +58,19 @@ async def test_should_run_with_custom_categories_if_provided( output_file = tmp_path / "trace.json" await browser.start_tracing( page=page, - screenshots=True, path=output_file, - categories=["disabled-by-default-v8.cpu_profiler.hires"], + categories=["disabled-by-default-cc.debug"], ) + await rafraf(page) await browser.stop_tracing() with open(output_file, mode="r") as of: trace_json = json.load(of) + trace_config = trace_json["metadata"].get("trace-config") + trace_events = trace_json["traceEvents"] assert ( - "disabled-by-default-v8.cpu_profiler.hires" - in trace_json["metadata"]["trace-config"] + trace_config is not None and "disabled-by-default-cc.debug" in trace_config + ) or any( + event.get("cat") == "disabled-by-default-cc.debug" for event in trace_events ) diff --git a/tests/async/test_fetch_global.py b/tests/async/test_fetch_global.py index 6b74208e2..e2a7678c5 100644 --- a/tests/async/test_fetch_global.py +++ b/tests/async/test_fetch_global.py @@ -85,7 +85,7 @@ async def test_should_support_global_timeout_option( ) -> None: request = await playwright.request.new_context(timeout=100) server.set_route("/empty.html", lambda req: None) - with pytest.raises(Error, match="Request timed out after 100ms"): + with pytest.raises(Error, match="Timeout 100ms exceeded"): await request.get(server.EMPTY_PAGE) diff --git a/tests/async/test_geolocation.py b/tests/async/test_geolocation.py index 5791b5984..12b00a4fa 100644 --- a/tests/async/test_geolocation.py +++ b/tests/async/test_geolocation.py @@ -48,7 +48,7 @@ async def test_should_isolate_contexts( await page.goto(server.EMPTY_PAGE) context2 = await browser.new_context( - permissions=["geolocation"], geolocation={"latitude": 20, "longitude": 20} + permissions=["geolocation"], geolocation={"latitude": 10.5, "longitude": 10.5} ) page2 = await context2.new_page() @@ -66,7 +66,7 @@ async def test_should_isolate_contexts( resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}) }))""" ) - assert geolocation2 == {"latitude": 20, "longitude": 20} + assert geolocation2 == {"latitude": 10.5, "longitude": 10.5} await context2.close() diff --git a/tests/async/test_page_add_locator_handler.py b/tests/async/test_page_add_locator_handler.py index 4492037a7..4a5a44323 100644 --- a/tests/async/test_page_add_locator_handler.py +++ b/tests/async/test_page_add_locator_handler.py @@ -312,7 +312,7 @@ def _handler() -> None: with pytest.raises(Error) as exc_info: await page.locator("#target").click(timeout=3000) assert await page.evaluate("window.clicked") == 0 - await expect(page.locator("#interstitial")).to_be_visible() + assert await page.locator("#interstitial").is_visible() assert called == 1 assert ( 'locator handler has finished, waiting for get_by_role("button", name="close") to be hidden' diff --git a/tests/async/test_page_request_intercept.py b/tests/async/test_page_request_intercept.py index 934aed8a0..dc8f7416a 100644 --- a/tests/async/test_page_request_intercept.py +++ b/tests/async/test_page_request_intercept.py @@ -34,7 +34,7 @@ def _handler(request: TestServerRequest) -> None: async def handle(route: Route) -> None: with pytest.raises(Error) as error: await route.fetch(timeout=1000) - assert "Request timed out after 1000ms" in error.value.message + assert "Timeout 1000ms exceeded" in error.value.message await page.route("**/*", lambda route: handle(route)) with pytest.raises(Error) as error: diff --git a/tests/async/test_page_route.py b/tests/async/test_page_route.py index b04f96145..fecafdfba 100644 --- a/tests/async/test_page_route.py +++ b/tests/async/test_page_route.py @@ -1155,6 +1155,18 @@ def glob_to_regex(pattern: str) -> re.Pattern: assert url_matches("http://first.host/", "http://second.host/foo", "**/foo") assert url_matches("http://playwright.dev/", "http://localhost/", "*//localhost/") + custom_prefixes = ["about", "data", "chrome", "edge", "file"] + for prefix in custom_prefixes: + assert url_matches( + "http://playwright.dev/", f"{prefix}:blank", f"{prefix}:blank" + ) + assert not url_matches( + "http://playwright.dev/", f"{prefix}:blank", "http://playwright.dev/" + ) + assert url_matches(None, f"{prefix}:blank", f"{prefix}:blank") + assert url_matches(None, f"{prefix}:blank", f"{prefix}:*") + assert not url_matches(None, f"not{prefix}:blank", f"{prefix}:*") + # Added for Python implementation assert url_matches( None, diff --git a/tests/async/test_selectors_misc.py b/tests/async/test_selectors_misc.py index 5527d6ec8..5ad6c3519 100644 --- a/tests/async/test_selectors_misc.py +++ b/tests/async/test_selectors_misc.py @@ -12,6 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + +from playwright._impl._browser import Browser +from playwright._impl._errors import Error +from playwright._impl._selectors import Selectors from playwright.async_api import Page @@ -52,3 +57,26 @@ async def test_should_work_with_internal_and(page: Page) -> None: '.bar >> internal:and="span"', "els => els.map(e => e.textContent)" ) ) == ["world2"] + + +async def test_should_throw_already_registered_error_when_registering( + selectors: Selectors, + browser: Browser, +) -> None: + create_tag_selector = """ + () => ({ + query(root, selector) { + return root.querySelector(selector); + }, + queryAll(root, selector) { + return Array.from(root.querySelectorAll(selector)); + } + }) + """ + name = f"alreadyRegistered-{browser.browser_type.name}" + await selectors.register(name, create_tag_selector) + with pytest.raises( + Error, + match=f'Selectors.register: "{name}" selector engine has been already registered', + ): + await selectors.register(name, create_tag_selector) diff --git a/tests/async/test_tracing.py b/tests/async/test_tracing.py index e735c96a8..e902eafbd 100644 --- a/tests/async/test_tracing.py +++ b/tests/async/test_tracing.py @@ -136,6 +136,7 @@ async def test_should_collect_trace_with_resources_but_no_js( await page.click('"Click"') await page.mouse.move(20, 20) await page.mouse.dblclick(30, 30) + await page.request.get(server.EMPTY_PAGE) await page.keyboard.insert_text("abc") await page.wait_for_timeout(2000) # Give it some time to produce screenshots. await page.route("**/empty.html", lambda route: route.continue_()) @@ -153,6 +154,7 @@ async def test_should_collect_trace_with_resources_but_no_js( re.compile(r"Click"), re.compile(r"Mouse move"), re.compile(r"Double click"), + re.compile(r"GET \"/empty\.html\""), re.compile(r'Insert "abc"'), re.compile(r"Wait for timeout"), re.compile(r'Navigate to "/empty\.html"'), diff --git a/tests/async/test_unroute_behavior.py b/tests/async/test_unroute_behavior.py index 036423cdc..ff737eaab 100644 --- a/tests/async/test_unroute_behavior.py +++ b/tests/async/test_unroute_behavior.py @@ -451,3 +451,59 @@ async def _goto_ignore_exceptions() -> None: await page.close() # Should not throw. await route.fulfill() + + +async def test_should_not_continue_requests_in_flight_page( + page: Page, server: Server +) -> None: + await page.goto(server.EMPTY_PAGE) + + route_future: "asyncio.Future[Route]" = asyncio.Future() + + async def handle(route: Route) -> None: + route_future.set_result(route) + await asyncio.sleep(3) + await route.fulfill(status=200) + + async def _evaluate_ignore_exceptions() -> None: + try: + await page.evaluate("() => fetch('/')") + except Error: + pass + + await page.route( + "**/*", + handle, + ) + + asyncio.create_task(_evaluate_ignore_exceptions()) + await route_future + await page.unroute_all(behavior="wait") + + +async def test_should_not_continue_requests_in_flight_context( + page: Page, context: BrowserContext, server: Server +) -> None: + await page.goto(server.EMPTY_PAGE) + + route_future: "asyncio.Future[Route]" = asyncio.Future() + + async def handle(route: Route) -> None: + route_future.set_result(route) + await asyncio.sleep(3) + await route.fulfill(status=200) + + async def _evaluate_ignore_exceptions() -> None: + try: + await page.evaluate("() => fetch('/')") + except Error: + pass + + await context.route( + "**/*", + handle, + ) + + asyncio.create_task(_evaluate_ignore_exceptions()) + await route_future + await context.unroute_all(behavior="wait") diff --git a/tests/sync/conftest.py b/tests/sync/conftest.py index 3d7ae9116..58193c0de 100644 --- a/tests/sync/conftest.py +++ b/tests/sync/conftest.py @@ -143,7 +143,7 @@ def action_titles(self) -> Locator: @property def stack_frames(self) -> Locator: - return self.page.get_by_test_id("stack-trace-list").locator(".list-view-entry") + return self.page.get_by_role("list", name="Stack trace").get_by_role("listitem") def select_action(self, title: str, ordinal: int = 0) -> None: self.page.locator(".action-title", has_text=title).nth(ordinal).click() diff --git a/tests/sync/test_fetch_global.py b/tests/sync/test_fetch_global.py index 7305834a9..bf3970c21 100644 --- a/tests/sync/test_fetch_global.py +++ b/tests/sync/test_fetch_global.py @@ -67,7 +67,7 @@ def test_should_support_global_timeout_option( ) -> None: request = playwright.request.new_context(timeout=100) server.set_route("/empty.html", lambda req: None) - with pytest.raises(Error, match="Request timed out after 100ms"): + with pytest.raises(Error, match="Timeout 100ms exceeded"): request.get(server.EMPTY_PAGE) diff --git a/tests/sync/test_page_add_locator_handler.py b/tests/sync/test_page_add_locator_handler.py index b069520ec..b2d037f07 100644 --- a/tests/sync/test_page_add_locator_handler.py +++ b/tests/sync/test_page_add_locator_handler.py @@ -310,7 +310,7 @@ def _handler() -> None: with pytest.raises(Error) as exc_info: page.locator("#target").click(timeout=3000) assert page.evaluate("window.clicked") == 0 - expect(page.locator("#interstitial")).to_be_visible() + assert page.locator("#interstitial").is_visible() assert called == 1 assert ( 'locator handler has finished, waiting for get_by_role("button", name="close") to be hidden' diff --git a/tests/sync/test_tracing.py b/tests/sync/test_tracing.py index 1a42aab9b..8d0eaa191 100644 --- a/tests/sync/test_tracing.py +++ b/tests/sync/test_tracing.py @@ -138,6 +138,7 @@ def test_should_collect_trace_with_resources_but_no_js( page.click('"Click"') page.mouse.move(20, 20) page.mouse.dblclick(30, 30) + page.request.get(server.EMPTY_PAGE) page.keyboard.insert_text("abc") page.wait_for_timeout(2000) # Give it some time to produce screenshots. page.route("**/empty.html", lambda route: route.continue_()) @@ -155,6 +156,7 @@ def test_should_collect_trace_with_resources_but_no_js( re.compile(r"Click"), re.compile(r"Mouse move"), re.compile(r"Double click"), + re.compile(r"GET \"/empty\.html\""), re.compile(r'Insert "abc"'), re.compile(r"Wait for timeout"), re.compile(r'Navigate to "/empty\.html"'), From bd5b0331e67ed22bc75417092e87a456dd73c832 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Wed, 16 Jul 2025 11:49:13 -0700 Subject: [PATCH 05/45] chore: roll to 1.54.1 (#2919) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c4a75870a..5c2911865 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ import zipfile from typing import Dict -driver_version = "1.54.0" +driver_version = "1.54.1" base_wheel_bundles = [ { From cc27d2512920d9bc7208722d3293815f3f2e6139 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 22 Jul 2025 14:42:42 +0200 Subject: [PATCH 06/45] test: update client certificate test certs (#2923) --- tests/assets/client-certificates/README.md | 57 +--------- .../client/localhost/localhost.csr | 27 +++++ .../client/localhost/localhost.ext | 1 + .../client/localhost/localhost.key | 52 +++++++++ .../client/localhost/localhost.pem | 30 ++++++ .../client/self-signed/cert.pem | 50 ++++----- .../client/self-signed/csr.pem | 46 ++++---- .../client/self-signed/key.pem | 100 +++++++++--------- .../client/trusted/cert-legacy.pfx | Bin 0 -> 4045 bytes .../client/trusted/cert.pem | 52 ++++----- .../client/trusted/cert.pfx | Bin 0 -> 4195 bytes .../client/trusted/csr.pem | 46 ++++---- .../client/trusted/key.pem | 100 +++++++++--------- tests/assets/client-certificates/generate.sh | 88 +++++++++++++++ .../server/server_cert.pem | 54 +++++----- .../client-certificates/server/server_key.pem | 100 +++++++++--------- 16 files changed, 474 insertions(+), 329 deletions(-) create mode 100644 tests/assets/client-certificates/client/localhost/localhost.csr create mode 100644 tests/assets/client-certificates/client/localhost/localhost.ext create mode 100644 tests/assets/client-certificates/client/localhost/localhost.key create mode 100644 tests/assets/client-certificates/client/localhost/localhost.pem create mode 100644 tests/assets/client-certificates/client/trusted/cert-legacy.pfx create mode 100644 tests/assets/client-certificates/client/trusted/cert.pfx create mode 100755 tests/assets/client-certificates/generate.sh diff --git a/tests/assets/client-certificates/README.md b/tests/assets/client-certificates/README.md index b0ee78e70..2c73bd92f 100644 --- a/tests/assets/client-certificates/README.md +++ b/tests/assets/client-certificates/README.md @@ -1,60 +1,7 @@ # Client Certificate test-certificates -## Server +Regenerate all certificates by running: -```bash -openssl req \ - -x509 \ - -newkey rsa:4096 \ - -keyout server/server_key.pem \ - -out server/server_cert.pem \ - -nodes \ - -days 365 \ - -subj "/CN=localhost/O=Client\ Certificate\ Demo" \ - -addext "subjectAltName=DNS:localhost,DNS:local.playwright" ``` - -## Trusted client-certificate (server signed/valid) - -``` -mkdir -p client/trusted -# generate server-signed (valid) certifcate -openssl req \ - -newkey rsa:4096 \ - -keyout client/trusted/key.pem \ - -out client/trusted/csr.pem \ - -nodes \ - -days 365 \ - -subj "/CN=Alice" - -# sign with server_cert.pem -openssl x509 \ - -req \ - -in client/trusted/csr.pem \ - -CA server/server_cert.pem \ - -CAkey server/server_key.pem \ - -out client/trusted/cert.pem \ - -set_serial 01 \ - -days 365 -``` - -## Self-signed certificate (invalid) - -``` -mkdir -p client/self-signed -openssl req \ - -newkey rsa:4096 \ - -keyout client/self-signed/key.pem \ - -out client/self-signed/csr.pem \ - -nodes \ - -days 365 \ - -subj "/CN=Bob" - -# sign with self-signed/key.pem -openssl x509 \ - -req \ - -in client/self-signed/csr.pem \ - -signkey client/self-signed/key.pem \ - -out client/self-signed/cert.pem \ - -days 365 +bash generate.sh ``` diff --git a/tests/assets/client-certificates/client/localhost/localhost.csr b/tests/assets/client-certificates/client/localhost/localhost.csr new file mode 100644 index 000000000..cbf60ba43 --- /dev/null +++ b/tests/assets/client-certificates/client/localhost/localhost.csr @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEizCCAnMCAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAuxTIfOTWJzsLSYT/RXu6eUYe95oFA0gMM6pdQAws +U08ufU8S68UzBIAb6zdwDqM6LD5FqLrwaTsUW73dKefb003EgA19L3sZTHSwnSv9 +g9+rnKw/8EA+1thFo7h3EfdWkO/nuxnPfEnHu5oxMk/mCu+hdjfgh01mxc00PIO/ +wioxZtuhzKNIUu+qQAj70Dwbkzy3Zb1QAnj+02AUYnrVTYhusmP4pjrfYgAWU1x4 +WN+XTSI27LnQHE8t8OxuICFwqEEPcJg4pCFNQgmO0gcRDbd4U7JgQ0xMXZ9na9k0 +f1GOgtjKsz5IIDrAwLnrIOEXiIYlNaQ/v28BrlWytaOHGFKmDxAbGXHz1WuftmR1 +vYQivC5fkXU3+QKWoGPZaLIhA7/ZOMSVgoo+Rv+mS6Mze/6LphatUMcI1oYX4ZBY +FrsVsAeAO/FPZDcwVct0bdPCRRFBQpgl6DxMyItHMexNHwURi4YHwhYiBBe+Njc1 +tTh6dniA11MUVC5WmKME7CxgXxHVXE4MYcf/w1x4sOnxHneb1bCa8jOPELRc6byR +8mk23fbBP42EPkrgpCtLhbHgskLKobLFav43xaIiYFn744U7rbINI+5RfJ8BH5Kw +CB5RlAsymPxwDNcPXyyyeHEEUAQmtnJPrGPSGMN/QODgCWRXEvY556NGx0D5riu7 +MXkCAwEAAaAyMDAGCSqGSIb3DQEJDjEjMCEwHwYDVR0RBBgwFoIJbG9jYWxob3N0 +ggkxMjcuMC4wLjEwDQYJKoZIhvcNAQELBQADggIBAEYqpx28FFRHqXfyumTkkFb/ +hrlUD9qKUYSdjpsPLFRUYiLofNlYhCtEFEuHyLkNHqLO5Z6E7yOpm/HvdXY0eBQy +tK0verggKXy8MnpLVtsFOf4DgenTwFRG3NbNk2/KynwiAwJ0iwOdDfAmwrzYFLeR +uXysPE0lGJu277akuo/PDg3QEY44iDJgI4rqtY25sR8J23M9Roa5qN4bdaXl6l10 +UJhdugTG90gRFmmUVLLRa21A3j4m7pvWHu7zYJN7ylS/NQ6ZEnpNno7Ie6MLzrm6 +h8MOSoxtmrXi0e3aYf7PC3u3YLAjyUzHicgMdXk8SjtUXi6l3U5hU+6WBeOKKmwa +vi+FUpRfnutkWpinNqikmMw0Sqy+bpXwbwgm2oD6driiMneBCT37gW/IyUPWVxzg +dVgeQIt3i8SMwnGUrzfVo/gqjq4qFBpzS3h9jPPrMYQN2LQdmyo13R9UH26cLGE1 +cgVmqUKa11kX8353wfA36JuW5yGv2yaK4V1kwFISznMvk5AGdOHONdZOrN4sLIIY +7Npqp5AO1KdoYcNVptCA4n6mN/30fTmA/W1ajsKcfIn5RlmvdgpooKoEgaU/X9RL +d2ydIpijWbQqm+nF9skpO4jIFPfuQj6mMXCPWtsvbpHCoo0iMHHWfVFcpvsXGYN2 +6ji5z3opVapp1m5uTmcA +-----END CERTIFICATE REQUEST----- diff --git a/tests/assets/client-certificates/client/localhost/localhost.ext b/tests/assets/client-certificates/client/localhost/localhost.ext new file mode 100644 index 000000000..d4d022726 --- /dev/null +++ b/tests/assets/client-certificates/client/localhost/localhost.ext @@ -0,0 +1 @@ +subjectAltName=DNS:localhost,DNS:127.0.0.1 diff --git a/tests/assets/client-certificates/client/localhost/localhost.key b/tests/assets/client-certificates/client/localhost/localhost.key new file mode 100644 index 000000000..7f6adba53 --- /dev/null +++ b/tests/assets/client-certificates/client/localhost/localhost.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +DjCwHSmcSUmAmx7IXfoyRQuKLeRw +-----END PRIVATE KEY----- diff --git a/tests/assets/client-certificates/client/localhost/localhost.pem b/tests/assets/client-certificates/client/localhost/localhost.pem new file mode 100644 index 000000000..5c1a67505 --- /dev/null +++ b/tests/assets/client-certificates/client/localhost/localhost.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFKDCCAxCgAwIBAgIBATANBgkqhkiG9w0BAQsFADA2MRIwEAYDVQQDDAlsb2Nh +bGhvc3QxIDAeBgNVBAoMF0NsaWVudCBDZXJ0aWZpY2F0ZSBEZW1vMB4XDTI1MDcy +MTE1NTYxOVoXDTM1MDcxOTE1NTYxOVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuxTIfOTWJzsLSYT/RXu6eUYe +95oFA0gMM6pdQAwsU08ufU8S68UzBIAb6zdwDqM6LD5FqLrwaTsUW73dKefb003E +gA19L3sZTHSwnSv9g9+rnKw/8EA+1thFo7h3EfdWkO/nuxnPfEnHu5oxMk/mCu+h +djfgh01mxc00PIO/wioxZtuhzKNIUu+qQAj70Dwbkzy3Zb1QAnj+02AUYnrVTYhu +smP4pjrfYgAWU1x4WN+XTSI27LnQHE8t8OxuICFwqEEPcJg4pCFNQgmO0gcRDbd4 +U7JgQ0xMXZ9na9k0f1GOgtjKsz5IIDrAwLnrIOEXiIYlNaQ/v28BrlWytaOHGFKm +DxAbGXHz1WuftmR1vYQivC5fkXU3+QKWoGPZaLIhA7/ZOMSVgoo+Rv+mS6Mze/6L +phatUMcI1oYX4ZBYFrsVsAeAO/FPZDcwVct0bdPCRRFBQpgl6DxMyItHMexNHwUR +i4YHwhYiBBe+Njc1tTh6dniA11MUVC5WmKME7CxgXxHVXE4MYcf/w1x4sOnxHneb +1bCa8jOPELRc6byR8mk23fbBP42EPkrgpCtLhbHgskLKobLFav43xaIiYFn744U7 +rbINI+5RfJ8BH5KwCB5RlAsymPxwDNcPXyyyeHEEUAQmtnJPrGPSGMN/QODgCWRX +EvY556NGx0D5riu7MXkCAwEAAaNjMGEwHwYDVR0RBBgwFoIJbG9jYWxob3N0ggkx +MjcuMC4wLjEwHQYDVR0OBBYEFIE6kbIR0PlzaJuZg52JqXuFFQHBMB8GA1UdIwQY +MBaAFPHaEfjJoIftSkHTb8mwme27LtifMA0GCSqGSIb3DQEBCwUAA4ICAQB7E57H +19qUD4DjLxJGAVpDphD8mZg9aR7bUd6laXZ12VQnn3+OrF+6JjJ2TIr8ssRkkzc6 +rMhRqob+DxeB94JqlFQDmwP37wJXnuTtuGj71NHQal15b5ZB28fFdwWgUlYECWyg +sYeMK5HMnNDziniRnjPoKU6f8urmUW2G8N2SOufnRar/StY/8u24IFh/vDTVJKWB +a22pVk/PIx6JcErfMx0OJdoIUZd/C9DT/a7t+Pt+t3EPVNM2fcjs9aMcg6tlU6Tp +YwHMzkFQYmcrp2QEhwy23z2E+wnLbeHTULDsHVOLudUGDcSgkW48bPNTOtZTysQ+ +P4mJf+Ju2HC/EwuVrOa14uuay9c+hC1Q1gsGbLZB8NE6jOX5QYN2a7iiUEdCdufo +wWIXEC+2dJiEji3ehwJp0q4jK15X0IOIfxLH4PholORH659DdCfpm7YAjoaPE9nr +28MMd9Lj6zWmfyeb5p0IUDpkreL2huxjXHJkmSm4dcxDxLqsP6KSPlhrPvjAVUqw +5M/jUarDAHaPLNlwqcqWODb891FIyRmc6YHtSRGujMyfJ8LRS2f9rHeyM3m2jMHf +8EahOgyQ8nvSs/a42VS97IHkGl6qufK7ZUq/VMhQRb+KbaIDN5TLtNwEQ1I40Bm9 +UOoSX/uP1PstOZvf9gnYto8sf75B+PxO692uYw== +-----END CERTIFICATE----- diff --git a/tests/assets/client-certificates/client/self-signed/cert.pem b/tests/assets/client-certificates/client/self-signed/cert.pem index 3c0771794..d14ac95b2 100644 --- a/tests/assets/client-certificates/client/self-signed/cert.pem +++ b/tests/assets/client-certificates/client/self-signed/cert.pem @@ -1,28 +1,28 @@ -----BEGIN CERTIFICATE----- -MIIEyzCCArOgAwIBAgIUYps4gh4MqFYg8zqQhHYL7zYfbLkwDQYJKoZIhvcNAQEL -BQAwDjEMMAoGA1UEAwwDQm9iMB4XDTI0MDcxOTEyNDc0MFoXDTI1MDcxOTEyNDc0 +MIIEyzCCArOgAwIBAgIUUo60oaPj20QM6oeGSn+2CT5j7GYwDQYJKoZIhvcNAQEL +BQAwDjEMMAoGA1UEAwwDQm9iMB4XDTI1MDcyMTE1NTYyMFoXDTM1MDcxOTE1NTYy MFowDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC -AgEA179eTsqcc1c3AOQHzCZEyYLPta2CCAscUFqcEZ9vWvjW0uzOv9TDlB33Unov -jch4CElZOBhzTadVsbmnYKpxwyVU89WCuQKvedz4k1vu7S1YryfNbmS8PWbnQ4ds -9NB7SgJNHZILvx9DXuWeFEyzRIo1984z4HheBzrkf791LqpYKaKziANUo8h8t0dm -TX/boOz8cEnQNwtTC0ZX3aD0obG/UAhr/22ZGPo/E659fh4ptyYX2LrIUHGy+Eux -nJ9Y4cTqa88Ee6K6AkDiT/AoNQNxE4X++jqLuie8j/ZYpI1Oll38GwKVOyy1msRL -toGmISNwkMIQDGABrJlxgpP4QQAQ+08v9srzXOlkdxdr7OCP81r+ccBXiSQEe7BA -kdJ8l98l5dprJ++GJ+SZcV4+/iGR0dKU2IdAG5HiKZIFn6ch9Ux+UMqeGaYCpkHr -TiietHwcXgtVBlE0jFmB/HspmI/O0abK+grMmueaH7XtTI8YHnw0mUpL8+yp7mfA -7zFusgFgyiBPXeD/NQgg8vja67k++d1VGoXm2xr+5WPQCSbgQoMkkOBMLHWJTefd -6F4Z5M+oI0VwYbf6eQW246wJgpCHSPR0Vdijd6MAGRWKUuLfDsA9+12iGbKvwJ2e -nJlStft2V2LZcjBfdIMbigW1aSVNN5w6m6YVrQPry3WPkWcCAwEAAaMhMB8wHQYD -VR0OBBYEFPxKWTFQJSg4HD2qjxL0dnXX/z4qMA0GCSqGSIb3DQEBCwUAA4ICAQBz -4H1d5eGRU9bekUvi7LbZ5CP/I6w6PL/9AlXqO3BZKxplK7fYGHd3uqyDorJEsvjV -hxwvFlEnS0JIU3nRzhJU/h4Yaivf1WLRFwGZ4TPBjX9KFU27exFWD3rppazkWybJ -i4WuEdP3TJMdKLcNTtXWUDroDOgPlS66u6oZ+mUyUROil+B+fgQgVDhjRc5fvRgZ -Lng8wuejCo3ExQyxkwn2G5guyIimgHmOQghPtLO5xlc67Z4GPUZ1m4tC+BCiFO4D -YIXl3QiIpmU7Pss39LLKMGXXAgLRqyMzqE52lsznu18v5vDLfTaRH4u/wjzULhXz -SrV1IUJmhgEXta4EeDmPH0itgKtkbwjgCOD7drrFrJq/EnvIaJ5cpxiI1pFmYD8g -VVD7/KT/CyT1Uz1dI8QaP/JX8XEgtMJaSkPfjPErIViN9rh9ECCNLgFyv7Y0Plar -A6YlvdyV1Rta/BHndf5Hqz9QWNhbFCMQRGVQNEcoKwpFyjAE9SXoKJvFIK/w5WXu -qKzIYA26QXE3p734Xu1n8QiFJIyltVHbyUlD0k06194t5a2WK+/eDeReIsk0QOI8 -FGqhyPZ7YjR5tSZTmgljtViqBO5AA23QOVFqtjOUrjXP5pTbPJel99Z/FTkqSwvB -Rt4OX7HfuokWQDTT0TMn5jVtJyi54cH7f9MmsNJ23g== +AgEAzQMXYOZz3ILrbF9qpDng2pw2wJf1UFopehwaYyu7riWJ9+ADrhSnCSFBM3Sc +MBc/8dIR6etWwci8QwJ/MtvIU0yx4llq+53G+19Bc1teC6q/b4QCRDIcTGxOZoR+ +jfYZVjPODEyJ5y5MZIo34ZP4bu+JnpT4W7+uojm3jOoyNPqXMcc70uAhfSKG+Jfr +wZKteA4T5VKFytVcWgh4v03z8zTYeW3kD4lCsongBz6yu2dn3D6XMROnxiPwi+IR +QqX1pwnJ0UA3CTeOHEw1jA3koxWIIg44PWaPaCj9Udrhf4ew00XLPWVZP8T5rVf8 +yUfecWQCR7FueJFqoLhPMMFi17rYmGZUvw3/YkXBjay4Q9e+G2WS3Xk8u+I1sCuV +BJNBRv9DqtMC9D/N9NI8GkLrXwZmk82SXG+cQ0TSkNUHYI/03YKoqsn5H8PsG7Tc ++Y2Ca6TaCWims7lvOg7U0E6lu2h5NGcdWHFPJ9qfe+xho/yfYYwGqEKanGAu1kd5 +SbIaX6/YiM5/Pp/96MeRNrB9kLzDnZTNuGtdCawVFgbkWmfX2Z6/a0d6SvZGDzBx +xTVZRB0my01E1FP53MS8YRH98HUjGEwNRVq2e1W+aXldKppgZ85GZD3l2YTeuI0i +PJCDUzQiaWzFtWc8s13YQ1HLCOyOXF7QqMyNCiLGb4xQ56kCAwEAAaMhMB8wHQYD +VR0OBBYEFMqdCDxkZm8HxNI4MLLveAVTdH7UMA0GCSqGSIb3DQEBCwUAA4ICAQAB +MKd3WEJ8zI51FuyeTcMq6L1zk2vmKTFg6T7HZJhNZoD1AYvvsKJ8mrqeSwhxqjlE +0H2FGLY+Z8Fw3+TE1QuvTuz3gwRI+yzBEyqi/fEGCGrhOVcWaXGgLCtWG+BA4Su8 +HenK97/n3OnXUnBozRPZMH02IaLrOiEGbpaabXKCCabpm5U1oGq437e3SeAeIL7U +WxuHhHBx0yo9j7ACaCL6mz8xpk8NaRZpPy22MTlrPKwbOK2eYf3Jy5fHa/f6edTs +KqZewI7t4oe/OqKdjyTgGYkjTE3Xcmo2T/fmcAeEP3HJX267kCzBi5J3McwonWxD +N8zz9qKSf5YGQy140eEOTjjESwlPz6zfrTW92YdCIr63k9UCDL2HGQTRSxB6g4BQ +loVzKS9/BKhulGqvSGvEoj6D+qG/PgFlBtJoE71X+vSIxvdbnOVmOi/l+NGNuP1Z +nwnDtZWp4BKshhSKvqeOI+EyNMQ4FL20S4w8T+LR873jKrbd2MEuAsJiygWh+/ZJ +haHTEhFxvH/a4i8gb/SGZlFB6oyPJ+XM5kZo4fcp7PnzxhYrIaEpPq+AR/3657hm +AajXpS5lTCkNJc85QeHHj/0geDsOvfK4XUj2lgaJ0gXpgsoxnSCSq6Xox6ZqAVON +0ra1KkGBTQH+5DxJ2Gp1UBaucrLYZTfXuJ8fPYeeNQ== -----END CERTIFICATE----- diff --git a/tests/assets/client-certificates/client/self-signed/csr.pem b/tests/assets/client-certificates/client/self-signed/csr.pem index 4c99e1349..283dd45a6 100644 --- a/tests/assets/client-certificates/client/self-signed/csr.pem +++ b/tests/assets/client-certificates/client/self-signed/csr.pem @@ -1,26 +1,26 @@ -----BEGIN CERTIFICATE REQUEST----- MIIEUzCCAjsCAQAwDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOC -Ag8AMIICCgKCAgEA179eTsqcc1c3AOQHzCZEyYLPta2CCAscUFqcEZ9vWvjW0uzO -v9TDlB33Unovjch4CElZOBhzTadVsbmnYKpxwyVU89WCuQKvedz4k1vu7S1YryfN -bmS8PWbnQ4ds9NB7SgJNHZILvx9DXuWeFEyzRIo1984z4HheBzrkf791LqpYKaKz -iANUo8h8t0dmTX/boOz8cEnQNwtTC0ZX3aD0obG/UAhr/22ZGPo/E659fh4ptyYX -2LrIUHGy+EuxnJ9Y4cTqa88Ee6K6AkDiT/AoNQNxE4X++jqLuie8j/ZYpI1Oll38 -GwKVOyy1msRLtoGmISNwkMIQDGABrJlxgpP4QQAQ+08v9srzXOlkdxdr7OCP81r+ -ccBXiSQEe7BAkdJ8l98l5dprJ++GJ+SZcV4+/iGR0dKU2IdAG5HiKZIFn6ch9Ux+ -UMqeGaYCpkHrTiietHwcXgtVBlE0jFmB/HspmI/O0abK+grMmueaH7XtTI8YHnw0 -mUpL8+yp7mfA7zFusgFgyiBPXeD/NQgg8vja67k++d1VGoXm2xr+5WPQCSbgQoMk -kOBMLHWJTefd6F4Z5M+oI0VwYbf6eQW246wJgpCHSPR0Vdijd6MAGRWKUuLfDsA9 -+12iGbKvwJ2enJlStft2V2LZcjBfdIMbigW1aSVNN5w6m6YVrQPry3WPkWcCAwEA -AaAAMA0GCSqGSIb3DQEBCwUAA4ICAQCb07d2IjUy1PeHCj/2k/z9FrZSo6K3c8y6 -b/u/MZ0AXPKLPDSo7UYpOJ8Z2cBiJ8jQapjTSEL8POUYqcvCmP55R6u68KmvINHo -+Ly7pP+xPrbA4Q0WmPnz37hQn+I1he0GuEQyjZZqUln9zwp67TsWNKxKtCH+1j8M -Ltzx6kuHCdPtDUtv291yhVRqvbjiDs+gzdQYNJtAkUbHwHFxu8oZhg8QZGyXYMN8 -TGoQ1LTezFZXJtX69K7WnrDGrjsgB6EMvwkqAFSYNH0LFvI0xo13OOgXr9mrwohA -76uZtjXL9B15EqrMce6mdUZi46QJuQ2avTi57Lz+fqvsBYdQO89VcFSmqu2nfspN -QZDrooyjHrlls8MpoBd8fde9oT4uA4/d9SJtuHUnjgGN7Qr7eTruWXL8wVMwFnvL -igWE4detO9y2gpRLq6uEqzWYMGtN9PXJCGU8C8m9E2EBUKMrT/bpNbboatLcgRrW -acj0BRVqoVzk1sRq7Sa6ejywqgARvIhTehg6DqdMdcENCPQ7rxDRu5PSDM8/mwIj -0KYl8d2PlECB4ofRyLcy17BZzjP6hSnkGzcFk0/bChZOSIRnwvKbvfXnB45hhPk8 -XwT/6UNSwC2STP3gtOmLqrWj+OE0gy0AkDMvP3UnQVGMUvgfYg+N4ROCVtlqzxe9 -W65c05Mm1g== +Ag8AMIICCgKCAgEAzQMXYOZz3ILrbF9qpDng2pw2wJf1UFopehwaYyu7riWJ9+AD +rhSnCSFBM3ScMBc/8dIR6etWwci8QwJ/MtvIU0yx4llq+53G+19Bc1teC6q/b4QC +RDIcTGxOZoR+jfYZVjPODEyJ5y5MZIo34ZP4bu+JnpT4W7+uojm3jOoyNPqXMcc7 +0uAhfSKG+JfrwZKteA4T5VKFytVcWgh4v03z8zTYeW3kD4lCsongBz6yu2dn3D6X +MROnxiPwi+IRQqX1pwnJ0UA3CTeOHEw1jA3koxWIIg44PWaPaCj9Udrhf4ew00XL +PWVZP8T5rVf8yUfecWQCR7FueJFqoLhPMMFi17rYmGZUvw3/YkXBjay4Q9e+G2WS +3Xk8u+I1sCuVBJNBRv9DqtMC9D/N9NI8GkLrXwZmk82SXG+cQ0TSkNUHYI/03YKo +qsn5H8PsG7Tc+Y2Ca6TaCWims7lvOg7U0E6lu2h5NGcdWHFPJ9qfe+xho/yfYYwG +qEKanGAu1kd5SbIaX6/YiM5/Pp/96MeRNrB9kLzDnZTNuGtdCawVFgbkWmfX2Z6/ +a0d6SvZGDzBxxTVZRB0my01E1FP53MS8YRH98HUjGEwNRVq2e1W+aXldKppgZ85G +ZD3l2YTeuI0iPJCDUzQiaWzFtWc8s13YQ1HLCOyOXF7QqMyNCiLGb4xQ56kCAwEA +AaAAMA0GCSqGSIb3DQEBCwUAA4ICAQAn/ZI7IkBUEfhZHefwtF+QHCyxSEKvqwHq +fSqKVdarBPz8Ik8m3icj8R/DcS3y5jgzx3x8bXQoDpgsAQgeb825NRv2wAQAGoH1 +8vh204lTyjqzrgtK7eQeQDc7fjeigIkxQsAK9zk4BaFUWp0wEC0RLVAgvlQTl7vu +n1jSSrhK8tvGy62/cIxZfwD0bAMHlW4m1A4fUuSGWQX2KldgA8tnmT6wx0If/nKb +VB68AMbyMHUeb32v9wEvx2nHlwMjqNFeg7vYyJXOfBdDILUl+OTBoQY1X+jSx5iM +txTzmA8Hcgx0Fq+BnbQuZCLqFpNWEfenAtQtaAFuJwMiKCf6kgbqkDVShJkmt+vC +j3dcsVMZDsdMk4qRpiJhaTQOYmsMGCj4uoDpFGjwPoUwlDkjYgHAAsm9uCkshc+m +WZO7I6Z3Tbi3XskJvAMc3dTWjtc6nApEtr/mn8LcETfOp7RRSfjllj6ijWUrVwUy +BpzU9C/zLTkhFX0DVDCIV+jEefF8JPfzSKLgXyRbInTz1/6/sKXtswXW0NjzqLMI +C9ggMBhOiDv9KJn3G/mY4CqIfo9KMzF+++4t+wdXTir8DWNlMUAn1vlBwxZAgKCM +GonVExBU0VIGCpyTRLkesEHnPMgybP6gLzP3++54x288OS5JwuPPtkDcsBHUjTq8 +HxTJvUul/Q== -----END CERTIFICATE REQUEST----- diff --git a/tests/assets/client-certificates/client/self-signed/key.pem b/tests/assets/client-certificates/client/self-signed/key.pem index 70d5e3dd0..c3ddd1a91 100644 --- a/tests/assets/client-certificates/client/self-signed/key.pem +++ b/tests/assets/client-certificates/client/self-signed/key.pem @@ -1,52 +1,52 @@ -----BEGIN PRIVATE KEY----- -MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDXv15OypxzVzcA -5AfMJkTJgs+1rYIICxxQWpwRn29a+NbS7M6/1MOUHfdSei+NyHgISVk4GHNNp1Wx -uadgqnHDJVTz1YK5Aq953PiTW+7tLVivJ81uZLw9ZudDh2z00HtKAk0dkgu/H0Ne -5Z4UTLNEijX3zjPgeF4HOuR/v3UuqlgporOIA1SjyHy3R2ZNf9ug7PxwSdA3C1ML -RlfdoPShsb9QCGv/bZkY+j8Trn1+Him3JhfYushQcbL4S7Gcn1jhxOprzwR7oroC -QOJP8Cg1A3EThf76Oou6J7yP9likjU6WXfwbApU7LLWaxEu2gaYhI3CQwhAMYAGs -mXGCk/hBABD7Ty/2yvNc6WR3F2vs4I/zWv5xwFeJJAR7sECR0nyX3yXl2msn74Yn -5JlxXj7+IZHR0pTYh0AbkeIpkgWfpyH1TH5Qyp4ZpgKmQetOKJ60fBxeC1UGUTSM -WYH8eymYj87Rpsr6Csya55ofte1MjxgefDSZSkvz7KnuZ8DvMW6yAWDKIE9d4P81 -CCDy+NrruT753VUahebbGv7lY9AJJuBCgySQ4EwsdYlN593oXhnkz6gjRXBht/p5 -BbbjrAmCkIdI9HRV2KN3owAZFYpS4t8OwD37XaIZsq/AnZ6cmVK1+3ZXYtlyMF90 -gxuKBbVpJU03nDqbphWtA+vLdY+RZwIDAQABAoICAETxu6J0LuDQ+xvGwxMjG5JF -wjitlMMbQdYPzpX3HC+3G3dWA4/b3xAjL1jlAPNPH8SOI/vAHICxO7pKuMk0Tpxs -/qPZFCgpSogn7CuzEjwq5I88qfJgMKNyke7LhS8KvItfBuOvOx+9Ttsxh323MQZz -IGHrPDq8XFf1IvYL6deaygesHbEWV2Lre6daIsAbXsUjVlxPykD81nHg7c0+VU6i -rZ9WwaRjkqwftC6G8UVvQCdt/erdbYv/eZDNJ5oEdfPX6I3BHw6fZs+3ilq/RSoD -yovRozS1ptc7QY/DynnzSizVJe4/ug6p7/LgTc2pyrwGRj+MNHKv73kHo/V1cbxF -fBJCpxlfcGcEP27BkENiTKyRQEF1bjStw+UUKygrRXLm3MDtAVX8TrDERta4LAeW -XvPiJbSOwWk2yYCs62RyKl+T1no7alIvc6SUy8rvKKm+AihjaTsxTeACC1cBc41m -5HMz1dqdUWcB5jbnPsV+27dNK1/zIC+e0OXtoSXvS+IbQXo/awHJyXv5ClgldbB9 -hESFTYz/uI6ftuTM6coHQfASLgmnq0fOd1gyqO6Jr9ZSvxcPNheGpyzN3I3o5i2j -LTYJdX3AoI5rQ5d7/GS2qIwWf0q8rxQnq1/34ABWD0umSa9tenCXkl7FIB4drwPB -4n7n+SL7rhmv0vFKIjepAoIBAQD19MuggpKRHicmNH2EzPOyahttuhnB7Le7j6FC -afuYUBFNcxww+L34GMRhmQZrGIYmuQ3QV4RjYh2bowEEX+F5R1V90iBtYQL1P73a -jYtTfaJn0t62EBSC//w2rtaRJPgGhbXbnyid64J0ujRFCelej8FRJdBV342ctRAL -0RazxQ/KcTRl9pncALxGhnSsBElZlDtZd/dWnWBDZ/fg/C97VV9ZQLcpyGvL516i -GpB8BQsHiIe9Jt5flZvcKB7z/KItGzPB4WK6dpV8t/FeQiUpZXkQlqO03XaZT4NP -AEGH3rKIRMpP7TORYFhbYrZwov3kzLaggax2wGPTkfMFNlTjAoIBAQDgjsYfShkz -6Dl1UTYBrDMy9pakJbC6qmd0KOKX+4XH/Dc1mOzR8NGgoY7xWXFUlozgntKKnJda -M6GfOt/dxc0Sq7moYzA7Jv4+9hNdU3jX5YrqAbcaSFj6k4yauO2BKCBahQo8qseY -a3N5f0gp+5ftTMvOTwGw3JRJFJq0/DvKWAYLIaJ0Oo77zGs0vxa1Aqob10MloXt5 -DMwjazWujntTzTJY1vsfsBHa8OEObMwiftqnmn6L4Qprd3AzQkaNlZEsvERyLfFq -1pu4EsDJJGdVfpZYfo+6vTglLXFBLEUQmh4/018Mw4O4pGgCVMj/wict/gTViQGC -qSj+IOThsTytAoIBAHu3L3nEU/8EwMJ54q0a/nW+458U3gHqlRyWCZJDhxc9Jwbj -IMoNRFj39Ef3VgAmrMvrh2RFsUTgRG5V1pwhsmNzmzAXstHx2zALaO73BZ7wcfFx -Yy8G9ZpTMsU6upj1lICLX0diTmbo4IzgYIxdiPJUsvOjZqDbOvsZJEIdYSL5u5Cj -0qx7FzdPc2SyGxuvaEnTwuqk6le5/4LIWCnmD+gksDpP0BIHSxmcfsBhRk3rp3mZ -llVxqKdBtM1PrQojCFxR833RZfzOyzCZwaIc+V5SOUw7yYqfXxmMokrpoQy72ueq -Wm1LrgWxBaCqDYSop7cftbkUoPB2o3/3SNtVUesCggEAReqOKy3R/QRf53QaoZiw -9DwsmP0XMndd8J/ONU3d0G9p7SkpCxC05BOJQwH7NEAPqtwoZ3nr8ezDdKVLEGzG -tfp7ur7vRGuWm5nYW6Viqa3Re5x/GxLNiW8pRv8vC5inwidMEamGraE++eQ0XsXz -/rF7f0fAGgYDsWFV7eXe49hWQV7+iru0yxdRhcG9WyxyNGrogC3wGLdwU9LMiwXX -xjbMZzbAR5R1arq3B9u+Dzt57tc+cWTm7qDocT1AZFLeOZSApyBA22foYf6MwdOw -zMC2JOV68MR7V6/3ZDhZZJrnsi2omXvCZlnh/F/TmTYlJr/BV47pxnnOxpkNSmv5 -nQKCAQBRqrsUVO7NOgR1sVX7YDaekQiJKS6Vq/7y2gR4FoLm/MMzNZQgGo9afmKg -F2hSv6tuoqc33Wm0FnoSEMaI8ky0qgA5kwXvhfQ6pDf/2zASFBwjwhTyJziDlhum -iwWe1F7lNaVNpxAXzJBaBTWvHznuM42cGv5bbPBSRuIRniGsyn/zYMrISWgL+h/Q -fsQ2rfPSqollPw+IUPN0mX+1zg6PFxaR4HM9UrRX7cnRKG20GIDPodsUl8IMg+SO -M5YG/UqDD10hfeEutvQIvl0oJraBWT34cqUZLVpUwJzf1be7zl9MzHGcym/ni7lX -dg6m3MAyZ1IXjHlogOdmGvnq07/w +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 +v3/WVVAXEcIowL3T4jzJ0RLdf1qE8Bc= -----END PRIVATE KEY----- diff --git a/tests/assets/client-certificates/client/trusted/cert-legacy.pfx b/tests/assets/client-certificates/client/trusted/cert-legacy.pfx new file mode 100644 index 0000000000000000000000000000000000000000..9f06aa35c82f05be583a0cf8de295a68fdb9ccbf GIT binary patch literal 4045 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 gS(8*}6{v#btuKpsyio37vPl1#YXpev^1o5<{9 literal 0 HcmV?d00001 diff --git a/tests/assets/client-certificates/client/trusted/csr.pem b/tests/assets/client-certificates/client/trusted/csr.pem index 8ead6da3d..fc930ea7c 100644 --- a/tests/assets/client-certificates/client/trusted/csr.pem +++ b/tests/assets/client-certificates/client/trusted/csr.pem @@ -1,26 +1,26 @@ -----BEGIN CERTIFICATE REQUEST----- MIIEVTCCAj0CAQAwEDEOMAwGA1UEAwwFQWxpY2UwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQCac3+4rNmH4/N1s4HqR2X168tgS/aA6sHW5at8mWRnq54N -m11RvnK55jHQYVAdBgJy5M07w0wakp8inxzlY95wqxBimYG63Un/1p7mX9FkB4LN -ISCc6j/s/Ufv85MXPbn0S5rm9UcQO9cINJb1RP1YgDDLN5cxMz6X4nyofN8H6Lhv -h4JDdBw4DfDEFERkVfF+bkZ7YW4XHEChgzm3RxCF0eeGzIXGrkkK9AsSdJAhOvTl -HPFCQKXTYZhsL5+3Ma4RnWnDWvLTHx6KzoU+twTM2mYhhQuQgQpnmDHxGge8kGeH -GtfdgAjtVJTE57xF/shP0JU+tuIV8NNhQ/vEmhL0Wa093/EvpTVp0EUEuDh9ORRH -5K5M4bKJyU4XX5noiht6yOn00uaoJcWduUAWsU+cDSvDTMw81opWWm0QIAV3G2yu -RSkumHAKqvQLeyeyiKz+OEhyEiZ7EZNExPD0TSpApSTU6aCTUAvPYGQ59VjsMHTu -J9r4wKIYaDvfL+t72vg2vTQma5cTOBJfIdxH9blFTjEnToH3LX8t0XndQ2RkiRnI -ze2p2jUShxo/lWCjCw+2Iaw0A0fNUK1BbOrFRPq1u7AnEuMJt7HF50MloItM97R9 -vofDwgDIzlX/PzlVRcn1WCo8Fr/0EXxPPreX0YDIp1ANQ8fSv7bKb2vQIxWuCQID -AQABoAAwDQYJKoZIhvcNAQELBQADggIBAGgf3EC8WL3RGmuGA+d/4wd1jNfrfU6n -xjnDwdEEX0TQZGGPjh5xvoCK76yZPkO6+z0IYSepEmWBS27HJKl7nuoOvS7MjQyJ -C+3Bdk3ToCeQjmNBlRBKsUw5ftTU902oMl5BptHGj1KGjYBLAkPdXb44wXSVKJ8q -ihFhWlovsva6GDoUorksU3vOwijdlGzTANQHJGFncgrRud9ATavpGS3KVxR73R3A -aBbu3Qw+QIfu8Qx5eBJp8CbMrpAmjfuq17STvqr5bC10Fnn4NegrnHOQG9JcK02+ -5Bn3+9X/n1mue7aohIdErLEiDMSqMOwFfrJeaH6YM1G4QkWyqGugtmHsWOUf0nlU -nkH1krvfw9rb6b+03c4A6GSeHnbX5ufFDSf5gaR6Wy7c0jBnoxVbtBLH2zXlrd0k -iRQG7C6XZzGMS7hb7GL7+bkRy9kWjmDL7z7Fp+EgzKhNmzuWII3E9X9va33HoQ/Q -UdK3JVToxRQg6XRKOxL9+U/+8i6U8lxObLWkWh2cypZqbz5qJxa+2u5JYO/KEoHZ -G963UX7XWezR98vZuTc1XHGZtBDMrjjDd7Kmb4/i/xBPeWwseeGtzFy9z2pnEnkL -uKE4C8wUNpzUUlsn4LneZXObIoErE7FqAAlVFujVe7iaJBmXoUXZR36drbfiaODK -vwAGyrYHaOlR +A4ICDwAwggIKAoICAQC4ggn74iAHLgOWOiOvB2CPe+Hr7W6STYJLZOoqPdh7mv7Q +Gm8cYxfD+26p13aEaW4/qn45losWdEPPy2ZiVIF+kcOP0R4AqsB0w9UHT4WSzCWt +Dqs8ywDMJ6tHge0++8S1bTpdutn/m8DPnKtkD9RQUzLFmGDO+mB08Xu+egTzJvUR +bHXRJ27E+CXUXLEHbAJd8EKjJiQYXhcj4lzUXOUg+xkpPAGe1dgQ6BDkv3xq/81S +9NTIT5YriHEm6egi9AFJLZbbZtCpRQm1MDMq7zfD7oL6ECLGrJ/aaxLEM49gMdw2 +SscYHotVxX2OMAKgN/ytB18L/mIQ4pOWp3HJQ+nks9Zu1V8cg7Pz5pWwjqoncbyU +aBi3vGsitAot3cyLXbN9hH8zQB8QqGyNLvqQnCJOBlYxOPA8M8NQGDThWKspcix0 +LhkmnYsWWt3+KEGEpFRcJCEthm/DAd1GImP7/dg1/btT/zOsIIkIoCyBwznuR4M4 +XoNTbIBsiTnO/6PjOdTrjLVX7vMueTjdbCgK3VeOuwetbZ7aZ/hvMJCjJ4wuM3l2 +ZyfEQlP4gJLJS3Fw2PRsySZJl9JyEzwTqYVosDF5SF8uw0HIcFaliZCqBbgYfOiQ +zjIX/GiHZdor1ZrhAbO/MpSxuxGzxF7Em4n+4p816NnC9S2JbHRetkM9P1adHwID +AQABoAAwDQYJKoZIhvcNAQELBQADggIBACpScaAoLAs9DuTcI5Y4dbHf7LwF4Zxx +UgPNzE1HB1Pr6NaHRiOWrlCtDEB5UwHrr0oZuRTqSEGg3Pe5Z2QtPG/sdFgfm4BP +29o6qo0CXiEVBwU7/K0/lL2/0a7uSbD0Tkw9d6Bgik7/Z5rzXZIi2vtUcrOtHskP +C+Z9w3vH9a+RnUeo52mCnRi4SaiSEnD5jvhmgaI9iF+k5pYBiMrKRj5W/F1QCkf4 +7OuSK97xN0eG/I4Oxgzi/qt51ySCYZbqoh7dIpwi/a4UsK8kdzDDI1M3J7bU07cO +CJRfr0EETqCQw/gAKoag3tRFNvWQB6Z9G3Ev5jeaCLpcc32NlpN5xH3VW8A0Zb81 +dn5BXkPSxjwJaD0a3cLFkfgrasoe7ZMmHrVpQDw+9USuGCYXMPzNZLEeTFbrRkUn +sqi30e28E1H69zVWj+OKzCWEH/azVlfaoVbwM+njUJDe5V09KvFtI7aZYmvLxbUX +4ifoRUVoKedyKnueVmoIG57lF2VzeEhX5YjCngxIg+YuE99HkMQAZSlS6uJcVM92 +tsC/+pYECBk8ukenbxmKXROl3u4p2M1iCSL/8EOVROuyjnuzCXJZOpNptdpX4ZgL +kHP1erq7/U8ZU8HviUsfMoisagx8dA8uj/4fk0jfNxOJqlZL9eJhpgBfYHqmAz3h +m+PQVw96eeoK -----END CERTIFICATE REQUEST----- diff --git a/tests/assets/client-certificates/client/trusted/key.pem b/tests/assets/client-certificates/client/trusted/key.pem index d60201e5a..a11cdc8a7 100644 --- a/tests/assets/client-certificates/client/trusted/key.pem +++ b/tests/assets/client-certificates/client/trusted/key.pem @@ -1,52 +1,52 @@ -----BEGIN PRIVATE KEY----- -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 -Y+eu85cike5J4vnz8uG04yt7rXjIrUav +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC4ggn74iAHLgOW +OiOvB2CPe+Hr7W6STYJLZOoqPdh7mv7QGm8cYxfD+26p13aEaW4/qn45losWdEPP +y2ZiVIF+kcOP0R4AqsB0w9UHT4WSzCWtDqs8ywDMJ6tHge0++8S1bTpdutn/m8DP +nKtkD9RQUzLFmGDO+mB08Xu+egTzJvURbHXRJ27E+CXUXLEHbAJd8EKjJiQYXhcj +4lzUXOUg+xkpPAGe1dgQ6BDkv3xq/81S9NTIT5YriHEm6egi9AFJLZbbZtCpRQm1 +MDMq7zfD7oL6ECLGrJ/aaxLEM49gMdw2SscYHotVxX2OMAKgN/ytB18L/mIQ4pOW +p3HJQ+nks9Zu1V8cg7Pz5pWwjqoncbyUaBi3vGsitAot3cyLXbN9hH8zQB8QqGyN +LvqQnCJOBlYxOPA8M8NQGDThWKspcix0LhkmnYsWWt3+KEGEpFRcJCEthm/DAd1G +ImP7/dg1/btT/zOsIIkIoCyBwznuR4M4XoNTbIBsiTnO/6PjOdTrjLVX7vMueTjd +bCgK3VeOuwetbZ7aZ/hvMJCjJ4wuM3l2ZyfEQlP4gJLJS3Fw2PRsySZJl9JyEzwT +qYVosDF5SF8uw0HIcFaliZCqBbgYfOiQzjIX/GiHZdor1ZrhAbO/MpSxuxGzxF7E +m4n+4p816NnC9S2JbHRetkM9P1adHwIDAQABAoICACBW8qcKoHCBuTE4qY6BLYSY +wyWWLT5JhZ/vZTfYNTydEzKon3cLS1wXkvMECAr3a9KO8KbpYyGhaU1fqmdrxnLH +2842ahrV0vvkY09vucrcK3Jk0tDKCC7AeT4EYPAcMwNVzNgm6xTpWOdK36OfPqiB +nLGTnsxIiGWW+giN3JY96tCOASySy9CMah0JziGt5dBPT27HPaZjv4yTnY+/ZI3e +VS+sC+CqPL/h3SwrAATFJ1j1/uHJSVoCBUs7zmtp91u7OOjl4Yb5ydTPSPiqi0y1 +XpG0CFRoZ3BiOhzXqLbEpoOBodnxaJy1C+fDNIKerZQqaZdxlAC/pfzPBpuvYqxe +RRPV85+AXZ5nosSoqjPprKDfDrLfwnEAJIXtZjDQvJ1mA49Tbtx30rzk3u36BU7v +4JXBTcCiaxhPw4MlPzCYKXXL8B02m8vC/RYZO29YHytERbAAMd4uUfGU0lOKWi6W +CEHXYTjbqSDuytpuxT9InLVxEC+u+h9CxAi3FpawLmu5ELW5qA5yMHRoyYOCKUDO +EsjV/qDSo6T1kkYZ0qj4Ya9DeVWgMRekn9TWZmzeYMzDnZtAp1OscmcETFH28xo0 +iuxQiStWEZaGdxD5njF+0GHtlECxMmPx03IH9bCxt6GU1aFCdfvhzqtovze4OCI2 +SJOxeJRAoom/LcCBQgCBAoIBAQDZygQguNUxwkaEpwsUhAstwse9SwY2fisNQTbU +Lj1rbvCDlOADrthIBsb9jpsUyqow9UbeB+mq13xrFz7BfmQ0Bf2+3P8mV0IxwmvE +mg8Wbi8pkYoet4yzSc2f8yHCvAsejFFWNU7/7JB0gpeRxXJTjdgoTiLEop5cus8r +SmBzCphzOMM8iUV/ByOgIAEOU3bo2GuSRbEEJ0kA1bd/l12uT7tApT+0umo/OPRR +yYEIexuIa7fean7ZHf31GbQdJm9vgoi+ar0mZg9yHSiqlOnghWVheEeNVGH+XgFt +4qQkbTW8Ie0Am88uT+3fbxIZHbJ4GkPnKwc+klN5p2efxmlfAoIBAQDY4TOoj2zf +Uvu8E1KboCiGjfHvvm3UhYVVUVqoctHQihSOQSThmKCGv025K4iSvxfC77XfuJv8 +ZC14TT176VvSZVrV4vZDod572/co5WyEGqaVfqwqjBmCWAODT+IgmIZrDfDRLIkC +4cQObd8DkzzdP8msqp731UlrEZvOkh9XW4rwBmFXeBqgDKDgM7Zge4Clu6lWABxT +BnGv00OjqRGd6IH1+tDTm0w0qNkXzZ3UDAuwVEHBjMJnPr7t9LEi5e3AluspjoF9 +Ie/KtQQ6D6guYtdRZismg3CGlnGwcf/yjS1YEVTqyybY52M9N3aHDVlPesn1BWO7 +UKQgHbPQq6RBAoIBAQCQv+n6bZ6VEdCYvgVpP1HGulzS/RhGA5lNl/h/EbSUwQlu +CvbQu9bYGFkNkUiVixWOsJbHX274s3voGW0GYaDryseZoXyb2QcP126VHufEOrtx +319zhv8m8niORKQ9r4mcZhpxN8En6+0e4uUmZ5rS2cW/FB+bnZGvhCHJXge4rmQg +wKtSgtID2ZTeCidphCPWInFsqJE8d3fX7DOnw8zp2+hS0QIEdpnDJ3GLImh2YIwu +IZn1Y8anO33c95Z0gWUzMgj8tii9arv9Vk//ADZpmX+GRtEXp+vxij1c8XOzGjrK +ram969DJsSoihMn8k3ZYyOw0qq6H8e01QARpdw/1AoIBAE/9Bxt1And/WJ7uFXqW +YDv4IDIG7uUB9cIYxjH4Xw/lzV0GA788ln/8EINp3e4Zkn7wAAkqQkWdAPQssK+B +yr7XaOAX3DHngnH2F7s6moJCfgwG8yKiF0pugaUtkj3pYzIaqyXKoiGw+KlFtonQ +BROo0g3fw8+uF2zoyqkuVWbXuW97Ou2Su2cqIS9vgyUkh7cYdoTkd43bg5SQe5Lh +6UBvH3eEcP6KeVm2qJLR4BLz+l+nQ7VJ3+1KRArpQ2eWm9B7GPJzv6hSGumNR6jO +W334MGeyIdoLgjXxSK8F7JsdnIqtob8S/BnlhUFvskRvFPBuXgwDV9wfCtlZexdM +JsECggEAJy5r3YTLiNFqXiwIyZ+GFQYlyhYAH75tP6yaF5Sr6FaDDaGTQxtMgp+V +UAOZIOHQAWpjQtgnjJAYLyDgkhVWJqQILBC3V1GHj6Kr+3avyV4cGuqP7wxsS2kt +vUY9emtg0dil/8l6IL3DzjXhRI8+00FZSvcueQYVgv+fv1zPx3T/RuUp8x4E0lq1 +YmlLjgi1EznRu1YW1IczsGwBQDpghKnf2E0tUn9wUr1Kb8k0uOVdzUZETvJjWYeD ++GwoR5lW/BhSIwE4zeK66RY18n7GOIS7y7Yvn4onr3j3geqIKAEjQrlBkQlPkhfd +21eLmxOuln98Q3na7ftpAWQdyjcE6Q== -----END PRIVATE KEY----- diff --git a/tests/assets/client-certificates/generate.sh b/tests/assets/client-certificates/generate.sh new file mode 100755 index 000000000..f835f5f32 --- /dev/null +++ b/tests/assets/client-certificates/generate.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# Client Certificate test-certificates + +cd "$(dirname "$0")" + +## Server + +openssl req \ + -x509 \ + -newkey rsa:4096 \ + -keyout server/server_key.pem \ + -out server/server_cert.pem \ + -nodes \ + -days 3650 \ + -subj "/CN=localhost/O=Client\ Certificate\ Demo" \ + -addext "subjectAltName=DNS:localhost,DNS:local.playwright" + +## Trusted client-certificate (server signed/valid) + +mkdir -p client/trusted +# generate server-signed (valid) certificate +openssl req \ + -newkey rsa:4096 \ + -keyout client/trusted/key.pem \ + -out client/trusted/csr.pem \ + -nodes \ + -days 3650 \ + -subj "/CN=Alice" + +# sign with server_cert.pem +openssl x509 \ + -req \ + -in client/trusted/csr.pem \ + -CA server/server_cert.pem \ + -CAkey server/server_key.pem \ + -out client/trusted/cert.pem \ + -set_serial 01 \ + -days 3650 +# create pfx +openssl pkcs12 -export -out client/trusted/cert.pfx -inkey client/trusted/key.pem -in client/trusted/cert.pem -passout pass:secure + +## Trusted certificate for localhost (server signed/valid) + +mkdir -p client/localhost + +# generate server-signed (valid) certificate +openssl req \ + -newkey rsa:4096 \ + -keyout client/localhost/localhost.key \ + -out client/localhost/localhost.csr \ + -nodes \ + -days 3650 \ + -subj "/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,DNS:127.0.0.1" + +# put extensions +echo "subjectAltName=DNS:localhost,DNS:127.0.0.1" > client/localhost/localhost.ext + +# sign with server_cert.pem +openssl x509 \ + -req \ + -in client/localhost/localhost.csr \ + -CA server/server_cert.pem \ + -CAkey server/server_key.pem \ + -set_serial 01 \ + -out client/localhost/localhost.pem \ + -days 3650 \ + -extfile client/localhost/localhost.ext + +## Self-signed certificate (invalid) + +mkdir -p client/self-signed +openssl req \ + -newkey rsa:4096 \ + -keyout client/self-signed/key.pem \ + -out client/self-signed/csr.pem \ + -nodes \ + -days 3650 \ + -subj "/CN=Bob" + +# sign with self-signed/key.pem +openssl x509 \ + -req \ + -in client/self-signed/csr.pem \ + -signkey client/self-signed/key.pem \ + -out client/self-signed/cert.pem \ + -days 3650 diff --git a/tests/assets/client-certificates/server/server_cert.pem b/tests/assets/client-certificates/server/server_cert.pem index 52d8f5314..18ee3d805 100644 --- a/tests/assets/client-certificates/server/server_cert.pem +++ b/tests/assets/client-certificates/server/server_cert.pem @@ -1,32 +1,32 @@ -----BEGIN CERTIFICATE----- -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 -2Ct10prMWB/3 +cmlnaHQwDQYJKoZIhvcNAQELBQADggIBAH1/6Sp4dW96yvwi9ptgVRYfRSRWfYYy +2nU6kJ1DPOW7hTPf2wLf6Z2KqiJXn8tECHfM4pnPSgDhtZHDDHAu9l7Diqzk/NIl +AblRs++4vSKdnCx1uh3EFLjIMmHa/cRUzfuR+oGfri3v7jgTBV6UJJQ0pMqIDk/P +VEOWWukllru40M8Rwy4F6LmpwIWMtNDxmhtXSbQJ6TZn+isNSoaWgHjQmayrVyXZ +OcNYuR8M/ECvnufuIOW53+hhwG1X0jEoBdXqqXbTKcGjA+yLHp008AAB5DNQblYm +FJ4N0v6eYLhOO0u3n0b/ZsqUkZx2h38tlZoAdJNqy+d4TE3WbGRNgNJrTjAOzpPL +cQ5RNr3dDsUDFPnCoK3LUf9BWerARoDt+bbrM/VqvWH/0uqaU0/vo8IchSyQ9wGv +SwrWLJU32HQUJ2VQP/m4ADf3X2Ozj+e8bBf7XPPD8KDyL4aR0KuYzrsOnhFlBkHD +PtNjc7SgiE7AZe1WOEmhBQcr+vI6S8qFTurTntCBkvrFgYBAIIhK2XwFN5sFtByr +GCB67NQMp37g2qFNYi8EmKQvPLpAcC3Je5PvHFSj2y9Z14FclGoxjU50SlH9/A5I +iHGcIVjCv9NhNDFAwakF/sTnrpT/FIS7tm9GwoJGWCdYg4pl72WYCW6hJBG+XY7G +t3jt9TP8623D -----END CERTIFICATE----- diff --git a/tests/assets/client-certificates/server/server_key.pem b/tests/assets/client-certificates/server/server_key.pem index ff6a3fc11..3a762bde7 100644 --- a/tests/assets/client-certificates/server/server_key.pem +++ b/tests/assets/client-certificates/server/server_key.pem @@ -1,52 +1,52 @@ -----BEGIN PRIVATE KEY----- -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 -8ynLaTs3oV3BjQya9pAUyzIX5L67 +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 +dvQgA6dBjtYg5cQMHNbnfT3j3WzyQg== -----END PRIVATE KEY----- From 6dd5aa23c2cff964652e534532e93098119ff678 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 22 Jul 2025 16:54:59 +0200 Subject: [PATCH 07/45] chore: update pre-commit / linters (#2925) --- .pre-commit-config.yaml | 12 ++++++------ local-requirements.txt | 2 +- playwright/_impl/_clock.py | 2 +- playwright/_impl/_set_input_files_helpers.py | 2 +- tests/async/test_page.py | 3 +-- tests/async/test_page_add_locator_handler.py | 1 - tests/async/test_worker.py | 2 +- tests/common/test_signals.py | 1 - tests/sync/test_page_add_locator_handler.py | 1 - 9 files changed, 11 insertions(+), 15 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5c8c8f1db..57fdca816 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,20 +15,20 @@ repos: - id: check-executables-have-shebangs - id: check-merge-conflict - repo: https://github.com/psf/black - rev: 24.8.0 + rev: 25.1.0 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.17.0 hooks: - id: mypy - additional_dependencies: [types-pyOpenSSL==24.1.0.20240722, types-requests==2.32.0.20240914] + additional_dependencies: [types-pyOpenSSL==24.1.0.20240722, types-requests==2.32.4.20250611] - repo: https://github.com/pycqa/flake8 - rev: 7.1.1 + rev: 7.3.0 hooks: - id: flake8 - repo: https://github.com/pycqa/isort - rev: 5.13.2 + rev: 6.0.1 hooks: - id: isort - repo: local @@ -39,7 +39,7 @@ repos: language: node pass_filenames: false types: [python] - additional_dependencies: ["pyright@1.1.384"] + additional_dependencies: ["pyright@1.1.403"] - repo: local hooks: - id: check-license-header diff --git a/local-requirements.txt b/local-requirements.txt index e19488956..28f60ba65 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -2,7 +2,7 @@ autobahn==23.1.2 black==25.1.0 build==1.2.2.post1 flake8==7.2.0 -mypy==1.16.0 +mypy==1.17.0 objgraph==3.6.2 Pillow==11.2.1 pixelmatch==0.3.0 diff --git a/playwright/_impl/_clock.py b/playwright/_impl/_clock.py index 928536019..f6eb7c42d 100644 --- a/playwright/_impl/_clock.py +++ b/playwright/_impl/_clock.py @@ -89,7 +89,7 @@ async def set_system_time( def parse_time( - time: Union[float, str, datetime.datetime] + time: Union[float, str, datetime.datetime], ) -> Dict[str, Union[int, str]]: if isinstance(time, (float, int)): return {"timeNumber": int(time * 1_000)} diff --git a/playwright/_impl/_set_input_files_helpers.py b/playwright/_impl/_set_input_files_helpers.py index f868886a3..0f40d5b99 100644 --- a/playwright/_impl/_set_input_files_helpers.py +++ b/playwright/_impl/_set_input_files_helpers.py @@ -139,7 +139,7 @@ async def convert_input_files( def resolve_paths_and_directory_for_input_files( - items: Sequence[Union[str, Path]] + items: Sequence[Union[str, Path]], ) -> Tuple[Optional[List[str]], Optional[str]]: local_paths: Optional[List[str]] = None local_directory: Optional[str] = None diff --git a/tests/async/test_page.py b/tests/async/test_page.py index 03907c4b9..607c86fb3 100644 --- a/tests/async/test_page.py +++ b/tests/async/test_page.py @@ -662,7 +662,7 @@ async def load() -> None: async def test_set_content_should_work_with_tricky_content(page: Page) -> None: - await page.set_content("
hello world
" + "\x7F") + await page.set_content("
hello world
" + "\x7f") assert await page.eval_on_selector("div", "div => div.textContent") == "hello world" @@ -1403,7 +1403,6 @@ async def test_should_not_throw_when_continuing_after_page_is_closed( async def handle_route(route: Route) -> None: await page.close() await route.continue_() - nonlocal done done.set_result(True) await page.route("**/*", handle_route) diff --git a/tests/async/test_page_add_locator_handler.py b/tests/async/test_page_add_locator_handler.py index 4a5a44323..5f44170f8 100644 --- a/tests/async/test_page_add_locator_handler.py +++ b/tests/async/test_page_add_locator_handler.py @@ -30,7 +30,6 @@ async def test_should_work(page: Page, server: Server) -> None: original_locator = page.get_by_text("This interstitial covers the button") async def handler(locator: Locator) -> None: - nonlocal original_locator assert locator == original_locator nonlocal before_count nonlocal after_count diff --git a/tests/async/test_worker.py b/tests/async/test_worker.py index de1a858e8..bba22fc0d 100644 --- a/tests/async/test_worker.py +++ b/tests/async/test_worker.py @@ -199,5 +199,5 @@ async def test_workers_should_format_number_using_context_locale( "() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))" ) worker = await worker_info.value - assert await worker.evaluate("() => (10000.20).toLocaleString()") == "10\u00A0000,2" + assert await worker.evaluate("() => (10000.20).toLocaleString()") == "10\u00a0000,2" await context.close() diff --git a/tests/common/test_signals.py b/tests/common/test_signals.py index 174eaf6f2..ba600b44d 100644 --- a/tests/common/test_signals.py +++ b/tests/common/test_signals.py @@ -50,7 +50,6 @@ async def main() -> None: page = await context.new_page() notified = False try: - nonlocal sigint_received while not sigint_received: if not notified: wait_queue.put("ready") diff --git a/tests/sync/test_page_add_locator_handler.py b/tests/sync/test_page_add_locator_handler.py index b2d037f07..7a2b6a438 100644 --- a/tests/sync/test_page_add_locator_handler.py +++ b/tests/sync/test_page_add_locator_handler.py @@ -29,7 +29,6 @@ def test_should_work(page: Page, server: Server) -> None: original_locator = page.get_by_text("This interstitial covers the button") def handler(locator: Locator) -> None: - nonlocal original_locator assert locator == original_locator nonlocal before_count nonlocal after_count From 2b75be585dac7f0ed762a37e48df3b8296527f9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 23:04:07 +0200 Subject: [PATCH 08/45] build(deps): bump pytest-asyncio from 1.0.0 to 1.1.0 (#2932) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 28f60ba65..64544740f 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -9,7 +9,7 @@ pixelmatch==0.3.0 pre-commit==3.5.0 pyOpenSSL==25.1.0 pytest==8.4.1 -pytest-asyncio==1.0.0 +pytest-asyncio==1.1.0 pytest-cov==6.2.1 pytest-repeat==0.9.4 pytest-rerunfailures==15.1 From a74efb8e1ce5a60480a48f63a8a6d76dd3d55fb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:04:39 +0200 Subject: [PATCH 09/45] build(deps): bump pytest-xdist from 3.7.0 to 3.8.0 (#2916) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 64544740f..de20c0daa 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -14,7 +14,7 @@ pytest-cov==6.2.1 pytest-repeat==0.9.4 pytest-rerunfailures==15.1 pytest-timeout==2.4.0 -pytest-xdist==3.7.0 +pytest-xdist==3.8.0 requests==2.32.4 service_identity==24.2.0 twisted==25.5.0 From 8ad65d414aee14f5445ad1b7023f92aedc322700 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 09:44:39 +0200 Subject: [PATCH 10/45] build(deps): bump mypy from 1.17.0 to 1.17.1 (#2933) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index de20c0daa..d9f834c3f 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -2,7 +2,7 @@ autobahn==23.1.2 black==25.1.0 build==1.2.2.post1 flake8==7.2.0 -mypy==1.17.0 +mypy==1.17.1 objgraph==3.6.2 Pillow==11.2.1 pixelmatch==0.3.0 From 7dc8b32b9c34c43620e0c7f19cc08b71410aff22 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 5 Aug 2025 10:46:40 +0200 Subject: [PATCH 11/45] test: unflake dialog inline script test (#2934) --- tests/sync/test_browsercontext_events.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/sync/test_browsercontext_events.py b/tests/sync/test_browsercontext_events.py index 6e44b76d5..a100fb04a 100644 --- a/tests/sync/test_browsercontext_events.py +++ b/tests/sync/test_browsercontext_events.py @@ -172,23 +172,25 @@ def test_dialog_event_should_work_with_inline_script_tag( ) -> None: def handle_route(request: TestServerRequest) -> None: request.setHeader("content-type", "text/html") - request.write(b"""""") + request.write(b"") request.finish() server.set_route("/popup.html", handle_route) page.goto(server.EMPTY_PAGE) page.set_content("Click me") - def handle_dialog(dialog: Dialog) -> None: - assert dialog.message == "hey?" - assert dialog.page == popup - dialog.accept("hello") + with ( + page.context.expect_event("dialog") as dialog_info, + page.expect_popup() as popup_info, + ): + page.click("a") - page.context.on("dialog", handle_dialog) + dialog: Dialog = dialog_info.value + popup: Page = popup_info.value - with page.expect_popup() as popup_info: - page.click("a") - popup = popup_info.value + assert dialog.message == "hey?" + assert dialog.page == popup + dialog.accept("hello") assert popup.evaluate("window.result") == "hello" From 3fea01e81a33f1ef31e271bdda78d398f18e87be Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 5 Aug 2025 11:32:51 +0200 Subject: [PATCH 12/45] test: unflake test_context_add_cookies_should_work (#2935) --- tests/async/test_defaultbrowsercontext.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/async/test_defaultbrowsercontext.py b/tests/async/test_defaultbrowsercontext.py index 25ef0c3f8..f5d2a606d 100644 --- a/tests/async/test_defaultbrowsercontext.py +++ b/tests/async/test_defaultbrowsercontext.py @@ -111,7 +111,7 @@ async def test_context_add_cookies_should_work( ] ) assert await page.evaluate("() => document.cookie") == "username=John Doe" - assert await page.context.cookies() == [ + assert _filter_cookies(await page.context.cookies()) == [ { "name": "username", "value": "John Doe", @@ -127,7 +127,7 @@ async def test_context_add_cookies_should_work( def _filter_cookies(cookies: Sequence[Cookie]) -> List[Cookie]: return list( - filter(lambda cookie: cookie["domain"] != "copilot.microsoft.com", cookies) + filter(lambda cookie: not cookie["domain"].endswith("microsoft.com"), cookies) ) From 8963460fffe065bfeb58a162f6e35fc8e3e65d4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 13:37:31 +0200 Subject: [PATCH 13/45] build(deps): bump build from 1.2.2.post1 to 1.3.0 (#2937) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index d9f834c3f..8c214a014 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -1,6 +1,6 @@ autobahn==23.1.2 black==25.1.0 -build==1.2.2.post1 +build==1.3.0 flake8==7.2.0 mypy==1.17.1 objgraph==3.6.2 From 5cc395e9214b5ebb32ca8629d6905e53824978b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:35:18 +0200 Subject: [PATCH 14/45] build(deps): bump actions/checkout from 4 to 5 in the actions group (#2940) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/publish.yml | 2 +- .github/workflows/publish_docker.yml | 2 +- .github/workflows/test_docker.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c18a04bc6..200b2a65a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 with: @@ -80,7 +80,7 @@ jobs: browser: chromium runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 with: @@ -127,7 +127,7 @@ jobs: browser-channel: msedge runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 with: @@ -163,7 +163,7 @@ jobs: os: [ubuntu-22.04, macos-13, windows-2022] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Get conda @@ -184,7 +184,7 @@ jobs: run: working-directory: examples/todomvc/ steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b682372fd..c6e71028a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,7 +25,7 @@ jobs: # Required for conda-incubator/setup-miniconda@v3 shell: bash -el {0} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Get conda diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml index 7d83136bc..7c2b73e13 100644 --- a/.github/workflows/publish_docker.yml +++ b/.github/workflows/publish_docker.yml @@ -15,7 +15,7 @@ jobs: contents: read # This is required for actions/checkout to succeed environment: Docker steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Azure login uses: azure/login@v2 with: diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml index c1f2be3de..e5252e389 100644 --- a/.github/workflows/test_docker.yml +++ b/.github/workflows/test_docker.yml @@ -30,7 +30,7 @@ jobs: - ubuntu-24.04 - ubuntu-24.04-arm steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 with: From d33a807492c0a944186bc37dd5d71d57e1c36901 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:35:43 +0200 Subject: [PATCH 15/45] build(deps): bump greenlet from 3.2.3 to 3.2.4 (#2939) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 14dc0500a..75d362fe8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile pyproject.toml -o requirements.txt -greenlet==3.2.3 +greenlet==3.2.4 # via playwright (pyproject.toml) pyee==13.0.0 # via playwright (pyproject.toml) From e49496e7a803b2fd05e5a7e814db4c0af8962743 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:03:42 +0200 Subject: [PATCH 16/45] build(deps): bump types-requests from 2.32.4.20250611 to 2.32.4.20250809 (#2941) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 8c214a014..2f4f0d488 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -19,4 +19,4 @@ requests==2.32.4 service_identity==24.2.0 twisted==25.5.0 types-pyOpenSSL==24.1.0.20240722 -types-requests==2.32.4.20250611 +types-requests==2.32.4.20250809 From 6e9ff7a80f02db4e0ccdb87efadbf543a0c3ceb9 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 12 Aug 2025 15:34:31 +0200 Subject: [PATCH 17/45] test: unflake cr tracing test (#2942) --- tests/async/test_chromium_tracing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/async/test_chromium_tracing.py b/tests/async/test_chromium_tracing.py index fd065efde..4cd796e0d 100644 --- a/tests/async/test_chromium_tracing.py +++ b/tests/async/test_chromium_tracing.py @@ -114,5 +114,6 @@ async def test_should_support_a_buffer_without_a_path( ) -> None: await browser.start_tracing(page=page, screenshots=True) await page.goto(server.PREFIX + "/grid.html") + await rafraf(page) trace = await browser.stop_tracing() assert "screenshot" in trace.decode() From 1be34f206e1b57cf94cbb295158f75860f744e9c Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 14 Aug 2025 15:25:41 +0200 Subject: [PATCH 18/45] fix: timeout option support with fetch api (#2947) --- playwright/_impl/_fetch.py | 1 + tests/async/test_fetch_global.py | 9 +++++++++ tests/async/test_page_request_intercept.py | 2 +- tests/sync/test_fetch_global.py | 9 +++++++++ tests/sync/test_page_request_intercept.py | 2 +- 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/playwright/_impl/_fetch.py b/playwright/_impl/_fetch.py index e4174ea27..50bf4ad4a 100644 --- a/playwright/_impl/_fetch.py +++ b/playwright/_impl/_fetch.py @@ -412,6 +412,7 @@ async def _inner_fetch( self._timeout_settings.timeout, { "url": url, + "timeout": timeout, "params": object_to_array(params) if isinstance(params, dict) else None, "encodedParams": params if isinstance(params, str) else None, "method": method, diff --git a/tests/async/test_fetch_global.py b/tests/async/test_fetch_global.py index e2a7678c5..1f7593194 100644 --- a/tests/async/test_fetch_global.py +++ b/tests/async/test_fetch_global.py @@ -89,6 +89,15 @@ async def test_should_support_global_timeout_option( await request.get(server.EMPTY_PAGE) +async def test_should_support_timeout_option_in_get_method( + playwright: Playwright, server: Server +) -> None: + request = await playwright.request.new_context() + server.set_route("/empty.html", lambda req: None) + with pytest.raises(Error, match="APIRequestContext.get: Timeout 123ms exceeded."): + await request.get(server.EMPTY_PAGE, timeout=123) + + async def test_should_propagate_extra_http_headers_with_redirects( playwright: Playwright, server: Server ) -> None: diff --git a/tests/async/test_page_request_intercept.py b/tests/async/test_page_request_intercept.py index dc8f7416a..a9859e87b 100644 --- a/tests/async/test_page_request_intercept.py +++ b/tests/async/test_page_request_intercept.py @@ -34,7 +34,7 @@ def _handler(request: TestServerRequest) -> None: async def handle(route: Route) -> None: with pytest.raises(Error) as error: await route.fetch(timeout=1000) - assert "Timeout 1000ms exceeded" in error.value.message + assert "Route.fetch: Timeout 1000ms exceeded." in error.value.message await page.route("**/*", lambda route: handle(route)) with pytest.raises(Error) as error: diff --git a/tests/sync/test_fetch_global.py b/tests/sync/test_fetch_global.py index bf3970c21..5cb3abc5b 100644 --- a/tests/sync/test_fetch_global.py +++ b/tests/sync/test_fetch_global.py @@ -71,6 +71,15 @@ def test_should_support_global_timeout_option( request.get(server.EMPTY_PAGE) +def test_should_support_timeout_option_in_get_method( + playwright: Playwright, server: Server +) -> None: + request = playwright.request.new_context() + server.set_route("/empty.html", lambda req: None) + with pytest.raises(Error, match="APIRequestContext.get: Timeout 123ms exceeded."): + request.get(server.EMPTY_PAGE, timeout=123) + + def test_should_propagate_extra_http_headers_with_redirects( playwright: Playwright, server: Server ) -> None: diff --git a/tests/sync/test_page_request_intercept.py b/tests/sync/test_page_request_intercept.py index 86cf21b63..1d3dd0f46 100644 --- a/tests/sync/test_page_request_intercept.py +++ b/tests/sync/test_page_request_intercept.py @@ -34,7 +34,7 @@ def _handle(request: TestServerRequest) -> None: def handle(route: Route) -> None: with pytest.raises(Error) as error: route.fetch(timeout=1000) - assert "Request timed out after 1000ms" in error.value.message + assert "Route.fetch: Timeout 1000ms exceeded." in error.value.message page.route("**/*", lambda route: handle(route)) with pytest.raises(Error) as error: From 254aabddf3a68c9385cc9e363d2b61fa4bba777d Mon Sep 17 00:00:00 2001 From: Xianghu Zhao Date: Tue, 19 Aug 2025 21:17:08 +0800 Subject: [PATCH 19/45] fix: screenshot type inferred from path file extension (#2951) --- playwright/_impl/_element_handle.py | 12 ++++++++++ playwright/_impl/_page.py | 4 +++- tests/async/test_screenshot.py | 34 +++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/playwright/_impl/_element_handle.py b/playwright/_impl/_element_handle.py index 88f1a7358..1561e19fc 100644 --- a/playwright/_impl/_element_handle.py +++ b/playwright/_impl/_element_handle.py @@ -13,6 +13,7 @@ # limitations under the License. import base64 +import mimetypes from pathlib import Path from typing import ( TYPE_CHECKING, @@ -323,6 +324,8 @@ async def screenshot( ) -> bytes: params = locals_to_params(locals()) if "path" in params: + if "type" not in params: + params["type"] = determine_screenshot_type(params["path"]) del params["path"] if "mask" in params: params["mask"] = list( @@ -450,3 +453,12 @@ def convert_select_option_values( elements = list(map(lambda e: e._channel, element)) 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": + return "png" + if mime_type == "image/jpeg": + return "jpeg" + raise Error(f'Unsupported screenshot mime type for path "{path}": {mime_type}') diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index a0fa4eec2..1019b2f6e 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -51,7 +51,7 @@ ) from playwright._impl._console_message import ConsoleMessage from playwright._impl._download import Download -from playwright._impl._element_handle import ElementHandle +from playwright._impl._element_handle import ElementHandle, determine_screenshot_type from playwright._impl._errors import Error, TargetClosedError, is_target_closed_error from playwright._impl._event_context_manager import EventContextManagerImpl from playwright._impl._file_chooser import FileChooser @@ -800,6 +800,8 @@ async def screenshot( ) -> bytes: params = locals_to_params(locals()) if "path" in params: + if "type" not in params: + params["type"] = determine_screenshot_type(params["path"]) del params["path"] if "mask" in params: params["mask"] = list( diff --git a/tests/async/test_screenshot.py b/tests/async/test_screenshot.py index 3cd536f96..36149225f 100644 --- a/tests/async/test_screenshot.py +++ b/tests/async/test_screenshot.py @@ -12,13 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pathlib import Path from typing import Callable +from PIL import Image + from playwright.async_api import Page from tests.server import Server from tests.utils import must +def assert_image_file_format(path: Path, image_format: str) -> None: + with Image.open(path) as img: + assert img.format == image_format + + async def test_should_screenshot_with_mask( page: Page, server: Server, assert_to_be_golden: Callable[[bytes, str], None] ) -> None: @@ -43,3 +51,29 @@ async def test_should_screenshot_with_mask( ), "mask-should-work-with-element-handle.png", ) + + +async def test_should_infer_screenshot_type_from_path( + page: Page, tmp_path: Path +) -> None: + output_png_file = tmp_path / "foo.png" + await page.screenshot(path=output_png_file) + assert_image_file_format(output_png_file, "PNG") + + output_jpeg_file = tmp_path / "bar.jpeg" + await page.screenshot(path=output_jpeg_file) + assert_image_file_format(output_jpeg_file, "JPEG") + + output_jpg_file = tmp_path / "bar.jpg" + await page.screenshot(path=output_jpg_file) + assert_image_file_format(output_jpg_file, "JPEG") + + +async def test_should_screenshot_with_type_argument(page: Page, tmp_path: Path) -> None: + output_jpeg_with_png_extension = tmp_path / "foo_jpeg.png" + await page.screenshot(path=output_jpeg_with_png_extension, type="jpeg") + assert_image_file_format(output_jpeg_with_png_extension, "JPEG") + + 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") From 3cbe13e58a4a20b4b3aaa1afbdc69747a7c37933 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 21 Aug 2025 17:08:58 +0200 Subject: [PATCH 20/45] chore: roll to 1.55.0 (#2956) --- README.md | 4 +- playwright/_impl/_helper.py | 26 +++- playwright/async_api/_generated.py | 27 +++- playwright/sync_api/_generated.py | 35 ++++- setup.py | 2 +- .../content-script.js | 0 .../index.js | 2 +- .../manifest.json | 4 +- .../extension-mv3-with-logging/background.js | 5 + .../extension-mv3-with-logging/content.js | 1 + .../extension-mv3-with-logging/manifest.json | 17 +++ tests/async/test_extension.py | 125 ++++++++++++++++++ tests/async/test_launcher.py | 38 +----- tests/async/test_page_route.py | 17 +++ tests/async/test_tracing.py | 1 - tests/sync/test_extension.py | 101 ++++++++++++++ tests/sync/test_launcher.py | 38 +----- tests/sync/test_tracing.py | 1 - 18 files changed, 352 insertions(+), 92 deletions(-) rename tests/assets/{simple-extension => extension-mv3-simple}/content-script.js (100%) rename tests/assets/{simple-extension => extension-mv3-simple}/index.js (63%) rename tests/assets/{simple-extension => extension-mv3-simple}/manifest.json (80%) create mode 100644 tests/assets/extension-mv3-with-logging/background.js create mode 100644 tests/assets/extension-mv3-with-logging/content.js create mode 100644 tests/assets/extension-mv3-with-logging/manifest.json create mode 100644 tests/async/test_extension.py create mode 100644 tests/sync/test_extension.py diff --git a/README.md b/README.md index fa9e246a9..cf85c6116 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 139.0.7258.5 | ✅ | ✅ | ✅ | +| Chromium 140.0.7339.16 | ✅ | ✅ | ✅ | | WebKit 26.0 | ✅ | ✅ | ✅ | -| Firefox 140.0.2 | ✅ | ✅ | ✅ | +| Firefox 141.0 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/_helper.py b/playwright/_impl/_helper.py index 66e59c65f..8f1ca8594 100644 --- a/playwright/_impl/_helper.py +++ b/playwright/_impl/_helper.py @@ -29,6 +29,7 @@ Optional, Pattern, Set, + Tuple, TypedDict, TypeVar, Union, @@ -221,14 +222,35 @@ def map_token(original: str, replacement: str) -> str: processed_parts.append(new_prefix + new_suffix) relative_path = "/".join(processed_parts) - resolved_url = urljoin(base_url if base_url is not None else "", relative_path) + resolved_url, case_insensitive_part = resolve_base_url(base_url, relative_path) for replacement, original in token_map.items(): - resolved_url = resolved_url.replace(replacement, original, 1) + normalize = case_insensitive_part and replacement in case_insensitive_part + resolved_url = resolved_url.replace( + replacement, original.lower() if normalize else original, 1 + ) return ensure_trailing_slash(resolved_url) +def resolve_base_url( + base_url: Optional[str], given_url: str +) -> Tuple[str, Optional[str]]: + try: + resolved = urljoin(base_url if base_url is not None else "", given_url) + parsed = urlparse(resolved) + # Schema and domain are case-insensitive. + hostname_port = ( + parsed.hostname or "" + ) # can't use parsed.netloc because it includes userinfo (username:password) + if parsed.port: + hostname_port += f":{parsed.port}" + case_insensitive_prefix = f"{parsed.scheme}://{hostname_port}" + return resolved, case_insensitive_prefix + except Exception: + return given_url, None + + # In Node.js, new URL('http://localhost') returns 'http://localhost/'. # To ensure the same url matching behavior, do the same. def ensure_trailing_slash(url: str) -> str: diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index bedf233de..bdda2b2b0 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -11487,8 +11487,8 @@ async def main(): async def pause(self) -> None: """Page.pause - Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume' - button in the page overlay or to call `playwright.resume()` in the DevTools console. + Pauses script execution. Playwright will stop executing the script and wait for the user to either press the + 'Resume' button in the page overlay or to call `playwright.resume()` in the DevTools console. User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from the place it was paused. @@ -13921,6 +13921,10 @@ async def new_context( `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. + Client certificate authentication is only active when at least one client certificate is provided. If you want to + reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that + does not match any of the domains you plan to visit. + **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`. @@ -14152,6 +14156,10 @@ async def new_page( `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. + Client certificate authentication is only active when at least one client certificate is provided. If you want to + reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that + does not match any of the domains you plan to visit. + **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`. @@ -14559,6 +14567,13 @@ async def launch_persistent_context( **parent** directory of the "Profile Path" seen at `chrome://version`. Note that browsers do not allow launching multiple instances with the same User Data Directory. + + **NOTE** Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not + supported. Pointing `userDataDir` to Chrome's main "User Data" directory (the profile used for your regular + browsing) may result in pages not loading or the browser exiting. Create and use a separate directory (for example, + an empty folder) as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port + for details. + channel : Union[str, None] Browser distribution channel. @@ -14733,6 +14748,10 @@ async def launch_persistent_context( `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. + Client certificate authentication is only active when at least one client certificate is provided. If you want to + reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that + does not match any of the domains you plan to visit. + **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`. @@ -18801,6 +18820,10 @@ async def new_context( `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. + Client certificate authentication is only active when at least one client certificate is provided. If you want to + reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that + does not match any of the domains you plan to visit. + **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`. diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index 8f4b60764..83fedfbe9 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -11569,8 +11569,8 @@ def run(playwright: Playwright): def pause(self) -> None: """Page.pause - Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume' - button in the page overlay or to call `playwright.resume()` in the DevTools console. + Pauses script execution. Playwright will stop executing the script and wait for the user to either press the + 'Resume' button in the page overlay or to call `playwright.resume()` in the DevTools console. User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from the place it was paused. @@ -12255,7 +12255,7 @@ def add_locator_handler( ```py # Setup the handler. - def handler(): + async def handler(): await page.get_by_role(\"button\", name=\"No thanks\").click() await page.add_locator_handler(page.get_by_text(\"Sign up to the newsletter\"), handler) @@ -12268,7 +12268,7 @@ def handler(): ```py # Setup the handler. - def handler(): + async def handler(): await page.get_by_role(\"button\", name=\"Remind me later\").click() await page.add_locator_handler(page.get_by_text(\"Confirm your security details\"), handler) @@ -12283,7 +12283,7 @@ def handler(): ```py # Setup the handler. - def handler(): + async def handler(): await page.evaluate(\"window.removeObstructionsForTestIfNeeded()\") await page.add_locator_handler(page.locator(\"body\"), handler, no_wait_after=True) @@ -12296,7 +12296,7 @@ def handler(): invocations by setting `times`: ```py - def handler(locator): + async def handler(locator): await locator.click() await page.add_locator_handler(page.get_by_label(\"Close\"), handler, times=1) ``` @@ -13952,6 +13952,10 @@ def new_context( `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. + Client certificate authentication is only active when at least one client certificate is provided. If you want to + reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that + does not match any of the domains you plan to visit. + **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`. @@ -14185,6 +14189,10 @@ def new_page( `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. + Client certificate authentication is only active when at least one client certificate is provided. If you want to + reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that + does not match any of the domains you plan to visit. + **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`. @@ -14598,6 +14606,13 @@ def launch_persistent_context( **parent** directory of the "Profile Path" seen at `chrome://version`. Note that browsers do not allow launching multiple instances with the same User Data Directory. + + **NOTE** Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not + supported. Pointing `userDataDir` to Chrome's main "User Data" directory (the profile used for your regular + browsing) may result in pages not loading or the browser exiting. Create and use a separate directory (for example, + an empty folder) as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port + for details. + channel : Union[str, None] Browser distribution channel. @@ -14772,6 +14787,10 @@ def launch_persistent_context( `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. + Client certificate authentication is only active when at least one client certificate is provided. If you want to + reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that + does not match any of the domains you plan to visit. + **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`. @@ -18922,6 +18941,10 @@ def new_context( `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. + Client certificate authentication is only active when at least one client certificate is provided. If you want to + reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that + does not match any of the domains you plan to visit. + **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`. diff --git a/setup.py b/setup.py index 5c2911865..d147f3be7 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ import zipfile from typing import Dict -driver_version = "1.54.1" +driver_version = "1.55.0" base_wheel_bundles = [ { diff --git a/tests/assets/simple-extension/content-script.js b/tests/assets/extension-mv3-simple/content-script.js similarity index 100% rename from tests/assets/simple-extension/content-script.js rename to tests/assets/extension-mv3-simple/content-script.js diff --git a/tests/assets/simple-extension/index.js b/tests/assets/extension-mv3-simple/index.js similarity index 63% rename from tests/assets/simple-extension/index.js rename to tests/assets/extension-mv3-simple/index.js index a0bb3f4ea..1523a8364 100644 --- a/tests/assets/simple-extension/index.js +++ b/tests/assets/extension-mv3-simple/index.js @@ -1,2 +1,2 @@ // Mock script for background extension -window.MAGIC = 42; +globalThis.MAGIC = 42; diff --git a/tests/assets/simple-extension/manifest.json b/tests/assets/extension-mv3-simple/manifest.json similarity index 80% rename from tests/assets/simple-extension/manifest.json rename to tests/assets/extension-mv3-simple/manifest.json index da2cd082e..39b77fc21 100644 --- a/tests/assets/simple-extension/manifest.json +++ b/tests/assets/extension-mv3-simple/manifest.json @@ -2,7 +2,7 @@ "name": "Simple extension", "version": "0.1", "background": { - "scripts": ["index.js"] + "service_worker": "index.js" }, "content_scripts": [{ "matches": [""], @@ -10,5 +10,5 @@ "js": ["content-script.js"] }], "permissions": ["background", "activeTab"], - "manifest_version": 2 + "manifest_version": 3 } diff --git a/tests/assets/extension-mv3-with-logging/background.js b/tests/assets/extension-mv3-with-logging/background.js new file mode 100644 index 000000000..3b1a406fb --- /dev/null +++ b/tests/assets/extension-mv3-with-logging/background.js @@ -0,0 +1,5 @@ +console.log("Service worker script loaded"); + +chrome.runtime.onInstalled.addListener(() => { + console.log("Extension installed"); +}); diff --git a/tests/assets/extension-mv3-with-logging/content.js b/tests/assets/extension-mv3-with-logging/content.js new file mode 100644 index 000000000..e718206c2 --- /dev/null +++ b/tests/assets/extension-mv3-with-logging/content.js @@ -0,0 +1 @@ +console.log("Test console log from a third-party execution context"); diff --git a/tests/assets/extension-mv3-with-logging/manifest.json b/tests/assets/extension-mv3-with-logging/manifest.json new file mode 100644 index 000000000..5ad1fde38 --- /dev/null +++ b/tests/assets/extension-mv3-with-logging/manifest.json @@ -0,0 +1,17 @@ +{ + "manifest_version": 3, + "name": "Console Log Extension", + "version": "1.0", + "background": { + "service_worker": "background.js" + }, + "permissions": [ + "tabs" + ], + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ] + } diff --git a/tests/async/test_extension.py b/tests/async/test_extension.py new file mode 100644 index 000000000..853afd8a5 --- /dev/null +++ b/tests/async/test_extension.py @@ -0,0 +1,125 @@ +# Copyright (c) Microsoft Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +from pathlib import Path +from typing import Any, AsyncGenerator, Awaitable, Callable, Dict, List, Optional + +import pytest + +from playwright.async_api import BrowserContext, BrowserType + +from ..server import Server + + +@pytest.fixture() +async def launch_persistent_context( + browser_type: BrowserType, + browser_channel: Optional[str], + tmp_path: Path, + launch_arguments: Dict[str, Any], + is_headless_shell: bool, +) -> AsyncGenerator[Callable[..., Awaitable[BrowserContext]], None]: + if browser_channel and browser_channel.startswith("chrome"): + pytest.skip( + "--load-extension is not supported in Chrome anymore. https://groups.google.com/a/chromium.org/g/chromium-extensions/c/1-g8EFx2BBY/m/S0ET5wPjCAAJ" + ) + if is_headless_shell: + pytest.skip("Headless Shell has no support for extensions") + + contexts: List[BrowserContext] = [] + + async def launch(extension_path: str, **kwargs: Any) -> BrowserContext: + context = await browser_type.launch_persistent_context( + str(tmp_path), + **launch_arguments, + **kwargs, + args=[ + f"--disable-extensions-except={extension_path}", + f"--load-extension={extension_path}", + ], + ) + contexts.append(context) + return context + + yield launch + + for context in contexts: + await context.close() + + +@pytest.mark.only_browser("chromium") +async def test_should_give_access_to_the_service_worker( + launch_persistent_context: Callable[..., Awaitable[BrowserContext]], + assetdir: Path, +) -> None: + extension_path = str(assetdir / "extension-mv3-simple") + context = await launch_persistent_context(extension_path) + service_workers = context.service_workers + service_worker = ( + service_workers[0] + if len(service_workers) + else await context.wait_for_event("serviceworker") + ) + assert service_worker + assert service_worker in context.service_workers + while not await service_worker.evaluate("globalThis.MAGIC") == 42: + await context.pages[0].wait_for_timeout(100) + await context.close() + assert len(context.background_pages) == 0 + + +@pytest.mark.only_browser("chromium") +async def test_should_give_access_to_the_service_worker_when_recording_video( + launch_persistent_context: Callable[..., Awaitable[BrowserContext]], + tmp_path: Path, + assetdir: Path, +) -> None: + extension_path = str(assetdir / "extension-mv3-simple") + context = await launch_persistent_context( + extension_path, record_video_dir=(tmp_path / "videos") + ) + service_workers = context.service_workers + service_worker = ( + service_workers[0] + if len(service_workers) + else await context.wait_for_event("serviceworker") + ) + assert service_worker + assert service_worker in context.service_workers + while not await service_worker.evaluate("globalThis.MAGIC") == 42: + await context.pages[0].wait_for_timeout(100) + await context.close() + assert len(context.background_pages) == 0 + + +# https://github.com/microsoft/playwright/issues/32762 +@pytest.mark.only_browser("chromium") +async def test_should_report_console_messages_from_content_script( + launch_persistent_context: Callable[..., Awaitable[BrowserContext]], + assetdir: Path, + server: Server, +) -> None: + extension_path = str(assetdir / "extension-mv3-with-logging") + context = await launch_persistent_context(extension_path) + page = await context.new_page() + [message, _] = await asyncio.gather( + page.context.wait_for_event( + "console", + lambda e: "Test console log from a third-party execution context" in e.text, + ), + page.goto(server.EMPTY_PAGE), + ) + assert "Test console log from a third-party execution context" in message.text + await context.close() diff --git a/tests/async/test_launcher.py b/tests/async/test_launcher.py index 1b974725b..bd5dd82de 100644 --- a/tests/async/test_launcher.py +++ b/tests/async/test_launcher.py @@ -15,7 +15,7 @@ import asyncio import os from pathlib import Path -from typing import Dict, Optional +from typing import Dict import pytest @@ -107,39 +107,3 @@ async def test_browser_close_should_be_callable_twice( browser.close(), ) await browser.close() - - -@pytest.mark.only_browser("chromium") -async def test_browser_launch_should_return_background_pages( - browser_type: BrowserType, - tmp_path: Path, - browser_channel: Optional[str], - assetdir: Path, - launch_arguments: Dict, -) -> None: - if browser_channel: - pytest.skip() - - extension_path = str(assetdir / "simple-extension") - context = await browser_type.launch_persistent_context( - str(tmp_path), - **{ - **launch_arguments, - "headless": False, - "args": [ - f"--disable-extensions-except={extension_path}", - f"--load-extension={extension_path}", - ], - }, - ) - background_page = None - if len(context.background_pages): - background_page = context.background_pages[0] - else: - background_page = await context.wait_for_event("backgroundpage") - assert background_page - assert background_page in context.background_pages - assert background_page not in context.pages - await context.close() - assert len(context.background_pages) == 0 - assert len(context.pages) == 0 diff --git a/tests/async/test_page_route.py b/tests/async/test_page_route.py index fecafdfba..b561af0a2 100644 --- a/tests/async/test_page_route.py +++ b/tests/async/test_page_route.py @@ -1128,6 +1128,23 @@ def glob_to_regex(pattern: str) -> re.Pattern: "http://playwright.dev/foo/", "http://playwright.dev/foo/bar?x=y", "./bar?x=y" ) + # Case insensitive matching + assert url_matches( + None, "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR" + ) + assert url_matches( + "http://ignored", + "https://playwright.dev/fooBAR", + "HtTpS://pLaYwRiGhT.dEv/fooBAR", + ) + # Path and search query are case-sensitive + assert not url_matches( + None, "https://playwright.dev/foobar", "https://playwright.dev/fooBAR" + ) + assert not url_matches( + None, "https://playwright.dev/foobar?a=b", "https://playwright.dev/foobar?A=B" + ) + # This is not supported, we treat ? as a query separator. assert not url_matches( None, diff --git a/tests/async/test_tracing.py b/tests/async/test_tracing.py index e902eafbd..cbd282820 100644 --- a/tests/async/test_tracing.py +++ b/tests/async/test_tracing.py @@ -385,6 +385,5 @@ async def test_should_show_tracing_group_in_action_list( re.compile(r"inner group 1"), re.compile(r"Click"), re.compile(r"inner group 2"), - re.compile(r"Is visible"), ] ) diff --git a/tests/sync/test_extension.py b/tests/sync/test_extension.py new file mode 100644 index 000000000..2cb8aee77 --- /dev/null +++ b/tests/sync/test_extension.py @@ -0,0 +1,101 @@ +# Copyright (c) Microsoft Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from typing import Any, Callable, Dict, Generator, List, Optional + +import pytest + +from playwright.sync_api import BrowserContext, BrowserType + + +@pytest.fixture() +def launch_persistent_context( + browser_type: BrowserType, + browser_channel: Optional[str], + tmp_path: Path, + launch_arguments: Dict[str, Any], + is_headless_shell: bool, +) -> Generator[Callable[..., BrowserContext], None, None]: + if browser_channel and browser_channel.startswith("chrome"): + pytest.skip( + "--load-extension is not supported in Chrome anymore. https://groups.google.com/a/chromium.org/g/chromium-extensions/c/1-g8EFx2BBY/m/S0ET5wPjCAAJ" + ) + if is_headless_shell: + pytest.skip("Headless Shell has no support for extensions") + + contexts: List[BrowserContext] = [] + + def launch(extension_path: str, **kwargs: Any) -> BrowserContext: + context = browser_type.launch_persistent_context( + str(tmp_path), + **launch_arguments, + **kwargs, + args=[ + f"--disable-extensions-except={extension_path}", + f"--load-extension={extension_path}", + ], + ) + contexts.append(context) + return context + + yield launch + + for context in contexts: + context.close() + + +@pytest.mark.only_browser("chromium") +def test_should_give_access_to_the_service_worker( + launch_persistent_context: Callable[..., BrowserContext], + assetdir: Path, +) -> None: + extension_path = str(assetdir / "extension-mv3-simple") + context = launch_persistent_context(extension_path) + service_workers = context.service_workers + service_worker = ( + service_workers[0] + if len(service_workers) + else context.wait_for_event("serviceworker") + ) + assert service_worker + assert service_worker in context.service_workers + while not service_worker.evaluate("globalThis.MAGIC") == 42: + context.pages[0].wait_for_timeout(100) + context.close() + assert len(context.background_pages) == 0 + + +@pytest.mark.only_browser("chromium") +def test_should_give_access_to_the_service_worker_when_recording_video( + launch_persistent_context: Callable[..., BrowserContext], + tmp_path: Path, + assetdir: Path, +) -> None: + extension_path = str(assetdir / "extension-mv3-simple") + context = launch_persistent_context( + extension_path, record_video_dir=(tmp_path / "videos") + ) + service_workers = context.service_workers + service_worker = ( + service_workers[0] + if len(service_workers) + else context.wait_for_event("serviceworker") + ) + assert service_worker + assert service_worker in context.service_workers + while not service_worker.evaluate("globalThis.MAGIC") == 42: + context.pages[0].wait_for_timeout(100) + context.close() + assert len(context.background_pages) == 0 diff --git a/tests/sync/test_launcher.py b/tests/sync/test_launcher.py index 52deeb827..2e5ec1573 100644 --- a/tests/sync/test_launcher.py +++ b/tests/sync/test_launcher.py @@ -14,7 +14,7 @@ import os from pathlib import Path -from typing import Dict, Optional +from typing import Dict import pytest @@ -88,39 +88,3 @@ def test_browser_close_should_be_callable_twice( browser = browser_type.launch(**launch_arguments) browser.close() browser.close() - - -@pytest.mark.only_browser("chromium") -def test_browser_launch_should_return_background_pages( - browser_type: BrowserType, - tmp_path: Path, - browser_channel: Optional[str], - assetdir: Path, - launch_arguments: Dict, -) -> None: - if browser_channel: - pytest.skip() - - extension_path = str(assetdir / "simple-extension") - context = browser_type.launch_persistent_context( - str(tmp_path), - **{ - **launch_arguments, - "headless": False, - "args": [ - f"--disable-extensions-except={extension_path}", - f"--load-extension={extension_path}", - ], - }, - ) - background_page = None - if len(context.background_pages): - background_page = context.background_pages[0] - else: - background_page = context.wait_for_event("backgroundpage") - assert background_page - assert background_page in context.background_pages - assert background_page not in context.pages - context.close() - assert len(context.background_pages) == 0 - assert len(context.pages) == 0 diff --git a/tests/sync/test_tracing.py b/tests/sync/test_tracing.py index 8d0eaa191..ce26600e5 100644 --- a/tests/sync/test_tracing.py +++ b/tests/sync/test_tracing.py @@ -386,6 +386,5 @@ def test_should_show_tracing_group_in_action_list( re.compile(r"inner group 1"), re.compile(r"Click"), re.compile(r"inner group 2"), - re.compile(r"Is visible"), ] ) From 4a03d717fa82f1daed6d5f1dab505e8cd06aa8f2 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 28 Aug 2025 11:27:29 +0200 Subject: [PATCH 21/45] chore(roll): roll Playwright to 1.55.0-beta-1756314050000 (#2960) --- ROLLING.md | 10 ++++++---- setup.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ROLLING.md b/ROLLING.md index f5f500a3f..811d7fcb3 100644 --- a/ROLLING.md +++ b/ROLLING.md @@ -5,10 +5,12 @@ * create virtual environment, if don't have one: `python -m venv env` * activate venv: `source env/bin/activate` * install all deps: - - `python -m pip install --upgrade pip` - - `pip install -r local-requirements.txt` - - `pre-commit install` - - `pip install -e .` +``` +python -m pip install --upgrade pip +pip install -r local-requirements.txt +pre-commit install +pip install -e . +``` * change driver version in `setup.py` * download new driver: `python -m build --wheel` * generate API: `./scripts/update_api.sh` diff --git a/setup.py b/setup.py index d147f3be7..543395520 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ import zipfile from typing import Dict -driver_version = "1.55.0" +driver_version = "1.55.0-beta-1756314050000" base_wheel_bundles = [ { From e93c23a33ab2b3b8c7168031986702ae39473d89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:52:38 +0200 Subject: [PATCH 22/45] build(deps): bump requests from 2.32.4 to 2.32.5 (#2962) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 2f4f0d488..ac7d4a464 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -15,7 +15,7 @@ pytest-repeat==0.9.4 pytest-rerunfailures==15.1 pytest-timeout==2.4.0 pytest-xdist==3.8.0 -requests==2.32.4 +requests==2.32.5 service_identity==24.2.0 twisted==25.5.0 types-pyOpenSSL==24.1.0.20240722 From 3763d818a09ad2f47f8be9a8e8c2c404ce2a3a2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 23:16:55 +0200 Subject: [PATCH 23/45] build(deps): bump actions/setup-python from 5 to 6 in the actions group (#2969) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/publish_docker.yml | 2 +- .github/workflows/test_docker.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 200b2a65a..65ba1f433 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" - name: Install dependencies & browsers @@ -82,7 +82,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies & browsers @@ -129,7 +129,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" - name: Install dependencies & browsers @@ -186,7 +186,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.10' - name: Install dependencies & browsers diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml index 7c2b73e13..7494f1abc 100644 --- a/.github/workflows/publish_docker.yml +++ b/.github/workflows/publish_docker.yml @@ -25,7 +25,7 @@ jobs: - name: Login to ACR via OIDC run: az acr login --name playwright - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" - name: Set up Docker QEMU for arm64 docker builds diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml index e5252e389..464eb3b46 100644 --- a/.github/workflows/test_docker.yml +++ b/.github/workflows/test_docker.yml @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" - name: Install dependencies From e43199cd3d108073ed90266f082fbc1749e0219a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:50:46 +0200 Subject: [PATCH 24/45] build(deps): bump pillow from 11.2.1 to 11.3.0 in the pip group (#2908) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index ac7d4a464..1dc49533b 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -4,7 +4,7 @@ build==1.3.0 flake8==7.2.0 mypy==1.17.1 objgraph==3.6.2 -Pillow==11.2.1 +Pillow==11.3.0 pixelmatch==0.3.0 pre-commit==3.5.0 pyOpenSSL==25.1.0 From 8ab311b6a692f5ff311f028bca5851cd5ae0ad3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:51:22 +0200 Subject: [PATCH 25/45] build(deps): bump pytest-cov from 6.2.1 to 6.3.0 (#2968) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- local-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local-requirements.txt b/local-requirements.txt index 1dc49533b..8a72b5745 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -10,7 +10,7 @@ pre-commit==3.5.0 pyOpenSSL==25.1.0 pytest==8.4.1 pytest-asyncio==1.1.0 -pytest-cov==6.2.1 +pytest-cov==6.3.0 pytest-repeat==0.9.4 pytest-rerunfailures==15.1 pytest-timeout==2.4.0 From 6fa9500cf051f9abb2b21af7725fa7d1e951a8bb Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 9 Sep 2025 11:51:24 +0200 Subject: [PATCH 26/45] chore: fix api generation under Python 3.13 (#2970) --- scripts/documentation_provider.py | 3 +++ scripts/generate_api.py | 1 + 2 files changed, 4 insertions(+) diff --git a/scripts/documentation_provider.py b/scripts/documentation_provider.py index 6ea931fac..a842a1aad 100644 --- a/scripts/documentation_provider.py +++ b/scripts/documentation_provider.py @@ -13,6 +13,7 @@ # limitations under the License. import json +import pathlib import re import subprocess from sys import stderr @@ -359,6 +360,8 @@ def serialize_python_type(self, value: Any, direction: str) -> str: match = re.match(r"^$", str_value) if match: return match.group(1) + if str_value == str(pathlib.Path): + return "pathlib.Path" match = re.match( r"playwright._impl._event_context_manager.EventContextManagerImpl\[playwright._impl.[^.]+.(.*)\]", str_value, diff --git a/scripts/generate_api.py b/scripts/generate_api.py index 01f8f525a..b0e7e2a32 100644 --- a/scripts/generate_api.py +++ b/scripts/generate_api.py @@ -57,6 +57,7 @@ def process_type(value: Any, param: bool = False) -> str: value = str(value) + value = re.sub("pathlib._local.Path", "pathlib.Path", value) value = re.sub(r"", r"\1", value) value = re.sub(r"NoneType", "None", value) value = re.sub(r"playwright\._impl\._api_structures.([\w]+)", r"\1", value) From db390c6e826029c0863a1dce5ca2cda44c54fede Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Mon, 6 Oct 2025 06:19:07 -0700 Subject: [PATCH 27/45] chore: roll to 1.56.0 (#2986) --- README.md | 4 +- playwright/_impl/_api_structures.py | 1 + playwright/_impl/_assertions.py | 4 +- playwright/_impl/_browser_context.py | 12 +---- playwright/_impl/_glob.py | 12 +---- playwright/_impl/_page.py | 20 +++++++- playwright/async_api/_generated.py | 65 +++++++++++++++++------- playwright/sync_api/_generated.py | 65 +++++++++++++++++------- setup.py | 2 +- tests/async/test_assertions.py | 8 +-- tests/async/test_console.py | 2 +- tests/async/test_page_aria_snapshot.py | 10 ++++ tests/async/test_page_event_console.py | 35 +++++++++++++ tests/async/test_page_event_pageerror.py | 36 +++++++++++++ tests/async/test_page_event_request.py | 62 ++++++++++++++++++++++ tests/async/test_page_route.py | 12 +++-- tests/sync/test_assertions.py | 8 +-- tests/sync/test_console.py | 5 +- 18 files changed, 286 insertions(+), 77 deletions(-) create mode 100644 tests/async/test_page_event_console.py create mode 100644 tests/async/test_page_event_pageerror.py create mode 100644 tests/async/test_page_event_request.py diff --git a/README.md b/README.md index cf85c6116..b54d5a364 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 140.0.7339.16 | ✅ | ✅ | ✅ | +| Chromium 141.0.7390.37 | ✅ | ✅ | ✅ | | WebKit 26.0 | ✅ | ✅ | ✅ | -| Firefox 141.0 | ✅ | ✅ | ✅ | +| Firefox 142.0.1 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/_api_structures.py b/playwright/_impl/_api_structures.py index 0afa0d02e..c0d0ee442 100644 --- a/playwright/_impl/_api_structures.py +++ b/playwright/_impl/_api_structures.py @@ -218,6 +218,7 @@ class FrameExpectResult(TypedDict): matches: bool received: Any log: List[str] + errorMessage: Optional[str] AriaRole = Literal[ diff --git a/playwright/_impl/_assertions.py b/playwright/_impl/_assertions.py index 3aadbf5fe..aea37d35c 100644 --- a/playwright/_impl/_assertions.py +++ b/playwright/_impl/_assertions.py @@ -80,8 +80,10 @@ async def _expect_impl( out_message = ( f"{message} '{expected}'" if expected is not None else f"{message}" ) + error_message = result.get("errorMessage") + error_message = f"\n{error_message}" if error_message else "" raise AssertionError( - f"{out_message}\nActual value: {actual} {format_call_log(result.get('log'))}" + f"{out_message}\nActual value: {actual}{error_message} {format_call_log(result.get('log'))}" ) diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py index 391e61ec6..bab7d1bf1 100644 --- a/playwright/_impl/_browser_context.py +++ b/playwright/_impl/_browser_context.py @@ -88,6 +88,7 @@ class BrowserContext(ChannelOwner): Events = SimpleNamespace( + # Deprecated in v1.56, never emitted anymore. BackgroundPage="backgroundpage", Close="close", Console="console", @@ -117,7 +118,6 @@ def __init__( self._timeout_settings = TimeoutSettings(None) self._owner_page: Optional[Page] = None self._options: Dict[str, Any] = initializer["options"] - self._background_pages: Set[Page] = set() self._service_workers: Set[Worker] = set() self._base_url: Optional[str] = self._options.get("baseURL") self._videos_dir: Optional[str] = self._options.get("recordVideo") @@ -149,10 +149,6 @@ def __init__( ) ), ) - self._channel.on( - "backgroundPage", - lambda params: self._on_background_page(from_channel(params["page"])), - ) self._channel.on( "serviceWorker", @@ -658,10 +654,6 @@ def expect_page( ) -> EventContextManagerImpl[Page]: return self.expect_event(BrowserContext.Events.Page, predicate, timeout) - def _on_background_page(self, page: Page) -> None: - self._background_pages.add(page) - self.emit(BrowserContext.Events.BackgroundPage, page) - def _on_service_worker(self, worker: Worker) -> None: worker._context = self self._service_workers.add(worker) @@ -736,7 +728,7 @@ def _on_response(self, response: Response, page: Optional[Page]) -> None: @property def background_pages(self) -> List[Page]: - return list(self._background_pages) + return [] @property def service_workers(self) -> List[Worker]: diff --git a/playwright/_impl/_glob.py b/playwright/_impl/_glob.py index 08b7ce466..a0e6dcd4b 100644 --- a/playwright/_impl/_glob.py +++ b/playwright/_impl/_glob.py @@ -28,20 +28,12 @@ def glob_to_regex_pattern(glob: str) -> str: tokens.append("\\" + char if char in escaped_chars else char) i += 1 elif c == "*": - before_deep = glob[i - 1] if i > 0 else None star_count = 1 while i + 1 < len(glob) and glob[i + 1] == "*": star_count += 1 i += 1 - after_deep = glob[i + 1] if i + 1 < len(glob) else None - is_deep = ( - star_count > 1 - and (before_deep == "/" or before_deep is None) - and (after_deep == "/" or after_deep is None) - ) - if is_deep: - tokens.append("((?:[^/]*(?:/|$))*)") - i += 1 + if star_count > 1: + tokens.append("(.*)") else: tokens.append("([^/]*)") else: diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 1019b2f6e..29a583a7c 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -79,6 +79,7 @@ async_writefile, locals_to_params, make_dirs_for_file, + parse_error, serialize_error, url_matches, ) @@ -344,8 +345,6 @@ def _on_close(self) -> None: self._is_closed = True if self in self._browser_context._pages: self._browser_context._pages.remove(self) - if self in self._browser_context._background_pages: - self._browser_context._background_pages.remove(self) self._dispose_har_routers() self.emit(Page.Events.Close, self) @@ -1434,6 +1433,23 @@ async def remove_locator_handler(self, locator: "Locator") -> None: {"uid": uid}, ) + async def requests(self) -> List[Request]: + request_objects = await self._channel.send("requests", None) + return [from_channel(r) for r in request_objects] + + async def console_messages(self) -> List[ConsoleMessage]: + message_dicts = await self._channel.send("consoleMessages", None) + return [ + ConsoleMessage( + {**event, "page": self._channel}, self._loop, self._dispatcher_fiber + ) + for event in message_dicts + ] + + async def page_errors(self) -> List[Error]: + error_objects = await self._channel.send("pageErrors", None) + return [parse_error(error["error"]) for error in error_objects] + class Worker(ChannelOwner): Events = SimpleNamespace(Close="close") diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index bdda2b2b0..71f5aff82 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -12254,6 +12254,49 @@ async def remove_locator_handler(self, locator: "Locator") -> None: await self._impl_obj.remove_locator_handler(locator=locator._impl_obj) ) + async def requests(self) -> typing.List["Request"]: + """Page.requests + + Returns up to (currently) 100 last network request from this page. See `page.on('request')` for more details. + + Returned requests should be accessed immediately, otherwise they might be collected to prevent unbounded memory + growth as new requests come in. Once collected, retrieving most information about the request is impossible. + + Note that requests reported through the `page.on('request')` request are not collected, so there is a trade off + between efficient memory usage with `page.requests()` and the amount of available information reported + through `page.on('request')`. + + Returns + ------- + List[Request] + """ + + return mapping.from_impl_list(await self._impl_obj.requests()) + + async def console_messages(self) -> typing.List["ConsoleMessage"]: + """Page.console_messages + + Returns up to (currently) 200 last console messages from this page. See `page.on('console')` for more details. + + Returns + ------- + List[ConsoleMessage] + """ + + return mapping.from_impl_list(await self._impl_obj.console_messages()) + + async def page_errors(self) -> typing.List["Error"]: + """Page.page_errors + + Returns up to (currently) 200 last page errors from this page. See `page.on('page_error')` for more details. + + Returns + ------- + List[Error] + """ + + return mapping.from_impl_list(await self._impl_obj.page_errors()) + mapping.register(PageImpl, Page) @@ -12297,13 +12340,7 @@ def on( f: typing.Callable[["Page"], "typing.Union[typing.Awaitable[None], None]"], ) -> None: """ - **NOTE** Only works with Chromium browser's persistent context. - - Emitted when new background page is created in the context. - - ```py - background_page = await context.wait_for_event(\"backgroundpage\") - ```""" + This event is not emitted.""" @typing.overload def on( @@ -12477,13 +12514,7 @@ def once( f: typing.Callable[["Page"], "typing.Union[typing.Awaitable[None], None]"], ) -> None: """ - **NOTE** Only works with Chromium browser's persistent context. - - Emitted when new background page is created in the context. - - ```py - background_page = await context.wait_for_event(\"backgroundpage\") - ```""" + This event is not emitted.""" @typing.overload def once( @@ -12679,9 +12710,7 @@ def browser(self) -> typing.Optional["Browser"]: def background_pages(self) -> typing.List["Page"]: """BrowserContext.background_pages - **NOTE** Background pages are only supported on Chromium-based browsers. - - All existing background pages in the context. + Returns an empty list. Returns ------- @@ -16617,7 +16646,7 @@ def and_(self, locator: "Locator") -> "Locator": The following example finds a button with a specific title. ```py - button = page.get_by_role(\"button\").and_(page.getByTitle(\"Subscribe\")) + button = page.get_by_role(\"button\").and_(page.get_by_title(\"Subscribe\")) ``` Parameters diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index 83fedfbe9..024014c51 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -12342,6 +12342,49 @@ def remove_locator_handler(self, locator: "Locator") -> None: self._sync(self._impl_obj.remove_locator_handler(locator=locator._impl_obj)) ) + def requests(self) -> typing.List["Request"]: + """Page.requests + + Returns up to (currently) 100 last network request from this page. See `page.on('request')` for more details. + + Returned requests should be accessed immediately, otherwise they might be collected to prevent unbounded memory + growth as new requests come in. Once collected, retrieving most information about the request is impossible. + + Note that requests reported through the `page.on('request')` request are not collected, so there is a trade off + between efficient memory usage with `page.requests()` and the amount of available information reported + through `page.on('request')`. + + Returns + ------- + List[Request] + """ + + return mapping.from_impl_list(self._sync(self._impl_obj.requests())) + + def console_messages(self) -> typing.List["ConsoleMessage"]: + """Page.console_messages + + Returns up to (currently) 200 last console messages from this page. See `page.on('console')` for more details. + + Returns + ------- + List[ConsoleMessage] + """ + + return mapping.from_impl_list(self._sync(self._impl_obj.console_messages())) + + def page_errors(self) -> typing.List["Error"]: + """Page.page_errors + + Returns up to (currently) 200 last page errors from this page. See `page.on('page_error')` for more details. + + Returns + ------- + List[Error] + """ + + return mapping.from_impl_list(self._sync(self._impl_obj.page_errors())) + mapping.register(PageImpl, Page) @@ -12383,13 +12426,7 @@ def on( self, event: Literal["backgroundpage"], f: typing.Callable[["Page"], "None"] ) -> None: """ - **NOTE** Only works with Chromium browser's persistent context. - - Emitted when new background page is created in the context. - - ```py - background_page = context.wait_for_event(\"backgroundpage\") - ```""" + This event is not emitted.""" @typing.overload def on( @@ -12529,13 +12566,7 @@ def once( self, event: Literal["backgroundpage"], f: typing.Callable[["Page"], "None"] ) -> None: """ - **NOTE** Only works with Chromium browser's persistent context. - - Emitted when new background page is created in the context. - - ```py - background_page = context.wait_for_event(\"backgroundpage\") - ```""" + This event is not emitted.""" @typing.overload def once( @@ -12701,9 +12732,7 @@ def browser(self) -> typing.Optional["Browser"]: def background_pages(self) -> typing.List["Page"]: """BrowserContext.background_pages - **NOTE** Background pages are only supported on Chromium-based browsers. - - All existing background pages in the context. + Returns an empty list. Returns ------- @@ -16680,7 +16709,7 @@ def and_(self, locator: "Locator") -> "Locator": The following example finds a button with a specific title. ```py - button = page.get_by_role(\"button\").and_(page.getByTitle(\"Subscribe\")) + button = page.get_by_role(\"button\").and_(page.get_by_title(\"Subscribe\")) ``` Parameters diff --git a/setup.py b/setup.py index 543395520..c2a56354b 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ import zipfile from typing import Dict -driver_version = "1.55.0-beta-1756314050000" +driver_version = "1.56.0-beta-1759412259000" base_wheel_bundles = [ { diff --git a/tests/async/test_assertions.py b/tests/async/test_assertions.py index 3213e5523..49e3c3e7f 100644 --- a/tests/async/test_assertions.py +++ b/tests/async/test_assertions.py @@ -516,7 +516,7 @@ async def test_to_have_values_fails_when_multiple_not_specified( ) locator = page.locator("select") await locator.select_option(["B"]) - with pytest.raises(Error) as excinfo: + with pytest.raises(AssertionError) as excinfo: await expect(locator).to_have_values(["R", "G"], timeout=500) assert "Error: Not a select element with a multiple attribute" in str(excinfo.value) @@ -530,7 +530,7 @@ async def test_to_have_values_fails_when_not_a_select_element( """ ) locator = page.locator("input") - with pytest.raises(Error) as excinfo: + with pytest.raises(AssertionError) as excinfo: await expect(locator).to_have_values(["R", "G"], timeout=500) assert "Error: Not a select element with a multiple attribute" in str(excinfo.value) @@ -564,7 +564,7 @@ async def test_assertions_boolean_checked_with_intermediate_true_and_checked( await page.set_content("") await page.locator("input").evaluate("e => e.indeterminate = true") with pytest.raises( - Error, match="Can't assert indeterminate and checked at the same time" + AssertionError, match="Can't assert indeterminate and checked at the same time" ): await expect(page.locator("input")).to_be_checked( checked=False, indeterminate=True @@ -658,7 +658,7 @@ async def test_assertions_locator_to_be_editable_throws( await page.goto(server.EMPTY_PAGE) await page.set_content("") with pytest.raises( - Error, + AssertionError, match=r"Element is not an ,