# 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 math
from dataclasses import dataclass, field
from typing import Any
from selenium.webdriver.common.bidi.common import command_builder
from selenium.webdriver.common.bidi.session import Session
[docs]
class PointerType:
"""Represents the possible pointer types."""
MOUSE = "mouse"
PEN = "pen"
TOUCH = "touch"
VALID_TYPES = {MOUSE, PEN, TOUCH}
[docs]
class Origin:
"""Represents the possible origin types."""
VIEWPORT = "viewport"
POINTER = "pointer"
[docs]
@dataclass
class ElementOrigin:
"""Represents an element origin for input actions."""
type: str
element: dict
def __init__(self, element_reference: dict):
self.type = "element"
self.element = element_reference
[docs]
def to_dict(self) -> dict:
"""Convert the ElementOrigin to a dictionary."""
return {"type": self.type, "element": self.element}
[docs]
@dataclass
class PointerParameters:
"""Represents pointer parameters for pointer actions."""
pointer_type: str = PointerType.MOUSE
def __post_init__(self):
if self.pointer_type not in PointerType.VALID_TYPES:
raise ValueError(f"Invalid pointer type: {self.pointer_type}. Must be one of {PointerType.VALID_TYPES}")
[docs]
def to_dict(self) -> dict:
"""Convert the PointerParameters to a dictionary."""
return {"pointerType": self.pointer_type}
[docs]
@dataclass
class PointerCommonProperties:
"""Common properties for pointer actions."""
width: int = 1
height: int = 1
pressure: float = 0.0
tangential_pressure: float = 0.0
twist: int = 0
altitude_angle: float = 0.0
azimuth_angle: float = 0.0
def __post_init__(self):
if self.width < 1:
raise ValueError("width must be at least 1")
if self.height < 1:
raise ValueError("height must be at least 1")
if not (0.0 <= self.pressure <= 1.0):
raise ValueError("pressure must be between 0.0 and 1.0")
if not (0.0 <= self.tangential_pressure <= 1.0):
raise ValueError("tangential_pressure must be between 0.0 and 1.0")
if not (0 <= self.twist <= 359):
raise ValueError("twist must be between 0 and 359")
if not (0.0 <= self.altitude_angle <= math.pi / 2):
raise ValueError("altitude_angle must be between 0.0 and π/2")
if not (0.0 <= self.azimuth_angle <= 2 * math.pi):
raise ValueError("azimuth_angle must be between 0.0 and 2π")
[docs]
def to_dict(self) -> dict:
"""Convert the PointerCommonProperties to a dictionary."""
result: dict[str, Any] = {}
if self.width != 1:
result["width"] = self.width
if self.height != 1:
result["height"] = self.height
if self.pressure != 0.0:
result["pressure"] = self.pressure
if self.tangential_pressure != 0.0:
result["tangentialPressure"] = self.tangential_pressure
if self.twist != 0:
result["twist"] = self.twist
if self.altitude_angle != 0.0:
result["altitudeAngle"] = self.altitude_angle
if self.azimuth_angle != 0.0:
result["azimuthAngle"] = self.azimuth_angle
return result
# Action classes
[docs]
@dataclass
class PauseAction:
"""Represents a pause action."""
duration: int | None = None
@property
def type(self) -> str:
return "pause"
[docs]
def to_dict(self) -> dict:
"""Convert the PauseAction to a dictionary."""
result: dict[str, Any] = {"type": self.type}
if self.duration is not None:
result["duration"] = self.duration
return result
[docs]
@dataclass
class KeyDownAction:
"""Represents a key down action."""
value: str = ""
@property
def type(self) -> str:
return "keyDown"
[docs]
def to_dict(self) -> dict:
"""Convert the KeyDownAction to a dictionary."""
return {"type": self.type, "value": self.value}
[docs]
@dataclass
class KeyUpAction:
"""Represents a key up action."""
value: str = ""
@property
def type(self) -> str:
return "keyUp"
[docs]
def to_dict(self) -> dict:
"""Convert the KeyUpAction to a dictionary."""
return {"type": self.type, "value": self.value}
[docs]
@dataclass
class PointerDownAction:
"""Represents a pointer down action."""
button: int = 0
properties: PointerCommonProperties | None = None
@property
def type(self) -> str:
return "pointerDown"
[docs]
def to_dict(self) -> dict:
"""Convert the PointerDownAction to a dictionary."""
result: dict[str, Any] = {"type": self.type, "button": self.button}
if self.properties:
result.update(self.properties.to_dict())
return result
[docs]
@dataclass
class PointerUpAction:
"""Represents a pointer up action."""
button: int = 0
@property
def type(self) -> str:
return "pointerUp"
[docs]
def to_dict(self) -> dict:
"""Convert the PointerUpAction to a dictionary."""
return {"type": self.type, "button": self.button}
[docs]
@dataclass
class PointerMoveAction:
"""Represents a pointer move action."""
x: float = 0
y: float = 0
duration: int | None = None
origin: str | ElementOrigin | None = None
properties: PointerCommonProperties | None = None
@property
def type(self) -> str:
return "pointerMove"
[docs]
def to_dict(self) -> dict:
"""Convert the PointerMoveAction to a dictionary."""
result: dict[str, Any] = {"type": self.type, "x": self.x, "y": self.y}
if self.duration is not None:
result["duration"] = self.duration
if self.origin is not None:
if isinstance(self.origin, ElementOrigin):
result["origin"] = self.origin.to_dict()
else:
result["origin"] = self.origin
if self.properties:
result.update(self.properties.to_dict())
return result
# Source Actions
[docs]
@dataclass
class NoneSourceActions:
"""Represents a sequence of none actions."""
id: str = ""
actions: list[PauseAction] = field(default_factory=list)
@property
def type(self) -> str:
return "none"
[docs]
def to_dict(self) -> dict:
"""Convert the NoneSourceActions to a dictionary."""
return {"type": self.type, "id": self.id, "actions": [action.to_dict() for action in self.actions]}
[docs]
@dataclass
class KeySourceActions:
"""Represents a sequence of key actions."""
id: str = ""
actions: list[PauseAction | KeyDownAction | KeyUpAction] = field(default_factory=list)
@property
def type(self) -> str:
return "key"
[docs]
def to_dict(self) -> dict:
"""Convert the KeySourceActions to a dictionary."""
return {"type": self.type, "id": self.id, "actions": [action.to_dict() for action in self.actions]}
[docs]
@dataclass
class PointerSourceActions:
"""Represents a sequence of pointer actions."""
id: str = ""
parameters: PointerParameters | None = None
actions: list[PauseAction | PointerDownAction | PointerUpAction | PointerMoveAction] = field(default_factory=list)
def __post_init__(self):
if self.parameters is None:
self.parameters = PointerParameters()
@property
def type(self) -> str:
return "pointer"
[docs]
def to_dict(self) -> dict:
"""Convert the PointerSourceActions to a dictionary."""
result: dict[str, Any] = {
"type": self.type,
"id": self.id,
"actions": [action.to_dict() for action in self.actions],
}
if self.parameters:
result["parameters"] = self.parameters.to_dict()
return result
[docs]
@dataclass
class WheelSourceActions:
"""Represents a sequence of wheel actions."""
id: str = ""
actions: list[PauseAction | WheelScrollAction] = field(default_factory=list)
@property
def type(self) -> str:
return "wheel"
[docs]
def to_dict(self) -> dict:
"""Convert the WheelSourceActions to a dictionary."""
return {"type": self.type, "id": self.id, "actions": [action.to_dict() for action in self.actions]}
[docs]
@dataclass
class FileDialogInfo:
"""Represents file dialog information from input.fileDialogOpened event."""
context: str
multiple: bool
element: dict | None = None
[docs]
@classmethod
def from_dict(cls, data: dict) -> "FileDialogInfo":
"""Creates a FileDialogInfo instance from a dictionary.
Args:
data: A dictionary containing the file dialog information.
Returns:
FileDialogInfo: A new instance of FileDialogInfo.
"""
return cls(context=data["context"], multiple=data["multiple"], element=data.get("element"))
# Event Class
[docs]
class FileDialogOpened:
"""Event class for input.fileDialogOpened event."""
event_class = "input.fileDialogOpened"
[docs]
@classmethod
def from_json(cls, json):
"""Create FileDialogInfo from JSON data."""
return FileDialogInfo.from_dict(json)