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 ,