diff --git a/README.md b/README.md index 1efcead54..9a5529b13 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 131.0.6778.33 | ✅ | ✅ | ✅ | +| Chromium 133.0.6943.16 | ✅ | ✅ | ✅ | | WebKit 18.2 | ✅ | ✅ | ✅ | -| Firefox 132.0 | ✅ | ✅ | ✅ | +| Firefox 134.0 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/_assertions.py b/playwright/_impl/_assertions.py index b226e241f..8ec657531 100644 --- a/playwright/_impl/_assertions.py +++ b/playwright/_impl/_assertions.py @@ -525,14 +525,22 @@ async def to_be_checked( self, timeout: float = None, checked: bool = None, + indeterminate: bool = None, ) -> None: __tracebackhide__ = True - if checked is None: - checked = True - checked_string = "checked" if checked else "unchecked" + expected_value = {} + if indeterminate is not None: + expected_value["indeterminate"] = indeterminate + if checked is not None: + expected_value["checked"] = checked + checked_string: str + if indeterminate: + checked_string = "indeterminate" + else: + checked_string = "unchecked" if checked is False else "checked" await self._expect_impl( - ("to.be.checked" if checked else "to.be.unchecked"), - FrameExpectOptions(timeout=timeout), + "to.be.checked", + FrameExpectOptions(timeout=timeout, expectedValue=expected_value), None, f"Locator expected to be {checked_string}", ) @@ -726,7 +734,9 @@ async def to_have_accessible_description( timeout: float = None, ) -> None: __tracebackhide__ = True - expected_values = to_expected_text_values([description], ignoreCase=ignoreCase) + expected_values = to_expected_text_values( + [description], ignoreCase=ignoreCase, normalize_white_space=True + ) await self._expect_impl( "to.have.accessible.description", FrameExpectOptions(expectedText=expected_values, timeout=timeout), @@ -750,7 +760,9 @@ async def to_have_accessible_name( timeout: float = None, ) -> None: __tracebackhide__ = True - expected_values = to_expected_text_values([name], ignoreCase=ignoreCase) + expected_values = to_expected_text_values( + [name], ignoreCase=ignoreCase, normalize_white_space=True + ) await self._expect_impl( "to.have.accessible.name", FrameExpectOptions(expectedText=expected_values, timeout=timeout), @@ -779,6 +791,34 @@ async def to_have_role(self, role: AriaRole, timeout: float = None) -> None: "Locator expected to have accessible role", ) + async def to_have_accessible_error_message( + self, + errorMessage: Union[str, Pattern[str]], + ignoreCase: bool = None, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + expected_values = to_expected_text_values( + [errorMessage], ignoreCase=ignoreCase, normalize_white_space=True + ) + await self._expect_impl( + "to.have.accessible.error.message", + FrameExpectOptions(expectedText=expected_values, timeout=timeout), + None, + "Locator expected to have accessible error message", + ) + + async def not_to_have_accessible_error_message( + self, + errorMessage: Union[str, Pattern[str]], + ignoreCase: bool = None, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_accessible_error_message( + errorMessage=errorMessage, ignoreCase=ignoreCase, timeout=timeout + ) + async def not_to_have_role(self, role: AriaRole, timeout: float = None) -> None: __tracebackhide__ = True await self._not.to_have_role(role, timeout) diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index 97bb049e3..4b15531af 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -131,6 +131,7 @@ def __init__( self, parent: ChannelOwner, type: str, guid: str, initializer: Dict ) -> None: super().__init__(parent, type, guid, initializer) + self._channel.mark_as_internal_type() self._redirected_from: Optional["Request"] = from_nullable_channel( initializer.get("redirectedFrom") ) @@ -767,6 +768,7 @@ def __init__( self, parent: ChannelOwner, type: str, guid: str, initializer: Dict ) -> None: super().__init__(parent, type, guid, initializer) + self._channel.mark_as_internal_type() self._request: Request = from_channel(self._initializer["request"]) timing = self._initializer["timing"] self._request._timing["startTime"] = timing["startTime"] diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index e1480f5bf..7b92fbafb 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -6879,6 +6879,18 @@ async def pause_at(self, time: typing.Union[float, str, datetime.datetime]) -> N await page.clock.pause_at(\"2020-02-02\") ``` + For best results, install the clock before navigating the page and set it to a time slightly before the intended + test time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. + Once the page has fully loaded, you can safely use `clock.pause_at()` to pause the clock. + + ```py + # Initialize clock with some time before the test time and let the page load + # naturally. `Date.now` will progress as the timers fire. + await page.clock.install(time=datetime.datetime(2024, 12, 10, 8, 0, 0)) + await page.goto(\"http://localhost:3333\") + await page.clock.pause_at(datetime.datetime(2024, 12, 10, 10, 0, 0)) + ``` + Parameters ---------- time : Union[datetime.datetime, float, str] @@ -8036,7 +8048,7 @@ def set_default_timeout(self, timeout: float) -> None: Parameters ---------- timeout : float - Maximum time in milliseconds + Maximum time in milliseconds. Pass `0` to disable timeout. """ return mapping.from_maybe_impl( @@ -11497,8 +11509,6 @@ async def pdf( Returns the PDF buffer. - **NOTE** Generating a pdf is currently only supported in Chromium headless. - `page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call `page.emulate_media()` before calling `page.pdf()`: @@ -12750,7 +12760,7 @@ def set_default_timeout(self, timeout: float) -> None: Parameters ---------- timeout : float - Maximum time in milliseconds + Maximum time in milliseconds. Pass `0` to disable timeout. """ return mapping.from_maybe_impl( @@ -12858,9 +12868,13 @@ async def grant_permissions( Parameters ---------- permissions : Sequence[str] - A permission or an array of permissions to grant. Permissions can be one of the following values: + A list of permissions to grant. + + **NOTE** Supported permissions differ between browsers, and even between different versions of the same browser. + Any permission may stop working after an update. + + Here are some permissions that may be supported by some browsers: - `'accelerometer'` - - `'accessibility-events'` - `'ambient-light-sensor'` - `'background-sync'` - `'camera'` @@ -14161,9 +14175,9 @@ async def close(self, *, reason: typing.Optional[str] = None) -> None: In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the browser server. - **NOTE** This is similar to force quitting the browser. Therefore, you should call `browser_context.close()` - on any `BrowserContext`'s you explicitly created earlier with `browser.new_context()` **before** calling - `browser.close()`. + **NOTE** This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close + events, call `browser_context.close()` on any `BrowserContext` instances you explicitly created earlier + using `browser.new_context()` **before** calling `browser.close()`. The `Browser` object itself is considered to be disposed and cannot be used anymore. @@ -14346,7 +14360,7 @@ async def launch( channel : Union[str, None] Browser distribution channel. - Use "chromium" to [opt in to new headless mode](../browsers.md#opt-in-to-new-headless-mode). + Use "chromium" to [opt in to new headless mode](../browsers.md#chromium-new-headless-mode). Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to use branded [Google Chrome and Microsoft Edge](../browsers.md#google-chrome--microsoft-edge). @@ -14504,7 +14518,7 @@ async def launch_persistent_context( channel : Union[str, None] Browser distribution channel. - Use "chromium" to [opt in to new headless mode](../browsers.md#opt-in-to-new-headless-mode). + Use "chromium" to [opt in to new headless mode](../browsers.md#chromium-new-headless-mode). Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to use branded [Google Chrome and Microsoft Edge](../browsers.md#google-chrome--microsoft-edge). @@ -15522,7 +15536,6 @@ async def dispatch_event( You can also specify `JSHandle` as the property value if you want live objects to be passed into the event: ```py - # note you can only create data_transfer in chromium and firefox data_transfer = await page.evaluate_handle(\"new DataTransfer()\") await locator.dispatch_event(\"#source\", \"dragstart\", {\"dataTransfer\": data_transfer}) ``` @@ -16445,18 +16458,22 @@ def or_(self, locator: "Locator") -> "Locator": Creates a locator matching all elements that match one or both of the two locators. - Note that when both locators match something, the resulting locator will have multiple matches and violate - [locator strictness](https://playwright.dev/python/docs/locators#strictness) guidelines. + Note that when both locators match something, the resulting locator will have multiple matches, potentially causing + a [locator strictness](https://playwright.dev/python/docs/locators#strictness) violation. **Usage** Consider a scenario where you'd like to click on a \"New email\" button, but sometimes a security settings dialog shows up instead. In this case, you can wait for either a \"New email\" button, or a dialog and act accordingly. + **NOTE** If both \"New email\" button and security dialog appear on screen, the \"or\" locator will match both of them, + possibly throwing the [\"strict mode violation\" error](https://playwright.dev/python/docs/locators#strictness). In this case, you can use + `locator.first()` to only match one of them. + ```py new_email = page.get_by_role(\"button\", name=\"New\") dialog = page.get_by_text(\"Confirm security settings\") - await expect(new_email.or_(dialog)).to_be_visible() + await expect(new_email.or_(dialog).first).to_be_visible() if (await dialog.is_visible()): await page.get_by_role(\"button\", name=\"Dismiss\").click() await new_email.click() @@ -16877,7 +16894,9 @@ async def is_disabled(self, *, timeout: typing.Optional[float] = None) -> bool: async def is_editable(self, *, timeout: typing.Optional[float] = None) -> bool: """Locator.is_editable - Returns whether the element is [editable](https://playwright.dev/python/docs/actionability#editable). + Returns whether the element is [editable](https://playwright.dev/python/docs/actionability#editable). If the target element is not an ``, + `