from __future__ import annotations from typing import Any POINTER_ID = "finger1" def _pointer(*actions: dict[str, Any]) -> list[dict[str, Any]]: return [ { "type": "pointer", "id": POINTER_ID, "parameters": {"pointerType": "touch"}, "actions": list(actions), } ] def build_swipe_actions( waypoints: list[tuple[float, float]], total_duration_ms: int ) -> list[dict[str, Any]]: """W3C actions moving through ``waypoints`` in one continuous touch.""" if len(waypoints) < 2: raise ValueError("swipe path requires at least two waypoints") segments = len(waypoints) - 1 per_segment = max(1, total_duration_ms // segments) sx, sy = waypoints[0] actions: list[dict[str, Any]] = [ {"type": "pointerMove", "duration": 0, "x": int(sx), "y": int(sy)}, {"type": "pointerDown", "button": 0}, ] for point in waypoints[1:]: actions.append( { "type": "pointerMove", "duration": per_segment, "x": int(point[0]), "y": int(point[1]), } ) ex, ey = waypoints[-1] actions.append( {"type": "pointerUp", "button": 0, "x": int(ex), "y": int(ey)} ) return _pointer(*actions) def build_double_tap_actions( x: float, y: float, interval_ms: int ) -> list[dict[str, Any]]: actions = [ {"type": "pointerMove", "duration": 0, "x": int(x), "y": int(y)}, {"type": "pointerDown", "button": 0}, {"type": "pointerUp", "button": 0}, {"type": "pause", "duration": max(1, interval_ms)}, {"type": "pointerDown", "button": 0}, {"type": "pointerUp", "button": 0}, ] return _pointer(*actions) def perform_actions(client: Any, actions: list[dict[str, Any]]) -> None: """Send a W3C actions payload via the Appium/Selenium command seam. Import is lazy so the pure payload builders stay importable without selenium on the path (used by unit tests). """ from selenium.webdriver.remote.command import Command client.execute(Command.W3C_ACTIONS, {"actions": actions})