1use async_trait::async_trait;
18use std::time::Duration;
19
20use smix_error::{ExpectationFailure, FailureCode, FailureInit};
21use smix_host_coord_resolver::{HostResolveError, resolve_to_norm_coord};
22use smix_input::{KeyName, SwipeDirection};
23use smix_runner_client::{HttpRunnerClient, IncludeScope, OcrFrame, SystemPopup, TapMode};
24use smix_screen::A11yNode;
25use smix_selector::{Selector, describe_selector};
26use smix_selector_resolver::{resolve_selector, resolve_selector_all};
27
28use crate::Orientation;
29use crate::traits::{Driver, Platform};
30
31pub struct AndroidDriver {
35 runner: HttpRunnerClient,
36}
37
38impl AndroidDriver {
39 #[must_use]
40 pub fn new(runner: HttpRunnerClient) -> Self {
41 AndroidDriver { runner }
42 }
43
44 pub fn runner(&self) -> &HttpRunnerClient {
45 &self.runner
46 }
47}
48
49async fn resolve_with_implicit_wait(
53 driver: &AndroidDriver,
54 selector: &Selector,
55 include: Option<IncludeScope>,
56) -> Result<(f64, f64), ExpectationFailure> {
57 let start = std::time::Instant::now();
58 let timeout = Duration::from_millis(5000);
59 loop {
60 let tree = driver.tree(include).await?;
61 match resolve_to_norm_coord(&tree, selector) {
62 Ok(coord) => return Ok(coord),
63 Err(HostResolveError::NotFound) => {
64 if start.elapsed() > timeout {
65 return Err(ExpectationFailure::new(FailureInit {
66 code: Some(FailureCode::ElementNotFound),
67 message: format!(
68 "AndroidDriver: element not found: {}",
69 describe_selector(selector)
70 ),
71 selector: Some(selector.clone()),
72 ..Default::default()
73 }));
74 }
75 tokio::time::sleep(Duration::from_millis(250)).await;
76 continue;
77 }
78 Err(e) => {
79 return Err(ExpectationFailure::new(FailureInit {
80 code: Some(FailureCode::DriverError),
81 message: format!("AndroidDriver: resolve error: {e:?}"),
82 selector: Some(selector.clone()),
83 ..Default::default()
84 }));
85 }
86 }
87 }
88}
89
90fn defer_err(method: &str) -> ExpectationFailure {
91 ExpectationFailure::new(FailureInit {
92 code: Some(FailureCode::DriverError),
93 message: format!(
94 "AndroidDriver::{method}: Kotlin runner endpoint not yet shipped (v6.0 c3b earned defer)"
95 ),
96 ..Default::default()
97 })
98}
99
100#[async_trait]
101impl Driver for AndroidDriver {
102 fn platform(&self) -> Platform {
103 Platform::Android
104 }
105
106 fn set_session_id(&mut self, id: Option<String>) {
112 match id {
113 Some(sid) => self.runner.set_session_id(sid),
114 None => self.runner.clear_session_id(),
115 }
116 }
117
118 async fn tree(&self, include: Option<IncludeScope>) -> Result<A11yNode, ExpectationFailure> {
121 self.runner.get_tree(include).await.map_err(|e| {
125 ExpectationFailure::new(FailureInit {
126 code: Some(FailureCode::DriverError),
127 message: format!("AndroidDriver::tree: {e}"),
128 ..Default::default()
129 })
130 })
131 }
132
133 async fn find(
134 &self,
135 selector: &Selector,
136 include: Option<IncludeScope>,
137 ) -> Result<bool, ExpectationFailure> {
138 let tree = self.tree(include).await?;
141 Ok(resolve_selector(&tree, selector).is_some())
142 }
143
144 async fn find_one(
145 &self,
146 selector: &Selector,
147 include: Option<IncludeScope>,
148 ) -> Result<Option<A11yNode>, ExpectationFailure> {
149 let tree = self.tree(include).await?;
150 Ok(resolve_selector(&tree, selector).cloned())
151 }
152
153 async fn find_all(
154 &self,
155 selector: &Selector,
156 include: Option<IncludeScope>,
157 ) -> Result<Vec<A11yNode>, ExpectationFailure> {
158 let tree = self.tree(include).await?;
159 Ok(resolve_selector_all(&tree, selector)
160 .into_iter()
161 .cloned()
162 .collect())
163 }
164
165 async fn find_norm_coord(
166 &self,
167 selector: &Selector,
168 ) -> Result<Option<(f64, f64)>, ExpectationFailure> {
169 let tree = self.tree(None).await?;
170 match resolve_to_norm_coord(&tree, selector) {
171 Ok(coord) => Ok(Some(coord)),
172 Err(HostResolveError::NotFound | HostResolveError::EmptyMatchedFrame) => Ok(None),
173 Err(HostResolveError::UnknownAppFrame) => Err(ExpectationFailure::new(FailureInit {
174 code: Some(FailureCode::DriverError),
175 message: "AndroidDriver::find_norm_coord: tree bounds w/h ≤ 0 (unknown app frame)"
176 .into(),
177 ..Default::default()
178 })),
179 Err(HostResolveError::CentroidOutOfFrame { .. }) => Ok(None),
180 }
181 }
182
183 async fn find_text_by_ocr(
184 &self,
185 text: &str,
186 locales: &[String],
187 recognition_level: &str,
188 ) -> Result<Option<OcrFrame>, ExpectationFailure> {
189 self.runner
194 .find_text_by_ocr(text, locales, recognition_level)
195 .await
196 .map_err(|e| {
197 ExpectationFailure::new(FailureInit {
198 code: Some(FailureCode::DriverError),
199 message: format!("AndroidDriver::find_text_by_ocr: {e}"),
200 ..Default::default()
201 })
202 })
203 }
204
205 async fn system_popups(
206 &self,
207 include: Option<IncludeScope>,
208 ) -> Result<Vec<SystemPopup>, ExpectationFailure> {
209 self.runner.system_popups(include).await.map_err(|e| {
213 ExpectationFailure::new(FailureInit {
214 code: Some(FailureCode::DriverError),
215 message: format!("AndroidDriver::system_popups: {e}"),
216 ..Default::default()
217 })
218 })
219 }
220
221 async fn system_popup_action(
222 &self,
223 popup_id: &str,
224 button_id: &str,
225 ) -> Result<bool, ExpectationFailure> {
226 self.runner
230 .system_popup_action(popup_id, button_id)
231 .await
232 .map_err(|e| {
233 ExpectationFailure::new(FailureInit {
234 code: Some(FailureCode::DriverError),
235 message: format!("AndroidDriver::system_popup_action: {e}"),
236 ..Default::default()
237 })
238 })
239 }
240
241 async fn wait_for(
242 &self,
243 selector: &Selector,
244 timeout: Duration,
245 include: Option<IncludeScope>,
246 ) -> Result<A11yNode, ExpectationFailure> {
247 let start = std::time::Instant::now();
250 loop {
251 let tree = self.tree(include).await?;
252 if let Some(node) = resolve_selector(&tree, selector) {
253 return Ok(node.clone());
254 }
255 if start.elapsed() >= timeout {
256 return Err(ExpectationFailure::new(FailureInit {
257 code: Some(FailureCode::ElementNotFound),
258 message: format!(
259 "AndroidDriver::wait_for timeout after {}ms: {}",
260 timeout.as_millis(),
261 describe_selector(selector)
262 ),
263 selector: Some(selector.clone()),
264 ..Default::default()
265 }));
266 }
267 tokio::time::sleep(Duration::from_millis(250)).await;
268 }
269 }
270
271 async fn tap(
274 &self,
275 selector: &Selector,
276 include: Option<IncludeScope>,
277 ) -> Result<(), ExpectationFailure> {
278 let (nx, ny) = resolve_with_implicit_wait(self, selector, include).await?;
281 self.runner.tap_at_norm_coord(nx, ny).await.map_err(|e| {
282 ExpectationFailure::new(FailureInit {
283 code: Some(FailureCode::DriverError),
284 message: format!("AndroidDriver::tap: runner.tap_at_norm_coord: {e}"),
285 ..Default::default()
286 })
287 })
288 }
289
290 async fn tap_with_mode(
291 &self,
292 _selector: &Selector,
293 _mode: TapMode,
294 _include: Option<IncludeScope>,
295 ) -> Result<(), ExpectationFailure> {
296 Err(defer_err("tap_with_mode"))
297 }
298
299 async fn tap_at_norm_coord(&self, nx: f64, ny: f64) -> Result<(), ExpectationFailure> {
300 self.runner.tap_at_norm_coord(nx, ny).await.map_err(|e| {
302 ExpectationFailure::new(FailureInit {
303 code: Some(FailureCode::DriverError),
304 message: format!("AndroidDriver::tap_at_norm_coord: {e}"),
305 ..Default::default()
306 })
307 })
308 }
309
310 async fn tap_by_id(&self, id: &str) -> Result<(), ExpectationFailure> {
311 let ok = self.runner.tap_by_id(id).await.map_err(|e| {
314 ExpectationFailure::new(FailureInit {
315 code: Some(FailureCode::DriverError),
316 message: format!("AndroidDriver::tap_by_id: {e}"),
317 ..Default::default()
318 })
319 })?;
320 if !ok {
321 return Err(ExpectationFailure::new(FailureInit {
322 code: Some(FailureCode::ElementNotFound),
323 message: format!("AndroidDriver::tap_by_id: no element with resource-id '{id}'"),
324 ..Default::default()
325 }));
326 }
327 Ok(())
328 }
329
330 async fn double_tap(
331 &self,
332 selector: &Selector,
333 include: Option<IncludeScope>,
334 ) -> Result<(), ExpectationFailure> {
335 let (nx, ny) = resolve_with_implicit_wait(self, selector, include).await?;
338 self.runner
339 .double_tap_at_norm_coord(nx, ny)
340 .await
341 .map_err(|e| {
342 ExpectationFailure::new(FailureInit {
343 code: Some(FailureCode::DriverError),
344 message: format!("AndroidDriver::double_tap: {e}"),
345 ..Default::default()
346 })
347 })
348 }
349
350 async fn long_press(
351 &self,
352 selector: &Selector,
353 duration: Duration,
354 include: Option<IncludeScope>,
355 ) -> Result<(), ExpectationFailure> {
356 let (nx, ny) = resolve_with_implicit_wait(self, selector, include).await?;
360 let duration_ms = duration.as_millis() as u64;
361 self.runner
362 .long_press_at_norm_coord(nx, ny, duration_ms)
363 .await
364 .map_err(|e| {
365 ExpectationFailure::new(FailureInit {
366 code: Some(FailureCode::DriverError),
367 message: format!("AndroidDriver::long_press: {e}"),
368 ..Default::default()
369 })
370 })
371 }
372
373 async fn fill(
374 &self,
375 selector: &Selector,
376 text: &str,
377 include: Option<IncludeScope>,
378 ) -> Result<(), ExpectationFailure> {
379 let (nx, ny) = resolve_with_implicit_wait(self, selector, include).await?;
383 self.runner.tap_at_norm_coord(nx, ny).await.map_err(|e| {
384 ExpectationFailure::new(FailureInit {
385 code: Some(FailureCode::DriverError),
386 message: format!("AndroidDriver::fill: focus tap failed: {e}"),
387 ..Default::default()
388 })
389 })?;
390 self.runner.input_text(text).await.map_err(|e| {
391 ExpectationFailure::new(FailureInit {
392 code: Some(FailureCode::DriverError),
393 message: format!("AndroidDriver::fill: input_text failed: {e}"),
394 ..Default::default()
395 })
396 })
397 }
398
399 async fn clear(
400 &self,
401 selector: &Selector,
402 include: Option<IncludeScope>,
403 ) -> Result<(), ExpectationFailure> {
404 let (nx, ny) = resolve_with_implicit_wait(self, selector, include).await?;
408 self.runner.tap_at_norm_coord(nx, ny).await.map_err(|e| {
409 ExpectationFailure::new(FailureInit {
410 code: Some(FailureCode::DriverError),
411 message: format!("AndroidDriver::clear: focus tap failed: {e}"),
412 ..Default::default()
413 })
414 })?;
415 for _ in 0..50 {
416 self.runner.press_key(KeyName::Delete).await.map_err(|e| {
417 ExpectationFailure::new(FailureInit {
418 code: Some(FailureCode::DriverError),
419 message: format!("AndroidDriver::clear: delete press failed: {e}"),
420 ..Default::default()
421 })
422 })?;
423 }
424 Ok(())
425 }
426
427 async fn press_key(&self, key: KeyName) -> Result<(), ExpectationFailure> {
428 self.runner.press_key(key).await.map(|_| ()).map_err(|e| {
430 ExpectationFailure::new(FailureInit {
431 code: Some(FailureCode::DriverError),
432 message: format!("AndroidDriver::press_key: {e}"),
433 ..Default::default()
434 })
435 })
436 }
437
438 async fn scroll(
439 &self,
440 selector: &Selector,
441 direction: SwipeDirection,
442 ) -> Result<(), ExpectationFailure> {
443 let start = std::time::Instant::now();
446 let timeout = Duration::from_secs(20);
447 const MAX_SWIPES: u32 = 30;
448 for _ in 0..=MAX_SWIPES {
449 let tree = self.tree(None).await?;
450 if resolve_selector(&tree, selector).is_some() {
451 return Ok(());
452 }
453 if start.elapsed() > timeout {
454 break;
455 }
456 self.swipe_once(direction).await?;
457 }
458 Err(ExpectationFailure::new(FailureInit {
459 code: Some(FailureCode::ElementNotFound),
460 message: format!(
461 "AndroidDriver::scroll: element not visible after {} swipes: {}",
462 MAX_SWIPES,
463 describe_selector(selector)
464 ),
465 selector: Some(selector.clone()),
466 ..Default::default()
467 }))
468 }
469
470 async fn swipe_once(&self, direction: SwipeDirection) -> Result<(), ExpectationFailure> {
471 self.runner.swipe_once(direction).await.map_err(|e| {
472 ExpectationFailure::new(FailureInit {
473 code: Some(FailureCode::DriverError),
474 message: format!("AndroidDriver::swipe_once: {e}"),
475 ..Default::default()
476 })
477 })
478 }
479
480 async fn swipe_at_norm_coord(
481 &self,
482 from: (f64, f64),
483 to: (f64, f64),
484 ) -> Result<(), ExpectationFailure> {
485 self.runner
486 .swipe_at_norm_coord(from, to)
487 .await
488 .map_err(|e| {
489 ExpectationFailure::new(FailureInit {
490 code: Some(FailureCode::DriverError),
491 message: format!("AndroidDriver::swipe_at_norm_coord: {e}"),
492 ..Default::default()
493 })
494 })
495 }
496
497 async fn hide_keyboard(&self) -> Result<(), ExpectationFailure> {
498 self.runner.hide_keyboard().await.map_err(|e| {
499 ExpectationFailure::new(FailureInit {
500 code: Some(FailureCode::DriverError),
501 message: format!("AndroidDriver::hide_keyboard: {e}"),
502 ..Default::default()
503 })
504 })
505 }
506
507 async fn back(&self) -> Result<(), ExpectationFailure> {
508 self.runner.back().await.map_err(|e| {
510 ExpectationFailure::new(FailureInit {
511 code: Some(FailureCode::DriverError),
512 message: format!("AndroidDriver::back: {e}"),
513 ..Default::default()
514 })
515 })
516 }
517
518 async fn set_orientation(&self, orientation: Orientation) -> Result<(), ExpectationFailure> {
519 self.runner
520 .set_orientation(orientation.as_wire())
521 .await
522 .map_err(|e| {
523 ExpectationFailure::new(FailureInit {
524 code: Some(FailureCode::DriverError),
525 message: format!("AndroidDriver::set_orientation: {e}"),
526 ..Default::default()
527 })
528 })
529 }
530
531 async fn foreground(&self, bundle_id: &str) -> Result<(), ExpectationFailure> {
532 self.runner.foreground(bundle_id).await.map_err(|e| {
536 ExpectationFailure::new(FailureInit {
537 code: Some(FailureCode::DriverError),
538 message: format!("AndroidDriver::foreground: {e}"),
539 ..Default::default()
540 })
541 })
542 }
543
544 async fn webview_eval(&self, js: &str) -> Result<serde_json::Value, ExpectationFailure> {
545 self.runner.webview_eval(js).await.map_err(|e| {
550 ExpectationFailure::new(FailureInit {
551 code: Some(FailureCode::DriverError),
552 message: format!("AndroidDriver::webview_eval: {e}"),
553 ..Default::default()
554 })
555 })
556 }
557}