use std::sync::Arc;
use xa11y::*;
#[derive(Clone)]
struct MockNode {
data: ElementData,
children: Vec<usize>, parent: Option<usize>,
}
struct MockProvider {
nodes: Vec<MockNode>,
last_action: std::sync::Mutex<Option<(u64, Action)>>,
}
impl Provider for MockProvider {
fn get_children(&self, element: Option<&ElementData>) -> Result<Vec<ElementData>> {
match element {
None => {
if self.nodes.is_empty() {
return Ok(vec![]);
}
Ok(vec![self.nodes[0].data.clone()])
}
Some(el) => {
let idx = el.handle as usize;
if idx >= self.nodes.len() {
return Ok(vec![]);
}
Ok(self.nodes[idx]
.children
.iter()
.map(|&i| self.nodes[i].data.clone())
.collect())
}
}
}
fn get_parent(&self, element: &ElementData) -> Result<Option<ElementData>> {
let idx = element.handle as usize;
if idx >= self.nodes.len() {
return Ok(None);
}
Ok(self.nodes[idx].parent.map(|i| self.nodes[i].data.clone()))
}
fn perform_action(
&self,
element: &ElementData,
action: Action,
_data: Option<ActionData>,
) -> Result<()> {
*self.last_action.lock().unwrap() = Some((element.handle, action));
Ok(())
}
fn subscribe(&self, _element: &ElementData) -> Result<Subscription> {
Err(Error::Platform {
code: -1,
message: "MockProvider does not support subscribe".to_string(),
})
}
}
fn sample_provider() -> Arc<MockProvider> {
let elements = vec![
(
Role::Application,
Some("Test App"),
None,
None,
None,
vec![],
StateSet::default(),
None,
None,
None,
),
(
Role::Window,
Some("My App"),
None,
None,
Some(Rect {
x: 0,
y: 0,
width: 1920,
height: 1080,
}),
vec![],
StateSet::default(),
None,
None,
None,
),
(
Role::Toolbar,
Some("Main Toolbar"),
None,
None,
Some(Rect {
x: 0,
y: 0,
width: 1920,
height: 44,
}),
vec![],
StateSet::default(),
None,
None,
None,
),
(
Role::Button,
Some("Back"),
None,
Some("Navigate back"),
Some(Rect {
x: 10,
y: 5,
width: 60,
height: 34,
}),
vec![Action::Press, Action::Focus],
StateSet {
enabled: true,
visible: true,
..StateSet::default()
},
None,
None,
None,
),
(
Role::TextField,
Some("Address Bar"),
Some("https://example.com"),
None,
Some(Rect {
x: 80,
y: 5,
width: 600,
height: 34,
}),
vec![Action::Focus, Action::SetValue],
StateSet {
enabled: true,
visible: true,
editable: true,
..StateSet::default()
},
None,
None,
None,
),
(
Role::WebArea,
None,
None,
None,
Some(Rect {
x: 0,
y: 44,
width: 1920,
height: 1036,
}),
vec![],
StateSet::default(),
None,
None,
None,
),
(
Role::Heading,
Some("Welcome"),
None,
None,
None,
vec![],
StateSet::default(),
None,
None,
None,
),
(
Role::Button,
Some("Submit"),
None,
None,
None,
vec![Action::Press, Action::Focus],
StateSet {
enabled: true,
visible: true,
..StateSet::default()
},
None,
None,
None,
),
(
Role::Button,
Some("Cancel"),
None,
None,
None,
vec![Action::Press, Action::Focus],
StateSet {
enabled: false,
visible: true,
..StateSet::default()
},
None,
None,
None,
),
(
Role::CheckBox,
Some("I agree to terms"),
None,
None,
None,
vec![Action::Press, Action::Toggle],
StateSet {
enabled: true,
visible: true,
checked: Some(Toggled::Off),
..StateSet::default()
},
None,
None,
None,
),
];
let children_map: Vec<Vec<usize>> = vec![
vec![1], vec![2, 5], vec![3, 4], vec![], vec![], vec![6, 7, 8, 9], vec![], vec![], vec![], vec![], ];
let parent_map: Vec<Option<usize>> = vec![
None, Some(0), Some(1), Some(2), Some(2), Some(1), Some(5), Some(5), Some(5), Some(5), ];
let mut nodes = Vec::new();
for (i, (role, name, value, desc, bounds, actions, states, nv, minv, maxv)) in
elements.into_iter().enumerate()
{
nodes.push(MockNode {
data: ElementData {
role,
name: name.map(String::from),
value: value.map(String::from),
description: desc.map(String::from),
bounds,
actions,
states,
numeric_value: nv,
min_value: minv,
max_value: maxv,
stable_id: None,
pid: Some(1234),
raw: RawPlatformData::Synthetic,
handle: i as u64,
},
children: children_map[i].clone(),
parent: parent_map[i],
});
}
Arc::new(MockProvider {
nodes,
last_action: std::sync::Mutex::new(None),
})
}
fn sample_app() -> App {
let p = sample_provider();
App::by_name_with(p as Arc<dyn Provider>, "Test App").unwrap()
}
fn sample_root() -> Element {
let app = sample_app();
app.children().unwrap().into_iter().next().unwrap()
}
#[test]
fn element_root() {
let root = sample_root();
assert_eq!(root.role, Role::Window);
assert_eq!(root.name.as_deref(), Some("My App"));
}
#[test]
fn element_children() {
let root = sample_root();
let children = root.children().unwrap();
assert_eq!(children.len(), 2);
assert_eq!(children[0].role, Role::Toolbar);
assert_eq!(children[1].role, Role::WebArea);
}
#[test]
fn element_nested_children() {
let root = sample_root();
let toolbar = &root.children().unwrap()[0];
let children = toolbar.children().unwrap();
assert_eq!(children.len(), 2);
assert_eq!(children[0].role, Role::Button);
assert_eq!(children[1].role, Role::TextField);
}
#[test]
fn element_parent() {
let root = sample_root();
let toolbar = &root.children().unwrap()[0];
let button = &toolbar.children().unwrap()[0];
let parent = button.parent().unwrap().unwrap();
assert_eq!(parent.role, Role::Toolbar);
let app_parent = root.parent().unwrap().unwrap();
assert_eq!(app_parent.role, Role::Application);
}
#[test]
fn element_display() {
let root = sample_root();
let display = root.to_string();
assert!(display.contains("window"));
assert!(display.contains("My App"));
}
#[test]
fn app_locator() {
let app = sample_app();
let buttons = app.locator("button").elements().unwrap();
assert_eq!(buttons.len(), 3);
}
#[test]
fn role_snake_case_roundtrip() {
let roles = vec![
Role::Unknown,
Role::Window,
Role::Button,
Role::TextField,
Role::TextArea,
Role::StaticText,
Role::ComboBox,
Role::ListItem,
Role::MenuItem,
Role::MenuBar,
Role::TabGroup,
Role::TableRow,
Role::TableCell,
Role::ScrollBar,
Role::ScrollThumb,
Role::ProgressBar,
Role::TreeItem,
Role::WebArea,
Role::SplitGroup,
];
for role in roles {
let snake = role.to_snake_case();
let parsed = Role::from_snake_case(snake).unwrap();
assert_eq!(parsed, role, "roundtrip failed for {}", snake);
}
}
#[test]
fn role_display() {
assert_eq!(format!("{}", Role::Button), "button");
assert_eq!(format!("{}", Role::TextField), "text_field");
assert_eq!(format!("{}", Role::CheckBox), "check_box");
}
#[test]
fn stateset_default() {
let states = StateSet::default();
assert!(states.enabled);
assert!(states.visible);
assert!(!states.focused);
assert!(states.checked.is_none());
assert!(!states.selected);
assert!(states.expanded.is_none());
assert!(!states.editable);
assert!(!states.required);
assert!(!states.busy);
assert!(!states.focusable);
assert!(!states.modal);
}
#[test]
fn toggled_variants() {
assert_ne!(Toggled::Off, Toggled::On);
assert_ne!(Toggled::On, Toggled::Mixed);
assert_ne!(Toggled::Off, Toggled::Mixed);
}
#[test]
fn rect_negative_coords() {
let rect = Rect {
x: -1920,
y: -500,
width: 1920,
height: 1080,
};
assert_eq!(rect.x, -1920);
assert_eq!(rect.y, -500);
}
#[test]
fn action_display() {
assert_eq!(format!("{}", Action::Press), "Press");
assert_eq!(format!("{}", Action::SetValue), "SetValue");
assert_eq!(format!("{}", Action::ScrollIntoView), "ScrollIntoView");
}
#[test]
fn validate_text_selection_start_must_be_lte_end() {
let valid = ActionData::TextSelection { start: 0, end: 5 };
assert!(valid.validate(Action::SetTextSelection).is_ok());
let equal = ActionData::TextSelection { start: 3, end: 3 };
assert!(equal.validate(Action::SetTextSelection).is_ok());
let reversed = ActionData::TextSelection { start: 5, end: 2 };
assert!(matches!(
reversed.validate(Action::SetTextSelection),
Err(Error::InvalidActionData { .. })
));
}
#[test]
fn validate_numeric_value_must_be_finite() {
let valid = ActionData::NumericValue(42.0);
assert!(valid.validate(Action::SetValue).is_ok());
let nan = ActionData::NumericValue(f64::NAN);
assert!(matches!(
nan.validate(Action::SetValue),
Err(Error::InvalidActionData { .. })
));
let inf = ActionData::NumericValue(f64::INFINITY);
assert!(matches!(
inf.validate(Action::SetValue),
Err(Error::InvalidActionData { .. })
));
}
#[test]
fn validate_other_action_data_always_ok() {
let text = ActionData::Value("hello".to_string());
assert!(text.validate(Action::TypeText).is_ok());
let scroll = ActionData::ScrollAmount(0.0);
assert!(scroll.validate(Action::ScrollDown).is_ok());
}
#[test]
fn error_display() {
let err = Error::PermissionDenied {
instructions: "Enable in System Preferences".to_string(),
};
assert!(format!("{}", err).contains("Permission denied"));
let err = Error::SelectorNotMatched {
selector: "button[name=\"Submit\"]".to_string(),
};
assert!(format!("{}", err).contains("Submit"));
let err = Error::ElementStale {
selector: "button".to_string(),
};
assert!(format!("{}", err).contains("stale"));
let err = Error::ActionNotSupported {
action: Action::Toggle,
role: Role::StaticText,
};
assert!(format!("{}", err).contains("Toggle"));
let err = Error::InvalidSelector {
selector: "bad".to_string(),
message: "oops".to_string(),
};
assert!(format!("{}", err).contains("bad"));
}
#[test]
fn element_json_serialization() {
let element = ElementData {
role: Role::Button,
name: Some("Submit".to_string()),
value: None,
description: None,
bounds: Some(Rect {
x: 100,
y: 200,
width: 80,
height: 30,
}),
actions: vec![Action::Press],
states: StateSet {
enabled: true,
visible: true,
focused: true,
..StateSet::default()
},
pid: None,
stable_id: None,
numeric_value: None,
min_value: None,
max_value: None,
raw: RawPlatformData::Synthetic,
handle: 0,
};
let json = serde_json::to_string_pretty(&element).unwrap();
let deserialized: ElementData = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.role, Role::Button);
assert_eq!(deserialized.name.as_deref(), Some("Submit"));
assert!(deserialized.states.focused);
}
#[test]
fn raw_platform_data_serialization() {
let raw_mac = RawPlatformData::MacOS {
ax_role: "AXButton".to_string(),
ax_subrole: None,
ax_identifier: Some("submit-btn".to_string()),
};
let json = serde_json::to_string(&raw_mac).unwrap();
let deserialized: RawPlatformData = serde_json::from_str(&json).unwrap();
match deserialized {
RawPlatformData::MacOS {
ax_role,
ax_identifier,
..
} => {
assert_eq!(ax_role, "AXButton");
assert_eq!(ax_identifier.as_deref(), Some("submit-btn"));
}
_ => panic!("expected MacOS variant"),
}
let raw_linux = RawPlatformData::Linux {
atspi_role: "push button".to_string(),
bus_name: ":1.42".to_string(),
object_path: "/org/a11y/atspi/accessible/1234".to_string(),
};
let json = serde_json::to_string(&raw_linux).unwrap();
assert!(json.contains("push button"));
}
#[test]
fn platform_provider_creates_or_fails_gracefully() {
let _result = xa11y::create_provider();
}
#[test]
fn query_by_role() {
let app = sample_app();
let buttons = app
.locator("window")
.descendant("button")
.elements()
.unwrap();
assert_eq!(buttons.len(), 3);
}
#[test]
fn query_by_exact_name() {
let app = sample_app();
let results = app
.locator("window")
.descendant(r#"[name="Submit"]"#)
.elements()
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].role, Role::Button);
}
#[test]
fn query_role_and_name() {
let app = sample_app();
let results = app
.locator("window")
.descendant(r#"button[name="Submit"]"#)
.elements()
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].name.as_deref(), Some("Submit"));
}
#[test]
fn query_name_contains() {
let app = sample_app();
let results = app
.locator("window")
.descendant(r#"[name*="addr"]"#)
.elements()
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].name.as_deref(), Some("Address Bar"));
}
#[test]
fn query_name_starts_with() {
let app = sample_app();
let results = app
.locator("window")
.descendant(r#"[name^="addr"]"#)
.elements()
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].name.as_deref(), Some("Address Bar"));
}
#[test]
fn query_name_ends_with() {
let app = sample_app();
let results = app
.locator("window")
.descendant(r#"[name$="bar"]"#)
.elements()
.unwrap();
assert_eq!(results.len(), 2); }
#[test]
fn query_direct_child() {
let app = sample_app();
let results = app
.locator("window")
.descendant("toolbar")
.child("button")
.elements()
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].name.as_deref(), Some("Back"));
}
#[test]
fn query_descendant_buttons() {
let app = sample_app();
let results = app.locator("window button").elements().unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn query_nth() {
let app = sample_app();
let results = app.locator("window button:nth(2)").elements().unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].name.as_deref(), Some("Submit"));
}
#[test]
fn query_nth_out_of_range() {
let app = sample_app();
let results = app.locator("window button:nth(99)").elements().unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn query_complex() {
let app = sample_app();
let results = app
.locator(r#"window toolbar > text_field[name*="Address"]"#)
.elements()
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].value.as_deref(), Some("https://example.com"));
}
#[test]
fn query_no_match() {
let app = sample_app();
let results = app
.locator("window")
.descendant("slider")
.elements()
.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn query_invalid_selector() {
let app = sample_app();
let result = app.locator("foobar").elements();
assert!(result.is_err());
}
#[test]
fn query_check_box() {
let app = sample_app();
let results = app
.locator("window")
.descendant("check_box")
.elements()
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].states.checked, Some(Toggled::Off));
}
#[test]
fn query_web_area_children() {
let app = sample_app();
let results = app.locator("window web_area > button").elements().unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn locator_basic_query() {
let app = sample_app();
let loc = app.locator(r#"window button[name="Submit"]"#);
let element = loc.element().unwrap();
assert_eq!(element.role, Role::Button);
assert_eq!(element.name.as_deref(), Some("Submit"));
assert!(loc.exists().unwrap());
}
#[test]
fn locator_press_dispatches_action() {
let p = sample_provider();
let app = App::by_name_with(Arc::clone(&p) as Arc<dyn Provider>, "Test App").unwrap();
let loc = app.locator(r#"window button[name="Submit"]"#);
loc.press().unwrap();
let (handle, action) = p.last_action.lock().unwrap().unwrap();
assert_eq!(action, Action::Press);
assert_eq!(handle, 7); }
#[test]
fn locator_not_found() {
let app = sample_app();
let loc = app.locator(r#"button[name="NonExistent"]"#);
assert!(!loc.exists().unwrap());
assert!(loc.press().is_err());
}
#[test]
fn locator_nth() {
let app = sample_app();
let loc = app.locator("window button").nth(1);
assert_eq!(loc.element().unwrap().name.as_deref(), Some("Submit"));
}
#[test]
fn locator_count() {
let app = sample_app();
let loc = app.locator("window button");
assert_eq!(loc.count().unwrap(), 3);
}
#[test]
fn locator_child() {
let app = sample_app();
let loc = app.locator("window toolbar").child("button");
assert_eq!(loc.element().unwrap().name.as_deref(), Some("Back"));
}
#[test]
fn locator_states() {
let app = sample_app();
let loc = app.locator(r#"window button[name="Cancel"]"#);
let element = loc.element().unwrap();
assert!(!element.states.enabled);
assert!(element.states.visible);
}
#[test]
fn locator_selector_getter() {
let app = sample_app();
let loc = app.locator("button").child("text_field");
assert_eq!(loc.selector(), "button > text_field");
}
#[test]
fn action_data_variants() {
let text = ActionData::Value("hello".to_string());
let json = serde_json::to_string(&text).unwrap();
assert!(json.contains("hello"));
let numeric = ActionData::NumericValue(42.5);
let json = serde_json::to_string(&numeric).unwrap();
assert!(json.contains("42.5"));
let scroll = ActionData::ScrollAmount(100.0);
let json = serde_json::to_string(&scroll).unwrap();
assert!(json.contains("100"));
}
fn multi_app_provider() -> Arc<MultiAppMockProvider> {
let defs: Vec<(Role, Option<&str>, Option<u32>)> = vec![
(Role::Application, Some("App1"), Some(100)),
(Role::Window, Some("Win1"), Some(100)),
(Role::Button, Some("Btn1"), Some(100)),
(Role::Application, Some("App2"), Some(200)),
(Role::Window, Some("Win2"), Some(200)),
(Role::Button, Some("Btn2"), Some(200)),
(Role::Button, Some("Btn3"), Some(200)),
];
let children_map: Vec<Vec<usize>> = vec![
vec![1], vec![2], vec![], vec![4], vec![5, 6], vec![], vec![], ];
let parent_map: Vec<Option<usize>> = vec![
None, Some(0), Some(1), None, Some(3), Some(4), Some(4), ];
let mut nodes = Vec::new();
for (i, (role, name, pid)) in defs.into_iter().enumerate() {
nodes.push(MockNode {
data: ElementData {
role,
name: name.map(String::from),
value: None,
description: None,
bounds: None,
actions: vec![],
states: StateSet::default(),
numeric_value: None,
min_value: None,
max_value: None,
stable_id: None,
pid,
raw: RawPlatformData::Synthetic,
handle: i as u64,
},
children: children_map[i].clone(),
parent: parent_map[i],
});
}
Arc::new(MultiAppMockProvider { nodes })
}
struct MultiAppMockProvider {
nodes: Vec<MockNode>,
}
impl Provider for MultiAppMockProvider {
fn get_children(&self, element: Option<&ElementData>) -> Result<Vec<ElementData>> {
match element {
None => {
Ok(self
.nodes
.iter()
.filter(|n| n.data.role == Role::Application)
.map(|n| n.data.clone())
.collect())
}
Some(el) => {
let idx = el.handle as usize;
if idx >= self.nodes.len() {
return Ok(vec![]);
}
Ok(self.nodes[idx]
.children
.iter()
.map(|&i| self.nodes[i].data.clone())
.collect())
}
}
}
fn get_parent(&self, element: &ElementData) -> Result<Option<ElementData>> {
let idx = element.handle as usize;
if idx >= self.nodes.len() {
return Ok(None);
}
Ok(self.nodes[idx].parent.map(|i| self.nodes[i].data.clone()))
}
fn perform_action(&self, _: &ElementData, _: Action, _: Option<ActionData>) -> Result<()> {
Ok(())
}
fn subscribe(&self, _: &ElementData) -> Result<Subscription> {
Err(Error::Platform {
code: -1,
message: "not supported".to_string(),
})
}
}
#[test]
fn find_application_by_name_from_root() {
let p = multi_app_provider();
let app = App::by_name_with(p as Arc<dyn Provider>, "App2").unwrap();
assert_eq!(app.data.role, Role::Application);
assert_eq!(app.name, "App2");
assert_eq!(app.pid, Some(200));
}
#[test]
fn find_all_applications() {
let p = multi_app_provider();
let apps = App::list_with(p as Arc<dyn Provider>).unwrap();
assert_eq!(apps.len(), 2);
assert_eq!(apps[0].name, "App1");
assert_eq!(apps[1].name, "App2");
}
#[test]
fn find_application_only_checks_top_level() {
let p = multi_app_provider();
let apps = App::list_with(p as Arc<dyn Provider>).unwrap();
assert_eq!(apps.len(), 2);
}
#[test]
fn find_button_across_apps() {
let p = multi_app_provider();
let apps = App::list_with(p as Arc<dyn Provider>).unwrap();
let mut total_buttons = 0;
for app in &apps {
total_buttons += app.locator("button").count().unwrap();
}
assert_eq!(total_buttons, 3); }
#[test]
fn find_with_limit_stops_early() {
let p = multi_app_provider();
let app1 = App::by_name_with(p as Arc<dyn Provider>, "App1").unwrap();
let first = app1.locator("button").first().element().unwrap();
assert_eq!(first.name.as_deref(), Some("Btn1"));
}
#[test]
fn find_multi_segment_across_apps() {
let p = multi_app_provider();
let app2 = App::by_name_with(p as Arc<dyn Provider>, "App2").unwrap();
let results = app2.locator("window > button").elements().unwrap();
assert_eq!(results.len(), 2); }
#[test]
fn app_locator_scopes_search() {
let p = multi_app_provider();
let app2 = App::by_name_with(p as Arc<dyn Provider>, "App2").unwrap();
let buttons = app2.locator("button").elements().unwrap();
assert_eq!(buttons.len(), 2); assert_eq!(buttons[0].name.as_deref(), Some("Btn2"));
}
#[test]
fn app_locator_does_not_find_sibling_app_elements() {
let p = multi_app_provider();
let app1 = App::by_name_with(p as Arc<dyn Provider>, "App1").unwrap();
let buttons = app1.locator("button").elements().unwrap();
assert_eq!(buttons.len(), 1);
assert_eq!(buttons[0].name.as_deref(), Some("Btn1"));
}
#[test]
fn locator_count_matches_elements_len() {
let p = multi_app_provider();
let app1 = App::by_name_with(p as Arc<dyn Provider>, "App1").unwrap();
let loc = app1.locator("button");
assert_eq!(loc.count().unwrap(), loc.elements().unwrap().len());
}
#[test]
fn app_by_name_not_found() {
let p = multi_app_provider();
let result = App::by_name_with(p as Arc<dyn Provider>, "NoSuchApp");
assert!(result.is_err());
}
#[test]
fn locator_nth_out_of_range() {
let p = multi_app_provider();
let apps = App::list_with(p as Arc<dyn Provider>).unwrap();
let loc = apps[0].locator("button").nth(99);
assert!(loc.element().is_err());
}
#[test]
fn element_children_of_leaf_is_empty() {
let p = multi_app_provider();
let app1 = App::by_name_with(p as Arc<dyn Provider>, "App1").unwrap();
let btn = app1.locator("button").first().element().unwrap();
assert!(btn.children().unwrap().is_empty());
}
#[test]
fn element_parent_of_top_level_is_none() {
let p = multi_app_provider();
let app = App::list_with(Arc::clone(&p) as Arc<dyn Provider>)
.unwrap()
.into_iter()
.next()
.unwrap();
let app_element = Element::new(app.data.clone(), Arc::clone(&p) as Arc<dyn Provider>);
assert!(app_element.parent().unwrap().is_none());
}
#[test]
fn element_parent_navigates_up() {
let p = multi_app_provider();
let app2 = App::by_name_with(p as Arc<dyn Provider>, "App2").unwrap();
let btn = app2.locator(r#"button[name="Btn2"]"#).element().unwrap();
let parent = btn.parent().unwrap().unwrap();
assert_eq!(parent.role, Role::Window);
assert_eq!(parent.name.as_deref(), Some("Win2"));
}
#[test]
fn handle_preserved_through_find() {
let p = multi_app_provider();
let app1 = App::by_name_with(Arc::clone(&p) as Arc<dyn Provider>, "App1").unwrap();
assert_eq!(app1.data.handle, 0); let app2 = App::by_name_with(Arc::clone(&p) as Arc<dyn Provider>, "App2").unwrap();
let btn = app2.locator(r#"button[name="Btn2"]"#).element().unwrap();
assert_eq!(btn.handle, 5); }