Skip to main content

smix_driver/
traits.rs

1//! v6.0 c1a — `Driver` trait: cross-platform sense + act abstraction.
2//!
3//! Per docs/plan-cold/v6-cross-platform-yaml-design.md §4.1
4//! (audit-revised 2026-06-23): two-trait architecture.
5//!
6//! - `Driver` (this trait) — sense + act, backed by HTTP runner client
7//!   (XCUITest server for iOS, ktor/UIAutomator2 for Android per v6.0 c3)
8//! - `DeviceControl` (in smix-sdk, v6.0 c1b) — sim/host control,
9//!   backed by simctl (iOS) / adb (Android)
10//!
11//! Methods NOT on this trait (App-layer aggregation or platform-specific
12//! getter):
13//! - `App::describe()` — calls `Driver::tree()` + `collect_visible_summaries()`
14//! - `IosDriver::runner()` — typed HttpRunnerClient getter, iOS-internal
15//!   (Android uses a different runner client type)
16//! - `IosDriver::dispose()` — runner cleanup, internal
17//! - `App::screenshot()` — calls `DeviceControl::screenshot()` (simctl-backed)
18//! - `App::mark_fixture_action()` — pure ledger record, no platform dispatch
19
20use async_trait::async_trait;
21use std::time::Duration;
22
23use smix_error::ExpectationFailure;
24use smix_input::{KeyName, SwipeDirection};
25use smix_runner_client::{IncludeScope, OcrFrame, SystemPopup, TapMode};
26use smix_screen::A11yNode;
27use smix_selector::Selector;
28
29use crate::Orientation;
30
31/// Platform identifier for cross-platform dispatch.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum Platform {
34    Ios,
35    Android,
36}
37
38/// Sense + act capabilities for a single device. Two-trait architecture
39/// pair with `smix_sdk::DeviceControl` (sim/host control in smix-sdk).
40///
41/// Signatures intentionally match iOS impl (`IosDriver` inherent methods)
42/// 1:1 so the trait impl is a pure delegation layer.
43#[async_trait]
44pub trait Driver: Send + Sync {
45    /// Platform identifier — `Platform::Ios` or `Platform::Android`.
46    fn platform(&self) -> Platform;
47
48    /// v6.0 c1b — iOS-only escape hatch: access `IosDriver`'s inherent
49    /// `runner()` for capsule recording (start_record / stop_record) +
50    /// `describe()` aggregation. Android impl returns `None`. Default
51    /// `None` so non-iOS impls don't need to implement.
52    fn as_ios_driver(&self) -> Option<&crate::IosDriver> {
53        None
54    }
55
56    /// v0.2.1 — set the target bundle id sent to the runner. For iOS,
57    /// this becomes the `App-Bundle-Id` header on every request so the
58    /// runner's `resolveApp()` rebinds `XCUIApplication(bundleIdentifier:)`
59    /// per request. Default no-op — only iOS currently uses this; Android
60    /// path stays unaffected. gol-611-v0.2.1 §Phase C.
61    fn set_target_bundle_id(&mut self, _bundle: &str) {}
62
63    /// v0.2.1 — enable `App-Activate: true` header on every request so
64    /// the iOS runner calls `.activate()` on the resolved target before
65    /// operating. Default no-op. gol-611-v0.2.1 §Phase C.
66    fn set_auto_activate(&mut self, _activate: bool) {}
67
68    // === Sense (9) ============================================================
69
70    /// Fetch full a11y tree (`GET /tree`).
71    async fn tree(&self, include: Option<IncludeScope>) -> Result<A11yNode, ExpectationFailure>;
72
73    /// Boolean existence quick-probe.
74    async fn find(
75        &self,
76        selector: &Selector,
77        include: Option<IncludeScope>,
78    ) -> Result<bool, ExpectationFailure>;
79
80    /// Resolve selector → single matching node.
81    async fn find_one(
82        &self,
83        selector: &Selector,
84        include: Option<IncludeScope>,
85    ) -> Result<Option<A11yNode>, ExpectationFailure>;
86
87    /// Resolve selector → all matching nodes.
88    async fn find_all(
89        &self,
90        selector: &Selector,
91        include: Option<IncludeScope>,
92    ) -> Result<Vec<A11yNode>, ExpectationFailure>;
93
94    /// Resolve selector → centroid as viewport-normalized `(nx, ny)`.
95    async fn find_norm_coord(
96        &self,
97        selector: &Selector,
98    ) -> Result<Option<(f64, f64)>, ExpectationFailure>;
99
100    /// Apple Vision / ML Kit OCR find text in current screenshot.
101    /// `recognition_level` is `"accurate"` or `"fast"`.
102    async fn find_text_by_ocr(
103        &self,
104        text: &str,
105        locales: &[String],
106        recognition_level: &str,
107    ) -> Result<Option<OcrFrame>, ExpectationFailure>;
108
109    /// List visible system-level popups (alerts, permission dialogs).
110    async fn system_popups(
111        &self,
112        include: Option<IncludeScope>,
113    ) -> Result<Vec<SystemPopup>, ExpectationFailure>;
114
115    /// Tap a button on a system popup by id.
116    async fn system_popup_action(
117        &self,
118        popup_id: &str,
119        button_id: &str,
120    ) -> Result<bool, ExpectationFailure>;
121
122    /// Wait until selector resolves (returns matched node when ready).
123    async fn wait_for(
124        &self,
125        selector: &Selector,
126        timeout: Duration,
127        include: Option<IncludeScope>,
128    ) -> Result<A11yNode, ExpectationFailure>;
129
130    // === Act (17) ============================================================
131
132    /// Tap an element (default mode = element-anchored coord tap).
133    async fn tap(
134        &self,
135        selector: &Selector,
136        include: Option<IncludeScope>,
137    ) -> Result<(), ExpectationFailure>;
138
139    /// Tap with explicit mode (Path A vs Path B).
140    async fn tap_with_mode(
141        &self,
142        selector: &Selector,
143        mode: TapMode,
144        include: Option<IncludeScope>,
145    ) -> Result<(), ExpectationFailure>;
146
147    /// Tap at viewport-normalized coordinate (§9 #3 escape hatch).
148    async fn tap_at_norm_coord(&self, nx: f64, ny: f64) -> Result<(), ExpectationFailure>;
149
150    /// Tap by accessibility identifier via swift `/tap-by-id` route
151    /// (IOHID synthesize at element-frame center; v5.12 c1+).
152    async fn tap_by_id(&self, id: &str) -> Result<(), ExpectationFailure>;
153
154    /// Double-tap a selector.
155    async fn double_tap(
156        &self,
157        selector: &Selector,
158        include: Option<IncludeScope>,
159    ) -> Result<(), ExpectationFailure>;
160
161    /// Long-press a selector for `duration`.
162    async fn long_press(
163        &self,
164        selector: &Selector,
165        duration: Duration,
166        include: Option<IncludeScope>,
167    ) -> Result<(), ExpectationFailure>;
168
169    /// Fill text into a focused / matched input.
170    async fn fill(
171        &self,
172        selector: &Selector,
173        text: &str,
174        include: Option<IncludeScope>,
175    ) -> Result<(), ExpectationFailure>;
176
177    /// Clear text from a focused / matched input.
178    async fn clear(
179        &self,
180        selector: &Selector,
181        include: Option<IncludeScope>,
182    ) -> Result<(), ExpectationFailure>;
183
184    /// Press a named key (Return / Tab / Backspace / etc).
185    async fn press_key(&self, key: KeyName) -> Result<(), ExpectationFailure>;
186
187    /// Scroll until a selector is visible.
188    async fn scroll(
189        &self,
190        selector: &Selector,
191        direction: SwipeDirection,
192    ) -> Result<(), ExpectationFailure>;
193
194    /// One-shot swipe in a direction (no probe loop).
195    async fn swipe_once(&self, direction: SwipeDirection) -> Result<(), ExpectationFailure>;
196
197    /// Swipe between two viewport-normalized coords.
198    async fn swipe_at_norm_coord(
199        &self,
200        from: (f64, f64),
201        to: (f64, f64),
202    ) -> Result<(), ExpectationFailure>;
203
204    /// Dismiss the keyboard.
205    async fn hide_keyboard(&self) -> Result<(), ExpectationFailure>;
206
207    /// Trigger "back" gesture (iOS edge-swipe / Android KEYCODE_BACK).
208    async fn back(&self) -> Result<(), ExpectationFailure>;
209
210    /// Rotate the device.
211    async fn set_orientation(&self, orientation: Orientation) -> Result<(), ExpectationFailure>;
212
213    /// Bring app to foreground.
214    async fn foreground(&self, bundle_id: &str) -> Result<(), ExpectationFailure>;
215
216    /// Evaluate JavaScript in a WebView (iOS: fixture bridge :28080;
217    /// Android: Chrome DevTools Protocol via adb forward).
218    async fn webview_eval(&self, js: &str) -> Result<serde_json::Value, ExpectationFailure>;
219}