diff --git a/playwright/_impl/_api_structures.py b/playwright/_impl/_api_structures.py index 8e3ad3117..4d9f3fa72 100644 --- a/playwright/_impl/_api_structures.py +++ b/playwright/_impl/_api_structures.py @@ -13,7 +13,7 @@ # limitations under the License. import sys -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union if sys.version_info >= (3, 8): # pragma: no cover from typing import Literal, TypedDict @@ -171,3 +171,27 @@ class FormField(TypedDict, total=False): name: str value: Optional[str] file: Optional[ServerFilePayload] + + +class ExpectedTextValue(TypedDict, total=False): + string: str + regexSource: str + regexFlags: str + matchSubstring: bool + normalizeWhiteSpace: bool + + +class FrameExpectOptions(TypedDict, total=False): + expressionArg: Any + expectedText: Optional[List[ExpectedTextValue]] + expectedNumber: Optional[int] + expectedValue: Optional[Any] + useInnerText: Optional[bool] + isNot: bool + timeout: Optional[float] + + +class FrameExpectResult(TypedDict): + matches: bool + received: Any + log: List[str] diff --git a/playwright/_impl/_assertions.py b/playwright/_impl/_assertions.py new file mode 100644 index 000000000..9b2160a77 --- /dev/null +++ b/playwright/_impl/_assertions.py @@ -0,0 +1,578 @@ +# 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 re +from typing import Any, List, Pattern, Union +from urllib.parse import urljoin + +from playwright._impl._api_structures import ExpectedTextValue, FrameExpectOptions +from playwright._impl._locator import Locator +from playwright._impl._page import Page + + +class AssertionsBase: + def __init__(self, locator: Locator, is_not: bool = False) -> None: + self._actual_locator = locator + self._loop = locator._loop + self._is_not = is_not + + async def _expect_impl( + self, + expression: str, + expect_options: FrameExpectOptions, + expected: Any, + message: str, + ) -> None: + __tracebackhide__ = True + expect_options["isNot"] = self._is_not + if expect_options.get("timeout") is None: + expect_options["timeout"] = 5_000 + if expect_options["isNot"]: + 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) + if result["matches"] == self._is_not: + log = "\n".join(result.get("log", "")).strip() + if log: + log = "\nCall log:\n" + log + if expected is not None: + raise AssertionError(f"{message} '{expected}' {log}") + raise AssertionError(f"{message} {log}") + + +class PageAssertions(AssertionsBase): + def __init__(self, page: Page, is_not: bool = False) -> None: + super().__init__(page.locator(":root"), is_not) + self._actual_page = page + + @property + def _not(self) -> "PageAssertions": + return PageAssertions(self._actual_page, not self._is_not) + + async def to_have_title( + self, title_or_reg_exp: Union[Pattern, str], timeout: float = None + ) -> None: + expected_values = to_expected_text_values( + [title_or_reg_exp], normalize_white_space=True + ) + __tracebackhide__ = True + await self._expect_impl( + "to.have.title", + FrameExpectOptions(expectedText=expected_values, timeout=timeout), + title_or_reg_exp, + "Page title expected to be", + ) + + async def not_to_have_title( + self, title_or_reg_exp: Union[Pattern, str], timeout: float = None + ) -> None: + __tracebackhide__ = True + await self._not.to_have_title(title_or_reg_exp, timeout) + + async def to_have_url( + self, url_or_reg_exp: Union[str, Pattern], timeout: float = None + ) -> None: + __tracebackhide__ = True + base_url = self._actual_page.context._options.get("baseURL") + if isinstance(url_or_reg_exp, str) and base_url: + url_or_reg_exp = urljoin(base_url, url_or_reg_exp) + expected_text = to_expected_text_values([url_or_reg_exp]) + await self._expect_impl( + "to.have.url", + FrameExpectOptions(expectedText=expected_text, timeout=timeout), + url_or_reg_exp, + "Page URL expected to be", + ) + + async def not_to_have_url( + self, url_or_reg_exp: Union[Pattern, str], timeout: float = None + ) -> None: + __tracebackhide__ = True + await self._not.to_have_url(url_or_reg_exp, timeout) + + +class LocatorAssertions(AssertionsBase): + def __init__(self, locator: Locator, is_not: bool = False) -> None: + super().__init__(locator, is_not) + self._actual_locator = locator + + @property + def _not(self) -> "LocatorAssertions": + return LocatorAssertions(self._actual_locator, not self._is_not) + + async def to_contain_text( + self, + expected: Union[List[Pattern], List[str], Pattern, str], + use_inner_text: bool = None, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + if isinstance(expected, list): + expected_text = to_expected_text_values( + expected, match_substring=True, normalize_white_space=True + ) + await self._expect_impl( + "to.contain.text.array", + FrameExpectOptions( + expectedText=expected_text, + useInnerText=use_inner_text, + timeout=timeout, + ), + expected, + "Locator expected to contain text", + ) + else: + expected_text = to_expected_text_values( + [expected], match_substring=True, normalize_white_space=True + ) + await self._expect_impl( + "to.have.text", + FrameExpectOptions( + expectedText=expected_text, + useInnerText=use_inner_text, + timeout=timeout, + ), + expected, + "Locator expected to contain text", + ) + + async def not_to_contain_text( + self, + expected: Union[List[Pattern], List[str], Pattern, str], + use_inner_text: bool = None, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_contain_text(expected, use_inner_text, timeout) + + async def to_have_attribute( + self, + name: str, + value: Union[str, Pattern], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + expected_text = to_expected_text_values([value]) + await self._expect_impl( + "to.have.attribute", + FrameExpectOptions( + expressionArg=name, expectedText=expected_text, timeout=timeout + ), + value, + "Locator expected to have attribute", + ) + + async def not_to_have_attribute( + self, + name: str, + value: Union[str, Pattern], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_attribute(name, value, timeout) + + async def to_have_class( + self, + expected: Union[List[Pattern], List[str], Pattern, str], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + if isinstance(expected, list): + expected_text = to_expected_text_values(expected) + await self._expect_impl( + "to.have.class.array", + FrameExpectOptions(expectedText=expected_text, timeout=timeout), + expected, + "Locator expected to have class", + ) + else: + expected_text = to_expected_text_values([expected]) + await self._expect_impl( + "to.have.class", + FrameExpectOptions(expectedText=expected_text, timeout=timeout), + expected, + "Locator expected to have class", + ) + + async def not_to_have_class( + self, + expected: Union[List[Pattern], List[str], Pattern, str], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_class(expected, timeout) + + async def to_have_count( + self, + count: int, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.have.count", + FrameExpectOptions(expectedNumber=count, timeout=timeout), + count, + "Locator expected to have count", + ) + + async def not_to_have_count( + self, + count: int, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_count(count, timeout) + + async def to_have_css( + self, + name: str, + value: Union[str, Pattern], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + expected_text = to_expected_text_values([value]) + await self._expect_impl( + "to.have.css", + FrameExpectOptions( + expressionArg=name, expectedText=expected_text, timeout=timeout + ), + value, + "Locator expected to have CSS", + ) + + async def not_to_have_css( + self, + name: str, + value: Union[str, Pattern], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_css(name, value, timeout) + + async def to_have_id( + self, + id: Union[str, Pattern], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + expected_text = to_expected_text_values([id]) + await self._expect_impl( + "to.have.id", + FrameExpectOptions(expectedText=expected_text, timeout=timeout), + id, + "Locator expected to have ID", + ) + + async def not_to_have_id( + self, + id: Union[str, Pattern], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_id(id, timeout) + + async def to_have_js_property( + self, + name: str, + value: Any, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.have.property", + FrameExpectOptions( + expressionArg=name, expectedValue=value, timeout=timeout + ), + value, + "Locator expected to have JS Property", + ) + + async def not_to_have_js_property( + self, + name: str, + value: Any, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_js_property(name, value, timeout) + + async def to_have_value( + self, + value: Union[str, Pattern], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + expected_text = to_expected_text_values([value]) + await self._expect_impl( + "to.have.value", + FrameExpectOptions(expectedText=expected_text, timeout=timeout), + value, + "Locator expected to have Value", + ) + + async def not_to_have_value( + self, + value: Union[str, Pattern], + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_value(value, timeout) + + async def to_have_text( + self, + expected: Union[List[Pattern], List[str], Pattern, str], + use_inner_text: bool = None, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + if isinstance(expected, list): + expected_text = to_expected_text_values( + expected, normalize_white_space=True + ) + await self._expect_impl( + "to.have.text.array", + FrameExpectOptions( + expectedText=expected_text, + useInnerText=use_inner_text, + timeout=timeout, + ), + expected, + "Locator expected to have text", + ) + else: + expected_text = to_expected_text_values( + [expected], normalize_white_space=True + ) + await self._expect_impl( + "to.have.text", + FrameExpectOptions( + expectedText=expected_text, + useInnerText=use_inner_text, + timeout=timeout, + ), + expected, + "Locator expected to have text", + ) + + async def not_to_have_text( + self, + expected: Union[List[Pattern], List[str], Pattern, str], + use_inner_text: bool = None, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_have_text(expected, use_inner_text, timeout) + + async def to_be_checked( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.be.checked", + FrameExpectOptions(timeout=timeout), + None, + "Locator expected to be checked", + ) + + async def not_to_be_checked( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_be_checked(timeout) + + async def to_be_disabled( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.be.disabled", + FrameExpectOptions(timeout=timeout), + None, + "Locator expected to be disabled", + ) + + async def not_to_be_disabled( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_be_disabled(timeout) + + async def to_be_editable( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.be.editable", + FrameExpectOptions(timeout=timeout), + None, + "Locator expected to be editable", + ) + + async def not_to_be_editable( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_be_editable(timeout) + + async def to_be_empty( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.be.empty", + FrameExpectOptions(timeout=timeout), + None, + "Locator expected to be empty", + ) + + async def not_to_be_empty( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_be_empty(timeout) + + async def to_be_enabled( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.be.enabled", + FrameExpectOptions(timeout=timeout), + None, + "Locator expected to be enabled", + ) + + async def not_to_be_enabled( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_be_enabled(timeout) + + async def to_be_hidden( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.be.hidden", + FrameExpectOptions(timeout=timeout), + None, + "Locator expected to be hidden", + ) + + async def not_to_be_hidden( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_be_hidden(timeout) + + async def to_be_visible( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.be.visible", + FrameExpectOptions(timeout=timeout), + None, + "Locator expected to be visible", + ) + + async def not_to_be_visible( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_be_visible(timeout) + + async def to_be_focused( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._expect_impl( + "to.be.focused", + FrameExpectOptions(timeout=timeout), + None, + "Locator expected to be focused", + ) + + async def not_to_be_focused( + self, + timeout: float = None, + ) -> None: + __tracebackhide__ = True + await self._not.to_be_focused(timeout) + + +def expected_regex( + pattern: Pattern, match_substring: bool, normalize_white_space: bool +) -> ExpectedTextValue: + expected = ExpectedTextValue( + regexSource=pattern.pattern, + matchSubstring=match_substring, + normalizeWhiteSpace=normalize_white_space, + ) + if pattern.flags != 0: + expected["regexFlags"] = "" + if (pattern.flags & int(re.IGNORECASE)) != 0: + expected["regexFlags"] += "i" + if (pattern.flags & int(re.DOTALL)) != 0: + expected["regexFlags"] += "s" + if (pattern.flags & int(re.MULTILINE)) != 0: + expected["regexFlags"] += "m" + assert ( + pattern.flags + & ~( + int(re.MULTILINE) + | int(re.IGNORECASE) + | int(re.DOTALL) + | int(re.UNICODE) + ) + == 0 + ), "Unexpected re.Pattern flag, only MULTILINE, IGNORECASE and DOTALL are supported." + return expected + + +def to_expected_text_values( + items: Union[List[Pattern], List[str], List[Union[str, Pattern]]], + match_substring: bool = False, + normalize_white_space: bool = False, +) -> List[ExpectedTextValue]: + out: List[ExpectedTextValue] = [] + assert isinstance(items, list) + for item in items: + if isinstance(item, str): + out.append( + ExpectedTextValue( + string=item, + matchSubstring=match_substring, + normalizeWhiteSpace=normalize_white_space, + ) + ) + elif isinstance(item, Pattern): + out.append(expected_regex(item, match_substring, normalize_white_space)) + return out diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index 0c8353a47..b7cd3601b 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -26,7 +26,13 @@ Union, ) -from playwright._impl._api_structures import FilePayload, FloatRect, Position +from playwright._impl._api_structures import ( + FilePayload, + FloatRect, + FrameExpectOptions, + FrameExpectResult, + Position, +) from playwright._impl._element_handle import ElementHandle from playwright._impl._helper import ( Error, @@ -35,7 +41,7 @@ locals_to_params, monotonic_time, ) -from playwright._impl._js_handle import Serializable +from playwright._impl._js_handle import Serializable, parse_value, serialize_argument if sys.version_info >= (3, 8): # pragma: no cover from typing import Literal @@ -475,6 +481,23 @@ async def set_checked( trial=trial, ) + async def _expect( + self, expression: str, options: FrameExpectOptions + ) -> FrameExpectResult: + if "expectedValue" in options: + options["expectedValue"] = serialize_argument(options["expectedValue"]) + result = await self._frame._channel.send_return_as_dict( + "expect", + { + "selector": self._selector, + "expression": expression, + **options, + }, + ) + if result.get("received"): + result["received"] = parse_value(result["received"]) + return result + class FrameLocator: def __init__(self, frame: "Frame", frame_selector: str) -> None: diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 1386fe27c..e9bcd38f0 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -83,6 +83,7 @@ if TYPE_CHECKING: # pragma: no cover from playwright._impl._browser_context import BrowserContext + from playwright._impl._fetch import APIRequestContext from playwright._impl._locator import FrameLocator, Locator from playwright._impl._network import WebSocket @@ -817,6 +818,10 @@ async def wait_for_function( def workers(self) -> List["Worker"]: return self._workers.copy() + @property + def request(self) -> "APIRequestContext": + return self.context.request + async def pause(self) -> None: await self._browser_context._pause() diff --git a/playwright/_impl/_path_utils.py b/playwright/_impl/_path_utils.py index 8be1e79ec..267a82ab0 100644 --- a/playwright/_impl/_path_utils.py +++ b/playwright/_impl/_path_utils.py @@ -21,4 +21,5 @@ def get_file_dirname() -> Path: frame = inspect.stack()[1] module = inspect.getmodule(frame[0]) assert module + assert module.__file__ return Path(module.__file__).parent.absolute() diff --git a/playwright/async_api/__init__.py b/playwright/async_api/__init__.py index 26d4364b3..a0146d884 100644 --- a/playwright/async_api/__init__.py +++ b/playwright/async_api/__init__.py @@ -18,9 +18,13 @@ web automation that is ever-green, capable, reliable and fast. """ +from typing import Union, overload + import playwright._impl._api_structures import playwright._impl._api_types import playwright.async_api._generated +from playwright._impl._assertions import LocatorAssertions as LocatorAssertionsImpl +from playwright._impl._assertions import PageAssertions as PageAssertionsImpl from playwright.async_api._context_manager import PlaywrightContextManager from playwright.async_api._generated import ( Accessibility, @@ -40,8 +44,10 @@ JSHandle, Keyboard, Locator, + LocatorAssertions, Mouse, Page, + PageAssertions, Playwright, Request, Response, @@ -76,7 +82,28 @@ def async_playwright() -> PlaywrightContextManager: return PlaywrightContextManager() +@overload +def expect(page_or_locator: Page) -> PageAssertions: + ... + + +@overload +def expect(page_or_locator: Locator) -> LocatorAssertions: + ... + + +def expect( + page_or_locator: Union[Page, Locator] +) -> Union[PageAssertions, LocatorAssertions]: + if isinstance(page_or_locator, Page): + return PageAssertions(PageAssertionsImpl(page_or_locator._impl_obj)) + elif isinstance(page_or_locator, Locator): + return LocatorAssertions(LocatorAssertionsImpl(page_or_locator._impl_obj)) + raise ValueError(f"Unsupported type: {type(page_or_locator)}") + + __all__ = [ + "expect", "async_playwright", "Accessibility", "APIRequest", diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 21a468c3f..c5b3373af 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -43,6 +43,8 @@ ViewportSize, ) from playwright._impl._api_types import Error +from playwright._impl._assertions import LocatorAssertions as LocatorAssertionsImpl +from playwright._impl._assertions import PageAssertions as PageAssertionsImpl from playwright._impl._async_base import ( AsyncBase, AsyncContextManager, @@ -3273,7 +3275,7 @@ async def evaluate(self, expression: str, arg: typing.Any = None) -> typing.Any: `ElementHandle` instances can be passed as an argument to the `frame.evaluate()`: ```py - body_handle = await frame.query_selector(\"body\") + body_handle = await frame.evaluate(\"document.body\") html = await frame.evaluate(\"([body, suffix]) => body.innerHTML + suffix\", [body_handle, \"hello\"]) await body_handle.dispose() ``` @@ -3362,6 +3364,8 @@ async def query_selector( Returns the ElementHandle pointing to the frame element. + > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects and web-first assertions instead. + The method finds an element matching the specified selector within the frame. See [Working with selectors](./selectors.md) for more details. If no elements match the selector, returns `null`. @@ -3390,6 +3394,8 @@ async def query_selector_all(self, selector: str) -> typing.List["ElementHandle" Returns the ElementHandles pointing to the frame elements. + > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects instead. + The method finds all elements matching the specified selector within the frame. See [Working with selectors](./selectors.md) for more details. If no elements match the selector, returns empty array. @@ -3423,6 +3429,9 @@ async def wait_for_selector( Returns when element specified by selector satisfies `state` option. Returns `null` if waiting for `hidden` or `detached`. + > NOTE: Playwright automatically waits for element to be ready before performing an action. Using `Locator` objects and + web-first assertions make the code wait-for-selector-free. + Wait for the `selector` to satisfy `state` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw. @@ -3763,6 +3772,9 @@ async def eval_on_selector( Returns the return value of `expression`. + > NOTE: This method does not wait for the element to pass actionability checks and therefore can lead to the flaky + tests. Use `locator.evaluate()`, other `Locator` helper methods or web-first assertions instead. + The method finds an element matching the specified selector within the frame and passes it as a first argument to `expression`. See [Working with selectors](./selectors.md) for more details. If no elements match the selector, the method throws an error. @@ -3815,6 +3827,9 @@ async def eval_on_selector_all( Returns the return value of `expression`. + > NOTE: In most cases, `locator.evaluate_all()`, other `Locator` helper methods and web-first assertions do a + better job. + The method finds all elements matching the specified selector within the frame and passes an array of matched elements as a first argument to `expression`. See [Working with selectors](./selectors.md) for more details. @@ -5443,7 +5458,7 @@ async def run(playwright): # Combine it with other selector engines. await page.click('tag=div >> text=\"Click me\"') # Can use it in any methods supporting selectors. - button_count = await page.eval_on_selector_all('tag=button', 'buttons => buttons.length') + button_count = await page.locator('tag=button').count() print(button_count) await browser.close() @@ -6379,6 +6394,18 @@ def workers(self) -> typing.List["Worker"]: """ return mapping.from_impl_list(self._impl_obj.workers) + @property + def request(self) -> "APIRequestContext": + """Page.request + + API testing helper associated with this page. Requests made with this API will use page cookies. + + Returns + ------- + APIRequestContext + """ + return mapping.from_impl(self._impl_obj.request) + @property def video(self) -> typing.Optional["Video"]: """Page.video @@ -6486,8 +6513,10 @@ async def query_selector( ) -> typing.Optional["ElementHandle"]: """Page.query_selector + > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects and web-first assertions instead. + The method finds an element matching the specified selector within the page. If no elements match the selector, the - return value resolves to `null`. To wait for an element on the page, use `page.wait_for_selector()`. + return value resolves to `null`. To wait for an element on the page, use `locator.wait_for()`. Shortcut for main frame's `frame.query_selector()`. @@ -6514,6 +6543,8 @@ async def query_selector( async def query_selector_all(self, selector: str) -> typing.List["ElementHandle"]: """Page.query_selector_all + > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects and web-first assertions instead. + The method finds all elements matching the specified selector within the page. If no elements match the selector, the return value resolves to `[]`. @@ -6549,6 +6580,9 @@ async def wait_for_selector( Returns when element specified by selector satisfies `state` option. Returns `null` if waiting for `hidden` or `detached`. + > NOTE: Playwright automatically waits for element to be ready before performing an action. Using `Locator` objects and + web-first assertions make the code wait-for-selector-free. + Wait for the `selector` to satisfy `state` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw. @@ -6907,7 +6941,7 @@ async def evaluate(self, expression: str, arg: typing.Any = None) -> typing.Any: `ElementHandle` instances can be passed as an argument to the `page.evaluate()`: ```py - body_handle = await page.query_selector(\"body\") + body_handle = await page.evaluate(\"document.body\") html = await page.evaluate(\"([body, suffix]) => body.innerHTML + suffix\", [body_handle, \"hello\"]) await body_handle.dispose() ``` @@ -7001,6 +7035,9 @@ async def eval_on_selector( ) -> typing.Any: """Page.eval_on_selector + > NOTE: This method does not wait for the element to pass actionability checks and therefore can lead to the flaky + tests. Use `locator.evaluate()`, other `Locator` helper methods or web-first assertions instead. + The method finds an element matching the specified selector within the page and passes it as a first argument to `expression`. If no elements match the selector, the method throws an error. Returns the value of `expression`. @@ -7052,6 +7089,9 @@ async def eval_on_selector_all( ) -> typing.Any: """Page.eval_on_selector_all + > NOTE: In most cases, `locator.evaluate_all()`, other `Locator` helper methods and web-first assertions do a + better job. + The method finds all elements matching the specified selector within the page and passes an array of matched elements as a first argument to `expression`. Returns the result of `expression` invocation. @@ -9704,7 +9744,7 @@ def expect_response( return response.ok # or with a lambda - async with page.expect_response(lambda response: response.url == \"https://example.com\" and response.status === 200) as response_info: + async with page.expect_response(lambda response: response.url == \"https://example.com\" and response.status == 200) as response_info: await page.click(\"input\") response = response_info.value return response.ok @@ -14374,3 +14414,934 @@ async def new_context( mapping.register(APIRequestImpl, APIRequest) + + +class PageAssertions(AsyncBase): + async def to_have_title( + self, + title_or_reg_exp: typing.Union[typing.Pattern, str], + *, + timeout: float = None + ) -> NoneType: + """PageAssertions.to_have_title + + Ensures the page has the given title. + + Parameters + ---------- + title_or_reg_exp : Union[Pattern, str] + Expected title or RegExp. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "page_assertions.to_have_title", + self._impl_obj.to_have_title( + title_or_reg_exp=title_or_reg_exp, timeout=timeout + ), + ) + ) + + async def not_to_have_title( + self, + title_or_reg_exp: typing.Union[typing.Pattern, str], + *, + timeout: float = None + ) -> NoneType: + """PageAssertions.not_to_have_title + + The opposite of `page_assertions.to_have_title()`. + + Parameters + ---------- + title_or_reg_exp : Union[Pattern, str] + Expected title or RegExp. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "page_assertions.not_to_have_title", + self._impl_obj.not_to_have_title( + title_or_reg_exp=title_or_reg_exp, timeout=timeout + ), + ) + ) + + async def to_have_url( + self, + url_or_reg_exp: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """PageAssertions.to_have_url + + Ensures the page is navigated to the given URL. + + Parameters + ---------- + url_or_reg_exp : Union[Pattern, str] + Expected substring or RegExp. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "page_assertions.to_have_url", + self._impl_obj.to_have_url( + url_or_reg_exp=url_or_reg_exp, timeout=timeout + ), + ) + ) + + async def not_to_have_url( + self, + url_or_reg_exp: typing.Union[typing.Pattern, str], + *, + timeout: float = None + ) -> NoneType: + """PageAssertions.not_to_have_url + + The opposite of `page_assertions.to_have_url()`. + + Parameters + ---------- + url_or_reg_exp : Union[Pattern, str] + Expected substring or RegExp. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "page_assertions.not_to_have_url", + self._impl_obj.not_to_have_url( + url_or_reg_exp=url_or_reg_exp, timeout=timeout + ), + ) + ) + + +mapping.register(PageAssertionsImpl, PageAssertions) + + +class LocatorAssertions(AsyncBase): + async def to_contain_text( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + use_inner_text: bool = None, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_contain_text + + Ensures the `Locator` points to an element that contains the given text. You can use regular expressions for the value + as well. + + Note that if array is passed as an expected value, entire lists can be asserted: + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected substring or RegExp or a list of those. + use_inner_text : Union[bool, NoneType] + Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_contain_text", + self._impl_obj.to_contain_text( + expected=expected, use_inner_text=use_inner_text, timeout=timeout + ), + ) + ) + + async def not_to_contain_text( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + use_inner_text: bool = None, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_contain_text + + The opposite of `locator_assertions.to_contain_text()`. + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected substring or RegExp or a list of those. + use_inner_text : Union[bool, NoneType] + Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_contain_text", + self._impl_obj.not_to_contain_text( + expected=expected, use_inner_text=use_inner_text, timeout=timeout + ), + ) + ) + + async def to_have_attribute( + self, + name: str, + value: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_attribute + + Ensures the `Locator` points to an element with given attribute. + + Parameters + ---------- + name : str + Attribute name. + value : Union[Pattern, str] + Expected attribute value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_have_attribute", + self._impl_obj.to_have_attribute( + name=name, value=value, timeout=timeout + ), + ) + ) + + async def not_to_have_attribute( + self, + name: str, + value: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_attribute + + The opposite of `locator_assertions.to_have_attribute()`. + + Parameters + ---------- + name : str + Attribute name. + value : Union[Pattern, str] + Expected attribute value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_have_attribute", + self._impl_obj.not_to_have_attribute( + name=name, value=value, timeout=timeout + ), + ) + ) + + async def to_have_class( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_class + + Ensures the `Locator` points to an element with given CSS class. + + Note that if array is passed as an expected value, entire lists can be asserted: + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected class or RegExp or a list of those. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_have_class", + self._impl_obj.to_have_class(expected=expected, timeout=timeout), + ) + ) + + async def not_to_have_class( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_class + + The opposite of `locator_assertions.to_have_class()`. + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected class or RegExp or a list of those. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_have_class", + self._impl_obj.not_to_have_class(expected=expected, timeout=timeout), + ) + ) + + async def to_have_count(self, count: int, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_have_count + + Ensures the `Locator` resolves to an exact number of DOM nodes. + + Parameters + ---------- + count : int + Expected count. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_have_count", + self._impl_obj.to_have_count(count=count, timeout=timeout), + ) + ) + + async def not_to_have_count(self, count: int, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_have_count + + The opposite of `locator_assertions.to_have_count()`. + + Parameters + ---------- + count : int + Expected count. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_have_count", + self._impl_obj.not_to_have_count(count=count, timeout=timeout), + ) + ) + + async def to_have_css( + self, + name: str, + value: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_css + + Ensures the `Locator` resolves to an element with the given computed CSS style. + + Parameters + ---------- + name : str + CSS property name. + value : Union[Pattern, str] + CSS property value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_have_css", + self._impl_obj.to_have_css(name=name, value=value, timeout=timeout), + ) + ) + + async def not_to_have_css( + self, + name: str, + value: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_css + + The opposite of `locator_assertions.to_have_css()`. + + Parameters + ---------- + name : str + CSS property name. + value : Union[Pattern, str] + CSS property value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_have_css", + self._impl_obj.not_to_have_css(name=name, value=value, timeout=timeout), + ) + ) + + async def to_have_id( + self, id: typing.Union[str, typing.Pattern], *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_id + + Ensures the `Locator` points to an element with the given DOM Node ID. + + Parameters + ---------- + id : Union[Pattern, str] + Element id. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_have_id", + self._impl_obj.to_have_id(id=id, timeout=timeout), + ) + ) + + async def not_to_have_id( + self, id: typing.Union[str, typing.Pattern], *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_id + + The opposite of `locator_assertions.to_have_id()`. + + Parameters + ---------- + id : Union[Pattern, str] + Element id. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_have_id", + self._impl_obj.not_to_have_id(id=id, timeout=timeout), + ) + ) + + async def to_have_js_property( + self, name: str, value: typing.Any, *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_js_property + + Ensures the `Locator` points to an element with given JavaScript property. Note that this property can be of a primitive + type as well as a plain serializable JavaScript object. + + Parameters + ---------- + name : str + Property name. + value : Any + Property value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_have_js_property", + self._impl_obj.to_have_js_property( + name=name, value=mapping.to_impl(value), timeout=timeout + ), + ) + ) + + async def not_to_have_js_property( + self, name: str, value: typing.Any, *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_js_property + + The opposite of `locator_assertions.to_have_js_property()`. + + Parameters + ---------- + name : str + Property name. + value : Any + Property value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_have_js_property", + self._impl_obj.not_to_have_js_property( + name=name, value=mapping.to_impl(value), timeout=timeout + ), + ) + ) + + async def to_have_value( + self, value: typing.Union[str, typing.Pattern], *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_value + + Ensures the `Locator` points to an element with the given input value. You can use regular expressions for the value as + well. + + Parameters + ---------- + value : Union[Pattern, str] + Expected value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_have_value", + self._impl_obj.to_have_value(value=value, timeout=timeout), + ) + ) + + async def not_to_have_value( + self, value: typing.Union[str, typing.Pattern], *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_value + + The opposite of `locator_assertions.to_have_value()`. + + Parameters + ---------- + value : Union[Pattern, str] + Expected value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_have_value", + self._impl_obj.not_to_have_value(value=value, timeout=timeout), + ) + ) + + async def to_have_text( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + use_inner_text: bool = None, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_text + + Ensures the `Locator` points to an element with the given text. You can use regular expressions for the value as well. + + Note that if array is passed as an expected value, entire lists can be asserted: + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected substring or RegExp or a list of those. + use_inner_text : Union[bool, NoneType] + Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_have_text", + self._impl_obj.to_have_text( + expected=expected, use_inner_text=use_inner_text, timeout=timeout + ), + ) + ) + + async def not_to_have_text( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + use_inner_text: bool = None, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_text + + The opposite of `locator_assertions.to_have_text()`. + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected substring or RegExp or a list of those. + use_inner_text : Union[bool, NoneType] + Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_have_text", + self._impl_obj.not_to_have_text( + expected=expected, use_inner_text=use_inner_text, timeout=timeout + ), + ) + ) + + async def to_be_checked(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_checked + + Ensures the `Locator` points to a checked input. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_be_checked", + self._impl_obj.to_be_checked(timeout=timeout), + ) + ) + + async def not_to_be_checked(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_checked + + The opposite of `locator_assertions.to_be_checked()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_be_checked", + self._impl_obj.not_to_be_checked(timeout=timeout), + ) + ) + + async def to_be_disabled(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_disabled + + Ensures the `Locator` points to a disabled element. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_be_disabled", + self._impl_obj.to_be_disabled(timeout=timeout), + ) + ) + + async def not_to_be_disabled(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_disabled + + The opposite of `locator_assertions.to_be_disabled()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_be_disabled", + self._impl_obj.not_to_be_disabled(timeout=timeout), + ) + ) + + async def to_be_editable(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_editable + + Ensures the `Locator` points to an editable element. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_be_editable", + self._impl_obj.to_be_editable(timeout=timeout), + ) + ) + + async def not_to_be_editable(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_editable + + The opposite of `locator_assertions.to_be_editable()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_be_editable", + self._impl_obj.not_to_be_editable(timeout=timeout), + ) + ) + + async def to_be_empty(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_empty + + Ensures the `Locator` points to an empty editable element or to a DOM node that has no text. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_be_empty", + self._impl_obj.to_be_empty(timeout=timeout), + ) + ) + + async def not_to_be_empty(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_empty + + The opposite of `locator_assertions.to_be_empty()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_be_empty", + self._impl_obj.not_to_be_empty(timeout=timeout), + ) + ) + + async def to_be_enabled(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_enabled + + Ensures the `Locator` points to an enabled element. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_be_enabled", + self._impl_obj.to_be_enabled(timeout=timeout), + ) + ) + + async def not_to_be_enabled(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_enabled + + The opposite of `locator_assertions.to_be_enabled()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_be_enabled", + self._impl_obj.not_to_be_enabled(timeout=timeout), + ) + ) + + async def to_be_hidden(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_hidden + + Ensures the `Locator` points to a hidden DOM node, which is the opposite of [visible](./actionability.md#visible). + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_be_hidden", + self._impl_obj.to_be_hidden(timeout=timeout), + ) + ) + + async def not_to_be_hidden(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_hidden + + The opposite of `locator_assertions.to_be_hidden()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_be_hidden", + self._impl_obj.not_to_be_hidden(timeout=timeout), + ) + ) + + async def to_be_visible(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_visible + + Ensures the `Locator` points to a [visible](./actionability.md#visible) DOM node. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_be_visible", + self._impl_obj.to_be_visible(timeout=timeout), + ) + ) + + async def not_to_be_visible(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_visible + + The opposite of `locator_assertions.to_be_visible()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_be_visible", + self._impl_obj.not_to_be_visible(timeout=timeout), + ) + ) + + async def to_be_focused(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_focused + + Ensures the `Locator` points to a focused DOM node. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.to_be_focused", + self._impl_obj.to_be_focused(timeout=timeout), + ) + ) + + async def not_to_be_focused(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_focused + + The opposite of `locator_assertions.to_be_focused()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + await self._async( + "locator_assertions.not_to_be_focused", + self._impl_obj.not_to_be_focused(timeout=timeout), + ) + ) + + +mapping.register(LocatorAssertionsImpl, LocatorAssertions) diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index ab911c82f..25ac519ad 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -43,6 +43,8 @@ ViewportSize, ) from playwright._impl._api_types import Error +from playwright._impl._assertions import LocatorAssertions as LocatorAssertionsImpl +from playwright._impl._assertions import PageAssertions as PageAssertionsImpl from playwright._impl._browser import Browser as BrowserImpl from playwright._impl._browser_context import BrowserContext as BrowserContextImpl from playwright._impl._browser_type import BrowserType as BrowserTypeImpl @@ -3222,7 +3224,7 @@ def evaluate(self, expression: str, arg: typing.Any = None) -> typing.Any: `ElementHandle` instances can be passed as an argument to the `frame.evaluate()`: ```py - body_handle = frame.query_selector(\"body\") + body_handle = frame.evaluate(\"document.body\") html = frame.evaluate(\"([body, suffix]) => body.innerHTML + suffix\", [body_handle, \"hello\"]) body_handle.dispose() ``` @@ -3309,6 +3311,8 @@ def query_selector( Returns the ElementHandle pointing to the frame element. + > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects and web-first assertions instead. + The method finds an element matching the specified selector within the frame. See [Working with selectors](./selectors.md) for more details. If no elements match the selector, returns `null`. @@ -3337,6 +3341,8 @@ def query_selector_all(self, selector: str) -> typing.List["ElementHandle"]: Returns the ElementHandles pointing to the frame elements. + > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects instead. + The method finds all elements matching the specified selector within the frame. See [Working with selectors](./selectors.md) for more details. If no elements match the selector, returns empty array. @@ -3370,6 +3376,9 @@ def wait_for_selector( Returns when element specified by selector satisfies `state` option. Returns `null` if waiting for `hidden` or `detached`. + > NOTE: Playwright automatically waits for element to be ready before performing an action. Using `Locator` objects and + web-first assertions make the code wait-for-selector-free. + Wait for the `selector` to satisfy `state` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw. @@ -3707,6 +3716,9 @@ def eval_on_selector( Returns the return value of `expression`. + > NOTE: This method does not wait for the element to pass actionability checks and therefore can lead to the flaky + tests. Use `locator.evaluate()`, other `Locator` helper methods or web-first assertions instead. + The method finds an element matching the specified selector within the frame and passes it as a first argument to `expression`. See [Working with selectors](./selectors.md) for more details. If no elements match the selector, the method throws an error. @@ -3759,6 +3771,9 @@ def eval_on_selector_all( Returns the return value of `expression`. + > NOTE: In most cases, `locator.evaluate_all()`, other `Locator` helper methods and web-first assertions do a + better job. + The method finds all elements matching the specified selector within the frame and passes an array of matched elements as a first argument to `expression`. See [Working with selectors](./selectors.md) for more details. @@ -5373,11 +5388,11 @@ def run(playwright): page.set_content('
') # Use the selector prefixed with its name. - button = page.query_selector('tag=button') + button = page.locator('tag=button') # Combine it with other selector engines. page.click('tag=div >> text=\"Click me\"') # Can use it in any methods supporting selectors. - button_count = page.eval_on_selector_all('tag=button', 'buttons => buttons.length') + button_count = page.locator('tag=button').count() print(button_count) browser.close() @@ -6202,6 +6217,18 @@ def workers(self) -> typing.List["Worker"]: """ return mapping.from_impl_list(self._impl_obj.workers) + @property + def request(self) -> "APIRequestContext": + """Page.request + + API testing helper associated with this page. Requests made with this API will use page cookies. + + Returns + ------- + APIRequestContext + """ + return mapping.from_impl(self._impl_obj.request) + @property def video(self) -> typing.Optional["Video"]: """Page.video @@ -6309,8 +6336,10 @@ def query_selector( ) -> typing.Optional["ElementHandle"]: """Page.query_selector + > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects and web-first assertions instead. + The method finds an element matching the specified selector within the page. If no elements match the selector, the - return value resolves to `null`. To wait for an element on the page, use `page.wait_for_selector()`. + return value resolves to `null`. To wait for an element on the page, use `locator.wait_for()`. Shortcut for main frame's `frame.query_selector()`. @@ -6337,6 +6366,8 @@ def query_selector( def query_selector_all(self, selector: str) -> typing.List["ElementHandle"]: """Page.query_selector_all + > NOTE: The use of `ElementHandle` is discouraged, use `Locator` objects and web-first assertions instead. + The method finds all elements matching the specified selector within the page. If no elements match the selector, the return value resolves to `[]`. @@ -6372,6 +6403,9 @@ def wait_for_selector( Returns when element specified by selector satisfies `state` option. Returns `null` if waiting for `hidden` or `detached`. + > NOTE: Playwright automatically waits for element to be ready before performing an action. Using `Locator` objects and + web-first assertions make the code wait-for-selector-free. + Wait for the `selector` to satisfy `state` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw. @@ -6727,7 +6761,7 @@ def evaluate(self, expression: str, arg: typing.Any = None) -> typing.Any: `ElementHandle` instances can be passed as an argument to the `page.evaluate()`: ```py - body_handle = page.query_selector(\"body\") + body_handle = page.evaluate(\"document.body\") html = page.evaluate(\"([body, suffix]) => body.innerHTML + suffix\", [body_handle, \"hello\"]) body_handle.dispose() ``` @@ -6819,6 +6853,9 @@ def eval_on_selector( ) -> typing.Any: """Page.eval_on_selector + > NOTE: This method does not wait for the element to pass actionability checks and therefore can lead to the flaky + tests. Use `locator.evaluate()`, other `Locator` helper methods or web-first assertions instead. + The method finds an element matching the specified selector within the page and passes it as a first argument to `expression`. If no elements match the selector, the method throws an error. Returns the value of `expression`. @@ -6870,6 +6907,9 @@ def eval_on_selector_all( ) -> typing.Any: """Page.eval_on_selector_all + > NOTE: In most cases, `locator.evaluate_all()`, other `Locator` helper methods and web-first assertions do a + better job. + The method finds all elements matching the specified selector within the page and passes an array of matched elements as a first argument to `expression`. Returns the result of `expression` invocation. @@ -14097,3 +14137,934 @@ def new_context( mapping.register(APIRequestImpl, APIRequest) + + +class PageAssertions(SyncBase): + def to_have_title( + self, + title_or_reg_exp: typing.Union[typing.Pattern, str], + *, + timeout: float = None + ) -> NoneType: + """PageAssertions.to_have_title + + Ensures the page has the given title. + + Parameters + ---------- + title_or_reg_exp : Union[Pattern, str] + Expected title or RegExp. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "page_assertions.to_have_title", + self._impl_obj.to_have_title( + title_or_reg_exp=title_or_reg_exp, timeout=timeout + ), + ) + ) + + def not_to_have_title( + self, + title_or_reg_exp: typing.Union[typing.Pattern, str], + *, + timeout: float = None + ) -> NoneType: + """PageAssertions.not_to_have_title + + The opposite of `page_assertions.to_have_title()`. + + Parameters + ---------- + title_or_reg_exp : Union[Pattern, str] + Expected title or RegExp. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "page_assertions.not_to_have_title", + self._impl_obj.not_to_have_title( + title_or_reg_exp=title_or_reg_exp, timeout=timeout + ), + ) + ) + + def to_have_url( + self, + url_or_reg_exp: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """PageAssertions.to_have_url + + Ensures the page is navigated to the given URL. + + Parameters + ---------- + url_or_reg_exp : Union[Pattern, str] + Expected substring or RegExp. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "page_assertions.to_have_url", + self._impl_obj.to_have_url( + url_or_reg_exp=url_or_reg_exp, timeout=timeout + ), + ) + ) + + def not_to_have_url( + self, + url_or_reg_exp: typing.Union[typing.Pattern, str], + *, + timeout: float = None + ) -> NoneType: + """PageAssertions.not_to_have_url + + The opposite of `page_assertions.to_have_url()`. + + Parameters + ---------- + url_or_reg_exp : Union[Pattern, str] + Expected substring or RegExp. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "page_assertions.not_to_have_url", + self._impl_obj.not_to_have_url( + url_or_reg_exp=url_or_reg_exp, timeout=timeout + ), + ) + ) + + +mapping.register(PageAssertionsImpl, PageAssertions) + + +class LocatorAssertions(SyncBase): + def to_contain_text( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + use_inner_text: bool = None, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_contain_text + + Ensures the `Locator` points to an element that contains the given text. You can use regular expressions for the value + as well. + + Note that if array is passed as an expected value, entire lists can be asserted: + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected substring or RegExp or a list of those. + use_inner_text : Union[bool, NoneType] + Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_contain_text", + self._impl_obj.to_contain_text( + expected=expected, use_inner_text=use_inner_text, timeout=timeout + ), + ) + ) + + def not_to_contain_text( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + use_inner_text: bool = None, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_contain_text + + The opposite of `locator_assertions.to_contain_text()`. + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected substring or RegExp or a list of those. + use_inner_text : Union[bool, NoneType] + Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_contain_text", + self._impl_obj.not_to_contain_text( + expected=expected, use_inner_text=use_inner_text, timeout=timeout + ), + ) + ) + + def to_have_attribute( + self, + name: str, + value: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_attribute + + Ensures the `Locator` points to an element with given attribute. + + Parameters + ---------- + name : str + Attribute name. + value : Union[Pattern, str] + Expected attribute value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_have_attribute", + self._impl_obj.to_have_attribute( + name=name, value=value, timeout=timeout + ), + ) + ) + + def not_to_have_attribute( + self, + name: str, + value: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_attribute + + The opposite of `locator_assertions.to_have_attribute()`. + + Parameters + ---------- + name : str + Attribute name. + value : Union[Pattern, str] + Expected attribute value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_have_attribute", + self._impl_obj.not_to_have_attribute( + name=name, value=value, timeout=timeout + ), + ) + ) + + def to_have_class( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_class + + Ensures the `Locator` points to an element with given CSS class. + + Note that if array is passed as an expected value, entire lists can be asserted: + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected class or RegExp or a list of those. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_have_class", + self._impl_obj.to_have_class(expected=expected, timeout=timeout), + ) + ) + + def not_to_have_class( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_class + + The opposite of `locator_assertions.to_have_class()`. + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected class or RegExp or a list of those. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_have_class", + self._impl_obj.not_to_have_class(expected=expected, timeout=timeout), + ) + ) + + def to_have_count(self, count: int, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_have_count + + Ensures the `Locator` resolves to an exact number of DOM nodes. + + Parameters + ---------- + count : int + Expected count. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_have_count", + self._impl_obj.to_have_count(count=count, timeout=timeout), + ) + ) + + def not_to_have_count(self, count: int, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_have_count + + The opposite of `locator_assertions.to_have_count()`. + + Parameters + ---------- + count : int + Expected count. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_have_count", + self._impl_obj.not_to_have_count(count=count, timeout=timeout), + ) + ) + + def to_have_css( + self, + name: str, + value: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_css + + Ensures the `Locator` resolves to an element with the given computed CSS style. + + Parameters + ---------- + name : str + CSS property name. + value : Union[Pattern, str] + CSS property value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_have_css", + self._impl_obj.to_have_css(name=name, value=value, timeout=timeout), + ) + ) + + def not_to_have_css( + self, + name: str, + value: typing.Union[str, typing.Pattern], + *, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_css + + The opposite of `locator_assertions.to_have_css()`. + + Parameters + ---------- + name : str + CSS property name. + value : Union[Pattern, str] + CSS property value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_have_css", + self._impl_obj.not_to_have_css(name=name, value=value, timeout=timeout), + ) + ) + + def to_have_id( + self, id: typing.Union[str, typing.Pattern], *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_id + + Ensures the `Locator` points to an element with the given DOM Node ID. + + Parameters + ---------- + id : Union[Pattern, str] + Element id. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_have_id", + self._impl_obj.to_have_id(id=id, timeout=timeout), + ) + ) + + def not_to_have_id( + self, id: typing.Union[str, typing.Pattern], *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_id + + The opposite of `locator_assertions.to_have_id()`. + + Parameters + ---------- + id : Union[Pattern, str] + Element id. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_have_id", + self._impl_obj.not_to_have_id(id=id, timeout=timeout), + ) + ) + + def to_have_js_property( + self, name: str, value: typing.Any, *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_js_property + + Ensures the `Locator` points to an element with given JavaScript property. Note that this property can be of a primitive + type as well as a plain serializable JavaScript object. + + Parameters + ---------- + name : str + Property name. + value : Any + Property value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_have_js_property", + self._impl_obj.to_have_js_property( + name=name, value=mapping.to_impl(value), timeout=timeout + ), + ) + ) + + def not_to_have_js_property( + self, name: str, value: typing.Any, *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_js_property + + The opposite of `locator_assertions.to_have_js_property()`. + + Parameters + ---------- + name : str + Property name. + value : Any + Property value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_have_js_property", + self._impl_obj.not_to_have_js_property( + name=name, value=mapping.to_impl(value), timeout=timeout + ), + ) + ) + + def to_have_value( + self, value: typing.Union[str, typing.Pattern], *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_value + + Ensures the `Locator` points to an element with the given input value. You can use regular expressions for the value as + well. + + Parameters + ---------- + value : Union[Pattern, str] + Expected value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_have_value", + self._impl_obj.to_have_value(value=value, timeout=timeout), + ) + ) + + def not_to_have_value( + self, value: typing.Union[str, typing.Pattern], *, timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_value + + The opposite of `locator_assertions.to_have_value()`. + + Parameters + ---------- + value : Union[Pattern, str] + Expected value. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_have_value", + self._impl_obj.not_to_have_value(value=value, timeout=timeout), + ) + ) + + def to_have_text( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + use_inner_text: bool = None, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.to_have_text + + Ensures the `Locator` points to an element with the given text. You can use regular expressions for the value as well. + + Note that if array is passed as an expected value, entire lists can be asserted: + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected substring or RegExp or a list of those. + use_inner_text : Union[bool, NoneType] + Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_have_text", + self._impl_obj.to_have_text( + expected=expected, use_inner_text=use_inner_text, timeout=timeout + ), + ) + ) + + def not_to_have_text( + self, + expected: typing.Union[ + typing.List[typing.Pattern], typing.List[str], typing.Pattern, str + ], + *, + use_inner_text: bool = None, + timeout: float = None + ) -> NoneType: + """LocatorAssertions.not_to_have_text + + The opposite of `locator_assertions.to_have_text()`. + + Parameters + ---------- + expected : Union[List[Pattern], List[str], Pattern, str] + Expected substring or RegExp or a list of those. + use_inner_text : Union[bool, NoneType] + Whether to use `element.innerText` instead of `element.textContent` when retrieving DOM node text. + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_have_text", + self._impl_obj.not_to_have_text( + expected=expected, use_inner_text=use_inner_text, timeout=timeout + ), + ) + ) + + def to_be_checked(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_checked + + Ensures the `Locator` points to a checked input. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_be_checked", + self._impl_obj.to_be_checked(timeout=timeout), + ) + ) + + def not_to_be_checked(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_checked + + The opposite of `locator_assertions.to_be_checked()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_be_checked", + self._impl_obj.not_to_be_checked(timeout=timeout), + ) + ) + + def to_be_disabled(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_disabled + + Ensures the `Locator` points to a disabled element. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_be_disabled", + self._impl_obj.to_be_disabled(timeout=timeout), + ) + ) + + def not_to_be_disabled(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_disabled + + The opposite of `locator_assertions.to_be_disabled()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_be_disabled", + self._impl_obj.not_to_be_disabled(timeout=timeout), + ) + ) + + def to_be_editable(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_editable + + Ensures the `Locator` points to an editable element. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_be_editable", + self._impl_obj.to_be_editable(timeout=timeout), + ) + ) + + def not_to_be_editable(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_editable + + The opposite of `locator_assertions.to_be_editable()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_be_editable", + self._impl_obj.not_to_be_editable(timeout=timeout), + ) + ) + + def to_be_empty(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_empty + + Ensures the `Locator` points to an empty editable element or to a DOM node that has no text. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_be_empty", + self._impl_obj.to_be_empty(timeout=timeout), + ) + ) + + def not_to_be_empty(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_empty + + The opposite of `locator_assertions.to_be_empty()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_be_empty", + self._impl_obj.not_to_be_empty(timeout=timeout), + ) + ) + + def to_be_enabled(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_enabled + + Ensures the `Locator` points to an enabled element. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_be_enabled", + self._impl_obj.to_be_enabled(timeout=timeout), + ) + ) + + def not_to_be_enabled(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_enabled + + The opposite of `locator_assertions.to_be_enabled()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_be_enabled", + self._impl_obj.not_to_be_enabled(timeout=timeout), + ) + ) + + def to_be_hidden(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_hidden + + Ensures the `Locator` points to a hidden DOM node, which is the opposite of [visible](./actionability.md#visible). + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_be_hidden", + self._impl_obj.to_be_hidden(timeout=timeout), + ) + ) + + def not_to_be_hidden(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_hidden + + The opposite of `locator_assertions.to_be_hidden()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_be_hidden", + self._impl_obj.not_to_be_hidden(timeout=timeout), + ) + ) + + def to_be_visible(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_visible + + Ensures the `Locator` points to a [visible](./actionability.md#visible) DOM node. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_be_visible", + self._impl_obj.to_be_visible(timeout=timeout), + ) + ) + + def not_to_be_visible(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_visible + + The opposite of `locator_assertions.to_be_visible()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_be_visible", + self._impl_obj.not_to_be_visible(timeout=timeout), + ) + ) + + def to_be_focused(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.to_be_focused + + Ensures the `Locator` points to a focused DOM node. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.to_be_focused", + self._impl_obj.to_be_focused(timeout=timeout), + ) + ) + + def not_to_be_focused(self, *, timeout: float = None) -> NoneType: + """LocatorAssertions.not_to_be_focused + + The opposite of `locator_assertions.to_be_focused()`. + + Parameters + ---------- + timeout : Union[float, NoneType] + Time to retry the assertion for. + """ + __tracebackhide__ = True + + return mapping.from_maybe_impl( + self._sync( + "locator_assertions.not_to_be_focused", + self._impl_obj.not_to_be_focused(timeout=timeout), + ) + ) + + +mapping.register(LocatorAssertionsImpl, LocatorAssertions) diff --git a/scripts/expected_api_mismatch.txt b/scripts/expected_api_mismatch.txt index 3e00c98f1..142734023 100644 --- a/scripts/expected_api_mismatch.txt +++ b/scripts/expected_api_mismatch.txt @@ -17,3 +17,4 @@ Parameter type mismatch in Page.unroute(handler=): documented as Union[Callable[ Method not implemented: Error.name Method not implemented: Error.stack Method not implemented: Error.message +Method not implemented: PlaywrightAssertions.expect diff --git a/scripts/generate_api.py b/scripts/generate_api.py index 7392f161b..cca95ee4d 100644 --- a/scripts/generate_api.py +++ b/scripts/generate_api.py @@ -26,6 +26,7 @@ ) from playwright._impl._accessibility import Accessibility +from playwright._impl._assertions import LocatorAssertions, PageAssertions from playwright._impl._browser import Browser from playwright._impl._browser_context import BrowserContext from playwright._impl._browser_type import BrowserType @@ -240,6 +241,7 @@ def return_value(value: Any) -> List[str]: from playwright._impl._locator import Locator as LocatorImpl, FrameLocator as FrameLocatorImpl from playwright._impl._api_types import Error from playwright._impl._fetch import APIRequest as APIRequestImpl, APIResponse as APIResponseImpl, APIRequestContext as APIRequestContextImpl +from playwright._impl._assertions import PageAssertions as PageAssertionsImpl, LocatorAssertions as LocatorAssertionsImpl """ @@ -274,6 +276,8 @@ def return_value(value: Any) -> List[str]: APIResponse, APIRequestContext, APIRequest, + PageAssertions, + LocatorAssertions, ] api_globals = globals() diff --git a/scripts/generate_async_api.py b/scripts/generate_async_api.py index 751ea711d..182e3bc32 100755 --- a/scripts/generate_async_api.py +++ b/scripts/generate_async_api.py @@ -41,7 +41,7 @@ def generate(t: Any) -> None: base_class = t.__bases__[0].__name__ if class_name in ["Page", "BrowserContext", "Browser"]: base_sync_class = "AsyncContextManager" - elif base_class in ["ChannelOwner", "object"]: + elif base_class in ["ChannelOwner", "object", "AssertionsBase"]: base_sync_class = "AsyncBase" else: base_sync_class = base_class @@ -98,6 +98,8 @@ def generate(t: Any) -> None: documentation_provider.print_entry( class_name, name, get_type_hints(value, api_globals) ) + if class_name in ["LocatorAssertions", "PageAssertions"]: + print(" __tracebackhide__ = True") if "expect_" in name: print("") print( diff --git a/scripts/generate_sync_api.py b/scripts/generate_sync_api.py index 99f636fe3..e50adf77e 100755 --- a/scripts/generate_sync_api.py +++ b/scripts/generate_sync_api.py @@ -42,7 +42,7 @@ def generate(t: Any) -> None: base_class = t.__bases__[0].__name__ if class_name in ["Page", "BrowserContext", "Browser"]: base_sync_class = "SyncContextManager" - elif base_class in ["ChannelOwner", "object"]: + elif base_class in ["ChannelOwner", "object", "AssertionsBase"]: base_sync_class = "SyncBase" else: base_sync_class = base_class @@ -95,6 +95,8 @@ def generate(t: Any) -> None: documentation_provider.print_entry( class_name, name, get_type_hints(value, api_globals) ) + if class_name in ["LocatorAssertions", "PageAssertions"]: + print(" __tracebackhide__ = True") if "expect_" in name: print( f" return EventContextManager(self, self._impl_obj.{name}({arguments(value, 12)}).future)" diff --git a/setup.py b/setup.py index 99887c2ae..ff17b9530 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ InWheel = None from wheel.bdist_wheel import bdist_wheel as BDistWheelCommand -driver_version = "1.18.0-next-1636539685000" +driver_version = "1.18.0-alpha-nov-18-2021" def extractall(zip: zipfile.ZipFile, path: str) -> None: diff --git a/tests/async/test_assertions.py b/tests/async/test_assertions.py new file mode 100644 index 000000000..ae8223edf --- /dev/null +++ b/tests/async/test_assertions.py @@ -0,0 +1,293 @@ +# 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 re +from datetime import datetime + +import pytest + +from playwright.async_api import Browser, Page, expect +from tests.server import Server + + +async def test_assertions_page_to_have_title(page: Page, server: Server) -> None: + await page.goto(server.EMPTY_PAGE) + await page.set_content( + """ + + """ + ) + await expect(page).to_have_title("new title") + await expect(page).to_have_title(re.compile("new title")) + with pytest.raises(AssertionError): + await expect(page).to_have_title("not the current title", timeout=100) + with pytest.raises(AssertionError): + await expect(page).to_have_title( + re.compile("not the current title"), timeout=100 + ) + with pytest.raises(AssertionError): + await expect(page).not_to_have_title(re.compile("new title"), timeout=100) + with pytest.raises(AssertionError): + await expect(page).not_to_have_title("new title", timeout=100) + await expect(page).not_to_have_title("great title", timeout=100) + await expect(page).to_have_title("great title") + await expect(page).to_have_title(re.compile("great title")) + + +async def test_assertions_page_to_have_url(page: Page, server: Server) -> None: + await page.goto(server.EMPTY_PAGE) + await page.set_content( + """ + + """ + ) + await expect(page).to_have_url(server.EMPTY_PAGE) + await expect(page).to_have_url(re.compile(r".*/empty\.html")) + with pytest.raises(AssertionError): + await expect(page).to_have_url("nooooo", timeout=100) + with pytest.raises(AssertionError): + await expect(page).to_have_url(re.compile("not-the-url"), timeout=100) + await expect(page).to_have_url(server.PREFIX + "/grid.html") + await expect(page).not_to_have_url(server.EMPTY_PAGE, timeout=100) + with pytest.raises(AssertionError): + await expect(page).not_to_have_url(re.compile(r".*/grid\.html"), timeout=100) + with pytest.raises(AssertionError): + await expect(page).not_to_have_url(server.PREFIX + "/grid.html", timeout=100) + await expect(page).to_have_url(re.compile(r".*/grid\.html")) + await expect(page).not_to_have_url("**/empty.html", timeout=100) + + +async def test_assertions_page_to_have_url_with_base_url( + browser: Browser, server: Server +) -> None: + page = await browser.new_page(base_url=server.PREFIX) + await page.goto("/empty.html") + await expect(page).to_have_url("/empty.html") + await expect(page).to_have_url(re.compile(r".*/empty\.html")) + await page.close() + + +async def test_assertions_locator_to_contain_text(page: Page, server: Server) -> None: + await page.goto(server.EMPTY_PAGE) + await page.set_content("