1use std::collections::HashMap;
2use std::fmt;
3use std::ops::Deref;
4use std::sync::Arc;
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::Error;
9use crate::provider::Provider;
10use crate::role::Role;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ElementData {
19 pub role: Role,
21
22 pub name: Option<String>,
30
31 pub value: Option<String>,
37
38 pub description: Option<String>,
45
46 pub bounds: Option<Rect>,
48
49 pub actions: Vec<String>,
56
57 pub states: StateSet,
59
60 pub numeric_value: Option<f64>,
62
63 pub min_value: Option<f64>,
65
66 pub max_value: Option<f64>,
68
69 pub stable_id: Option<String>,
76
77 pub pid: Option<u32>,
79
80 pub raw: RawPlatformData,
82
83 #[serde(skip, default)]
86 pub handle: u64,
87}
88
89#[derive(Clone)]
97pub struct Element {
98 data: ElementData,
99 provider: Arc<dyn Provider>,
100}
101
102impl Deref for Element {
103 type Target = ElementData;
104
105 fn deref(&self) -> &ElementData {
106 &self.data
107 }
108}
109
110impl fmt::Debug for Element {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 fmt::Debug::fmt(&self.data, f)
113 }
114}
115
116impl fmt::Display for Element {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 let name_part = self
119 .data
120 .name
121 .as_ref()
122 .map(|n| format!(" \"{}\"", n))
123 .unwrap_or_default();
124 let value_part = self
125 .data
126 .value
127 .as_ref()
128 .map(|v| format!(" value=\"{}\"", v))
129 .unwrap_or_default();
130 write!(
131 f,
132 "{}{}{}",
133 self.data.role.to_snake_case(),
134 name_part,
135 value_part,
136 )
137 }
138}
139
140impl Serialize for Element {
141 fn serialize<S: serde::Serializer>(
142 &self,
143 serializer: S,
144 ) -> std::result::Result<S::Ok, S::Error> {
145 self.data.serialize(serializer)
146 }
147}
148
149impl Element {
150 pub fn new(data: ElementData, provider: Arc<dyn Provider>) -> Self {
152 Self { data, provider }
153 }
154
155 pub fn data(&self) -> &ElementData {
157 &self.data
158 }
159
160 pub fn provider(&self) -> &Arc<dyn Provider> {
162 &self.provider
163 }
164
165 pub fn children(&self) -> crate::error::Result<Vec<Element>> {
169 let children = self.provider.get_children(Some(&self.data))?;
170 Ok(children
171 .into_iter()
172 .map(|d| Element::new(d, Arc::clone(&self.provider)))
173 .collect())
174 }
175
176 pub fn parent(&self) -> crate::error::Result<Option<Element>> {
180 let parent = self.provider.get_parent(&self.data)?;
181 Ok(parent.map(|d| Element::new(d, Arc::clone(&self.provider))))
182 }
183
184 pub fn pid(&self) -> Option<u32> {
186 self.data.pid
187 }
188
189 pub fn tree(&self, max_depth: Option<usize>) -> crate::error::Result<TreeNode> {
194 build_tree_node(self, max_depth, 0)
195 }
196
197 pub fn dump(&self, max_depth: Option<usize>) -> crate::error::Result<String> {
202 let node = self.tree(max_depth)?;
203 let mut out = String::new();
204 write_tree_node(&node, 0, &mut out);
205 Ok(out)
206 }
207
208 pub fn press(&self) -> crate::error::Result<()> {
218 self.provider.press(&self.data)
219 }
220
221 pub fn focus(&self) -> crate::error::Result<()> {
223 self.provider.focus(&self.data)
224 }
225
226 pub fn blur(&self) -> crate::error::Result<()> {
228 self.provider.blur(&self.data)
229 }
230
231 pub fn toggle(&self) -> crate::error::Result<()> {
233 self.provider.toggle(&self.data)
234 }
235
236 pub fn select(&self) -> crate::error::Result<()> {
238 self.provider.select(&self.data)
239 }
240
241 pub fn expand(&self) -> crate::error::Result<()> {
243 self.provider.expand(&self.data)
244 }
245
246 pub fn collapse(&self) -> crate::error::Result<()> {
248 self.provider.collapse(&self.data)
249 }
250
251 pub fn show_menu(&self) -> crate::error::Result<()> {
253 self.provider.show_menu(&self.data)
254 }
255
256 pub fn increment(&self) -> crate::error::Result<()> {
258 self.provider.increment(&self.data)
259 }
260
261 pub fn decrement(&self) -> crate::error::Result<()> {
263 self.provider.decrement(&self.data)
264 }
265
266 pub fn scroll_into_view(&self) -> crate::error::Result<()> {
270 self.provider.scroll_into_view(&self.data)
271 }
272
273 pub fn set_value(&self, value: &str) -> crate::error::Result<()> {
276 self.provider.set_value(&self.data, value)
277 }
278
279 pub fn set_numeric_value(&self, value: f64) -> crate::error::Result<()> {
283 if !value.is_finite() {
284 return Err(Error::InvalidActionData {
285 message: format!("set_numeric_value requires a finite value, got {}", value),
286 });
287 }
288 self.provider.set_numeric_value(&self.data, value)
289 }
290
291 pub fn type_text(&self, text: &str) -> crate::error::Result<()> {
295 self.provider.type_text(&self.data, text)
296 }
297
298 pub fn select_text(&self, start: u32, end: u32) -> crate::error::Result<()> {
302 if start > end {
303 return Err(Error::InvalidActionData {
304 message: format!("select_text start ({}) must be <= end ({})", start, end),
305 });
306 }
307 self.provider.set_text_selection(&self.data, start, end)
308 }
309
310 pub fn perform_action(&self, action: &str) -> crate::error::Result<()> {
316 self.provider.perform_action(&self.data, action)
317 }
318}
319
320fn build_tree_node(
321 element: &Element,
322 max_depth: Option<usize>,
323 depth: usize,
324) -> crate::error::Result<TreeNode> {
325 let children = if max_depth.is_none_or(|d| depth < d) {
326 element
327 .children()?
328 .into_iter()
329 .map(|child| build_tree_node(&child, max_depth, depth + 1))
330 .collect::<crate::error::Result<Vec<_>>>()?
331 } else {
332 vec![]
333 };
334 Ok(TreeNode {
335 role: element.data.role.to_snake_case().to_string(),
336 name: element.data.name.clone(),
337 value: element.data.value.clone(),
338 children,
339 })
340}
341
342fn write_tree_node(node: &TreeNode, depth: usize, out: &mut String) {
343 use fmt::Write as _;
344 let indent = " ".repeat(depth);
345 write!(out, "{}{}", indent, node.role).unwrap();
346 if let Some(ref n) = node.name {
347 write!(out, " \"{}\"", n).unwrap();
348 }
349 if let Some(ref v) = node.value {
350 write!(out, " value=\"{}\"", v).unwrap();
351 }
352 out.push('\n');
353 for child in &node.children {
354 write_tree_node(child, depth + 1, out);
355 }
356}
357
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
369pub struct StateSet {
370 pub enabled: bool,
371 pub visible: bool,
372 pub focused: bool,
373 pub checked: Option<Toggled>,
375 pub selected: bool,
376 pub expanded: Option<bool>,
378 pub editable: bool,
379 pub focusable: bool,
381 pub modal: bool,
383 pub required: bool,
385 pub busy: bool,
387}
388
389impl Default for StateSet {
390 fn default() -> Self {
391 Self {
392 enabled: true,
393 visible: true,
394 focused: false,
395 checked: None,
396 selected: false,
397 expanded: None,
398 editable: false,
399 focusable: false,
400 modal: false,
401 required: false,
402 busy: false,
403 }
404 }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
409pub enum Toggled {
410 Off,
411 On,
412 Mixed,
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
420pub struct Rect {
421 pub x: i32,
422 pub y: i32,
423 pub width: u32,
424 pub height: u32,
425}
426
427pub type RawPlatformData = HashMap<String, serde_json::Value>;
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct TreeNode {
441 pub role: String,
442 pub name: Option<String>,
443 pub value: Option<String>,
444 pub children: Vec<TreeNode>,
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
454 use crate::mock::{build_provider, MockProvider};
455 use crate::selector::Selector;
456
457 fn find_element(provider: &Arc<MockProvider>, selector: &str) -> Element {
461 let parsed = Selector::parse(selector).expect("selector must parse");
462 let provider_dyn: Arc<dyn Provider> = provider.clone();
463 let root = provider_dyn
464 .list_apps()
465 .expect("list_apps must succeed")
466 .into_iter()
467 .next()
468 .expect("mock provider must expose an application root");
469 let mut matches = provider_dyn
470 .find_elements(&root, &parsed, Some(1), None)
471 .expect("find_elements must succeed");
472 let data = matches.pop().expect("selector matched no elements");
473 Element::new(data, provider_dyn)
474 }
475
476 fn last_action(provider: &Arc<MockProvider>) -> (u64, String, Option<String>) {
477 provider
478 .actions()
479 .last()
480 .cloned()
481 .expect("expected at least one recorded action")
482 }
483
484 #[test]
485 fn nullary_actions_record_correct_name() {
486 let provider = build_provider();
487 let cases = [
488 (r#"button[name="Back"]"#, "press" as &str),
489 (r#"button[name="Back"]"#, "focus"),
490 (r#"button[name="Back"]"#, "blur"),
491 (r#"check_box[name="Agree"]"#, "toggle"),
492 (r#"list_item[name="Item 1"]"#, "select"),
493 (r#"list[name="Items"]"#, "expand"),
494 (r#"list[name="Items"]"#, "collapse"),
495 (r#"button[name="Back"]"#, "show_menu"),
496 (r#"slider[name="Volume"]"#, "increment"),
497 (r#"slider[name="Volume"]"#, "decrement"),
498 (r#"button[name="Back"]"#, "scroll_into_view"),
499 ];
500 for (selector, action) in cases {
501 provider.clear_actions();
502 let el = find_element(&provider, selector);
503 match action {
504 "press" => el.press().unwrap(),
505 "focus" => el.focus().unwrap(),
506 "blur" => el.blur().unwrap(),
507 "toggle" => el.toggle().unwrap(),
508 "select" => el.select().unwrap(),
509 "expand" => el.expand().unwrap(),
510 "collapse" => el.collapse().unwrap(),
511 "show_menu" => el.show_menu().unwrap(),
512 "increment" => el.increment().unwrap(),
513 "decrement" => el.decrement().unwrap(),
514 "scroll_into_view" => el.scroll_into_view().unwrap(),
515 _ => unreachable!(),
516 }
517 let (handle, name, data) = last_action(&provider);
518 assert_eq!(
519 name, action,
520 "wrong action recorded for selector {selector}"
521 );
522 assert_eq!(data, None, "nullary action should not carry data");
523 assert_eq!(handle, el.data.handle);
524 }
525 }
526
527 #[test]
528 fn set_value_records_text_payload() {
529 let provider = build_provider();
530 let el = find_element(&provider, r#"text_field[name="Search"]"#);
531 el.set_value("world").unwrap();
532 let (handle, name, data) = last_action(&provider);
533 assert_eq!(handle, el.data.handle);
534 assert_eq!(name, "set_value");
535 assert_eq!(data.as_deref(), Some("world"));
536 }
537
538 #[test]
539 fn set_numeric_value_records_payload() {
540 let provider = build_provider();
541 let el = find_element(&provider, r#"slider[name="Volume"]"#);
542 el.set_numeric_value(42.0).unwrap();
543 let (_, name, data) = last_action(&provider);
544 assert_eq!(name, "set_numeric_value");
545 assert_eq!(data.as_deref(), Some("42"));
546 }
547
548 #[test]
549 fn set_numeric_value_rejects_non_finite() {
550 let provider = build_provider();
551 let el = find_element(&provider, r#"slider[name="Volume"]"#);
552 for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
553 assert!(matches!(
554 el.set_numeric_value(bad),
555 Err(Error::InvalidActionData { .. })
556 ));
557 }
558 assert!(provider.actions().is_empty());
560 }
561
562 #[test]
563 fn type_text_records_payload() {
564 let provider = build_provider();
565 let el = find_element(&provider, r#"text_field[name="Search"]"#);
566 el.type_text("abc").unwrap();
567 let (_, name, data) = last_action(&provider);
568 assert_eq!(name, "type_text");
569 assert_eq!(data.as_deref(), Some("abc"));
570 }
571
572 #[test]
573 fn select_text_records_range() {
574 let provider = build_provider();
575 let el = find_element(&provider, r#"text_field[name="Search"]"#);
576 el.select_text(1, 4).unwrap();
577 let (_, name, data) = last_action(&provider);
578 assert_eq!(name, "set_text_selection");
579 assert_eq!(data.as_deref(), Some("1..4"));
580 }
581
582 #[test]
583 fn select_text_rejects_inverted_range() {
584 let provider = build_provider();
585 let el = find_element(&provider, r#"text_field[name="Search"]"#);
586 assert!(matches!(
587 el.select_text(5, 2),
588 Err(Error::InvalidActionData { .. })
589 ));
590 assert!(provider.actions().is_empty());
591 }
592
593 #[test]
594 fn perform_action_records_arbitrary_name() {
595 let provider = build_provider();
596 let el = find_element(&provider, r#"button[name="Back"]"#);
597 el.perform_action("raise").unwrap();
598 let (_, name, _) = last_action(&provider);
599 assert_eq!(name, "raise");
600 }
601
602 #[test]
603 fn locator_actions_desugar_to_element_actions() {
604 let provider = build_provider();
609 let provider_dyn: Arc<dyn Provider> = provider.clone();
610 let locator = crate::locator::Locator::new(provider_dyn, None, r#"button[name="Back"]"#);
611 locator.press().unwrap();
612 let (_, name, data) = last_action(&provider);
613 assert_eq!(name, "press");
614 assert_eq!(data, None);
615 }
616
617 #[test]
618 fn locator_validation_runs_before_auto_wait() {
619 let provider = build_provider();
624 let provider_dyn: Arc<dyn Provider> = provider.clone();
625 let locator =
626 crate::locator::Locator::new(provider_dyn, None, r#"button[name="never-matches"]"#);
627 let started = std::time::Instant::now();
628 let err = locator.set_numeric_value(f64::NAN).unwrap_err();
629 assert!(matches!(err, Error::InvalidActionData { .. }));
630 assert!(
631 started.elapsed() < std::time::Duration::from_secs(1),
632 "validation must short-circuit auto-wait",
633 );
634 }
635}