# 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 base64
from pathlib import Path
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
List,
Literal,
Optional,
Sequence,
Union,
cast,
)
from playwright._impl._api_structures import FilePayload, FloatRect, Position
from playwright._impl._connection import ChannelOwner, from_nullable_channel
from playwright._impl._helper import (
Error,
KeyboardModifier,
MouseButton,
async_writefile,
locals_to_params,
make_dirs_for_file,
)
from playwright._impl._js_handle import (
JSHandle,
Serializable,
parse_result,
serialize_argument,
)
from playwright._impl._set_input_files_helpers import convert_input_files
if TYPE_CHECKING: # pragma: no cover
from playwright._impl._frame import Frame
from playwright._impl._locator import Locator
class ElementHandle(JSHandle):
def __init__(
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
) -> None:
super().__init__(parent, type, guid, initializer)
async def _createSelectorForTest(self, name: str) -> Optional[str]:
return await self._channel.send("createSelectorForTest", dict(name=name))
def as_element(self) -> Optional["ElementHandle"]:
return self
async def owner_frame(self) -> Optional["Frame"]:
return from_nullable_channel(await self._channel.send("ownerFrame"))
async def content_frame(self) -> Optional["Frame"]:
return from_nullable_channel(await self._channel.send("contentFrame"))
async def get_attribute(self, name: str) -> Optional[str]:
return await self._channel.send("getAttribute", dict(name=name))
async def text_content(self) -> Optional[str]:
return await self._channel.send("textContent")
async def inner_text(self) -> str:
return await self._channel.send("innerText")
async def inner_html(self) -> str:
return await self._channel.send("innerHTML")
async def is_checked(self) -> bool:
return await self._channel.send("isChecked")
async def is_disabled(self) -> bool:
return await self._channel.send("isDisabled")
async def is_editable(self) -> bool:
return await self._channel.send("isEditable")
async def is_enabled(self) -> bool:
return await self._channel.send("isEnabled")
async def is_hidden(self) -> bool:
return await self._channel.send("isHidden")
async def is_visible(self) -> bool:
return await self._channel.send("isVisible")
async def dispatch_event(self, type: str, eventInit: Dict = None) -> None:
await self._channel.send(
"dispatchEvent", dict(type=type, eventInit=serialize_argument(eventInit))
)
async def scroll_into_view_if_needed(self, timeout: float = None) -> None:
await self._channel.send("scrollIntoViewIfNeeded", locals_to_params(locals()))
async def hover(
self,
modifiers: Sequence[KeyboardModifier] = None,
position: Position = None,
timeout: float = None,
noWaitAfter: bool = None,
force: bool = None,
trial: bool = None,
) -> None:
await self._channel.send("hover", locals_to_params(locals()))
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:
await self._channel.send("click", locals_to_params(locals()))
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:
await self._channel.send("dblclick", locals_to_params(locals()))
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,
force: bool = None,
noWaitAfter: bool = None,
) -> List[str]:
params = locals_to_params(
dict(
timeout=timeout,
force=force,
**convert_select_option_values(value, index, label, element),
)
)
return await self._channel.send("selectOption", 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:
await self._channel.send("tap", locals_to_params(locals()))
async def fill(
self,
value: str,
timeout: float = None,
noWaitAfter: bool = None,
force: bool = None,
) -> None:
await self._channel.send("fill", locals_to_params(locals()))
async def select_text(self, force: bool = None, timeout: float = None) -> None:
await self._channel.send("selectText", locals_to_params(locals()))
async def input_value(self, timeout: float = None) -> str:
return await self._channel.send("inputValue", locals_to_params(locals()))
async def set_input_files(
self,
files: Union[
str, Path, FilePayload, Sequence[Union[str, Path]], Sequence[FilePayload]
],
timeout: float = None,
noWaitAfter: bool = None,
) -> None:
frame = await self.owner_frame()
if not frame:
raise Error("Cannot set input files to detached element")
converted = await convert_input_files(files, frame.page.context)
await self._channel.send(
"setInputFiles",
{
"timeout": timeout,
**converted,
},
)
async def focus(self) -> None:
await self._channel.send("focus")
async def type(
self,
text: str,
delay: float = None,
timeout: float = None,
noWaitAfter: bool = None,
) -> None:
await self._channel.send("type", locals_to_params(locals()))
async def press(
self,
key: str,
delay: float = None,
timeout: float = None,
noWaitAfter: bool = None,
) -> None:
await self._channel.send("press", locals_to_params(locals()))
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 check(
self,
position: Position = None,
timeout: float = None,
force: bool = None,
noWaitAfter: bool = None,
trial: bool = None,
) -> None:
await self._channel.send("check", locals_to_params(locals()))
async def uncheck(
self,
position: Position = None,
timeout: float = None,
force: bool = None,
noWaitAfter: bool = None,
trial: bool = None,
) -> None:
await self._channel.send("uncheck", locals_to_params(locals()))
async def bounding_box(self) -> Optional[FloatRect]:
return await self._channel.send("boundingBox")
async def screenshot(
self,
timeout: float = None,
type: Literal["jpeg", "png"] = None,
path: Union[str, 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())
if "path" in params:
del params["path"]
if "mask" in params:
params["mask"] = list(
map(
lambda locator: (
{
"frame": locator._frame._channel,
"selector": locator._selector,
}
),
params["mask"],
)
)
encoded_binary = await self._channel.send("screenshot", params)
decoded_binary = base64.b64decode(encoded_binary)
if path:
make_dirs_for_file(path)
await async_writefile(path, decoded_binary)
return decoded_binary
async def query_selector(self, selector: str) -> Optional["ElementHandle"]:
return from_nullable_channel(
await self._channel.send("querySelector", dict(selector=selector))
)
async def query_selector_all(self, selector: str) -> List["ElementHandle"]:
return list(
map(
cast(Callable[[Any], Any], from_nullable_channel),
await self._channel.send("querySelectorAll", dict(selector=selector)),
)
)
async def eval_on_selector(
self,
selector: str,
expression: str,
arg: Serializable = None,
) -> Any:
return parse_result(
await self._channel.send(
"evalOnSelector",
dict(
selector=selector,
expression=expression,
arg=serialize_argument(arg),
),
)
)
async def eval_on_selector_all(
self,
selector: str,
expression: str,
arg: Serializable = None,
) -> Any:
return parse_result(
await self._channel.send(
"evalOnSelectorAll",
dict(
selector=selector,
expression=expression,
arg=serialize_argument(arg),
),
)
)
async def wait_for_element_state(
self,
state: Literal[
"disabled", "editable", "enabled", "hidden", "stable", "visible"
],
timeout: float = None,
) -> None:
await self._channel.send("waitForElementState", locals_to_params(locals()))
async def wait_for_selector(
self,
selector: str,
state: Literal["attached", "detached", "hidden", "visible"] = None,
timeout: float = None,
strict: bool = None,
) -> Optional["ElementHandle"]:
return from_nullable_channel(
await self._channel.send("waitForSelector", locals_to_params(locals()))
)
def convert_select_option_values(
value: Union[str, Sequence[str]] = None,
index: Union[int, Sequence[int]] = None,
label: Union[str, Sequence[str]] = None,
element: Union["ElementHandle", Sequence["ElementHandle"]] = None,
) -> Any:
if value is None and index is None and label is None and element is None:
return {}
options: Any = None
elements: Any = None
if value is not None:
if isinstance(value, str):
value = [value]
options = (options or []) + list(map(lambda e: dict(valueOrLabel=e), value))
if index is not None:
if isinstance(index, int):
index = [index]
options = (options or []) + list(map(lambda e: dict(index=e), index))
if label is not None:
if isinstance(label, str):
label = [label]
options = (options or []) + list(map(lambda e: dict(label=e), label))
if element:
if isinstance(element, ElementHandle):
element = [element]
elements = list(map(lambda e: e._channel, element))
return dict(options=options, elements=elements)