# 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 json import pathlib from typing import ( TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Literal, Optional, Pattern, Sequence, Tuple, TypeVar, Union, ) from playwright._impl._api_structures import ( AriaRole, FilePayload, FloatRect, FrameExpectOptions, FrameExpectResult, Position, ) from playwright._impl._element_handle import ElementHandle from playwright._impl._helper import ( Error, KeyboardModifier, MouseButton, locals_to_params, monotonic_time, to_impl, ) from playwright._impl._js_handle import Serializable, parse_value, serialize_argument from playwright._impl._str_utils import ( escape_for_attribute_selector, escape_for_text_selector, ) if TYPE_CHECKING: # pragma: no cover from playwright._impl._frame import Frame from playwright._impl._js_handle import JSHandle from playwright._impl._page import Page T = TypeVar("T") class Locator: def __init__( self, frame: "Frame", selector: str, has_text: Union[str, Pattern[str]] = None, has_not_text: Union[str, Pattern[str]] = None, has: "Locator" = None, has_not: "Locator" = None, ) -> None: self._frame = frame self._selector = selector self._loop = frame._loop self._dispatcher_fiber = frame._connection._dispatcher_fiber if has_text: self._selector += f" >> internal:has-text={escape_for_text_selector(has_text, exact=False)}" if has: if has._frame != frame: raise Error('Inner "has" locator must belong to the same frame.') self._selector += " >> internal:has=" + json.dumps( has._selector, ensure_ascii=False ) if has_not_text: self._selector += f" >> internal:has-not-text={escape_for_text_selector(has_not_text, exact=False)}" if has_not: locator = has_not if locator._frame != frame: raise Error('Inner "has_not" locator must belong to the same frame.') self._selector += " >> internal:has-not=" + json.dumps(locator._selector) def __repr__(self) -> str: return f"<Locator frame={self._frame!r} selector={self._selector!r}>" async def _with_element( self, task: Callable[[ElementHandle, float], Awaitable[T]], timeout: float = None, ) -> T: timeout = self._frame.page._timeout_settings.timeout(timeout) deadline = (monotonic_time() + timeout) if timeout else 0 handle = await self.element_handle(timeout=timeout) if not handle: raise Error(f"Could not resolve {self._selector} to DOM Element") try: return await task( handle, (deadline - monotonic_time()) if deadline else 0, ) finally: await handle.dispose() def _equals(self, locator: "Locator") -> bool: return self._frame == locator._frame and self._selector == locator._selector @property def page(self) -> "Page": return self._frame.page async def bounding_box(self, timeout: float = None) -> Optional[FloatRect]: return await self._with_element( lambda h, _: h.bounding_box(), timeout, ) async def check( self, position: Position = None, timeout: float = None, force: bool = None, noWaitAfter: bool = None, trial: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.check(self._selector, strict=True, **params) async def click( self, modifiers: Sequence[KeyboardModifier] = None, position: Position = None, delay: float = None, button: MouseButton = None, clickCount: int = None, timeout: float = None, force: bool = None, noWaitAfter: bool = None, trial: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.click(self._selector, strict=True, **params) async def dblclick( self, modifiers: Sequence[KeyboardModifier] = None, position: Position = None, delay: float = None, button: MouseButton = None, timeout: float = None, force: bool = None, noWaitAfter: bool = None, trial: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.dblclick(self._selector, strict=True, **params) async def dispatch_event( self, type: str, eventInit: Dict = None, timeout: float = None, ) -> None: params = locals_to_params(locals()) return await self._frame.dispatch_event(self._selector, strict=True, **params) async def evaluate( self, expression: str, arg: Serializable = None, timeout: float = None ) -> Any: return await self._with_element( lambda h, _: h.evaluate(expression, arg), timeout, ) async def evaluate_all(self, expression: str, arg: Serializable = None) -> Any: params = locals_to_params(locals()) return await self._frame.eval_on_selector_all(self._selector, **params) async def evaluate_handle( self, expression: str, arg: Serializable = None, timeout: float = None ) -> "JSHandle": return await self._with_element( lambda h, _: h.evaluate_handle(expression, arg), timeout ) async def fill( self, value: str, timeout: float = None, noWaitAfter: bool = None, force: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.fill(self._selector, strict=True, **params) async def clear( self, timeout: float = None, noWaitAfter: bool = None, force: bool = None, ) -> None: await self.fill("", timeout=timeout, force=force) def locator( self, selectorOrLocator: Union[str, "Locator"], hasText: Union[str, Pattern[str]] = None, hasNotText: Union[str, Pattern[str]] = None, has: "Locator" = None, hasNot: "Locator" = None, ) -> "Locator": if isinstance(selectorOrLocator, str): return Locator( self._frame, f"{self._selector} >> {selectorOrLocator}", has_text=hasText, has_not_text=hasNotText, has_not=hasNot, has=has, ) selectorOrLocator = to_impl(selectorOrLocator) if selectorOrLocator._frame != self._frame: raise Error("Locators must belong to the same frame.") return Locator( self._frame, f"{self._selector} >> internal:chain={json.dumps(selectorOrLocator._selector)}", has_text=hasText, has_not_text=hasNotText, has_not=hasNot, 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: AriaRole, 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, exact: 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, exact=exact, ) ) def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator": return self.locator(get_by_test_id_selector(test_id_attribute_name(), 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) async def element_handle( self, timeout: float = None, ) -> ElementHandle: params = locals_to_params(locals()) handle = await self._frame.wait_for_selector( self._selector, strict=True, state="attached", **params ) assert handle return handle async def element_handles(self) -> List[ElementHandle]: return await self._frame.query_selector_all(self._selector) @property def first(self) -> "Locator": return Locator(self._frame, f"{self._selector} >> nth=0") @property def last(self) -> "Locator": return Locator(self._frame, f"{self._selector} >> nth=-1") def nth(self, index: int) -> "Locator": return Locator(self._frame, f"{self._selector} >> nth={index}") @property def content_frame(self) -> "FrameLocator": return FrameLocator(self._frame, self._selector) def filter( self, hasText: Union[str, Pattern[str]] = None, hasNotText: Union[str, Pattern[str]] = None, has: "Locator" = None, hasNot: "Locator" = None, ) -> "Locator": return Locator( self._frame, self._selector, has_text=hasText, has_not_text=hasNotText, has=has, has_not=hasNot, ) def or_(self, locator: "Locator") -> "Locator": if locator._frame != self._frame: raise Error("Locators must belong to the same frame.") return Locator( self._frame, self._selector + " >> internal:or=" + json.dumps(locator._selector), ) def and_(self, locator: "Locator") -> "Locator": if locator._frame != self._frame: raise Error("Locators must belong to the same frame.") return Locator( self._frame, self._selector + " >> internal:and=" + json.dumps(locator._selector), ) async def focus(self, timeout: float = None) -> None: params = locals_to_params(locals()) return await self._frame.focus(self._selector, strict=True, **params) async def blur(self, timeout: float = None) -> None: await self._frame._channel.send( "blur", { "selector": self._selector, "strict": True, **locals_to_params(locals()), }, ) async def all( self, ) -> List["Locator"]: result = [] for index in range(await self.count()): result.append(self.nth(index)) return result async def count( self, ) -> int: return await self._frame._query_count(self._selector) async def drag_to( self, target: "Locator", force: bool = None, noWaitAfter: bool = None, timeout: float = None, trial: bool = None, sourcePosition: Position = None, targetPosition: Position = None, ) -> None: params = locals_to_params(locals()) del params["target"] return await self._frame.drag_and_drop( self._selector, target._selector, strict=True, **params ) async def get_attribute(self, name: str, timeout: float = None) -> Optional[str]: params = locals_to_params(locals()) return await self._frame.get_attribute( self._selector, strict=True, **params, ) async def hover( self, modifiers: Sequence[KeyboardModifier] = None, position: Position = None, timeout: float = None, noWaitAfter: bool = None, force: bool = None, trial: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.hover( self._selector, strict=True, **params, ) async def inner_html(self, timeout: float = None) -> str: params = locals_to_params(locals()) return await self._frame.inner_html( self._selector, strict=True, **params, ) async def inner_text(self, timeout: float = None) -> str: params = locals_to_params(locals()) return await self._frame.inner_text( self._selector, strict=True, **params, ) async def input_value(self, timeout: float = None) -> str: params = locals_to_params(locals()) return await self._frame.input_value( self._selector, strict=True, **params, ) async def is_checked(self, timeout: float = None) -> bool: params = locals_to_params(locals()) return await self._frame.is_checked( self._selector, strict=True, **params, ) async def is_disabled(self, timeout: float = None) -> bool: params = locals_to_params(locals()) return await self._frame.is_disabled( self._selector, strict=True, **params, ) async def is_editable(self, timeout: float = None) -> bool: params = locals_to_params(locals()) return await self._frame.is_editable( self._selector, strict=True, **params, ) async def is_enabled(self, timeout: float = None) -> bool: params = locals_to_params(locals()) return await self._frame.is_editable( self._selector, strict=True, **params, ) async def is_hidden(self, timeout: float = None) -> bool: params = locals_to_params(locals()) return await self._frame.is_hidden( self._selector, strict=True, **params, ) async def is_visible(self, timeout: float = None) -> bool: params = locals_to_params(locals()) return await self._frame.is_visible( self._selector, strict=True, **params, ) async def press( self, key: str, delay: float = None, timeout: float = None, noWaitAfter: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.press(self._selector, strict=True, **params) async def screenshot( self, timeout: float = None, type: Literal["jpeg", "png"] = None, path: Union[str, pathlib.Path] = None, quality: int = None, omitBackground: bool = None, animations: Literal["allow", "disabled"] = None, caret: Literal["hide", "initial"] = None, scale: Literal["css", "device"] = None, mask: Sequence["Locator"] = None, maskColor: str = None, style: str = None, ) -> bytes: params = locals_to_params(locals()) return await self._with_element( lambda h, timeout: h.screenshot( **{**params, "timeout": timeout}, ), ) async def aria_snapshot(self, timeout: float = None) -> str: return await self._frame._channel.send( "ariaSnapshot", { "selector": self._selector, **locals_to_params(locals()), }, ) async def scroll_into_view_if_needed( self, timeout: float = None, ) -> None: return await self._with_element( lambda h, timeout: h.scroll_into_view_if_needed(timeout=timeout), timeout, ) async def select_option( self, value: Union[str, Sequence[str]] = None, index: Union[int, Sequence[int]] = None, label: Union[str, Sequence[str]] = None, element: Union["ElementHandle", Sequence["ElementHandle"]] = None, timeout: float = None, noWaitAfter: bool = None, force: bool = None, ) -> List[str]: params = locals_to_params(locals()) return await self._frame.select_option( self._selector, strict=True, **params, ) async def select_text(self, force: bool = None, timeout: float = None) -> None: params = locals_to_params(locals()) return await self._with_element( lambda h, timeout: h.select_text(**{**params, "timeout": timeout}), timeout, ) async def set_input_files( self, files: Union[ str, pathlib.Path, FilePayload, Sequence[Union[str, pathlib.Path]], Sequence[FilePayload], ], timeout: float = None, noWaitAfter: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.set_input_files( self._selector, strict=True, **params, ) async def tap( self, modifiers: Sequence[KeyboardModifier] = None, position: Position = None, timeout: float = None, force: bool = None, noWaitAfter: bool = None, trial: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.tap( self._selector, strict=True, **params, ) async def text_content(self, timeout: float = None) -> Optional[str]: params = locals_to_params(locals()) return await self._frame.text_content( self._selector, strict=True, **params, ) async def type( self, text: str, delay: float = None, timeout: float = None, noWaitAfter: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.type( self._selector, strict=True, **params, ) async def press_sequentially( self, text: str, delay: float = None, timeout: float = None, noWaitAfter: bool = None, ) -> None: await self.type(text, delay=delay, timeout=timeout) async def uncheck( self, position: Position = None, timeout: float = None, force: bool = None, noWaitAfter: bool = None, trial: bool = None, ) -> None: params = locals_to_params(locals()) return await self._frame.uncheck( self._selector, strict=True, **params, ) async def all_inner_texts( self, ) -> List[str]: return await self._frame.eval_on_selector_all( self._selector, "ee => ee.map(e => e.innerText)" ) async def all_text_contents( self, ) -> List[str]: return await self._frame.eval_on_selector_all( self._selector, "ee => ee.map(e => e.textContent || '')" ) async def wait_for( self, timeout: float = None, state: Literal["attached", "detached", "hidden", "visible"] = None, ) -> None: await self._frame.wait_for_selector( self._selector, strict=True, timeout=timeout, state=state ) async def set_checked( self, checked: bool, position: Position = None, timeout: float = None, force: bool = None, noWaitAfter: bool = None, trial: bool = None, ) -> None: if checked: await self.check( position=position, timeout=timeout, force=force, trial=trial, ) else: await self.uncheck( position=position, timeout=timeout, force=force, trial=trial, ) async def _expect( self, expression: str, options: FrameExpectOptions ) -> FrameExpectResult: if "expectedValue" in options: options["expectedValue"] = serialize_argument(options["expectedValue"]) result = await self._frame._channel.send_return_as_dict( "expect", { "selector": self._selector, "expression": expression, **options, }, ) if result.get("received"): result["received"] = parse_value(result["received"]) return result async def highlight(self) -> None: await self._frame._highlight(self._selector) class FrameLocator: def __init__(self, frame: "Frame", frame_selector: str) -> None: self._frame = frame self._loop = frame._loop self._dispatcher_fiber = frame._connection._dispatcher_fiber self._frame_selector = frame_selector def locator( self, selectorOrLocator: Union["Locator", str], hasText: Union[str, Pattern[str]] = None, hasNotText: Union[str, Pattern[str]] = None, has: Locator = None, hasNot: Locator = None, ) -> Locator: if isinstance(selectorOrLocator, str): return Locator( self._frame, f"{self._frame_selector} >> internal:control=enter-frame >> {selectorOrLocator}", has_text=hasText, has_not_text=hasNotText, has=has, has_not=hasNot, ) selectorOrLocator = to_impl(selectorOrLocator) if selectorOrLocator._frame != self._frame: raise ValueError("Locators must belong to the same frame.") return Locator( self._frame, f"{self._frame_selector} >> internal:control=enter-frame >> {selectorOrLocator._selector}", has_text=hasText, has_not_text=hasNotText, has=has, has_not=hasNot, ) 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: AriaRole, 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, exact: 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, exact=exact, ) ) def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator": return self.locator(get_by_test_id_selector(test_id_attribute_name(), 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} >> internal:control=enter-frame >> {selector}", ) @property def first(self) -> "FrameLocator": return FrameLocator(self._frame, f"{self._frame_selector} >> nth=0") @property def last(self) -> "FrameLocator": return FrameLocator(self._frame, f"{self._frame_selector} >> nth=-1") @property def owner(self) -> "Locator": return Locator(self._frame, self._frame_selector) def nth(self, index: int) -> "FrameLocator": return FrameLocator(self._frame, f"{self._frame_selector} >> nth={index}") def __repr__(self) -> str: return f"<FrameLocator frame={self._frame!r} selector={self._frame_selector!r}>" _test_id_attribute_name: str = "data-testid" def test_id_attribute_name() -> str: return _test_id_attribute_name 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_attribute_name: str, test_id: Union[str, Pattern[str]] ) -> str: return f"internal:testid=[{test_id_attribute_name}={escape_for_attribute_selector(test_id, True)}]" def get_by_attribute_text_selector( attr_name: str, text: Union[str, Pattern[str]], exact: bool = None ) -> str: return f"internal:attr=[{attr_name}={escape_for_attribute_selector(text, exact=exact)}]" def get_by_label_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str: 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: 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 "internal:text=" + escape_for_text_selector(text, exact=exact) def bool_to_js_bool(value: bool) -> str: return "true" if value else "false" def get_by_role_selector( role: AriaRole, 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, exact: bool = None, ) -> str: props: List[Tuple[str, str]] = [] if checked is not None: props.append(("checked", bool_to_js_bool(checked))) if disabled is not None: props.append(("disabled", bool_to_js_bool(disabled))) if selected is not None: props.append(("selected", bool_to_js_bool(selected))) if expanded is not None: props.append(("expanded", bool_to_js_bool(expanded))) if includeHidden is not None: props.append(("include-hidden", bool_to_js_bool(includeHidden))) if level is not None: props.append(("level", str(level))) if name is not None: props.append( ( "name", escape_for_attribute_selector(name, exact=exact), ) ) if pressed is not None: props.append(("pressed", bool_to_js_bool(pressed))) props_str = "".join([f"[{t[0]}={t[1]}]" for t in props]) return f"internal:role={role}{props_str}"
Memory