initial commit
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+168
@@ -0,0 +1,168 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from selenium.webdriver.common.actions import interaction
|
||||
from selenium.webdriver.common.actions.key_actions import KeyActions
|
||||
from selenium.webdriver.common.actions.key_input import KeyInput
|
||||
from selenium.webdriver.common.actions.pointer_actions import PointerActions
|
||||
from selenium.webdriver.common.actions.pointer_input import PointerInput
|
||||
from selenium.webdriver.common.actions.wheel_actions import WheelActions
|
||||
from selenium.webdriver.common.actions.wheel_input import WheelInput
|
||||
from selenium.webdriver.remote.command import Command
|
||||
|
||||
|
||||
class ActionBuilder:
|
||||
def __init__(
|
||||
self,
|
||||
driver,
|
||||
mouse: PointerInput | None = None,
|
||||
wheel: WheelInput | None = None,
|
||||
keyboard: KeyInput | None = None,
|
||||
duration: int = 250,
|
||||
) -> None:
|
||||
mouse = mouse or PointerInput(interaction.POINTER_MOUSE, "mouse")
|
||||
keyboard = keyboard or KeyInput(interaction.KEY)
|
||||
wheel = wheel or WheelInput(interaction.WHEEL)
|
||||
self.devices: list[PointerInput | KeyInput | WheelInput] = [mouse, keyboard, wheel]
|
||||
self._key_action = KeyActions(keyboard)
|
||||
self._pointer_action = PointerActions(mouse, duration=duration)
|
||||
self._wheel_action = WheelActions(wheel)
|
||||
self.driver = driver
|
||||
|
||||
def get_device_with(self, name: str) -> Union["WheelInput", "PointerInput", "KeyInput"] | None:
|
||||
"""Get the device with the given name.
|
||||
|
||||
Args:
|
||||
name: The name of the device to get.
|
||||
|
||||
Returns:
|
||||
The device with the given name, or None if not found.
|
||||
"""
|
||||
return next(filter(lambda x: x == name, self.devices), None)
|
||||
|
||||
@property
|
||||
def pointer_inputs(self) -> list[PointerInput]:
|
||||
return [device for device in self.devices if isinstance(device, PointerInput)]
|
||||
|
||||
@property
|
||||
def key_inputs(self) -> list[KeyInput]:
|
||||
return [device for device in self.devices if isinstance(device, KeyInput)]
|
||||
|
||||
@property
|
||||
def key_action(self) -> KeyActions:
|
||||
return self._key_action
|
||||
|
||||
@property
|
||||
def pointer_action(self) -> PointerActions:
|
||||
return self._pointer_action
|
||||
|
||||
@property
|
||||
def wheel_action(self) -> WheelActions:
|
||||
return self._wheel_action
|
||||
|
||||
def add_key_input(self, name: str) -> KeyInput:
|
||||
"""Add a new key input device to the action builder.
|
||||
|
||||
Args:
|
||||
name: The name of the key input device.
|
||||
|
||||
Returns:
|
||||
The newly created key input device.
|
||||
|
||||
Example:
|
||||
>>> action_builder = ActionBuilder(driver)
|
||||
>>> action_builder.add_key_input(name="keyboard2")
|
||||
"""
|
||||
new_input = KeyInput(name)
|
||||
self._add_input(new_input)
|
||||
return new_input
|
||||
|
||||
def add_pointer_input(self, kind: str, name: str) -> PointerInput:
|
||||
"""Add a new pointer input device to the action builder.
|
||||
|
||||
Args:
|
||||
kind: The kind of pointer input device. Valid values are "mouse",
|
||||
"touch", or "pen".
|
||||
name: The name of the pointer input device.
|
||||
|
||||
Returns:
|
||||
The newly created pointer input device.
|
||||
|
||||
Example:
|
||||
>>> action_builder = ActionBuilder(driver)
|
||||
>>> action_builder.add_pointer_input(kind="mouse", name="mouse")
|
||||
"""
|
||||
new_input = PointerInput(kind, name)
|
||||
self._add_input(new_input)
|
||||
return new_input
|
||||
|
||||
def add_wheel_input(self, name: str) -> WheelInput:
|
||||
"""Add a new wheel input device to the action builder.
|
||||
|
||||
Args:
|
||||
name: The name of the wheel input device.
|
||||
|
||||
Returns:
|
||||
The newly created wheel input device.
|
||||
|
||||
Example:
|
||||
>>> action_builder = ActionBuilder(driver)
|
||||
>>> action_builder.add_wheel_input(name="wheel2")
|
||||
"""
|
||||
new_input = WheelInput(name)
|
||||
self._add_input(new_input)
|
||||
return new_input
|
||||
|
||||
def perform(self) -> None:
|
||||
"""Performs all stored actions.
|
||||
|
||||
Example:
|
||||
>>> action_builder = ActionBuilder(driver)
|
||||
>>> keyboard = action_builder.key_input
|
||||
>>> el = driver.find_element(id: "some_id")
|
||||
>>> action_builder.click(el).pause(keyboard).pause(keyboard).pause(keyboard).send_keys("keys").perform()
|
||||
"""
|
||||
enc: dict[str, list[Any]] = {"actions": []}
|
||||
for device in self.devices:
|
||||
encoded = device.encode()
|
||||
if encoded["actions"]:
|
||||
enc["actions"].append(encoded)
|
||||
device.actions = []
|
||||
self.driver.execute(Command.W3C_ACTIONS, enc)
|
||||
|
||||
def clear_actions(self) -> None:
|
||||
"""Clears actions that are already stored on the remote end.
|
||||
|
||||
Example:
|
||||
>>> action_builder = ActionBuilder(driver)
|
||||
>>> keyboard = action_builder.key_input
|
||||
>>> el = driver.find_element(By.ID, "some_id")
|
||||
>>> action_builder.click(el).pause(keyboard).pause(keyboard).pause(keyboard).send_keys("keys")
|
||||
>>> action_builder.clear_actions()
|
||||
"""
|
||||
self.driver.execute(Command.W3C_CLEAR_ACTIONS)
|
||||
|
||||
def _add_input(self, new_input: KeyInput | PointerInput | WheelInput) -> None:
|
||||
"""Add a new input device to the action builder.
|
||||
|
||||
Args:
|
||||
new_input: The new input device to add.
|
||||
"""
|
||||
self.devices.append(new_input)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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 uuid
|
||||
from typing import Any
|
||||
|
||||
|
||||
class InputDevice:
|
||||
"""Describes the input device being used for the action."""
|
||||
|
||||
def __init__(self, name: str | None = None):
|
||||
self.name = name or uuid.uuid4()
|
||||
self.actions: list[Any] = []
|
||||
|
||||
def add_action(self, action: Any) -> None:
|
||||
self.actions.append(action)
|
||||
|
||||
def clear_actions(self) -> None:
|
||||
self.actions = []
|
||||
|
||||
def create_pause(self, duration: float = 0) -> None:
|
||||
pass
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
|
||||
from selenium.webdriver.common.actions.input_device import InputDevice
|
||||
|
||||
KEY = "key"
|
||||
POINTER = "pointer"
|
||||
NONE = "none"
|
||||
WHEEL = "wheel"
|
||||
SOURCE_TYPES = {KEY, POINTER, WHEEL, NONE}
|
||||
|
||||
POINTER_MOUSE = "mouse"
|
||||
POINTER_TOUCH = "touch"
|
||||
POINTER_PEN = "pen"
|
||||
|
||||
POINTER_KINDS = {POINTER_MOUSE, POINTER_TOUCH, POINTER_PEN}
|
||||
|
||||
|
||||
class Interaction:
|
||||
PAUSE = "pause"
|
||||
|
||||
def __init__(self, source: InputDevice) -> None:
|
||||
self.source = source
|
||||
|
||||
|
||||
class Pause(Interaction):
|
||||
def __init__(self, source, duration: float = 0) -> None:
|
||||
super().__init__(source)
|
||||
self.duration = duration
|
||||
|
||||
def encode(self) -> dict[str, str | int]:
|
||||
return {"type": self.PAUSE, "duration": int(self.duration * 1000)}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from selenium.webdriver.common.actions.interaction import KEY, Interaction
|
||||
from selenium.webdriver.common.actions.key_input import KeyInput
|
||||
from selenium.webdriver.common.actions.pointer_input import PointerInput
|
||||
from selenium.webdriver.common.actions.wheel_input import WheelInput
|
||||
from selenium.webdriver.common.utils import keys_to_typing
|
||||
|
||||
|
||||
class KeyActions(Interaction):
|
||||
def __init__(self, source: KeyInput | PointerInput | WheelInput | None = None) -> None:
|
||||
if source is None:
|
||||
source = KeyInput(KEY)
|
||||
self.input_source = source
|
||||
super().__init__(source)
|
||||
|
||||
def key_down(self, letter: str) -> KeyActions:
|
||||
return self._key_action("create_key_down", letter)
|
||||
|
||||
def key_up(self, letter: str) -> KeyActions:
|
||||
return self._key_action("create_key_up", letter)
|
||||
|
||||
def pause(self, duration: int = 0) -> KeyActions:
|
||||
return self._key_action("create_pause", duration)
|
||||
|
||||
def send_keys(self, text: str | list) -> KeyActions:
|
||||
if not isinstance(text, list):
|
||||
text = keys_to_typing(text)
|
||||
for letter in text:
|
||||
self.key_down(letter)
|
||||
self.key_up(letter)
|
||||
return self
|
||||
|
||||
def _key_action(self, action: str, letter) -> KeyActions:
|
||||
meth = getattr(self.source, action)
|
||||
meth(letter)
|
||||
return self
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
from selenium.webdriver.common.actions import interaction
|
||||
from selenium.webdriver.common.actions.input_device import InputDevice
|
||||
from selenium.webdriver.common.actions.interaction import Interaction, Pause
|
||||
|
||||
|
||||
class KeyInput(InputDevice):
|
||||
def __init__(self, name: str) -> None:
|
||||
super().__init__()
|
||||
self.name = name
|
||||
self.type = interaction.KEY
|
||||
|
||||
def encode(self) -> dict:
|
||||
return {"type": self.type, "id": self.name, "actions": [acts.encode() for acts in self.actions]}
|
||||
|
||||
def create_key_down(self, key) -> None:
|
||||
self.add_action(TypingInteraction(self, "keyDown", key))
|
||||
|
||||
def create_key_up(self, key) -> None:
|
||||
self.add_action(TypingInteraction(self, "keyUp", key))
|
||||
|
||||
def create_pause(self, pause_duration: float = 0) -> None:
|
||||
self.add_action(Pause(self, pause_duration))
|
||||
|
||||
|
||||
class TypingInteraction(Interaction):
|
||||
def __init__(self, source, type_, key) -> None:
|
||||
super().__init__(source)
|
||||
self.type = type_
|
||||
self.key = key
|
||||
|
||||
def encode(self) -> dict:
|
||||
return {"type": self.type, "value": self.key}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
|
||||
|
||||
class MouseButton:
|
||||
LEFT = 0
|
||||
MIDDLE = 1
|
||||
RIGHT = 2
|
||||
BACK = 3
|
||||
FORWARD = 4
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
|
||||
from selenium.webdriver.common.actions import interaction
|
||||
from selenium.webdriver.common.actions.interaction import Interaction
|
||||
from selenium.webdriver.common.actions.mouse_button import MouseButton
|
||||
from selenium.webdriver.common.actions.pointer_input import PointerInput
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
|
||||
class PointerActions(Interaction):
|
||||
def __init__(self, source: PointerInput | None = None, duration: int = 250):
|
||||
"""Initialize a new PointerActions instance.
|
||||
|
||||
Args:
|
||||
source: Optional PointerInput instance. If not provided, a default
|
||||
mouse PointerInput will be created.
|
||||
duration: Override the default 250 msecs of DEFAULT_MOVE_DURATION
|
||||
in the source.
|
||||
"""
|
||||
if source is None:
|
||||
source = PointerInput(interaction.POINTER_MOUSE, "mouse")
|
||||
self.source = source
|
||||
self._duration = duration
|
||||
super().__init__(source)
|
||||
|
||||
def pointer_down(
|
||||
self,
|
||||
button=MouseButton.LEFT,
|
||||
width=None,
|
||||
height=None,
|
||||
pressure=None,
|
||||
tangential_pressure=None,
|
||||
tilt_x=None,
|
||||
tilt_y=None,
|
||||
twist=None,
|
||||
altitude_angle=None,
|
||||
azimuth_angle=None,
|
||||
):
|
||||
self._button_action(
|
||||
"create_pointer_down",
|
||||
button=button,
|
||||
width=width,
|
||||
height=height,
|
||||
pressure=pressure,
|
||||
tangential_pressure=tangential_pressure,
|
||||
tilt_x=tilt_x,
|
||||
tilt_y=tilt_y,
|
||||
twist=twist,
|
||||
altitude_angle=altitude_angle,
|
||||
azimuth_angle=azimuth_angle,
|
||||
)
|
||||
return self
|
||||
|
||||
def pointer_up(self, button=MouseButton.LEFT):
|
||||
self._button_action("create_pointer_up", button=button)
|
||||
return self
|
||||
|
||||
def move_to(
|
||||
self,
|
||||
element,
|
||||
x=0,
|
||||
y=0,
|
||||
width=None,
|
||||
height=None,
|
||||
pressure=None,
|
||||
tangential_pressure=None,
|
||||
tilt_x=None,
|
||||
tilt_y=None,
|
||||
twist=None,
|
||||
altitude_angle=None,
|
||||
azimuth_angle=None,
|
||||
):
|
||||
if not isinstance(element, WebElement):
|
||||
raise AttributeError("move_to requires a WebElement")
|
||||
|
||||
self.source.create_pointer_move(
|
||||
origin=element,
|
||||
duration=self._duration,
|
||||
x=int(x),
|
||||
y=int(y),
|
||||
width=width,
|
||||
height=height,
|
||||
pressure=pressure,
|
||||
tangential_pressure=tangential_pressure,
|
||||
tilt_x=tilt_x,
|
||||
tilt_y=tilt_y,
|
||||
twist=twist,
|
||||
altitude_angle=altitude_angle,
|
||||
azimuth_angle=azimuth_angle,
|
||||
)
|
||||
return self
|
||||
|
||||
def move_by(
|
||||
self,
|
||||
x,
|
||||
y,
|
||||
width=None,
|
||||
height=None,
|
||||
pressure=None,
|
||||
tangential_pressure=None,
|
||||
tilt_x=None,
|
||||
tilt_y=None,
|
||||
twist=None,
|
||||
altitude_angle=None,
|
||||
azimuth_angle=None,
|
||||
):
|
||||
self.source.create_pointer_move(
|
||||
origin=interaction.POINTER,
|
||||
duration=self._duration,
|
||||
x=int(x),
|
||||
y=int(y),
|
||||
width=width,
|
||||
height=height,
|
||||
pressure=pressure,
|
||||
tangential_pressure=tangential_pressure,
|
||||
tilt_x=tilt_x,
|
||||
tilt_y=tilt_y,
|
||||
twist=twist,
|
||||
altitude_angle=altitude_angle,
|
||||
azimuth_angle=azimuth_angle,
|
||||
)
|
||||
return self
|
||||
|
||||
def move_to_location(
|
||||
self,
|
||||
x,
|
||||
y,
|
||||
width=None,
|
||||
height=None,
|
||||
pressure=None,
|
||||
tangential_pressure=None,
|
||||
tilt_x=None,
|
||||
tilt_y=None,
|
||||
twist=None,
|
||||
altitude_angle=None,
|
||||
azimuth_angle=None,
|
||||
):
|
||||
self.source.create_pointer_move(
|
||||
origin="viewport",
|
||||
duration=self._duration,
|
||||
x=int(x),
|
||||
y=int(y),
|
||||
width=width,
|
||||
height=height,
|
||||
pressure=pressure,
|
||||
tangential_pressure=tangential_pressure,
|
||||
tilt_x=tilt_x,
|
||||
tilt_y=tilt_y,
|
||||
twist=twist,
|
||||
altitude_angle=altitude_angle,
|
||||
azimuth_angle=azimuth_angle,
|
||||
)
|
||||
return self
|
||||
|
||||
def click(self, element: WebElement | None = None, button=MouseButton.LEFT):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.pointer_down(button)
|
||||
self.pointer_up(button)
|
||||
return self
|
||||
|
||||
def context_click(self, element: WebElement | None = None):
|
||||
return self.click(element=element, button=MouseButton.RIGHT)
|
||||
|
||||
def click_and_hold(self, element: WebElement | None = None, button=MouseButton.LEFT):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.pointer_down(button=button)
|
||||
return self
|
||||
|
||||
def release(self, button=MouseButton.LEFT):
|
||||
self.pointer_up(button=button)
|
||||
return self
|
||||
|
||||
def double_click(self, element: WebElement | None = None):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.pointer_down(MouseButton.LEFT)
|
||||
self.pointer_up(MouseButton.LEFT)
|
||||
self.pointer_down(MouseButton.LEFT)
|
||||
self.pointer_up(MouseButton.LEFT)
|
||||
return self
|
||||
|
||||
def pause(self, duration: float = 0):
|
||||
self.source.create_pause(duration)
|
||||
return self
|
||||
|
||||
def _button_action(self, action, **kwargs):
|
||||
meth = getattr(self.source, action)
|
||||
meth(**kwargs)
|
||||
return self
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
|
||||
from typing import Any
|
||||
|
||||
from selenium.common.exceptions import InvalidArgumentException
|
||||
from selenium.webdriver.common.actions.input_device import InputDevice
|
||||
from selenium.webdriver.common.actions.interaction import POINTER, POINTER_KINDS
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
|
||||
class PointerInput(InputDevice):
|
||||
DEFAULT_MOVE_DURATION = 250
|
||||
|
||||
def __init__(self, kind, name):
|
||||
super().__init__()
|
||||
if kind not in POINTER_KINDS:
|
||||
raise InvalidArgumentException(f"Invalid PointerInput kind '{kind}'")
|
||||
self.type = POINTER
|
||||
self.kind = kind
|
||||
self.name = name
|
||||
|
||||
def create_pointer_move(
|
||||
self,
|
||||
duration=DEFAULT_MOVE_DURATION,
|
||||
x: float = 0,
|
||||
y: float = 0,
|
||||
origin: WebElement | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
action = {"type": "pointerMove", "duration": duration, "x": x, "y": y, **kwargs}
|
||||
if isinstance(origin, WebElement):
|
||||
action["origin"] = {"element-6066-11e4-a52e-4f735466cecf": origin.id}
|
||||
elif origin is not None:
|
||||
action["origin"] = origin
|
||||
self.add_action(self._convert_keys(action))
|
||||
|
||||
def create_pointer_down(self, **kwargs):
|
||||
data = {"type": "pointerDown", "duration": 0, **kwargs}
|
||||
self.add_action(self._convert_keys(data))
|
||||
|
||||
def create_pointer_up(self, button):
|
||||
self.add_action({"type": "pointerUp", "duration": 0, "button": button})
|
||||
|
||||
def create_pointer_cancel(self):
|
||||
self.add_action({"type": "pointerCancel"})
|
||||
|
||||
def create_pause(self, pause_duration: int | float = 0) -> None:
|
||||
self.add_action({"type": "pause", "duration": int(pause_duration * 1000)})
|
||||
|
||||
def encode(self):
|
||||
return {"type": self.type, "parameters": {"pointerType": self.kind}, "id": self.name, "actions": self.actions}
|
||||
|
||||
def _convert_keys(self, actions: dict[str, Any]):
|
||||
out = {}
|
||||
for k, v in actions.items():
|
||||
if v is None:
|
||||
continue
|
||||
if k in ("x", "y"):
|
||||
out[k] = int(v)
|
||||
continue
|
||||
splits = k.split("_")
|
||||
new_key = splits[0] + "".join(v.title() for v in splits[1:])
|
||||
out[new_key] = v
|
||||
return out
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
|
||||
|
||||
from selenium.webdriver.common.actions.interaction import WHEEL, Interaction
|
||||
from selenium.webdriver.common.actions.wheel_input import WheelInput
|
||||
|
||||
|
||||
class WheelActions(Interaction):
|
||||
def __init__(self, source: WheelInput | None = None):
|
||||
if source is None:
|
||||
source = WheelInput(WHEEL)
|
||||
super().__init__(source)
|
||||
|
||||
def pause(self, duration: float = 0):
|
||||
self.source.create_pause(duration)
|
||||
return self
|
||||
|
||||
def scroll(self, x=0, y=0, delta_x=0, delta_y=0, duration=0, origin="viewport"):
|
||||
self.source.create_scroll(x, y, delta_x, delta_y, duration, origin)
|
||||
return self
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The SFC licenses this file
|
||||
# to you 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.
|
||||
|
||||
from selenium.webdriver.common.actions import interaction
|
||||
from selenium.webdriver.common.actions.input_device import InputDevice
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
|
||||
class ScrollOrigin:
|
||||
def __init__(self, origin: str | WebElement, x_offset: int, y_offset: int) -> None:
|
||||
self._origin = origin
|
||||
self._x_offset = x_offset
|
||||
self._y_offset = y_offset
|
||||
|
||||
@classmethod
|
||||
def from_element(cls, element: WebElement, x_offset: int = 0, y_offset: int = 0):
|
||||
return cls(element, x_offset, y_offset)
|
||||
|
||||
@classmethod
|
||||
def from_viewport(cls, x_offset: int = 0, y_offset: int = 0):
|
||||
return cls("viewport", x_offset, y_offset)
|
||||
|
||||
@property
|
||||
def origin(self) -> str | WebElement:
|
||||
return self._origin
|
||||
|
||||
@property
|
||||
def x_offset(self) -> int:
|
||||
return self._x_offset
|
||||
|
||||
@property
|
||||
def y_offset(self) -> int:
|
||||
return self._y_offset
|
||||
|
||||
|
||||
class WheelInput(InputDevice):
|
||||
def __init__(self, name) -> None:
|
||||
super().__init__(name=name)
|
||||
self.name = name
|
||||
self.type = interaction.WHEEL
|
||||
|
||||
def encode(self) -> dict:
|
||||
return {"type": self.type, "id": self.name, "actions": self.actions}
|
||||
|
||||
def create_scroll(self, x: int, y: int, delta_x: int, delta_y: int, duration: int, origin) -> None:
|
||||
if isinstance(origin, WebElement):
|
||||
origin = {"element-6066-11e4-a52e-4f735466cecf": origin.id}
|
||||
self.add_action(
|
||||
{
|
||||
"type": "scroll",
|
||||
"x": x,
|
||||
"y": y,
|
||||
"deltaX": delta_x,
|
||||
"deltaY": delta_y,
|
||||
"duration": duration,
|
||||
"origin": origin,
|
||||
}
|
||||
)
|
||||
|
||||
def create_pause(self, pause_duration: int | float = 0) -> None:
|
||||
self.add_action({"type": "pause", "duration": int(pause_duration * 1000)})
|
||||
Reference in New Issue
Block a user