Skip to content

chore: roll to 1.54.0 #2913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->138.0.7204.23<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->18.5<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->139.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->139.0.7258.5<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->140.0.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
15 changes: 14 additions & 1 deletion playwright/_impl/_api_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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):
Expand Down Expand Up @@ -97,7 +110,7 @@ class ProxySettings(TypedDict, total=False):


class StorageState(TypedDict, total=False):
cookies: List[Cookie]
cookies: List[StorageStateCookie]
origins: List[OriginState]


Expand Down
26 changes: 24 additions & 2 deletions playwright/_impl/_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
AriaRole,
ExpectedTextValue,
FrameExpectOptions,
FrameExpectResult,
)
from playwright._impl._connection import format_call_log
from playwright._impl._errors import Error
Expand All @@ -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,
Expand All @@ -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:
Expand All @@ -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(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
11 changes: 6 additions & 5 deletions playwright/_impl/_browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()

Expand Down
23 changes: 21 additions & 2 deletions playwright/_impl/_console_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
52 changes: 46 additions & 6 deletions playwright/_impl/_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -56,6 +62,7 @@
Serializable,
add_source_url_to_script,
parse_result,
parse_value,
serialize_argument,
)
from playwright._impl._locator import (
Expand Down Expand Up @@ -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,
Expand All @@ -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,
)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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()))

Expand Down Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions playwright/_impl/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
21 changes: 4 additions & 17 deletions playwright/_impl/_locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 7 additions & 4 deletions playwright/_impl/_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading
Loading