From 4fda29c47d77e527c6465e2dd6838e8d180c04f7 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 5 Oct 2022 07:56:20 -0800 Subject: [PATCH 001/438] chore: roll to Playwright ToT (#1576) --- README.md | 4 +- playwright/_impl/_browser_context.py | 6 +- playwright/_impl/_fetch.py | 54 +- playwright/_impl/_frame.py | 66 +- playwright/_impl/_locator.py | 198 +++- playwright/_impl/_network.py | 5 + playwright/_impl/_page.py | 52 + playwright/_impl/_selectors.py | 4 + playwright/_impl/_str_utils.py | 27 +- playwright/async_api/_generated.py | 1344 ++++++++++++++++++++++++-- playwright/sync_api/_generated.py | 1324 +++++++++++++++++++++++-- setup.py | 2 +- tests/sync/test_locator_get_by.py | 106 ++ 13 files changed, 2970 insertions(+), 222 deletions(-) create mode 100644 tests/sync/test_locator_get_by.py diff --git a/README.md b/README.md index f0303e4f5..1afe5adfe 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 106.0.5249.30 | ✅ | ✅ | ✅ | +| Chromium 107.0.5304.18 | ✅ | ✅ | ✅ | | WebKit 16.0 | ✅ | ✅ | ✅ | -| Firefox 104.0 | ✅ | ✅ | ✅ | +| Firefox 105.0.1 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py index 59eeece99..38f375f4d 100644 --- a/playwright/_impl/_browser_context.py +++ b/playwright/_impl/_browser_context.py @@ -450,8 +450,7 @@ def _on_request_failed( page: Optional[Page], ) -> None: request._failure_text = failure_text - if request._timing: - request._timing["responseEnd"] = response_end_timing + request._set_response_end_timing(response_end_timing) self.emit(BrowserContext.Events.RequestFailed, request) if page: page.emit(Page.Events.RequestFailed, request) @@ -463,8 +462,7 @@ def _on_request_finished( response_end_timing: float, page: Optional[Page], ) -> None: - if request._timing: - request._timing["responseEnd"] = response_end_timing + request._set_response_end_timing(response_end_timing) self.emit(BrowserContext.Events.RequestFinished, request) if page: page.emit(Page.Events.RequestFinished, request) diff --git a/playwright/_impl/_fetch.py b/playwright/_impl/_fetch.py index d78ab64f2..c4428a37e 100644 --- a/playwright/_impl/_fetch.py +++ b/playwright/_impl/_fetch.py @@ -48,6 +48,12 @@ from playwright._impl._playwright import Playwright +FormType = Dict[str, Union[bool, float, str]] +DataType = Union[Any, bytes, str] +MultipartType = Dict[str, Union[bytes, bool, float, str, FilePayload]] +ParamsType = Dict[str, Union[bool, float, str]] + + class APIRequest: def __init__(self, playwright: "Playwright") -> None: self.playwright = playwright @@ -94,11 +100,11 @@ async def dispose(self) -> None: async def delete( self, url: str, - params: Dict[str, Union[bool, float, str]] = None, + params: ParamsType = None, headers: Headers = None, - data: Union[Any, bytes, str] = None, - form: Dict[str, Union[bool, float, str]] = None, - multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, + data: DataType = None, + form: FormType = None, + multipart: MultipartType = None, timeout: float = None, failOnStatusCode: bool = None, ignoreHTTPSErrors: bool = None, @@ -121,8 +127,11 @@ async def delete( async def head( self, url: str, - params: Dict[str, Union[bool, float, str]] = None, + params: ParamsType = None, headers: Headers = None, + data: DataType = None, + form: FormType = None, + multipart: MultipartType = None, timeout: float = None, failOnStatusCode: bool = None, ignoreHTTPSErrors: bool = None, @@ -133,6 +142,9 @@ async def head( method="HEAD", params=params, headers=headers, + data=data, + form=form, + multipart=multipart, timeout=timeout, failOnStatusCode=failOnStatusCode, ignoreHTTPSErrors=ignoreHTTPSErrors, @@ -142,8 +154,11 @@ async def head( async def get( self, url: str, - params: Dict[str, Union[bool, float, str]] = None, + params: ParamsType = None, headers: Headers = None, + data: DataType = None, + form: FormType = None, + multipart: MultipartType = None, timeout: float = None, failOnStatusCode: bool = None, ignoreHTTPSErrors: bool = None, @@ -154,6 +169,9 @@ async def get( method="GET", params=params, headers=headers, + data=data, + form=form, + multipart=multipart, timeout=timeout, failOnStatusCode=failOnStatusCode, ignoreHTTPSErrors=ignoreHTTPSErrors, @@ -163,10 +181,10 @@ async def get( async def patch( self, url: str, - params: Dict[str, Union[bool, float, str]] = None, + params: ParamsType = None, headers: Headers = None, - data: Union[Any, bytes, str] = None, - form: Dict[str, Union[bool, float, str]] = None, + data: DataType = None, + form: FormType = None, multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, timeout: float = None, failOnStatusCode: bool = None, @@ -190,10 +208,10 @@ async def patch( async def put( self, url: str, - params: Dict[str, Union[bool, float, str]] = None, + params: ParamsType = None, headers: Headers = None, - data: Union[Any, bytes, str] = None, - form: Dict[str, Union[bool, float, str]] = None, + data: DataType = None, + form: FormType = None, multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, timeout: float = None, failOnStatusCode: bool = None, @@ -217,10 +235,10 @@ async def put( async def post( self, url: str, - params: Dict[str, Union[bool, float, str]] = None, + params: ParamsType = None, headers: Headers = None, - data: Union[Any, bytes, str] = None, - form: Dict[str, Union[bool, float, str]] = None, + data: DataType = None, + form: FormType = None, multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, timeout: float = None, failOnStatusCode: bool = None, @@ -244,11 +262,11 @@ async def post( async def fetch( self, urlOrRequest: Union[str, network.Request], - params: Dict[str, Union[bool, float, str]] = None, + params: ParamsType = None, method: str = None, headers: Headers = None, - data: Union[Any, bytes, str] = None, - form: Dict[str, Union[bool, float, str]] = None, + data: DataType = None, + form: FormType = None, multipart: Dict[str, Union[bytes, bool, float, str, FilePayload]] = None, timeout: float = None, failOnStatusCode: bool = None, diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index 0f09aa422..3a77a00ad 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -45,7 +45,17 @@ parse_result, serialize_argument, ) -from playwright._impl._locator import FrameLocator, Locator +from playwright._impl._locator import ( + FrameLocator, + Locator, + get_by_alt_text_selector, + get_by_label_selector, + get_by_placeholder_selector, + get_by_role_selector, + get_by_test_id_selector, + get_by_text_selector, + get_by_title_selector, +) from playwright._impl._network import Response from playwright._impl._set_input_files_helpers import convert_input_files from playwright._impl._wait_helper import WaitHelper @@ -520,6 +530,60 @@ def locator( ) -> Locator: return Locator(self, selector, has_text=has_text, has=has) + def get_by_alt_text( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_alt_text_selector(text, exact=exact)) + + def get_by_label( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_label_selector(text, exact=exact)) + + def get_by_placeholder( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_placeholder_selector(text, exact=exact)) + + def get_by_role( + self, + role: str, + checked: bool = None, + disabled: bool = None, + expanded: bool = None, + includeHidden: bool = None, + level: int = None, + name: Union[str, Pattern[str]] = None, + pressed: bool = None, + selected: bool = None, + ) -> "Locator": + return self.locator( + get_by_role_selector( + role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=includeHidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, testId: str) -> "Locator": + return self.locator(get_by_test_id_selector(testId)) + + def get_by_text( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_text_selector(text, exact=exact)) + + def get_by_title( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_title_selector(text, exact=exact)) + def frame_locator(self, selector: str) -> FrameLocator: return FrameLocator(self, selector) diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index 18a385a17..e2d206b62 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -24,6 +24,7 @@ List, Optional, Pattern, + Tuple, TypeVar, Union, ) @@ -44,7 +45,12 @@ monotonic_time, ) from playwright._impl._js_handle import Serializable, parse_value, serialize_argument -from playwright._impl._str_utils import escape_regex_flags, escape_with_quotes +from playwright._impl._str_utils import ( + escape_for_attribute_selector, + escape_for_text_selector, + escape_regex_flags, + escape_with_quotes, +) if sys.version_info >= (3, 8): # pragma: no cover from typing import Literal @@ -209,6 +215,60 @@ def locator( has=has, ) + def get_by_alt_text( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_alt_text_selector(text, exact=exact)) + + def get_by_label( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_label_selector(text, exact=exact)) + + def get_by_placeholder( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_placeholder_selector(text, exact=exact)) + + def get_by_role( + self, + role: str, + checked: bool = None, + disabled: bool = None, + expanded: bool = None, + includeHidden: bool = None, + level: int = None, + name: Union[str, Pattern[str]] = None, + pressed: bool = None, + selected: bool = None, + ) -> "Locator": + return self.locator( + get_by_role_selector( + role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=includeHidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, testId: str) -> "Locator": + return self.locator(get_by_test_id_selector(testId)) + + def get_by_text( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_text_selector(text, exact=exact)) + + def get_by_title( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_title_selector(text, exact=exact)) + def frame_locator(self, selector: str) -> "FrameLocator": return FrameLocator(self._frame, self._selector + " >> " + selector) @@ -590,6 +650,60 @@ def locator( has=has, ) + def get_by_alt_text( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_alt_text_selector(text, exact=exact)) + + def get_by_label( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_label_selector(text, exact=exact)) + + def get_by_placeholder( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_placeholder_selector(text, exact=exact)) + + def get_by_role( + self, + role: str, + checked: bool = None, + disabled: bool = None, + expanded: bool = None, + includeHidden: bool = None, + level: int = None, + name: Union[str, Pattern[str]] = None, + pressed: bool = None, + selected: bool = None, + ) -> "Locator": + return self.locator( + get_by_role_selector( + role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=includeHidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, testId: str) -> "Locator": + return self.locator(get_by_test_id_selector(testId)) + + def get_by_text( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_text_selector(text, exact=exact)) + + def get_by_title( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self.locator(get_by_title_selector(text, exact=exact)) + def frame_locator(self, selector: str) -> "FrameLocator": return FrameLocator( self._frame, f"{self._frame_selector} >> control=enter-frame >> {selector}" @@ -608,3 +722,85 @@ def nth(self, index: int) -> "FrameLocator": def __repr__(self) -> str: return f"" + + +test_id_attribute_name: str = "data-testid" + + +def set_test_id_attribute_name(attribute_name: str) -> None: + global test_id_attribute_name + test_id_attribute_name = attribute_name + + +def get_by_test_id_selector(test_id: str) -> str: + return get_by_attribute_text_selector(test_id_attribute_name, test_id, exact=True) + + +def get_by_attribute_text_selector( + attr_name: str, text: Union[str, Pattern[str]], exact: bool = None +) -> str: + if isinstance(text, Pattern): + return f"attr=[{attr_name}=/{text.pattern}/{escape_regex_flags(text)}]" + suffix = "s" if exact else "i" + return f"attr=[{attr_name}={escape_for_attribute_selector(text)}{suffix}]" + + +def get_by_label_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str: + return get_by_text_selector(text, exact=exact) + " >> control=resolve-label" + + +def get_by_alt_text_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str: + return get_by_attribute_text_selector("alt", text, exact=exact) + + +def get_by_title_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str: + return get_by_attribute_text_selector("title", text, exact=exact) + + +def get_by_placeholder_selector( + text: Union[str, Pattern[str]], exact: bool = None +) -> str: + return get_by_attribute_text_selector("placeholder", text, exact=exact) + + +def get_by_text_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str: + return "text=" + escape_for_text_selector(text, exact=exact) + + +def get_by_role_selector( + role: str, + checked: bool = None, + disabled: bool = None, + expanded: bool = None, + includeHidden: bool = None, + level: int = None, + name: Union[str, Pattern[str]] = None, + pressed: bool = None, + selected: bool = None, +) -> str: + props: List[Tuple[str, str]] = [] + if checked is not None: + props.append(("checked", str(checked))) + if disabled is not None: + props.append(("disabled", str(disabled))) + if selected is not None: + props.append(("selected", str(selected))) + if expanded is not None: + props.append(("expanded", str(expanded))) + if includeHidden is not None: + props.append(("include-hiddenen", str(includeHidden))) + if level is not None: + props.append(("level", str(level))) + if name is not None: + props.append( + ( + "name", + f"/{name.pattern}/{escape_regex_flags(name)}" + if isinstance(name, Pattern) + else escape_for_attribute_selector(name), + ) + ) + if pressed is not None: + props.append(("pressed", str(pressed))) + props_str = "".join([f"[{t[0]}={t[1]}]" for t in props]) + return f"role={role}{props_str}" diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index 1ea51e465..f89626b6e 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -181,6 +181,11 @@ def failure(self) -> Optional[str]: def timing(self) -> ResourceTiming: return self._timing + def _set_response_end_timing(self, response_end_timing: float) -> None: + self._timing["responseEnd"] = response_end_timing + if self._timing["responseStart"] == -1: + self._timing["responseStart"] = response_end_timing + @property def headers(self) -> Headers: override = self._fallback_overrides.get("headers") diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 9e05c3f1c..118ffb91e 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -735,6 +735,58 @@ def locator( ) -> "Locator": return self._main_frame.locator(selector, has_text=has_text, has=has) + def get_by_alt_text( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self._main_frame.get_by_alt_text(text, exact=exact) + + def get_by_label( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self._main_frame.get_by_label(text, exact=exact) + + def get_by_placeholder( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self._main_frame.get_by_placeholder(text, exact=exact) + + def get_by_role( + self, + role: str, + checked: bool = None, + disabled: bool = None, + expanded: bool = None, + includeHidden: bool = None, + level: int = None, + name: Union[str, Pattern[str]] = None, + pressed: bool = None, + selected: bool = None, + ) -> "Locator": + return self._main_frame.get_by_role( + role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=includeHidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + + def get_by_test_id(self, testId: str) -> "Locator": + return self._main_frame.get_by_test_id(testId) + + def get_by_text( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self._main_frame.get_by_text(text, exact=exact) + + def get_by_title( + self, text: Union[str, Pattern[str]], exact: bool = None + ) -> "Locator": + return self._main_frame.get_by_title(text, exact=exact) + def frame_locator(self, selector: str) -> "FrameLocator": return self.main_frame.frame_locator(selector) diff --git a/playwright/_impl/_selectors.py b/playwright/_impl/_selectors.py index 03188ea5c..a66484d30 100644 --- a/playwright/_impl/_selectors.py +++ b/playwright/_impl/_selectors.py @@ -19,6 +19,7 @@ from playwright._impl._api_types import Error from playwright._impl._connection import ChannelOwner from playwright._impl._helper import async_readfile +from playwright._impl._locator import set_test_id_attribute_name class Selectors: @@ -46,6 +47,9 @@ async def register( await channel._channel.send("register", params) self._registrations.append(params) + def set_test_id_attribute(self, attribute_name: str) -> None: + set_test_id_attribute_name(attribute_name) + def _add_channel(self, channel: "SelectorsOwner") -> None: self._channels.add(channel) for params in self._registrations: diff --git a/playwright/_impl/_str_utils.py b/playwright/_impl/_str_utils.py index 22088b81c..8fce2a718 100644 --- a/playwright/_impl/_str_utils.py +++ b/playwright/_impl/_str_utils.py @@ -14,7 +14,7 @@ import json import re -from typing import Pattern +from typing import Pattern, Union def escape_with_quotes(text: str, char: str = "'") -> str: @@ -45,3 +45,28 @@ def escape_regex_flags(pattern: Pattern) -> str: == 0 ), "Unexpected re.Pattern flag, only MULTILINE, IGNORECASE and DOTALL are supported." return flags + + +def escape_for_regex(text: str) -> str: + return re.sub(r"[.*+?^>${}()|[\]\\]", "\\$&", text) + + +def escape_for_text_selector( + text: Union[str, Pattern[str]], exact: bool = None, case_sensitive: bool = None +) -> str: + if isinstance(text, Pattern): + return f"/{text.pattern}/{escape_regex_flags(text)}" + if exact: + return '"' + text.replace('"', '\\"') + '"' + if '"' in text or ">>" in text or text[0] == "/": + suffix = "" if case_sensitive else "i" + return re.sub(r"\s+", "\\s+", escape_for_regex(text)) + suffix + return text + + +def escape_for_attribute_selector(value: str) -> str: + # TODO: this should actually be + # cssEscape(value).replace(/\\ /g, ' ') + # However, our attribute selectors do not conform to CSS parsing spec, + # so we escape them differently. + return '"' + value.replace('"', '\\"') + '"' diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 27c16f5fb..d81253425 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -1346,8 +1346,8 @@ async def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -1382,8 +1382,8 @@ async def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -2611,8 +2611,8 @@ async def eval_on_selector( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -2659,8 +2659,8 @@ async def eval_on_selector_all( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -3244,8 +3244,8 @@ async def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -3296,8 +3296,8 @@ async def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -3746,8 +3746,8 @@ async def eval_on_selector( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. strict : Union[bool, None] @@ -3795,8 +3795,8 @@ async def eval_on_selector_all( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -4233,9 +4233,11 @@ def locator( ) -> "Locator": """Frame.locator - The method returns an element locator that can be used to perform actions in the frame. Locator is resolved to the - element immediately before performing an action, so a series of actions on the same locator can in fact be performed on - different DOM elements. That would happen if the DOM structure between those actions has changed. + The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to + the element immediately before performing an action, so a series of actions on the same locator can in fact be performed + on different DOM elements. That would happen if the DOM structure between those actions has changed. + + [Learn more about locators](https://playwright.dev/python/docs/locators). [Learn more about locators](https://playwright.dev/python/docs/locators). @@ -4264,6 +4266,250 @@ def locator( ) ) + def get_by_alt_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_alt_text + + Allows locating elements by their alt text. For example, this method will find the image by alt text \"Castle\": + + ```html + Castle + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_alt_text(text=text, exact=exact)) + + def get_by_label( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_label + + Allows locating input elements by the text of the associated label. For example, this method will find the input by + label text Password in the following DOM: + + ```html + + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_label(text=text, exact=exact)) + + def get_by_placeholder( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_placeholder + + Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder + \"Country\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_placeholder(text=text, exact=exact) + ) + + def get_by_role( + self, + role: str, + *, + checked: typing.Optional[bool] = None, + disabled: typing.Optional[bool] = None, + expanded: typing.Optional[bool] = None, + include_hidden: typing.Optional[bool] = None, + level: typing.Optional[int] = None, + name: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + pressed: typing.Optional[bool] = None, + selected: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_role + + Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), + [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and + [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). Note that role selector **does not replace** + accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines. + + Note that many html elements have an implicitly + [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You + can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not + recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values. + + Parameters + ---------- + role : str + Required aria role. + checked : Union[bool, None] + An attribute that is usually set by `aria-checked` or native `` controls. Available values for + checked are `true`, `false` and `"mixed"`. + + Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked). + disabled : Union[bool, None] + A boolean attribute that is usually set by `aria-disabled` or `disabled`. + + > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about + [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled). + expanded : Union[bool, None] + A boolean attribute that is usually set by `aria-expanded`. + + Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded). + include_hidden : Union[bool, None] + A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as + [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector. + + Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden). + level : Union[int, None] + A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for + `

-

` elements. + + Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level). + name : Union[Pattern[str], str, None] + A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + + Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + pressed : Union[bool, None] + An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`. + + Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed). + selected : Union[bool, None] + A boolean attribute that is usually set by `aria-selected`. + + Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected). + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_role( + role=role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=include_hidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, test_id: str) -> "Locator": + """Frame.get_by_test_id + + Locate element by the test id. By default, the `data-testid` attribute is used as a test id. Use + `selectors.set_test_id_attribute()` to configure a different test id attribute if necessary. + + Parameters + ---------- + test_id : str + Id to locate the element by. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_test_id(testId=test_id)) + + def get_by_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_text + + Allows locating elements that contain given text. + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_text(text=text, exact=exact)) + + def get_by_title( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_title + + Allows locating elements by their title. For example, this method will find the button by its title \"Submit\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_title(text=text, exact=exact)) + def frame_locator(self, selector: str) -> "FrameLocator": """Frame.frame_locator @@ -4272,7 +4518,7 @@ def frame_locator(self, selector: str) -> "FrameLocator": id=\"my-frame\">`: ```py - locator = frame.frame_locator(\"#my-iframe\").locator(\"text=Submit\") + locator = frame.frame_locator(\"#my-iframe\").get_by_text(\"Submit\") await locator.click() ``` @@ -5056,8 +5302,8 @@ async def main(): Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. timeout : Union[float, None] @@ -5199,7 +5445,10 @@ def locator( ) -> "Locator": """FrameLocator.locator - The method finds an element matching the specified selector in the FrameLocator's subtree. + The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options, + similar to `locator.filter()` method. + + [Learn more about locators](https://playwright.dev/python/docs/locators). Parameters ---------- @@ -5226,6 +5475,250 @@ def locator( ) ) + def get_by_alt_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_alt_text + + Allows locating elements by their alt text. For example, this method will find the image by alt text \"Castle\": + + ```html + Castle + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_alt_text(text=text, exact=exact)) + + def get_by_label( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_label + + Allows locating input elements by the text of the associated label. For example, this method will find the input by + label text Password in the following DOM: + + ```html + + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_label(text=text, exact=exact)) + + def get_by_placeholder( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_placeholder + + Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder + \"Country\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_placeholder(text=text, exact=exact) + ) + + def get_by_role( + self, + role: str, + *, + checked: typing.Optional[bool] = None, + disabled: typing.Optional[bool] = None, + expanded: typing.Optional[bool] = None, + include_hidden: typing.Optional[bool] = None, + level: typing.Optional[int] = None, + name: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + pressed: typing.Optional[bool] = None, + selected: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_role + + Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), + [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and + [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). Note that role selector **does not replace** + accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines. + + Note that many html elements have an implicitly + [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You + can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not + recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values. + + Parameters + ---------- + role : str + Required aria role. + checked : Union[bool, None] + An attribute that is usually set by `aria-checked` or native `` controls. Available values for + checked are `true`, `false` and `"mixed"`. + + Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked). + disabled : Union[bool, None] + A boolean attribute that is usually set by `aria-disabled` or `disabled`. + + > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about + [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled). + expanded : Union[bool, None] + A boolean attribute that is usually set by `aria-expanded`. + + Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded). + include_hidden : Union[bool, None] + A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as + [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector. + + Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden). + level : Union[int, None] + A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for + `

-

` elements. + + Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level). + name : Union[Pattern[str], str, None] + A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + + Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + pressed : Union[bool, None] + An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`. + + Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed). + selected : Union[bool, None] + A boolean attribute that is usually set by `aria-selected`. + + Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected). + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_role( + role=role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=include_hidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, test_id: str) -> "Locator": + """FrameLocator.get_by_test_id + + Locate element by the test id. By default, the `data-testid` attribute is used as a test id. Use + `selectors.set_test_id_attribute()` to configure a different test id attribute if necessary. + + Parameters + ---------- + test_id : str + Id to locate the element by. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_test_id(testId=test_id)) + + def get_by_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_text + + Allows locating elements that contain given text. + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_text(text=text, exact=exact)) + + def get_by_title( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_title + + Allows locating elements by their title. For example, this method will find the button by its title \"Submit\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_title(text=text, exact=exact)) + def frame_locator(self, selector: str) -> "FrameLocator": """FrameLocator.frame_locator @@ -5310,8 +5803,8 @@ async def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -5342,8 +5835,8 @@ async def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -5436,6 +5929,21 @@ async def main(): ) ) + def set_test_id_attribute(self, attribute_name: str) -> None: + """Selectors.set_test_id_attribute + + Defines custom attribute name to be used in `page.get_by_test_id()`. `data-testid` is used by default. + + Parameters + ---------- + attribute_name : str + Test id attribute name. + """ + + return mapping.from_maybe_impl( + self._impl_obj.set_test_id_attribute(attribute_name=attribute_name) + ) + mapping.register(SelectorsImpl, Selectors) @@ -6900,8 +7408,8 @@ async def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -6952,8 +7460,8 @@ async def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -7002,8 +7510,8 @@ async def eval_on_selector( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. strict : Union[bool, None] @@ -7049,8 +7557,8 @@ async def eval_on_selector_all( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -7461,13 +7969,13 @@ async def wait_for_load_state( when this method is called. If current document has already reached the required state, resolves immediately. ```py - await page.click(\"button\") # click triggers navigation. + await page.get_by_role(\"button\").click() # click triggers navigation. await page.wait_for_load_state() # the promise resolves after \"load\" event. ``` ```py async with page.expect_popup() as page_info: - await page.click(\"button\") # click triggers a popup. + await page.get_by_role(\"button\").click() # click triggers a popup. popup = await page_info.value # Following resolves after \"domcontentloaded\" event. await popup.wait_for_load_state(\"domcontentloaded\") @@ -8346,58 +8854,300 @@ async def fill( Whether to bypass the [actionability](../actionability.md) checks. Defaults to `false`. """ - return mapping.from_maybe_impl( - await self._impl_obj.fill( - selector=selector, - value=value, - timeout=timeout, - noWaitAfter=no_wait_after, - strict=strict, - force=force, - ) - ) + return mapping.from_maybe_impl( + await self._impl_obj.fill( + selector=selector, + value=value, + timeout=timeout, + noWaitAfter=no_wait_after, + strict=strict, + force=force, + ) + ) + + def locator( + self, + selector: str, + *, + has_text: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + has: typing.Optional["Locator"] = None + ) -> "Locator": + """Page.locator + + The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to + the element immediately before performing an action, so a series of actions on the same locator can in fact be performed + on different DOM elements. That would happen if the DOM structure between those actions has changed. + + [Learn more about locators](https://playwright.dev/python/docs/locators). + + Parameters + ---------- + selector : str + A selector to use when resolving DOM element. See [working with selectors](../selectors.md) for more details. + has_text : Union[Pattern[str], str, None] + Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a + [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches + `
Playwright
`. + has : Union[Locator, None] + Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one. + For example, `article` that has `text=Playwright` matches `
Playwright
`. + + Note that outer and inner locators must belong to the same frame. Inner locator must not contain `FrameLocator`s. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.locator( + selector=selector, has_text=has_text, has=has._impl_obj if has else None + ) + ) + + def get_by_alt_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_alt_text + + Allows locating elements by their alt text. For example, this method will find the image by alt text \"Castle\": + + ```html + Castle + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_alt_text(text=text, exact=exact)) + + def get_by_label( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_label + + Allows locating input elements by the text of the associated label. For example, this method will find the input by + label text Password in the following DOM: + + ```html + + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_label(text=text, exact=exact)) + + def get_by_placeholder( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_placeholder + + Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder + \"Country\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_placeholder(text=text, exact=exact) + ) + + def get_by_role( + self, + role: str, + *, + checked: typing.Optional[bool] = None, + disabled: typing.Optional[bool] = None, + expanded: typing.Optional[bool] = None, + include_hidden: typing.Optional[bool] = None, + level: typing.Optional[int] = None, + name: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + pressed: typing.Optional[bool] = None, + selected: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_role + + Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), + [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and + [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). Note that role selector **does not replace** + accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines. + + Note that many html elements have an implicitly + [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You + can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not + recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values. + + Parameters + ---------- + role : str + Required aria role. + checked : Union[bool, None] + An attribute that is usually set by `aria-checked` or native `` controls. Available values for + checked are `true`, `false` and `"mixed"`. + + Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked). + disabled : Union[bool, None] + A boolean attribute that is usually set by `aria-disabled` or `disabled`. + + > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about + [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled). + expanded : Union[bool, None] + A boolean attribute that is usually set by `aria-expanded`. + + Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded). + include_hidden : Union[bool, None] + A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as + [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector. + + Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden). + level : Union[int, None] + A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for + `

-

` elements. + + Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level). + name : Union[Pattern[str], str, None] + A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + + Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + pressed : Union[bool, None] + An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`. + + Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed). + selected : Union[bool, None] + A boolean attribute that is usually set by `aria-selected`. + + Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected). + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_role( + role=role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=include_hidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, test_id: str) -> "Locator": + """Page.get_by_test_id + + Locate element by the test id. By default, the `data-testid` attribute is used as a test id. Use + `selectors.set_test_id_attribute()` to configure a different test id attribute if necessary. + + Parameters + ---------- + test_id : str + Id to locate the element by. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_test_id(testId=test_id)) - def locator( + def get_by_text( self, - selector: str, + text: typing.Union[str, typing.Pattern[str]], *, - has_text: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, - has: typing.Optional["Locator"] = None + exact: typing.Optional[bool] = None ) -> "Locator": - """Page.locator + """Page.get_by_text - The method returns an element locator that can be used to perform actions on the page. Locator is resolved to the - element immediately before performing an action, so a series of actions on the same locator can in fact be performed on - different DOM elements. That would happen if the DOM structure between those actions has changed. + Allows locating elements that contain given text. - [Learn more about locators](https://playwright.dev/python/docs/locators). + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_text(text=text, exact=exact)) - Shortcut for main frame's `frame.locator()`. + def get_by_title( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_title + + Allows locating elements by their title. For example, this method will find the button by its title \"Submit\": + + ```html + + ``` Parameters ---------- - selector : str - A selector to use when resolving DOM element. See [working with selectors](../selectors.md) for more details. - has_text : Union[Pattern[str], str, None] - Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a - [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches - `
Playwright
`. - has : Union[Locator, None] - Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one. - For example, `article` that has `text=Playwright` matches `
Playwright
`. - - Note that outer and inner locators must belong to the same frame. Inner locator must not contain `FrameLocator`s. + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. Returns ------- Locator """ - return mapping.from_impl( - self._impl_obj.locator( - selector=selector, has_text=has_text, has=has._impl_obj if has else None - ) - ) + return mapping.from_impl(self._impl_obj.get_by_title(text=text, exact=exact)) def frame_locator(self, selector: str) -> "FrameLocator": """Page.frame_locator @@ -8407,7 +9157,7 @@ def frame_locator(self, selector: str) -> "FrameLocator": id=\"my-frame\">`: ```py - locator = page.frame_locator(\"#my-iframe\").locator(\"text=Submit\") + locator = page.frame_locator(\"#my-iframe\").get_by_text(\"Submit\") await locator.click() ``` @@ -8674,6 +9424,20 @@ async def drag_and_drop( ) -> None: """Page.drag_and_drop + This method drags the source element to the target element. It will first move to the source element, perform a + `mousedown`, then move to the target element and perform a `mouseup`. + + ```py + await page.drag_and_drop(\"#source\", \"#target\") + # or specify exact positions relative to the top-left corners of the elements: + await page.drag_and_drop( + \"#source\", + \"#target\", + source_position={\"x\": 34, \"y\": 7}, + target_position={\"x\": 10, \"y\": 20} + ) + ``` + Parameters ---------- source : str @@ -9227,8 +9991,8 @@ async def main(): Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. timeout : Union[float, None] @@ -9406,7 +10170,7 @@ def expect_event( ```py async with page.expect_event(\"framenavigated\") as event_info: - await page.click(\"button\") + await page.get_by_role(\"button\") frame = await event_info.value ``` @@ -10475,7 +11239,7 @@ async def run(playwright):
\"\"\") - await page.locator(\"button\").click() + await page.get_by_role(\"button\").click() async def main(): async with async_playwright() as playwright: @@ -10553,7 +11317,7 @@ async def run(playwright):
\"\"\") - await page.locator(\"button\").click() + await page.get_by_role(\"button\").click() async def main(): async with async_playwright() as playwright: @@ -10738,7 +11502,7 @@ def expect_event( ```py async with context.expect_event(\"page\") as event_info: - await page.locator(\"button\").click() + await page.get_by_role(\"button\").click() page = await event_info.value ``` @@ -11161,7 +11925,7 @@ async def new_context( security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`. record_har_content : Union["attach", "embed", "omit", None] Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` - is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. Defaults to `embed`, which stores content inline the HAR file as per HAR specification. Returns @@ -11356,7 +12120,7 @@ async def new_page( security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`. record_har_content : Union["attach", "embed", "omit", None] Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` - is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. Defaults to `embed`, which stores content inline the HAR file as per HAR specification. Returns @@ -11856,7 +12620,7 @@ async def launch_persistent_context( security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`. record_har_content : Union["attach", "embed", "omit", None] Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` - is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. Defaults to `embed`, which stores content inline the HAR file as per HAR specification. Returns @@ -12197,7 +12961,7 @@ async def start_chunk(self, *, title: typing.Optional[str] = None) -> None: await page.goto(\"https://playwright.dev\") await context.tracing.start_chunk() - await page.locator(\"text=Get Started\").click() + await page.get_by_text(\"Get Started\").click() # Everything between start_chunk and stop_chunk will be recorded in the trace. await context.tracing.stop_chunk(path = \"trace1.zip\") @@ -12596,8 +13360,8 @@ async def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. timeout : Union[float, None] @@ -12636,8 +13400,8 @@ async def evaluate_all( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -12676,8 +13440,8 @@ async def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. timeout : Union[float, None] @@ -12745,9 +13509,11 @@ def locator( ) -> "Locator": """Locator.locator - The method finds an element matching the specified selector in the `Locator`'s subtree. It also accepts filter options, + The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options, similar to `locator.filter()` method. + [Learn more about locators](https://playwright.dev/python/docs/locators). + Parameters ---------- selector : str @@ -12773,6 +13539,250 @@ def locator( ) ) + def get_by_alt_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_alt_text + + Allows locating elements by their alt text. For example, this method will find the image by alt text \"Castle\": + + ```html + Castle + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_alt_text(text=text, exact=exact)) + + def get_by_label( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_label + + Allows locating input elements by the text of the associated label. For example, this method will find the input by + label text Password in the following DOM: + + ```html + + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_label(text=text, exact=exact)) + + def get_by_placeholder( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_placeholder + + Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder + \"Country\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_placeholder(text=text, exact=exact) + ) + + def get_by_role( + self, + role: str, + *, + checked: typing.Optional[bool] = None, + disabled: typing.Optional[bool] = None, + expanded: typing.Optional[bool] = None, + include_hidden: typing.Optional[bool] = None, + level: typing.Optional[int] = None, + name: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + pressed: typing.Optional[bool] = None, + selected: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_role + + Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), + [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and + [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). Note that role selector **does not replace** + accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines. + + Note that many html elements have an implicitly + [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You + can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not + recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values. + + Parameters + ---------- + role : str + Required aria role. + checked : Union[bool, None] + An attribute that is usually set by `aria-checked` or native `` controls. Available values for + checked are `true`, `false` and `"mixed"`. + + Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked). + disabled : Union[bool, None] + A boolean attribute that is usually set by `aria-disabled` or `disabled`. + + > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about + [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled). + expanded : Union[bool, None] + A boolean attribute that is usually set by `aria-expanded`. + + Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded). + include_hidden : Union[bool, None] + A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as + [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector. + + Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden). + level : Union[int, None] + A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for + `

-

` elements. + + Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level). + name : Union[Pattern[str], str, None] + A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + + Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + pressed : Union[bool, None] + An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`. + + Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed). + selected : Union[bool, None] + A boolean attribute that is usually set by `aria-selected`. + + Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected). + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_role( + role=role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=include_hidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, test_id: str) -> "Locator": + """Locator.get_by_test_id + + Locate element by the test id. By default, the `data-testid` attribute is used as a test id. Use + `selectors.set_test_id_attribute()` to configure a different test id attribute if necessary. + + Parameters + ---------- + test_id : str + Id to locate the element by. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_test_id(testId=test_id)) + + def get_by_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_text + + Allows locating elements that contain given text. + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_text(text=text, exact=exact)) + + def get_by_title( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_title + + Allows locating elements by their title. For example, this method will find the button by its title \"Submit\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_title(text=text, exact=exact)) + def frame_locator(self, selector: str) -> "FrameLocator": """Locator.frame_locator @@ -12780,7 +13790,7 @@ def frame_locator(self, selector: str) -> "FrameLocator": that iframe: ```py - locator = page.frame_locator(\"iframe\").locator(\"text=Submit\") + locator = page.frame_locator(\"iframe\").get_by_text(\"Submit\") await locator.click() ``` @@ -12861,7 +13871,7 @@ def filter( # ... await row_locator .filter(has_text=\"text in column 1\") - .filter(has=page.locator(\"tr\", has_text=\"column 2 button\")) + .filter(has=page.get_by_role(\"button\", name=\"column 2 button\")) .screenshot() ``` @@ -12925,6 +13935,22 @@ async def drag_to( ) -> None: """Locator.drag_to + This method drags the locator to another target locator or target position. It will first move to the source element, + perform a `mousedown`, then move to the target element or position and perform a `mouseup`. + + ```py + source = page.locator(\"#source\") + target = page.locator(\"#target\") + + await source.drag_to(target) + # or specify exact positions relative to the top-left corners of the elements: + await source.drag_to( + target, + source_position={\"x\": 34, \"y\": 7}, + target_position={\"x\": 10, \"y\": 20} + ) + ``` + Parameters ---------- target : Locator @@ -13604,8 +14630,8 @@ async def type( An example of typing into a text field and then submitting the form: ```py - element = page.locator(\"input\") - await element.type(\"some text\") + element = page.get_by_label(\"Password\") + await element.type(\"my password\") await element.press(\"Enter\") ``` @@ -14035,6 +15061,11 @@ async def head( typing.Dict[str, typing.Union[str, float, bool]] ] = None, headers: typing.Optional[typing.Dict[str, str]] = None, + data: typing.Optional[typing.Union[typing.Any, bytes, str]] = None, + form: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None, + multipart: typing.Optional[ + typing.Dict[str, typing.Union[bytes, bool, float, str, FilePayload]] + ] = None, timeout: typing.Optional[float] = None, fail_on_status_code: typing.Optional[bool] = None, ignore_https_errors: typing.Optional[bool] = None, @@ -14054,6 +15085,19 @@ async def head( Query parameters to be sent with the URL. headers : Union[Dict[str, str], None] Allows to set HTTP headers. + data : Union[Any, bytes, str, None] + Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string and + `content-type` header will be set to `application/json` if not explicitly set. Otherwise the `content-type` header will + be set to `application/octet-stream` if not explicitly set. + form : Union[Dict[str, Union[bool, float, str]], None] + Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as + this request body. If this parameter is specified `content-type` header will be set to + `application/x-www-form-urlencoded` unless explicitly provided. + multipart : Union[Dict[str, Union[bool, bytes, float, str, {name: str, mimeType: str, buffer: bytes}]], None] + Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request + body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly + provided. File values can be passed either as [`fs.ReadStream`](https://nodejs.org/api/fs.html#fs_class_fs_readstream) + or as file-like object containing file name, mime-type and its content. timeout : Union[float, None] Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. fail_on_status_code : Union[bool, None] @@ -14074,6 +15118,9 @@ async def head( url=url, params=mapping.to_impl(params), headers=mapping.to_impl(headers), + data=mapping.to_impl(data), + form=mapping.to_impl(form), + multipart=mapping.to_impl(multipart), timeout=timeout, failOnStatusCode=fail_on_status_code, ignoreHTTPSErrors=ignore_https_errors, @@ -14089,6 +15136,11 @@ async def get( typing.Dict[str, typing.Union[str, float, bool]] ] = None, headers: typing.Optional[typing.Dict[str, str]] = None, + data: typing.Optional[typing.Union[typing.Any, bytes, str]] = None, + form: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None, + multipart: typing.Optional[ + typing.Dict[str, typing.Union[bytes, bool, float, str, FilePayload]] + ] = None, timeout: typing.Optional[float] = None, fail_on_status_code: typing.Optional[bool] = None, ignore_https_errors: typing.Optional[bool] = None, @@ -14100,6 +15152,16 @@ async def get( method will populate request cookies from the context and update context cookies from the response. The method will automatically follow redirects. + Request parameters can be configured with `params` option, they will be serialized into the URL search parameters: + + ```python + query_params = { + \"isbn\": \"1234\", + \"page\": \"23\" + } + api_request_context.get(\"https://example.com/api/getText\", params=query_params) + ``` + Parameters ---------- url : str @@ -14108,6 +15170,19 @@ async def get( Query parameters to be sent with the URL. headers : Union[Dict[str, str], None] Allows to set HTTP headers. + data : Union[Any, bytes, str, None] + Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string and + `content-type` header will be set to `application/json` if not explicitly set. Otherwise the `content-type` header will + be set to `application/octet-stream` if not explicitly set. + form : Union[Dict[str, Union[bool, float, str]], None] + Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as + this request body. If this parameter is specified `content-type` header will be set to + `application/x-www-form-urlencoded` unless explicitly provided. + multipart : Union[Dict[str, Union[bool, bytes, float, str, {name: str, mimeType: str, buffer: bytes}]], None] + Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request + body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly + provided. File values can be passed either as [`fs.ReadStream`](https://nodejs.org/api/fs.html#fs_class_fs_readstream) + or as file-like object containing file name, mime-type and its content. timeout : Union[float, None] Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. fail_on_status_code : Union[bool, None] @@ -14128,6 +15203,9 @@ async def get( url=url, params=mapping.to_impl(params), headers=mapping.to_impl(headers), + data=mapping.to_impl(data), + form=mapping.to_impl(form), + multipart=mapping.to_impl(multipart), timeout=timeout, failOnStatusCode=fail_on_status_code, ignoreHTTPSErrors=ignore_https_errors, @@ -14309,6 +15387,42 @@ async def post( The method will populate request cookies from the context and update context cookies from the response. The method will automatically follow redirects. + JSON objects can be passed directly to the request: + + ```python + data = { + \"title\": \"Book Title\", + \"body\": \"John Doe\", + } + api_request_context.post(\"https://example.com/api/createBook\", data=data) + ``` + + To send form data to the server use `form` option. Its value will be encoded into the request body with + `application/x-www-form-urlencoded` encoding (see below how to use `multipart/form-data` form encoding to send files): + + ```python + formData = { + \"title\": \"Book Title\", + \"body\": \"John Doe\", + } + api_request_context.post(\"https://example.com/api/findBook\", form=formData) + ``` + + The common way to send file(s) in the body of a request is to upload them as form fields with `multipart/form-data` + encoding. You can achieve that with Playwright API like this: + + ```python + api_request_context.post( + \"https://example.com/api/uploadScrip'\", + multipart={ + \"fileField\": { + \"name\": \"f.js\", + \"mimeType\": \"text/javascript\", + \"buffer\": b\"console.log(2022);\", + }, + }) + ``` + Parameters ---------- url : str @@ -14384,6 +15498,32 @@ async def fetch( Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update context cookies from the response. The method will automatically follow redirects. + JSON objects can be passed directly to the request: + + ```python + data = { + \"title\": \"Book Title\", + \"body\": \"John Doe\", + } + api_request_context.fetch(\"https://example.com/api/createBook\", method=\"post\", data=data) + ``` + + The common way to send file(s) in the body of a request is to encode it as form fields with `multipart/form-data` + encoding. You can achieve that with Playwright API like this: + + ```python + api_request_context.fetch( + \"https://example.com/api/uploadScrip'\", + method=\"post\", + multipart={ + \"fileField\": { + \"name\": \"f.js\", + \"mimeType\": \"text/javascript\", + \"buffer\": b\"console.log(2022);\", + }, + }) + ``` + Parameters ---------- url_or_request : Union[Request, str] @@ -14982,7 +16122,7 @@ async def to_have_css( ```py from playwright.async_api import expect - locator = page.locator(\"button\") + locator = page.get_by_role(\"button\") await expect(locator).to_have_css(\"display\", \"flex\") ``` @@ -15042,7 +16182,7 @@ async def to_have_id( ```py from playwright.async_api import expect - locator = page.locator(\"input\") + locator = page.get_by_role(\"textbox\") await expect(locator).to_have_id(\"lastname\") ``` @@ -15396,7 +16536,7 @@ async def to_be_checked( ```py from playwright.async_api import expect - locator = page.locator(\".subscribe\") + locator = page.get_by_label(\"Subscribe to newsletter\") await expect(locator).to_be_checked() ``` @@ -15487,7 +16627,7 @@ async def to_be_editable( ```py from playwright.async_api import expect - locator = page.locator(\".input\") + locator = page.get_by_role(\"textbox\") await expect(locator).to_be_editable() ``` @@ -15663,8 +16803,8 @@ async def to_be_visible( ) -> None: """LocatorAssertions.to_be_visible - Ensures that `Locator` points to an [attached](https://playwright.dev/python/docs/api/actionability#visible) and [visible](https://playwright.dev/python/docs/api/actionability#visible) DOM - node. + Ensures that `Locator` points to an [attached](https://playwright.dev/python/docs/api/actionability#attached) and [visible](https://playwright.dev/python/docs/api/actionability#visible) + DOM node. ```py from playwright.async_api import expect @@ -15715,7 +16855,7 @@ async def to_be_focused(self, *, timeout: typing.Optional[float] = None) -> None ```py from playwright.async_api import expect - locator = page.locator('input') + locator = page.get_by_role(\"textbox\") await expect(locator).to_be_focused() ``` diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index 2c9f54ff7..b1a55548b 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -1342,8 +1342,8 @@ def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -1378,8 +1378,8 @@ def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -2641,8 +2641,8 @@ def eval_on_selector( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -2691,8 +2691,8 @@ def eval_on_selector_all( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -3292,8 +3292,8 @@ def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -3344,8 +3344,8 @@ def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -3809,8 +3809,8 @@ def eval_on_selector( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. strict : Union[bool, None] @@ -3860,8 +3860,8 @@ def eval_on_selector_all( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -4314,9 +4314,11 @@ def locator( ) -> "Locator": """Frame.locator - The method returns an element locator that can be used to perform actions in the frame. Locator is resolved to the - element immediately before performing an action, so a series of actions on the same locator can in fact be performed on - different DOM elements. That would happen if the DOM structure between those actions has changed. + The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to + the element immediately before performing an action, so a series of actions on the same locator can in fact be performed + on different DOM elements. That would happen if the DOM structure between those actions has changed. + + [Learn more about locators](https://playwright.dev/python/docs/locators). [Learn more about locators](https://playwright.dev/python/docs/locators). @@ -4345,6 +4347,250 @@ def locator( ) ) + def get_by_alt_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_alt_text + + Allows locating elements by their alt text. For example, this method will find the image by alt text \"Castle\": + + ```html + Castle + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_alt_text(text=text, exact=exact)) + + def get_by_label( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_label + + Allows locating input elements by the text of the associated label. For example, this method will find the input by + label text Password in the following DOM: + + ```html + + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_label(text=text, exact=exact)) + + def get_by_placeholder( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_placeholder + + Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder + \"Country\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_placeholder(text=text, exact=exact) + ) + + def get_by_role( + self, + role: str, + *, + checked: typing.Optional[bool] = None, + disabled: typing.Optional[bool] = None, + expanded: typing.Optional[bool] = None, + include_hidden: typing.Optional[bool] = None, + level: typing.Optional[int] = None, + name: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + pressed: typing.Optional[bool] = None, + selected: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_role + + Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), + [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and + [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). Note that role selector **does not replace** + accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines. + + Note that many html elements have an implicitly + [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You + can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not + recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values. + + Parameters + ---------- + role : str + Required aria role. + checked : Union[bool, None] + An attribute that is usually set by `aria-checked` or native `` controls. Available values for + checked are `true`, `false` and `"mixed"`. + + Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked). + disabled : Union[bool, None] + A boolean attribute that is usually set by `aria-disabled` or `disabled`. + + > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about + [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled). + expanded : Union[bool, None] + A boolean attribute that is usually set by `aria-expanded`. + + Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded). + include_hidden : Union[bool, None] + A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as + [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector. + + Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden). + level : Union[int, None] + A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for + `

-

` elements. + + Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level). + name : Union[Pattern[str], str, None] + A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + + Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + pressed : Union[bool, None] + An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`. + + Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed). + selected : Union[bool, None] + A boolean attribute that is usually set by `aria-selected`. + + Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected). + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_role( + role=role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=include_hidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, test_id: str) -> "Locator": + """Frame.get_by_test_id + + Locate element by the test id. By default, the `data-testid` attribute is used as a test id. Use + `selectors.set_test_id_attribute()` to configure a different test id attribute if necessary. + + Parameters + ---------- + test_id : str + Id to locate the element by. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_test_id(testId=test_id)) + + def get_by_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_text + + Allows locating elements that contain given text. + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_text(text=text, exact=exact)) + + def get_by_title( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Frame.get_by_title + + Allows locating elements by their title. For example, this method will find the button by its title \"Submit\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_title(text=text, exact=exact)) + def frame_locator(self, selector: str) -> "FrameLocator": """Frame.frame_locator @@ -4353,7 +4599,7 @@ def frame_locator(self, selector: str) -> "FrameLocator": id=\"my-frame\">`: ```py - locator = frame.frame_locator(\"#my-iframe\").locator(\"text=Submit\") + locator = frame.frame_locator(\"#my-iframe\").get_by_text(\"Submit\") locator.click() ``` @@ -5160,8 +5406,8 @@ def run(playwright): Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. timeout : Union[float, None] @@ -5307,7 +5553,10 @@ def locator( ) -> "Locator": """FrameLocator.locator - The method finds an element matching the specified selector in the FrameLocator's subtree. + The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options, + similar to `locator.filter()` method. + + [Learn more about locators](https://playwright.dev/python/docs/locators). Parameters ---------- @@ -5334,6 +5583,250 @@ def locator( ) ) + def get_by_alt_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_alt_text + + Allows locating elements by their alt text. For example, this method will find the image by alt text \"Castle\": + + ```html + Castle + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_alt_text(text=text, exact=exact)) + + def get_by_label( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_label + + Allows locating input elements by the text of the associated label. For example, this method will find the input by + label text Password in the following DOM: + + ```html + + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_label(text=text, exact=exact)) + + def get_by_placeholder( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_placeholder + + Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder + \"Country\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_placeholder(text=text, exact=exact) + ) + + def get_by_role( + self, + role: str, + *, + checked: typing.Optional[bool] = None, + disabled: typing.Optional[bool] = None, + expanded: typing.Optional[bool] = None, + include_hidden: typing.Optional[bool] = None, + level: typing.Optional[int] = None, + name: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + pressed: typing.Optional[bool] = None, + selected: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_role + + Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), + [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and + [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). Note that role selector **does not replace** + accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines. + + Note that many html elements have an implicitly + [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You + can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not + recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values. + + Parameters + ---------- + role : str + Required aria role. + checked : Union[bool, None] + An attribute that is usually set by `aria-checked` or native `` controls. Available values for + checked are `true`, `false` and `"mixed"`. + + Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked). + disabled : Union[bool, None] + A boolean attribute that is usually set by `aria-disabled` or `disabled`. + + > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about + [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled). + expanded : Union[bool, None] + A boolean attribute that is usually set by `aria-expanded`. + + Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded). + include_hidden : Union[bool, None] + A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as + [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector. + + Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden). + level : Union[int, None] + A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for + `

-

` elements. + + Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level). + name : Union[Pattern[str], str, None] + A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + + Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + pressed : Union[bool, None] + An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`. + + Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed). + selected : Union[bool, None] + A boolean attribute that is usually set by `aria-selected`. + + Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected). + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_role( + role=role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=include_hidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, test_id: str) -> "Locator": + """FrameLocator.get_by_test_id + + Locate element by the test id. By default, the `data-testid` attribute is used as a test id. Use + `selectors.set_test_id_attribute()` to configure a different test id attribute if necessary. + + Parameters + ---------- + test_id : str + Id to locate the element by. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_test_id(testId=test_id)) + + def get_by_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_text + + Allows locating elements that contain given text. + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_text(text=text, exact=exact)) + + def get_by_title( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """FrameLocator.get_by_title + + Allows locating elements by their title. For example, this method will find the button by its title \"Submit\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_title(text=text, exact=exact)) + def frame_locator(self, selector: str) -> "FrameLocator": """FrameLocator.frame_locator @@ -5414,8 +5907,8 @@ def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -5446,8 +5939,8 @@ def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -5540,6 +6033,21 @@ def run(playwright): ) ) + def set_test_id_attribute(self, attribute_name: str) -> None: + """Selectors.set_test_id_attribute + + Defines custom attribute name to be used in `page.get_by_test_id()`. `data-testid` is used by default. + + Parameters + ---------- + attribute_name : str + Test id attribute name. + """ + + return mapping.from_maybe_impl( + self._impl_obj.set_test_id_attribute(attribute_name=attribute_name) + ) + mapping.register(SelectorsImpl, Selectors) @@ -6913,8 +7421,8 @@ def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -6965,8 +7473,8 @@ def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -7017,8 +7525,8 @@ def eval_on_selector( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. strict : Union[bool, None] @@ -7066,8 +7574,8 @@ def eval_on_selector_all( selector : str A selector to query for. See [working with selectors](../selectors.md) for more details. expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -7486,13 +7994,13 @@ def wait_for_load_state( when this method is called. If current document has already reached the required state, resolves immediately. ```py - page.click(\"button\") # click triggers navigation. + page.get_by_role(\"button\").click() # click triggers navigation. page.wait_for_load_state() # the promise resolves after \"load\" event. ``` ```py with page.expect_popup() as page_info: - page.click(\"button\") # click triggers a popup. + page.get_by_role(\"button\").click() # click triggers a popup. popup = page_info.value # Following resolves after \"domcontentloaded\" event. popup.wait_for_load_state(\"domcontentloaded\") @@ -8405,47 +8913,289 @@ def fill( ) ) - def locator( + def locator( + self, + selector: str, + *, + has_text: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + has: typing.Optional["Locator"] = None + ) -> "Locator": + """Page.locator + + The method returns an element locator that can be used to perform actions on this page / frame. Locator is resolved to + the element immediately before performing an action, so a series of actions on the same locator can in fact be performed + on different DOM elements. That would happen if the DOM structure between those actions has changed. + + [Learn more about locators](https://playwright.dev/python/docs/locators). + + Parameters + ---------- + selector : str + A selector to use when resolving DOM element. See [working with selectors](../selectors.md) for more details. + has_text : Union[Pattern[str], str, None] + Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a + [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches + `
Playwright
`. + has : Union[Locator, None] + Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one. + For example, `article` that has `text=Playwright` matches `
Playwright
`. + + Note that outer and inner locators must belong to the same frame. Inner locator must not contain `FrameLocator`s. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.locator( + selector=selector, has_text=has_text, has=has._impl_obj if has else None + ) + ) + + def get_by_alt_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_alt_text + + Allows locating elements by their alt text. For example, this method will find the image by alt text \"Castle\": + + ```html + Castle + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_alt_text(text=text, exact=exact)) + + def get_by_label( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_label + + Allows locating input elements by the text of the associated label. For example, this method will find the input by + label text Password in the following DOM: + + ```html + + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_label(text=text, exact=exact)) + + def get_by_placeholder( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_placeholder + + Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder + \"Country\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_placeholder(text=text, exact=exact) + ) + + def get_by_role( + self, + role: str, + *, + checked: typing.Optional[bool] = None, + disabled: typing.Optional[bool] = None, + expanded: typing.Optional[bool] = None, + include_hidden: typing.Optional[bool] = None, + level: typing.Optional[int] = None, + name: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + pressed: typing.Optional[bool] = None, + selected: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_role + + Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), + [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and + [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). Note that role selector **does not replace** + accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines. + + Note that many html elements have an implicitly + [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You + can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not + recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values. + + Parameters + ---------- + role : str + Required aria role. + checked : Union[bool, None] + An attribute that is usually set by `aria-checked` or native `` controls. Available values for + checked are `true`, `false` and `"mixed"`. + + Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked). + disabled : Union[bool, None] + A boolean attribute that is usually set by `aria-disabled` or `disabled`. + + > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about + [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled). + expanded : Union[bool, None] + A boolean attribute that is usually set by `aria-expanded`. + + Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded). + include_hidden : Union[bool, None] + A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as + [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector. + + Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden). + level : Union[int, None] + A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for + `

-

` elements. + + Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level). + name : Union[Pattern[str], str, None] + A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + + Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + pressed : Union[bool, None] + An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`. + + Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed). + selected : Union[bool, None] + A boolean attribute that is usually set by `aria-selected`. + + Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected). + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_role( + role=role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=include_hidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, test_id: str) -> "Locator": + """Page.get_by_test_id + + Locate element by the test id. By default, the `data-testid` attribute is used as a test id. Use + `selectors.set_test_id_attribute()` to configure a different test id attribute if necessary. + + Parameters + ---------- + test_id : str + Id to locate the element by. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_test_id(testId=test_id)) + + def get_by_text( self, - selector: str, + text: typing.Union[str, typing.Pattern[str]], *, - has_text: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, - has: typing.Optional["Locator"] = None + exact: typing.Optional[bool] = None ) -> "Locator": - """Page.locator + """Page.get_by_text - The method returns an element locator that can be used to perform actions on the page. Locator is resolved to the - element immediately before performing an action, so a series of actions on the same locator can in fact be performed on - different DOM elements. That would happen if the DOM structure between those actions has changed. + Allows locating elements that contain given text. - [Learn more about locators](https://playwright.dev/python/docs/locators). + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_text(text=text, exact=exact)) + + def get_by_title( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Page.get_by_title - Shortcut for main frame's `frame.locator()`. + Allows locating elements by their title. For example, this method will find the button by its title \"Submit\": + + ```html + + ``` Parameters ---------- - selector : str - A selector to use when resolving DOM element. See [working with selectors](../selectors.md) for more details. - has_text : Union[Pattern[str], str, None] - Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a - [string], matching is case-insensitive and searches for a substring. For example, `"Playwright"` matches - `
Playwright
`. - has : Union[Locator, None] - Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one. - For example, `article` that has `text=Playwright` matches `
Playwright
`. - - Note that outer and inner locators must belong to the same frame. Inner locator must not contain `FrameLocator`s. + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. Returns ------- Locator """ - return mapping.from_impl( - self._impl_obj.locator( - selector=selector, has_text=has_text, has=has._impl_obj if has else None - ) - ) + return mapping.from_impl(self._impl_obj.get_by_title(text=text, exact=exact)) def frame_locator(self, selector: str) -> "FrameLocator": """Page.frame_locator @@ -8455,7 +9205,7 @@ def frame_locator(self, selector: str) -> "FrameLocator": id=\"my-frame\">`: ```py - locator = page.frame_locator(\"#my-iframe\").locator(\"text=Submit\") + locator = page.frame_locator(\"#my-iframe\").get_by_text(\"Submit\") locator.click() ``` @@ -8732,6 +9482,20 @@ def drag_and_drop( ) -> None: """Page.drag_and_drop + This method drags the source element to the target element. It will first move to the source element, perform a + `mousedown`, then move to the target element and perform a `mouseup`. + + ```py + page.drag_and_drop(\"#source\", \"#target\") + # or specify exact positions relative to the top-left corners of the elements: + page.drag_and_drop( + \"#source\", + \"#target\", + source_position={\"x\": 34, \"y\": 7}, + target_position={\"x\": 10, \"y\": 20} + ) + ``` + Parameters ---------- source : str @@ -9298,8 +10062,8 @@ def run(playwright): Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. timeout : Union[float, None] @@ -9481,7 +10245,7 @@ def expect_event( ```py with page.expect_event(\"framenavigated\") as event_info: - page.click(\"button\") + page.get_by_role(\"button\") frame = event_info.value ``` @@ -10505,7 +11269,7 @@ def run(playwright):
\"\"\") - page.locator(\"button\").click() + page.get_by_role(\"button\").click() with sync_playwright() as playwright: run(playwright) @@ -10582,7 +11346,7 @@ def run(playwright):
\"\"\") - page.locator(\"button\").click() + page.get_by_role(\"button\").click() with sync_playwright() as playwright: run(playwright) @@ -10774,7 +11538,7 @@ def expect_event( ```py with context.expect_event(\"page\") as event_info: - page.locator(\"button\").click() + page.get_by_role(\"button\").click() page = event_info.value ``` @@ -11197,7 +11961,7 @@ def new_context( security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`. record_har_content : Union["attach", "embed", "omit", None] Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` - is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. Defaults to `embed`, which stores content inline the HAR file as per HAR specification. Returns @@ -11394,7 +12158,7 @@ def new_page( security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`. record_har_content : Union["attach", "embed", "omit", None] Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` - is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. Defaults to `embed`, which stores content inline the HAR file as per HAR specification. Returns @@ -11900,7 +12664,7 @@ def launch_persistent_context( security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`. record_har_content : Union["attach", "embed", "omit", None] Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` - is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. + is specified, resources are persisted as separate files and all of these files are archived along with the HAR file. Defaults to `embed`, which stores content inline the HAR file as per HAR specification. Returns @@ -12246,7 +13010,7 @@ def start_chunk(self, *, title: typing.Optional[str] = None) -> None: page.goto(\"https://playwright.dev\") context.tracing.start_chunk() - page.locator(\"text=Get Started\").click() + page.get_by_text(\"Get Started\").click() # Everything between start_chunk and stop_chunk will be recorded in the trace. context.tracing.stop_chunk(path = \"trace1.zip\") @@ -12655,8 +13419,8 @@ def evaluate( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. timeout : Union[float, None] @@ -12697,8 +13461,8 @@ def evaluate_all( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. @@ -12739,8 +13503,8 @@ def evaluate_handle( Parameters ---------- expression : str - JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted - as a function. Otherwise, evaluated as an expression. + JavaScript expression to be evaluated in the browser context. If the expresion evaluates to a function, the function is + automatically invoked. arg : Union[Any, None] Optional argument to pass to `expression`. timeout : Union[float, None] @@ -12812,9 +13576,11 @@ def locator( ) -> "Locator": """Locator.locator - The method finds an element matching the specified selector in the `Locator`'s subtree. It also accepts filter options, + The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options, similar to `locator.filter()` method. + [Learn more about locators](https://playwright.dev/python/docs/locators). + Parameters ---------- selector : str @@ -12840,6 +13606,250 @@ def locator( ) ) + def get_by_alt_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_alt_text + + Allows locating elements by their alt text. For example, this method will find the image by alt text \"Castle\": + + ```html + Castle + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_alt_text(text=text, exact=exact)) + + def get_by_label( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_label + + Allows locating input elements by the text of the associated label. For example, this method will find the input by + label text Password in the following DOM: + + ```html + + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_label(text=text, exact=exact)) + + def get_by_placeholder( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_placeholder + + Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder + \"Country\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_placeholder(text=text, exact=exact) + ) + + def get_by_role( + self, + role: str, + *, + checked: typing.Optional[bool] = None, + disabled: typing.Optional[bool] = None, + expanded: typing.Optional[bool] = None, + include_hidden: typing.Optional[bool] = None, + level: typing.Optional[int] = None, + name: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None, + pressed: typing.Optional[bool] = None, + selected: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_role + + Allows locating elements by their [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles), + [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and + [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). Note that role selector **does not replace** + accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines. + + Note that many html elements have an implicitly + [defined role](https://w3c.github.io/html-aam/#html-element-role-mappings) that is recognized by the role selector. You + can find all the [supported roles here](https://www.w3.org/TR/wai-aria-1.2/#role_definitions). ARIA guidelines **do not + recommend** duplicating implicit roles and attributes by setting `role` and/or `aria-*` attributes to default values. + + Parameters + ---------- + role : str + Required aria role. + checked : Union[bool, None] + An attribute that is usually set by `aria-checked` or native `` controls. Available values for + checked are `true`, `false` and `"mixed"`. + + Learn more about [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked). + disabled : Union[bool, None] + A boolean attribute that is usually set by `aria-disabled` or `disabled`. + + > NOTE: Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about + [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled). + expanded : Union[bool, None] + A boolean attribute that is usually set by `aria-expanded`. + + Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded). + include_hidden : Union[bool, None] + A boolean attribute that controls whether hidden elements are matched. By default, only non-hidden elements, as + [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector. + + Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden). + level : Union[int, None] + A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values for + `

-

` elements. + + Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level). + name : Union[Pattern[str], str, None] + A string attribute that matches [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + + Learn more about [accessible name](https://w3c.github.io/accname/#dfn-accessible-name). + pressed : Union[bool, None] + An attribute that is usually set by `aria-pressed`. Available values for pressed are `true`, `false` and `"mixed"`. + + Learn more about [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed). + selected : Union[bool, None] + A boolean attribute that is usually set by `aria-selected`. + + Learn more about [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected). + + Returns + ------- + Locator + """ + + return mapping.from_impl( + self._impl_obj.get_by_role( + role=role, + checked=checked, + disabled=disabled, + expanded=expanded, + includeHidden=include_hidden, + level=level, + name=name, + pressed=pressed, + selected=selected, + ) + ) + + def get_by_test_id(self, test_id: str) -> "Locator": + """Locator.get_by_test_id + + Locate element by the test id. By default, the `data-testid` attribute is used as a test id. Use + `selectors.set_test_id_attribute()` to configure a different test id attribute if necessary. + + Parameters + ---------- + test_id : str + Id to locate the element by. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_test_id(testId=test_id)) + + def get_by_text( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_text + + Allows locating elements that contain given text. + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_text(text=text, exact=exact)) + + def get_by_title( + self, + text: typing.Union[str, typing.Pattern[str]], + *, + exact: typing.Optional[bool] = None + ) -> "Locator": + """Locator.get_by_title + + Allows locating elements by their title. For example, this method will find the button by its title \"Submit\": + + ```html + + ``` + + Parameters + ---------- + text : Union[Pattern[str], str] + Text to locate the element for. + exact : Union[bool, None] + Whether to find an exact match: case-sensitive and whole-string. Default to false. + + Returns + ------- + Locator + """ + + return mapping.from_impl(self._impl_obj.get_by_title(text=text, exact=exact)) + def frame_locator(self, selector: str) -> "FrameLocator": """Locator.frame_locator @@ -12847,7 +13857,7 @@ def frame_locator(self, selector: str) -> "FrameLocator": that iframe: ```py - locator = page.frame_locator(\"iframe\").locator(\"text=Submit\") + locator = page.frame_locator(\"iframe\").get_by_text(\"Submit\") locator.click() ``` @@ -12930,7 +13940,7 @@ def filter( # ... row_locator .filter(has_text=\"text in column 1\") - .filter(has=page.locator(\"tr\", has_text=\"column 2 button\")) + .filter(has=page.get_by_role(\"button\", name=\"column 2 button\")) .screenshot() ``` @@ -12996,6 +14006,22 @@ def drag_to( ) -> None: """Locator.drag_to + This method drags the locator to another target locator or target position. It will first move to the source element, + perform a `mousedown`, then move to the target element or position and perform a `mouseup`. + + ```py + source = page.locator(\"#source\") + target = page.locator(\"#target\") + + source.drag_to(target) + # or specify exact positions relative to the top-left corners of the elements: + source.drag_to( + target, + source_position={\"x\": 34, \"y\": 7}, + target_position={\"x\": 10, \"y\": 20} + ) + ``` + Parameters ---------- target : Locator @@ -13703,8 +14729,8 @@ def type( An example of typing into a text field and then submitting the form: ```py - element = page.locator(\"input\") - element.type(\"some text\") + element = page.get_by_label(\"Password\") + element.type(\"my password\") element.press(\"Enter\") ``` @@ -14142,6 +15168,11 @@ def head( typing.Dict[str, typing.Union[str, float, bool]] ] = None, headers: typing.Optional[typing.Dict[str, str]] = None, + data: typing.Optional[typing.Union[typing.Any, bytes, str]] = None, + form: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None, + multipart: typing.Optional[ + typing.Dict[str, typing.Union[bytes, bool, float, str, FilePayload]] + ] = None, timeout: typing.Optional[float] = None, fail_on_status_code: typing.Optional[bool] = None, ignore_https_errors: typing.Optional[bool] = None, @@ -14161,6 +15192,19 @@ def head( Query parameters to be sent with the URL. headers : Union[Dict[str, str], None] Allows to set HTTP headers. + data : Union[Any, bytes, str, None] + Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string and + `content-type` header will be set to `application/json` if not explicitly set. Otherwise the `content-type` header will + be set to `application/octet-stream` if not explicitly set. + form : Union[Dict[str, Union[bool, float, str]], None] + Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as + this request body. If this parameter is specified `content-type` header will be set to + `application/x-www-form-urlencoded` unless explicitly provided. + multipart : Union[Dict[str, Union[bool, bytes, float, str, {name: str, mimeType: str, buffer: bytes}]], None] + Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request + body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly + provided. File values can be passed either as [`fs.ReadStream`](https://nodejs.org/api/fs.html#fs_class_fs_readstream) + or as file-like object containing file name, mime-type and its content. timeout : Union[float, None] Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. fail_on_status_code : Union[bool, None] @@ -14182,6 +15226,9 @@ def head( url=url, params=mapping.to_impl(params), headers=mapping.to_impl(headers), + data=mapping.to_impl(data), + form=mapping.to_impl(form), + multipart=mapping.to_impl(multipart), timeout=timeout, failOnStatusCode=fail_on_status_code, ignoreHTTPSErrors=ignore_https_errors, @@ -14198,6 +15245,11 @@ def get( typing.Dict[str, typing.Union[str, float, bool]] ] = None, headers: typing.Optional[typing.Dict[str, str]] = None, + data: typing.Optional[typing.Union[typing.Any, bytes, str]] = None, + form: typing.Optional[typing.Dict[str, typing.Union[str, float, bool]]] = None, + multipart: typing.Optional[ + typing.Dict[str, typing.Union[bytes, bool, float, str, FilePayload]] + ] = None, timeout: typing.Optional[float] = None, fail_on_status_code: typing.Optional[bool] = None, ignore_https_errors: typing.Optional[bool] = None, @@ -14209,6 +15261,16 @@ def get( method will populate request cookies from the context and update context cookies from the response. The method will automatically follow redirects. + Request parameters can be configured with `params` option, they will be serialized into the URL search parameters: + + ```python + query_params = { + \"isbn\": \"1234\", + \"page\": \"23\" + } + api_request_context.get(\"https://example.com/api/getText\", params=query_params) + ``` + Parameters ---------- url : str @@ -14217,6 +15279,19 @@ def get( Query parameters to be sent with the URL. headers : Union[Dict[str, str], None] Allows to set HTTP headers. + data : Union[Any, bytes, str, None] + Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string and + `content-type` header will be set to `application/json` if not explicitly set. Otherwise the `content-type` header will + be set to `application/octet-stream` if not explicitly set. + form : Union[Dict[str, Union[bool, float, str]], None] + Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as + this request body. If this parameter is specified `content-type` header will be set to + `application/x-www-form-urlencoded` unless explicitly provided. + multipart : Union[Dict[str, Union[bool, bytes, float, str, {name: str, mimeType: str, buffer: bytes}]], None] + Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request + body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly + provided. File values can be passed either as [`fs.ReadStream`](https://nodejs.org/api/fs.html#fs_class_fs_readstream) + or as file-like object containing file name, mime-type and its content. timeout : Union[float, None] Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. fail_on_status_code : Union[bool, None] @@ -14238,6 +15313,9 @@ def get( url=url, params=mapping.to_impl(params), headers=mapping.to_impl(headers), + data=mapping.to_impl(data), + form=mapping.to_impl(form), + multipart=mapping.to_impl(multipart), timeout=timeout, failOnStatusCode=fail_on_status_code, ignoreHTTPSErrors=ignore_https_errors, @@ -14424,6 +15502,42 @@ def post( The method will populate request cookies from the context and update context cookies from the response. The method will automatically follow redirects. + JSON objects can be passed directly to the request: + + ```python + data = { + \"title\": \"Book Title\", + \"body\": \"John Doe\", + } + api_request_context.post(\"https://example.com/api/createBook\", data=data) + ``` + + To send form data to the server use `form` option. Its value will be encoded into the request body with + `application/x-www-form-urlencoded` encoding (see below how to use `multipart/form-data` form encoding to send files): + + ```python + formData = { + \"title\": \"Book Title\", + \"body\": \"John Doe\", + } + api_request_context.post(\"https://example.com/api/findBook\", form=formData) + ``` + + The common way to send file(s) in the body of a request is to upload them as form fields with `multipart/form-data` + encoding. You can achieve that with Playwright API like this: + + ```python + api_request_context.post( + \"https://example.com/api/uploadScrip'\", + multipart={ + \"fileField\": { + \"name\": \"f.js\", + \"mimeType\": \"text/javascript\", + \"buffer\": b\"console.log(2022);\", + }, + }) + ``` + Parameters ---------- url : str @@ -14501,6 +15615,32 @@ def fetch( Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update context cookies from the response. The method will automatically follow redirects. + JSON objects can be passed directly to the request: + + ```python + data = { + \"title\": \"Book Title\", + \"body\": \"John Doe\", + } + api_request_context.fetch(\"https://example.com/api/createBook\", method=\"post\", data=data) + ``` + + The common way to send file(s) in the body of a request is to encode it as form fields with `multipart/form-data` + encoding. You can achieve that with Playwright API like this: + + ```python + api_request_context.fetch( + \"https://example.com/api/uploadScrip'\", + method=\"post\", + multipart={ + \"fileField\": { + \"name\": \"f.js\", + \"mimeType\": \"text/javascript\", + \"buffer\": b\"console.log(2022);\", + }, + }) + ``` + Parameters ---------- url_or_request : Union[Request, str] @@ -15123,7 +16263,7 @@ def to_have_css( ```py from playwright.sync_api import expect - locator = page.locator(\"button\") + locator = page.get_by_role(\"button\") expect(locator).to_have_css(\"display\", \"flex\") ``` @@ -15185,7 +16325,7 @@ def to_have_id( ```py from playwright.sync_api import expect - locator = page.locator(\"input\") + locator = page.get_by_role(\"textbox\") expect(locator).to_have_id(\"lastname\") ``` @@ -15551,7 +16691,7 @@ def to_be_checked( ```py from playwright.sync_api import expect - locator = page.locator(\".subscribe\") + locator = page.get_by_label(\"Subscribe to newsletter\") expect(locator).to_be_checked() ``` @@ -15638,7 +16778,7 @@ def to_be_editable( ```py from playwright.sync_api import expect - locator = page.locator(\".input\") + locator = page.get_by_role(\"textbox\") expect(locator).to_be_editable() ``` @@ -15820,8 +16960,8 @@ def to_be_visible( ) -> None: """LocatorAssertions.to_be_visible - Ensures that `Locator` points to an [attached](https://playwright.dev/python/docs/api/actionability#visible) and [visible](https://playwright.dev/python/docs/api/actionability#visible) DOM - node. + Ensures that `Locator` points to an [attached](https://playwright.dev/python/docs/api/actionability#attached) and [visible](https://playwright.dev/python/docs/api/actionability#visible) + DOM node. ```py from playwright.sync_api import expect @@ -15874,7 +17014,7 @@ def to_be_focused(self, *, timeout: typing.Optional[float] = None) -> None: ```py from playwright.sync_api import expect - locator = page.locator('input') + locator = page.get_by_role(\"textbox\") expect(locator).to_be_focused() ``` diff --git a/setup.py b/setup.py index 22e1c0c13..ebd8d0e82 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ InWheel = None from wheel.bdist_wheel import bdist_wheel as BDistWheelCommand -driver_version = "1.26.1" +driver_version = "1.27.0-alpha-1664914898000" def extractall(zip: zipfile.ZipFile, path: str) -> None: diff --git a/tests/sync/test_locator_get_by.py b/tests/sync/test_locator_get_by.py new file mode 100644 index 000000000..bc6f6ee05 --- /dev/null +++ b/tests/sync/test_locator_get_by.py @@ -0,0 +1,106 @@ +# 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 playwright.sync_api import Page, expect + + +def test_get_by_test_id(page: Page) -> None: + page.set_content("
Hello world
") + expect(page.get_by_test_id("Hello")).to_have_text("Hello world") + expect(page.main_frame.get_by_test_id("Hello")).to_have_text("Hello world") + expect(page.locator("div").get_by_test_id("Hello")).to_have_text("Hello world") + + +def test_get_by_test_id_escape_id(page: Page) -> None: + page.set_content("
Hello world
") + expect(page.get_by_test_id('He"llo')).to_have_text("Hello world") + + +def test_get_by_text(page: Page) -> None: + page.set_content("
yo
ya
\nye
") + assert ">\nye " in page.get_by_text("ye").evaluate("e => e.outerHTML") + assert ">\nye " in page.get_by_text(r"ye").evaluate("e => e.outerHTML") + + page.set_content("
ye
ye
") + assert "> ye " in page.get_by_text("ye", exact=True).first.evaluate( + "e => e.outerHTML" + ) + + page.set_content("
Hello world
Hello
") + assert ( + page.get_by_text("Hello", exact=True).evaluate("e => e.outerHTML") + == "
Hello
" + ) + + +def test_get_by_label(page: Page) -> None: + page.set_content( + "
" + ) + assert page.get_by_text("Name").evaluate("e => e.nodeName") == "LABEL" + assert page.get_by_label("Name").evaluate("e => e.nodeName") == "INPUT" + assert page.main_frame.get_by_label("Name").evaluate("e => e.nodeName") == "INPUT" + assert ( + page.locator("div").get_by_label("Name").evaluate("e => e.nodeName") == "INPUT" + ) + + +def test_get_by_placeholder(page: Page) -> None: + page.set_content( + """
+ + +
""" + ) + expect(page.get_by_placeholder("hello")).to_have_count(2) + expect(page.get_by_placeholder("Hello", exact=True)).to_have_count(1) + expect(page.get_by_placeholder(re.compile(r"wor", re.IGNORECASE))).to_have_count(1) + + # Coverage + expect(page.main_frame.get_by_placeholder("hello")).to_have_count(2) + expect(page.locator("div").get_by_placeholder("hello")).to_have_count(2) + + +def test_get_by_alt_text(page: Page) -> None: + page.set_content( + """
+ + +
""" + ) + expect(page.get_by_alt_text("hello")).to_have_count(2) + expect(page.get_by_alt_text("Hello", exact=True)).to_have_count(1) + expect(page.get_by_alt_text(re.compile(r"wor", re.IGNORECASE))).to_have_count(1) + + # Coverage + expect(page.main_frame.get_by_alt_text("hello")).to_have_count(2) + expect(page.locator("div").get_by_alt_text("hello")).to_have_count(2) + + +def test_get_by_title(page: Page) -> None: + page.set_content( + """
+ + +
""" + ) + expect(page.get_by_title("hello")).to_have_count(2) + expect(page.get_by_title("Hello", exact=True)).to_have_count(1) + expect(page.get_by_title(re.compile(r"wor", re.IGNORECASE))).to_have_count(1) + + # Coverage + expect(page.main_frame.get_by_title("hello")).to_have_count(2) + expect(page.locator("div").get_by_title("hello")).to_have_count(2) From d2dad56ef0fe3b9b91731973e2b2d72c0ff97576 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 6 Oct 2022 07:16:11 -0800 Subject: [PATCH 002/438] chore: roll Playwright to ToT (#1579) --- playwright/_impl/_locator.py | 28 +++++------ playwright/_impl/_str_utils.py | 15 +----- setup.py | 2 +- tests/sync/test_locator_get_by.py | 83 ++++++++++++++++++++++++++++++- 4 files changed, 97 insertions(+), 31 deletions(-) diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index e2d206b62..481d9a155 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -49,7 +49,6 @@ escape_for_attribute_selector, escape_for_text_selector, escape_regex_flags, - escape_with_quotes, ) if sys.version_info >= (3, 8): # pragma: no cover @@ -79,19 +78,17 @@ def __init__( self._dispatcher_fiber = frame._connection._dispatcher_fiber if has_text: - if isinstance(has_text, Pattern): - js_regex = f"/{has_text.pattern}/{escape_regex_flags(has_text)}" - self._selector += ( - f' >> has={json.dumps("text=" + js_regex, ensure_ascii=False)}' - ) - else: - escaped = escape_with_quotes(has_text, '"') - self._selector += f" >> :scope:has-text({escaped})" + text_selector = "text=" + escape_for_text_selector(has_text, exact=False) + self._selector += ( + f" >> internal:has={json.dumps(text_selector, ensure_ascii=False)}" + ) if has: if has._frame != frame: raise Error('Inner "has" locator must belong to the same frame.') - self._selector += " >> has=" + json.dumps(has._selector, ensure_ascii=False) + self._selector += " >> internal:has=" + json.dumps( + has._selector, ensure_ascii=False + ) def __repr__(self) -> str: return f"" @@ -645,7 +642,7 @@ def locator( ) -> Locator: return Locator( self._frame, - f"{self._frame_selector} >> control=enter-frame >> {selector}", + f"{self._frame_selector} >> internal:control=enter-frame >> {selector}", has_text=has_text, has=has, ) @@ -706,7 +703,8 @@ def get_by_title( def frame_locator(self, selector: str) -> "FrameLocator": return FrameLocator( - self._frame, f"{self._frame_selector} >> control=enter-frame >> {selector}" + self._frame, + f"{self._frame_selector} >> internal:control=enter-frame >> {selector}", ) @property @@ -740,13 +738,13 @@ def get_by_attribute_text_selector( attr_name: str, text: Union[str, Pattern[str]], exact: bool = None ) -> str: if isinstance(text, Pattern): - return f"attr=[{attr_name}=/{text.pattern}/{escape_regex_flags(text)}]" + return f"internal:attr=[{attr_name}=/{text.pattern}/{escape_regex_flags(text)}]" suffix = "s" if exact else "i" - return f"attr=[{attr_name}={escape_for_attribute_selector(text)}{suffix}]" + return f"internal:attr=[{attr_name}={escape_for_attribute_selector(text)}{suffix}]" def get_by_label_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str: - return get_by_text_selector(text, exact=exact) + " >> control=resolve-label" + return "internal:label=" + escape_for_text_selector(text, exact=exact) def get_by_alt_text_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str: diff --git a/playwright/_impl/_str_utils.py b/playwright/_impl/_str_utils.py index 8fce2a718..19e8a140b 100644 --- a/playwright/_impl/_str_utils.py +++ b/playwright/_impl/_str_utils.py @@ -12,23 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json import re from typing import Pattern, Union -def escape_with_quotes(text: str, char: str = "'") -> str: - stringified = json.dumps(text, ensure_ascii=False) - escaped_text = stringified[1:-1].replace('\\"', '"') - if char == "'": - return char + escaped_text.replace("'", "\\'") + char - if char == '"': - return char + escaped_text.replace('"', '\\"') + char - if char == "`": - return char + escaped_text.replace("`", "\\`") + char - raise ValueError("Invalid escape char") - - def escape_regex_flags(pattern: Pattern) -> str: flags = "" if pattern.flags != 0: @@ -60,7 +47,7 @@ def escape_for_text_selector( return '"' + text.replace('"', '\\"') + '"' if '"' in text or ">>" in text or text[0] == "/": suffix = "" if case_sensitive else "i" - return re.sub(r"\s+", "\\s+", escape_for_regex(text)) + suffix + return "/" + re.sub(r"\s+", "\\\\s+", escape_for_regex(text)) + "/" + suffix return text diff --git a/setup.py b/setup.py index ebd8d0e82..f2b76ad1e 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ InWheel = None from wheel.bdist_wheel import bdist_wheel as BDistWheelCommand -driver_version = "1.27.0-alpha-1664914898000" +driver_version = "1.28.0-alpha-oct-6-2022" def extractall(zip: zipfile.ZipFile, path: str) -> None: diff --git a/tests/sync/test_locator_get_by.py b/tests/sync/test_locator_get_by.py index bc6f6ee05..046c6797b 100644 --- a/tests/sync/test_locator_get_by.py +++ b/tests/sync/test_locator_get_by.py @@ -30,7 +30,12 @@ def test_get_by_test_id_escape_id(page: Page) -> None: def test_get_by_text(page: Page) -> None: - page.set_content("
yo
ya
\nye
") + page.set_content("
yo
ya
\nye
") + + expect(page.get_by_text("yo")).to_have_count(1) + expect(page.main_frame.get_by_text("yo")).to_have_count(1) + expect(page.locator("div").get_by_text("yo")).to_have_count(1) + assert ">\nye " in page.get_by_text("ye").evaluate("e => e.outerHTML") assert ">\nye " in page.get_by_text(r"ye").evaluate("e => e.outerHTML") @@ -50,6 +55,11 @@ def test_get_by_label(page: Page) -> None: page.set_content( "
" ) + + expect(page.get_by_label("Name")).to_have_count(1) + expect(page.main_frame.get_by_label("Name")).to_have_count(1) + expect(page.locator("div").get_by_label("Name")).to_have_count(1) + assert page.get_by_text("Name").evaluate("e => e.nodeName") == "LABEL" assert page.get_by_label("Name").evaluate("e => e.nodeName") == "INPUT" assert page.main_frame.get_by_label("Name").evaluate("e => e.nodeName") == "INPUT" @@ -58,6 +68,26 @@ def test_get_by_label(page: Page) -> None: ) +def test_get_by_label_with_nested_elements(page: Page) -> None: + page.set_content( + "" + ) + + expect(page.get_by_label("last name")).to_have_attribute("id", "target") + expect(page.get_by_label("st na")).to_have_attribute("id", "target") + expect(page.get_by_label("Name")).to_have_attribute("id", "target") + expect(page.get_by_label("Last Name", exact=True)).to_have_attribute("id", "target") + expect( + page.get_by_label(re.compile(r"Last\s+name", re.IGNORECASE)) + ).to_have_attribute("id", "target") + + expect(page.get_by_label("Last", exact=True)).to_have_count(0) + expect(page.get_by_label("last name", exact=True)).to_have_count(0) + expect(page.get_by_label("Name", exact=True)).to_have_count(0) + expect(page.get_by_label("what?")).to_have_count(0) + expect(page.get_by_label(re.compile(r"last name"))).to_have_count(0) + + def test_get_by_placeholder(page: Page) -> None: page.set_content( """
@@ -65,6 +95,11 @@ def test_get_by_placeholder(page: Page) -> None:
""" ) + + expect(page.get_by_placeholder("hello")).to_have_count(2) + expect(page.main_frame.get_by_placeholder("hello")).to_have_count(2) + expect(page.locator("div").get_by_placeholder("hello")).to_have_count(2) + expect(page.get_by_placeholder("hello")).to_have_count(2) expect(page.get_by_placeholder("Hello", exact=True)).to_have_count(1) expect(page.get_by_placeholder(re.compile(r"wor", re.IGNORECASE))).to_have_count(1) @@ -81,6 +116,11 @@ def test_get_by_alt_text(page: Page) -> None: """ ) + + expect(page.get_by_alt_text("hello")).to_have_count(2) + expect(page.main_frame.get_by_alt_text("hello")).to_have_count(2) + expect(page.locator("div").get_by_alt_text("hello")).to_have_count(2) + expect(page.get_by_alt_text("hello")).to_have_count(2) expect(page.get_by_alt_text("Hello", exact=True)).to_have_count(1) expect(page.get_by_alt_text(re.compile(r"wor", re.IGNORECASE))).to_have_count(1) @@ -97,6 +137,11 @@ def test_get_by_title(page: Page) -> None: """ ) + + expect(page.get_by_title("hello")).to_have_count(2) + expect(page.main_frame.get_by_title("hello")).to_have_count(2) + expect(page.locator("div").get_by_title("hello")).to_have_count(2) + expect(page.get_by_title("hello")).to_have_count(2) expect(page.get_by_title("Hello", exact=True)).to_have_count(1) expect(page.get_by_title(re.compile(r"wor", re.IGNORECASE))).to_have_count(1) @@ -104,3 +149,39 @@ def test_get_by_title(page: Page) -> None: # Coverage expect(page.main_frame.get_by_title("hello")).to_have_count(2) expect(page.locator("div").get_by_title("hello")).to_have_count(2) + + +def test_get_by_escaping(page: Page) -> None: + page.set_content( + """""" + ) + page.locator("input").evaluate( + """input => { + input.setAttribute('placeholder', 'hello\\nwo"rld'); + input.setAttribute('title', 'hello\\nwo"rld'); + input.setAttribute('alt', 'hello\\nwo"rld'); + }""" + ) + expect(page.get_by_text('hello\nwo"rld')).to_have_attribute("id", "label") + expect(page.get_by_label('hello\nwo"rld')).to_have_attribute("id", "control") + expect(page.get_by_placeholder('hello\nwo"rld')).to_have_attribute("id", "control") + expect(page.get_by_alt_text('hello\nwo"rld')).to_have_attribute("id", "control") + expect(page.get_by_title('hello\nwo"rld')).to_have_attribute("id", "control") + + page.set_content( + """""" + ) + page.locator("input").evaluate( + """input => { + input.setAttribute('placeholder', 'hello\\nworld'); + input.setAttribute('title', 'hello\\nworld'); + input.setAttribute('alt', 'hello\\nworld'); + }""" + ) + expect(page.get_by_text("hello\nworld")).to_have_attribute("id", "label") + expect(page.get_by_label("hello\nworld")).to_have_attribute("id", "control") + expect(page.get_by_placeholder("hello\nworld")).to_have_attribute("id", "control") + expect(page.get_by_alt_text("hello\nworld")).to_have_attribute("id", "control") + expect(page.get_by_title("hello\nworld")).to_have_attribute("id", "control") From bddee135971c55250c8bf22f10d85ba34c0d4612 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 6 Oct 2022 13:08:09 -0800 Subject: [PATCH 003/438] chore: move connect to json pipe (#1580) --- meta.yaml | 1 - playwright/_impl/_browser_type.py | 18 ++++-- playwright/_impl/_connection.py | 13 +++- playwright/_impl/_json_pipe.py | 79 +++++++++++++++++++++++++ playwright/_impl/_transport.py | 78 ------------------------ setup.py | 1 - tests/async/test_browsertype_connect.py | 2 +- tests/sync/test_browsertype_connect.py | 2 +- 8 files changed, 106 insertions(+), 88 deletions(-) create mode 100644 playwright/_impl/_json_pipe.py diff --git a/meta.yaml b/meta.yaml index 8f320bd09..8df796e27 100644 --- a/meta.yaml +++ b/meta.yaml @@ -25,7 +25,6 @@ requirements: - python - greenlet ==1.1.3 - pyee ==8.1.0 - - websockets ==10.1 - typing_extensions # [py<39] test: requires: diff --git a/playwright/_impl/_browser_type.py b/playwright/_impl/_browser_type.py index 577bf4058..0eca2c6b7 100644 --- a/playwright/_impl/_browser_type.py +++ b/playwright/_impl/_browser_type.py @@ -42,7 +42,7 @@ ServiceWorkersPolicy, locals_to_params, ) -from playwright._impl._transport import WebSocketTransport +from playwright._impl._json_pipe import JsonPipeTransport from playwright._impl._wait_helper import throw_on_timeout if TYPE_CHECKING: @@ -188,12 +188,22 @@ async def connect( ) -> Browser: if timeout is None: timeout = 30000 + if slow_mo is None: + slow_mo = 0 headers = {**(headers if headers else {}), "x-playwright-browser": self.name} - - transport = WebSocketTransport( - self._connection._loop, ws_endpoint, headers, slow_mo + local_utils = self._connection.local_utils + pipe_channel = await local_utils._channel.send( + "connect", + { + "wsEndpoint": ws_endpoint, + "headers": headers, + "slowMo": slow_mo, + "timeout": timeout, + }, ) + transport = JsonPipeTransport(self._connection._loop, pipe_channel) + connection = Connection( self._connection._dispatcher_fiber, self._connection._object_factory, diff --git a/playwright/_impl/_connection.py b/playwright/_impl/_connection.py index fd0b6ac7d..0243d85a1 100644 --- a/playwright/_impl/_connection.py +++ b/playwright/_impl/_connection.py @@ -316,6 +316,7 @@ def dispatch(self, msg: ParsedMessagePayload) -> None: self._objects[guid]._dispose() return object = self._objects[guid] + should_replace_guids_with_channels = "jsonPipe@" not in guid try: if self._is_sync: for listener in object._channel.listeners(method): @@ -323,9 +324,17 @@ def dispatch(self, msg: ParsedMessagePayload) -> None: # and switch to them in order, until they block inside and pass control to each # other and then eventually back to dispatcher as listener functions return. g = greenlet(listener) - g.switch(self._replace_guids_with_channels(params)) + if should_replace_guids_with_channels: + g.switch(self._replace_guids_with_channels(params)) + else: + g.switch(params) else: - object._channel.emit(method, self._replace_guids_with_channels(params)) + if should_replace_guids_with_channels: + object._channel.emit( + method, self._replace_guids_with_channels(params) + ) + else: + object._channel.emit(method, params) except BaseException as exc: print("Error occurred in event listener", file=sys.stderr) traceback.print_exc() diff --git a/playwright/_impl/_json_pipe.py b/playwright/_impl/_json_pipe.py new file mode 100644 index 000000000..a237a63df --- /dev/null +++ b/playwright/_impl/_json_pipe.py @@ -0,0 +1,79 @@ +# 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 asyncio +from typing import Dict, Optional, cast + +from pyee import AsyncIOEventEmitter + +from playwright._impl._api_types import Error +from playwright._impl._connection import Channel +from playwright._impl._helper import ParsedMessagePayload, parse_error +from playwright._impl._transport import Transport + + +class JsonPipeTransport(AsyncIOEventEmitter, Transport): + def __init__( + self, + loop: asyncio.AbstractEventLoop, + pipe_channel: Channel, + ) -> None: + super().__init__(loop) + Transport.__init__(self, loop) + self._stop_requested = False + self._pipe_channel = pipe_channel + + def request_stop(self) -> None: + self._stop_requested = True + self._loop.create_task(self._pipe_channel.send("close", {})) + + def dispose(self) -> None: + self.on_error_future.cancel() + self._stopped_future.cancel() + + async def wait_until_stopped(self) -> None: + await self._stopped_future + + async def connect(self) -> None: + self._stopped_future: asyncio.Future = asyncio.Future() + + def handle_message(message: Dict) -> None: + if not self._stop_requested: + self.on_message(cast(ParsedMessagePayload, message)) + + def handle_closed(error: Optional[Dict]) -> None: + self.emit("close") + self.on_error_future.set_exception( + parse_error(error["error"]) + if error + else Error("Playwright connection closed") + ) + self._stopped_future.set_result(None) + + self._pipe_channel.on( + "message", + lambda params: handle_message(params["message"]), + ) + self._pipe_channel.on( + "closed", + lambda params: handle_closed(params.get("error")), + ) + + async def run(self) -> None: + await self._stopped_future + + def send(self, message: Dict) -> None: + if self._stop_requested: + raise Error("Playwright connection closed") + self._loop.create_task(self._pipe_channel.send("send", {"message": message})) diff --git a/playwright/_impl/_transport.py b/playwright/_impl/_transport.py index 03293236b..d0802c17a 100644 --- a/playwright/_impl/_transport.py +++ b/playwright/_impl/_transport.py @@ -22,12 +22,6 @@ from pathlib import Path from typing import Callable, Dict, Optional, Union -import websockets -import websockets.exceptions -from pyee import AsyncIOEventEmitter -from websockets.client import connect as websocket_connect - -from playwright._impl._api_types import Error from playwright._impl._driver import get_driver_env from playwright._impl._helper import ParsedMessagePayload @@ -178,75 +172,3 @@ def send(self, message: Dict) -> None: self._output.write( len(data).to_bytes(4, byteorder="little", signed=False) + data ) - - -class WebSocketTransport(AsyncIOEventEmitter, Transport): - def __init__( - self, - loop: asyncio.AbstractEventLoop, - ws_endpoint: str, - headers: Dict[str, str] = None, - slow_mo: float = None, - ) -> None: - super().__init__(loop) - Transport.__init__(self, loop) - - self._stopped = False - self.ws_endpoint = ws_endpoint - self.headers = headers - self.slow_mo = slow_mo - - def request_stop(self) -> None: - self._stopped = True - self.emit("close") - self._loop.create_task(self._connection.close()) - - def dispose(self) -> None: - self.on_error_future.cancel() - - async def wait_until_stopped(self) -> None: - await self._connection.wait_closed() - - async def connect(self) -> None: - try: - self._connection = await websocket_connect( - self.ws_endpoint, - extra_headers=self.headers, - max_size=256 * 1024 * 1024, # 256Mb - ) - except Exception as exc: - self.on_error_future.set_exception(Error(f"websocket.connect: {str(exc)}")) - raise exc - - async def run(self) -> None: - while not self._stopped: - try: - message = await self._connection.recv() - if self.slow_mo is not None: - await asyncio.sleep(self.slow_mo / 1000) - if self._stopped: - self.on_error_future.set_exception( - Error("Playwright connection closed") - ) - break - obj = self.deserialize_message(message) - self.on_message(obj) - except ( - websockets.exceptions.ConnectionClosed, - websockets.exceptions.ConnectionClosedError, - ): - if not self._stopped: - self.emit("close") - self.on_error_future.set_exception( - Error("Playwright connection closed") - ) - break - except Exception as exc: - self.on_error_future.set_exception(exc) - break - - def send(self, message: Dict) -> None: - if self._stopped or (hasattr(self, "_connection") and self._connection.closed): - raise Error("Playwright connection closed") - data = self.serialize_message(message) - self._loop.create_task(self._connection.send(data)) diff --git a/setup.py b/setup.py index f2b76ad1e..d2cd10d00 100644 --- a/setup.py +++ b/setup.py @@ -211,7 +211,6 @@ def _download_and_extract_local_driver( packages=["playwright"], include_package_data=True, install_requires=[ - "websockets==10.1", "greenlet==1.1.3", "pyee==8.1.0", "typing-extensions;python_version<='3.8'", diff --git a/tests/async/test_browsertype_connect.py b/tests/async/test_browsertype_connect.py index f297892f4..33e9184aa 100644 --- a/tests/async/test_browsertype_connect.py +++ b/tests/async/test_browsertype_connect.py @@ -213,7 +213,7 @@ async def test_connect_to_closed_server_without_hangs( remote_server.kill() with pytest.raises(Error) as exc: await browser_type.connect(remote_server.ws_endpoint) - assert "websocket.connect: " in exc.value.message + assert "WebSocket error: " in exc.value.message async def test_should_fulfill_with_global_fetch_result( diff --git a/tests/sync/test_browsertype_connect.py b/tests/sync/test_browsertype_connect.py index bb8c5af5c..8d512db31 100644 --- a/tests/sync/test_browsertype_connect.py +++ b/tests/sync/test_browsertype_connect.py @@ -192,7 +192,7 @@ def test_connect_to_closed_server_without_hangs( remote_server.kill() with pytest.raises(Error) as exc: browser_type.connect(remote_server.ws_endpoint) - assert "websocket.connect: " in exc.value.message + assert "WebSocket error: " in exc.value.message def test_browser_type_connect_should_fulfill_with_global_fetch_result( From be076b7fc83188a3e59e0631fb1021ad4e9d5274 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 7 Oct 2022 10:10:19 -0700 Subject: [PATCH 004/438] test: add get_by_text escaping with multiple space sequences (#1582) Mirrors upstream test. Implementation is already good. --- tests/sync/test_locator_get_by.py | 46 +++++++++++++++++++------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/tests/sync/test_locator_get_by.py b/tests/sync/test_locator_get_by.py index 046c6797b..36d2cc4b8 100644 --- a/tests/sync/test_locator_get_by.py +++ b/tests/sync/test_locator_get_by.py @@ -153,35 +153,45 @@ def test_get_by_title(page: Page) -> None: def test_get_by_escaping(page: Page) -> None: page.set_content( - """