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 ,