mod integ;
#[cfg(test)]
mod tests {
use super::integ as h;
use xa11y::*;
#[test]
#[ignore]
fn check_permissions_granted() {
let _app = h::app_root();
}
#[test]
#[ignore]
fn apps_returns_nonempty() {
let apps = App::list().unwrap();
assert!(!apps.is_empty(), "should find at least one application");
let has_test_app = apps.iter().any(|a| a.name.contains("xa11y"));
assert!(
has_test_app,
"apps should include the test app. Apps: {:?}",
apps.iter().map(|a| &a.name).collect::<Vec<_>>()
);
}
#[test]
#[ignore]
fn tree_has_root_node() {
let app = h::app_root();
assert!(
app.data.role == Role::Application || app.data.role == Role::Window,
"Root role: {:?}",
app.data.role
);
}
#[test]
#[ignore]
fn tree_has_window() {
let app = h::app_root();
if app.data.role == Role::Window {
return; }
let windows = app.locator("window").elements().unwrap();
assert!(!windows.is_empty(), "No windows found. App: {}", app);
}
#[test]
#[ignore]
fn tree_has_buttons() {
let app = h::app_root();
let buttons = app.locator("button").elements().unwrap();
assert!(
buttons.len() >= 2,
"Expected >=2 buttons, found {}. App: {}",
buttons.len(),
app
);
}
#[test]
#[ignore]
fn tree_has_submit_button() {
let app = h::app_root();
let submit = h::named(&app, "Submit");
assert_eq!(submit.role, Role::Button);
}
#[test]
#[ignore]
fn tree_has_cancel_button_disabled() {
let app = h::app_root();
let cancel = h::named(&app, "Cancel");
assert_eq!(cancel.role, Role::Button);
let _ = cancel.states.enabled;
}
#[test]
#[ignore]
fn tree_has_checkbox_unchecked() {
let app = h::app_root();
let cb = h::named(&app, "I agree to terms");
assert_eq!(cb.role, Role::CheckBox);
assert!(
cb.states.checked.is_some(),
"Checkbox should have checked state"
);
}
#[test]
#[ignore]
fn tree_has_text_entry_with_value() {
let app = h::app_root();
let text_elements = app
.locator(r#"[role="text_field"]"#)
.elements()
.unwrap_or_default();
let textarea_elements = app
.locator(r#"[role="text_area"]"#)
.elements()
.unwrap_or_default();
let has_text = text_elements
.iter()
.chain(textarea_elements.iter())
.any(|n| n.value.is_some() || n.name.as_deref() == Some("Name"));
assert!(has_text, "Text entry not found. App: {}", app);
}
#[test]
#[ignore]
fn tree_has_welcome_label() {
let app = h::app_root();
let welcome = app.locator(r#"[name*="Welcome"]"#).elements().unwrap();
if welcome.is_empty() {
let labels = app.locator("static_text").elements().unwrap();
assert!(
!labels.is_empty(),
"No StaticText/label nodes found. App: {}",
app
);
} else {
assert!(
welcome[0].role == Role::StaticText || welcome[0].role == Role::Group,
"Welcome node role: {:?}",
welcome[0].role
);
}
}
#[test]
#[ignore]
fn tree_has_slider_at_50() {
let app = h::app_root();
let sliders = app.locator("slider").elements().unwrap();
assert!(!sliders.is_empty(), "No sliders found. App: {}", app);
assert!(sliders[0].value.is_some(), "Slider should have a value");
let val: f64 = sliders[0].value.as_deref().unwrap().parse().unwrap_or(0.0);
assert!(
(0.0..=100.0).contains(&val),
"Slider value should be in [0,100], got {}",
val
);
}
#[test]
#[ignore]
fn tree_has_progress_bar() {
let app = h::app_root();
let progress = app.locator("progress_bar").elements().unwrap();
assert!(!progress.is_empty(), "No progress bars found. App: {}", app);
}
#[test]
#[ignore]
fn tree_has_radio_buttons() {
let app = h::app_root();
let radios = app.locator("radio_button").elements().unwrap();
assert!(
radios.len() >= 2,
"Expected >=2 radio buttons, found {}. App: {}",
radios.len(),
app
);
}
#[test]
#[ignore]
fn tree_has_combo_box() {
let app = h::app_root();
let combos = app.locator("combo_box").elements().unwrap();
assert!(!combos.is_empty(), "ComboBox not found. App: {}", app);
}
#[test]
#[ignore]
fn tree_has_list_with_items() {
let app = h::app_root();
let lists = app.locator("list").elements().unwrap();
let items = app.locator("list_item").elements().unwrap();
assert!(
!lists.is_empty() || !items.is_empty(),
"Neither List nor ListItem found. App: {}",
app
);
}
#[test]
#[ignore]
fn tree_has_table_with_cells() {
let app = h::app_root();
let tables = app.locator("table").elements().unwrap();
let cells = app.locator("table_cell").elements().unwrap();
assert!(
!tables.is_empty() || !cells.is_empty(),
"Neither Table nor TableCell found. App: {}",
app
);
}
#[test]
#[ignore]
fn role_menu_bar() {
let app = h::app_root();
let nodes = app.locator("menu_bar").elements().unwrap();
assert!(!nodes.is_empty(), "MenuBar not found. App: {}", app);
}
#[test]
#[ignore]
fn role_menu_item() {
let app = h::app_root();
let nodes = app.locator("menu_item").elements().unwrap();
assert!(!nodes.is_empty(), "MenuItem not found. App: {}", app);
let has_file = nodes.iter().any(|n| n.name.as_deref() == Some("File"));
assert!(has_file, "File menu item not found");
}
#[test]
#[ignore]
fn role_toolbar() {
let app = h::app_root();
let nodes = app.locator("toolbar").elements().unwrap();
assert!(!nodes.is_empty(), "Toolbar not found. App: {}", app);
}
#[test]
#[ignore]
fn role_tab_and_tab_group() {
let app = h::app_root();
let tab_groups = app.locator("tab_group").elements().unwrap();
let tabs = app.locator("tab").elements().unwrap();
assert!(
!tab_groups.is_empty() || !tabs.is_empty(),
"Neither TabGroup nor Tab found. App: {}",
app
);
}
#[test]
#[ignore]
fn role_separator() {
let app = h::app_root();
let seps = app.locator("separator").elements().unwrap();
assert!(!seps.is_empty(), "Separator not found. App: {}", app);
}
#[test]
#[ignore]
fn role_image() {
let app = h::app_root();
let images = app.locator("image").elements().unwrap();
assert!(!images.is_empty(), "Image not found. App: {}", app);
}
#[test]
#[ignore]
fn role_link() {
let app = h::app_root();
let links = app.locator("link").elements().unwrap();
assert!(!links.is_empty(), "Link not found. App: {}", app);
}
#[test]
#[ignore]
fn role_tree_item() {
let app = h::app_root();
let items = app.locator("tree_item").elements().unwrap();
assert!(!items.is_empty(), "TreeItem not found. App: {}", app);
}
#[test]
#[ignore]
fn role_dialog() {
let app = h::app_root();
let dialogs = app.locator("dialog").elements().unwrap();
assert!(!dialogs.is_empty(), "Dialog not found. App: {}", app);
}
#[test]
#[ignore]
fn role_alert() {
let app = h::app_root();
let alerts = app.locator("alert").elements().unwrap();
assert!(!alerts.is_empty(), "Alert not found. App: {}", app);
}
#[test]
#[ignore]
fn role_heading() {
let app = h::app_root();
let headings = app.locator("heading").elements().unwrap();
assert!(!headings.is_empty(), "Heading not found. App: {}", app);
}
#[test]
#[ignore]
fn role_scroll_bar() {
let app = h::app_root();
let scrollbars = app.locator("scroll_bar").elements().unwrap();
assert!(!scrollbars.is_empty(), "ScrollBar not found. App: {}", app);
}
#[test]
#[ignore]
fn role_split_group() {
let app = h::app_root();
let node = app.locator(r#"[name*="SplitGroup"]"#).elements().unwrap();
assert!(!node.is_empty(), "SplitGroup node not found. App: {}", app);
}
#[test]
#[ignore]
fn role_static_text() {
let app = h::app_root();
let labels = app.locator("static_text").elements().unwrap();
assert!(!labels.is_empty(), "StaticText not found. App: {}", app);
}
#[test]
#[ignore]
fn tree_children_of_root() {
let app = h::app_root();
let children = app.children().unwrap();
assert!(!children.is_empty(), "Root should have children");
if !children.is_empty() {
let grandchildren = children[0].children().unwrap();
for gc in &grandchildren {
let parent = gc.parent().unwrap();
assert!(parent.is_some(), "Grandchild should have parent");
}
}
}
#[test]
#[ignore]
fn tree_children_of_leaf() {
let app = h::app_root();
let buttons = app.locator("button").elements().unwrap();
for btn in &buttons {
let children = btn.children().unwrap();
if children.is_empty() {
assert!(btn.children().unwrap().is_empty());
return;
}
}
}
#[test]
#[ignore]
fn tree_is_not_empty() {
let app = h::app_root();
let children = app.children().unwrap();
assert!(!children.is_empty(), "Root should have at least one child");
}
#[test]
#[ignore]
fn tree_display_readable() {
let app = h::app_root();
let display = app.to_string();
assert!(!display.is_empty());
assert!(
display.contains(&app.name),
"Display should include app name: {}",
display
);
}
#[test]
#[ignore]
fn tree_locator_finds_elements() {
let app = h::app_root();
let buttons = app.locator("button").elements().unwrap();
assert!(buttons.len() >= 2, "Expected >=2 buttons via locator");
let count = app.locator("button").count().unwrap();
assert_eq!(count, buttons.len());
}
#[test]
#[ignore]
fn element_description_on_image() {
let app = h::app_root();
let images = app.locator("image").elements().unwrap();
if !images.is_empty() {
let img = images.iter().find(|n| {
n.name.as_deref() == Some("Info Icon")
|| n.description.as_deref() == Some("An informational icon")
});
if let Some(img) = img {
assert!(img.description.is_some(), "Image should have description");
assert_eq!(img.description.as_deref(), Some("An informational icon"));
}
}
}
#[test]
#[ignore]
fn element_bounds_present() {
let app = h::app_root();
let submit = h::named(&app, "Submit");
assert!(submit.bounds.is_some(), "Submit should have bounds");
let b = submit.bounds.unwrap();
assert!(b.width > 0, "width > 0");
assert!(b.height > 0, "height > 0");
}
#[test]
#[ignore]
fn element_bounds_none_for_non_component_elements() {
let app = h::app_root();
#[cfg(not(target_os = "windows"))]
assert!(
app.data.bounds.is_none(),
"Application root should not have bounds (no Component interface)"
);
let submit = h::named(&app, "Submit");
assert!(submit.bounds.is_some(), "Submit button should have bounds");
}
#[test]
#[ignore]
fn element_actions_list_on_button() {
let app = h::app_root();
let submit = h::named(&app, "Submit");
assert!(!submit.actions.is_empty());
assert!(
submit.actions.iter().any(|a| a == "press"),
"Submit should support press, got: {:?}",
submit.actions
);
}
#[test]
#[ignore]
fn element_children_ids_valid() {
let app = h::app_root();
let children = app.children().unwrap();
assert!(!children.is_empty());
for child in &children {
let _ = child.role;
}
}
#[test]
#[ignore]
fn element_parent_field() {
let app = h::app_root();
let children = app.children().unwrap();
if !children.is_empty() {
let grandchildren = children[0].children().unwrap();
if !grandchildren.is_empty() {
let parent = grandchildren[0].parent().unwrap();
assert!(parent.is_some(), "Grandchild should have parent");
}
}
}
#[test]
#[ignore]
fn element_handle_nonzero() {
let app = h::app_root();
assert!(app.data.handle != 0, "Root handle should be nonzero");
}
#[test]
#[ignore]
fn state_enabled_default() {
let app = h::app_root();
let submit = h::named(&app, "Submit");
assert!(submit.states.enabled, "Submit should be enabled");
}
#[test]
#[ignore]
fn state_disabled_on_cancel() {
let app = h::app_root();
let cancel = h::named(&app, "Cancel");
#[cfg(not(target_os = "linux"))]
assert!(!cancel.states.enabled, "Cancel should be disabled");
#[cfg(target_os = "linux")]
{
assert_eq!(cancel.role, Role::Button);
let _ = cancel.states.enabled; }
}
#[test]
#[ignore]
fn state_visible_on_shown_widget() {
let app = h::app_root();
let submit = h::named(&app, "Submit");
assert!(submit.states.visible, "Submit should be visible");
}
#[test]
#[ignore]
fn state_focused_after_focus_action() {
let app = h::app_root();
let submit = h::named(&app, "Submit");
let result = h::try_act(&submit, "focus");
if result.is_ok() {
std::thread::sleep(std::time::Duration::from_millis(100));
let app2 = h::app_root();
let submit2 = h::named(&app2, "Submit");
if !submit2.states.focused {
println!("Focus action succeeded but focused state not reflected (AT-SPI adapter limitation)");
}
} else {
println!("Focus action not supported: {:?}", result.err());
}
}
#[test]
#[ignore]
fn state_checked_off_on_checkbox() {
let app = h::app_root();
let cb = h::named(&app, "I agree to terms");
assert_eq!(cb.states.checked, Some(Toggled::Off));
}
#[test]
#[ignore]
fn state_checked_on_radio() {
let app = h::app_root();
let radios = app.locator("radio_button").elements().unwrap();
let opt_a = radios
.iter()
.find(|n| n.name.as_deref() == Some("Option A"));
assert!(opt_a.is_some());
assert_eq!(opt_a.unwrap().states.checked, Some(Toggled::On));
}
#[test]
#[ignore]
fn state_expanded_collapsed_on_expander() {
let app = h::app_root();
let expander_by_name = app.locator(r#"[name*="Expander"]"#).elements().unwrap();
if expander_by_name.is_empty() {
println!(
"No expandable elements found by name. \
Expand/collapse actions tested separately."
);
}
}
#[test]
#[ignore]
fn state_editable_on_text_field() {
let app = h::app_root();
let text_fields = app.locator(r#"[name="Name"]"#).elements().unwrap();
if text_fields.is_empty() {
let fields = app.locator("text_field").elements().unwrap();
let areas = app.locator("text_area").elements().unwrap();
let all_text: Vec<&Element> = fields.iter().chain(areas.iter()).collect();
assert!(!all_text.is_empty(), "Text entry not found. App: {}", app);
assert!(all_text[0].states.editable, "Text entry should be editable");
} else {
assert!(
text_fields[0].states.editable,
"Text entry should be editable"
);
}
}
#[test]
#[ignore]
fn state_selected_on_list_item() {
let app = h::app_root();
let apple = h::named(&app, "Apple");
let app2 = h::act(&apple, "press");
let apple2 = h::named(&app2, "Apple");
println!(
"Apple selected state after Click: {:?}",
apple2.states.selected
);
}
#[test]
#[ignore]
fn sel_by_role() {
let app = h::app_root();
let buttons = app.locator("button").elements().unwrap();
assert!(buttons.len() >= 2);
for b in &buttons {
assert_eq!(b.role, Role::Button);
}
}
#[test]
#[ignore]
fn sel_by_exact_name() {
let app = h::app_root();
let submit = h::one(&app, r#"button[name="Submit"]"#);
assert_eq!(submit.role, Role::Button);
}
#[test]
#[ignore]
fn sel_by_role_and_name() {
let app = h::app_root();
let results = app.locator(r#"button[name="Cancel"]"#).elements().unwrap();
assert_eq!(results.len(), 1);
}
#[test]
#[ignore]
fn sel_name_contains() {
let app = h::app_root();
let results = app.locator(r#"[name*="agree"]"#).elements().unwrap();
assert!(
!results.is_empty(),
"Should find element with 'agree' in name"
);
}
#[test]
#[ignore]
fn sel_name_starts_with() {
let app = h::app_root();
let results = app.locator(r#"[name^="Welc"]"#).elements().unwrap();
if results.is_empty() {
let results = app.locator(r#"[name^="Sub"]"#).elements().unwrap();
assert!(!results.is_empty());
assert!(results[0]
.name
.as_deref()
.unwrap()
.to_lowercase()
.starts_with("sub"));
} else {
assert!(results[0]
.name
.as_deref()
.unwrap()
.to_lowercase()
.starts_with("welc"));
}
}
#[test]
#[ignore]
fn sel_name_ends_with() {
let app = h::app_root();
let results = app.locator(r#"[name$="xa11y"]"#).elements().unwrap();
if results.is_empty() {
let results = app.locator(r#"[name$="App"]"#).elements().unwrap();
if results.is_empty() {
let results = app.locator(r#"[name$="Submit"]"#).elements().unwrap();
assert!(
!results.is_empty(),
"Should find at least one element with name ending in 'Submit'"
);
}
}
}
#[test]
#[ignore]
fn sel_value_attribute() {
let app = h::app_root();
let results = app.locator(r#"[value*="Red"]"#).elements().unwrap();
if results.is_empty() {
let results = app.locator(r#"[value*="0.75"]"#).elements().unwrap();
assert!(
!results.is_empty(),
"Should find element with value containing '0.75' (ProgressBar)"
);
}
}
#[test]
#[ignore]
fn sel_descendant_combinator() {
let app = h::app_root();
let results = app.locator("group button").elements().unwrap();
if results.is_empty() {
let results = app.locator("window button").elements().unwrap();
assert!(!results.is_empty());
for r in &results {
assert_eq!(r.role, Role::Button);
}
} else {
for r in &results {
assert_eq!(r.role, Role::Button);
}
}
}
#[test]
#[ignore]
fn sel_child_combinator() {
let app = h::app_root();
let results = app.locator("application > window").elements().unwrap();
for r in &results {
assert_eq!(r.role, Role::Window);
}
}
#[test]
#[ignore]
fn sel_nth_pseudo() {
let app = h::app_root();
let first = app.locator("button:nth(1)").elements().unwrap();
assert_eq!(first.len(), 1);
}
#[test]
#[ignore]
fn sel_role_attribute() {
let app = h::app_root();
let results = app.locator(r#"[role="button"]"#).elements().unwrap();
assert!(!results.is_empty());
for r in &results {
assert_eq!(r.role, Role::Button);
}
}
#[test]
#[ignore]
fn sel_complex_chain() {
let app = h::app_root();
let results = app
.locator(r#"menu_bar menu_item[name="File"]"#)
.elements()
.unwrap();
assert!(!results.is_empty(), "Should find File menu item via chain");
assert_eq!(results[0].role, Role::MenuItem);
assert_eq!(results[0].name.as_deref(), Some("File"));
}
#[test]
#[ignore]
fn raw_data_always_present() {
let _app = h::app_root();
#[cfg(target_os = "linux")]
{
let atspi_role = _app
.data
.raw
.get("atspi_role")
.and_then(|v| v.as_str())
.expect("Expected atspi_role in raw data");
assert!(!atspi_role.is_empty());
}
#[cfg(target_os = "macos")]
{
let ax_role = _app
.data
.raw
.get("ax_role")
.and_then(|v| v.as_str())
.expect("Expected ax_role in raw data");
assert!(!ax_role.is_empty());
}
#[cfg(target_os = "windows")]
{
let control_type_id = _app
.data
.raw
.get("control_type_id")
.and_then(|v| v.as_i64())
.expect("Expected control_type_id in raw data");
assert!(control_type_id > 0);
}
}
#[test]
#[ignore]
fn action_press_button() {
let app = h::app_root();
let submit = h::named(&app, "Submit");
let result = h::try_act(&submit, "press");
match result {
Ok(()) => println!("Submit pressed"),
Err(e) => println!("Submit press result: {}", e),
}
}
#[test]
#[ignore]
fn action_toggle_checkbox() {
let app = h::app_root();
let cbs = app.locator("check_box").elements().unwrap();
assert!(!cbs.is_empty(), "No checkbox");
let initial = cbs[0].states.checked;
let app2 = h::act(&cbs[0], "press");
let cb2 = app2.locator("check_box").elements().unwrap();
if !cb2.is_empty() {
assert_ne!(
cb2[0].states.checked, initial,
"Checkbox should toggle from {:?}",
initial
);
}
}
#[test]
#[ignore]
fn action_toggle_enables_cancel() {
let app = h::app_root();
let was_enabled = h::named(&app, "Cancel").states.enabled;
let cbs = app.locator("check_box").elements().unwrap();
assert!(!cbs.is_empty(), "No checkbox");
let app2 = h::act(&cbs[0], "press");
let cancel2 = h::named(&app2, "Cancel");
if !was_enabled {
assert_ne!(cancel2.states.enabled, was_enabled);
} else {
assert_eq!(cancel2.role, Role::Button);
}
}
#[test]
#[ignore]
fn action_focus_text_entry() {
let app = h::app_root();
let text = find_text_entry(&app);
let result = h::try_act(&text, "focus");
assert!(result.is_ok(), "Focus should succeed: {:?}", result.err());
}
#[test]
#[ignore]
fn action_set_value_text() {
let app = h::app_root();
let text = find_text_entry(&app);
match text.provider().set_value(&text, "Jane Smith") {
Ok(()) => {
std::thread::sleep(std::time::Duration::from_millis(300));
let app2 = h::app_root();
let updated = app2
.locator(r#"[value="Jane Smith"]"#)
.elements()
.unwrap_or_default();
if updated.is_empty() {
println!("SetValue succeeded but value not reflected in tree (AT-SPI adapter limitation)");
}
}
Err(Error::TextValueNotSupported) => println!("TextValueNotSupported — OK"),
Err(e) => panic!("Unexpected error: {}", e),
}
}
#[test]
#[ignore]
fn action_set_value_numeric() {
let app = h::app_root();
let sliders = app.locator("slider").elements().unwrap();
assert!(!sliders.is_empty());
let result = sliders[0].provider().set_numeric_value(&sliders[0], 75.0);
assert!(result.is_ok(), "SetValue numeric: {:?}", result.err());
std::thread::sleep(std::time::Duration::from_millis(300));
let app2 = h::app_root();
let s2 = app2.locator("slider").elements().unwrap();
if !s2.is_empty() {
if let Some(v) = &s2[0].value {
let val: f64 = v.parse().unwrap_or(0.0);
assert!(
(val - 75.0).abs() < 2.0,
"Slider should be ~75, got {}",
val
);
}
}
}
#[test]
#[ignore]
fn action_increment_spinner() {
let app = h::app_root();
let sliders = app.locator("slider").elements().unwrap();
let spin = sliders.first();
if let Some(spin) = spin {
let initial: f64 = spin.value.as_deref().unwrap_or("0").parse().unwrap_or(0.0);
let result = h::try_act(spin, "increment");
if result.is_ok() {
std::thread::sleep(std::time::Duration::from_millis(300));
let app2 = h::app_root();
if let Some(s2) = app2.locator("slider").elements().unwrap().first() {
if let Some(v) = &s2.value {
let new_val: f64 = v.parse().unwrap_or(initial);
assert!(
new_val > initial,
"Value should increase: {} -> {}",
initial,
new_val
);
}
}
}
}
}
#[test]
#[ignore]
fn action_decrement_spinner() {
let app = h::app_root();
let sliders = app.locator("slider").elements().unwrap();
let spin = sliders.first();
if let Some(spin) = spin {
let before: f64 = spin.value.as_deref().unwrap_or("0").parse().unwrap_or(0.0);
let result = h::try_act(spin, "decrement");
if result.is_ok() {
std::thread::sleep(std::time::Duration::from_millis(300));
let app2 = h::app_root();
if let Some(s2) = app2.locator("slider").elements().unwrap().first() {
if let Some(v) = &s2.value {
let after: f64 = v.parse().unwrap_or(before);
assert!(
after < before,
"Value should decrease: {} -> {}",
before,
after
);
}
}
}
}
}
#[test]
#[ignore]
fn action_expand_collapse() {
let app = h::app_root();
let expander = app
.locator(r#"[name*="Expander"]"#)
.elements()
.unwrap()
.into_iter()
.next();
if let Some(node) = expander {
if h::try_act(&node, "expand").is_ok() {
std::thread::sleep(std::time::Duration::from_millis(300));
let app2 = h::app_root();
let n2 = app2
.locator(r#"[name*="Expander"]"#)
.elements()
.unwrap()
.into_iter()
.next();
if let Some(n) = n2 {
if n.states.expanded == Some(true) {
if h::try_act(&n, "collapse").is_ok() {
std::thread::sleep(std::time::Duration::from_millis(300));
let app3 = h::app_root();
let n3 = app3
.locator(r#"[name*="Expander"]"#)
.elements()
.unwrap()
.into_iter()
.next();
if let Some(n) = n3 {
assert_eq!(n.states.expanded, Some(false));
}
}
}
}
}
}
}
#[test]
#[ignore]
fn action_select_list_item() {
let app = h::app_root();
let apple = app.locator(r#"[name*="Apple"]"#).elements().unwrap();
if !apple.is_empty() {
let _ = h::try_act(&apple[0], "press");
}
}
#[test]
#[ignore]
fn nesting_deep_tree_traversal() {
let app = h::app_root();
let cells = app.locator(r#"[name*="Alice"]"#).elements().unwrap();
assert!(!cells.is_empty(), "Alice cell not found. App: {}", app);
let parent = cells[0].parent().unwrap();
assert!(parent.is_some());
}
#[test]
#[ignore]
fn nesting_subtree_of_table() {
let app = h::app_root();
let tables = app.locator("table").elements().unwrap();
if !tables.is_empty() {
let cells = app.locator("table table_cell").elements().unwrap();
if cells.len() >= 2 {
return;
}
let rows = app.locator("table table_row").elements().unwrap();
assert!(
rows.len() >= 2,
"Table should have at least 2 rows/cells, found {}. App: {}",
rows.len(),
app
);
}
}
#[test]
#[ignore]
fn thrash_toggle_checkbox_5_times() {
let app = h::app_root();
let cbs = app.locator("check_box").elements().unwrap();
assert!(!cbs.is_empty());
let mut current_app = app;
for _ in 0..5 {
let cbs = current_app.locator("check_box").elements().unwrap();
assert!(!cbs.is_empty());
current_app = h::act(&cbs[0], "press");
}
let final_cb = current_app.locator("check_box").elements().unwrap();
if !final_cb.is_empty() {
assert_eq!(
final_cb[0].states.checked,
Some(Toggled::On),
"After 5 toggles from Off, should be On"
);
}
}
#[test]
#[ignore]
fn thrash_slider_increment_10_times() {
let app = h::app_root();
let sliders = app.locator("slider").elements().unwrap();
let slider = sliders.first().expect("No slider");
let start_val: f64 = slider
.value
.as_deref()
.unwrap_or("0")
.parse()
.unwrap_or(0.0);
let mut current_app = app;
for _ in 0..10 {
let sliders = current_app.locator("slider").elements().unwrap();
let slider = sliders.first().expect("No slider");
current_app = h::act(slider, "increment");
}
let s = current_app.locator("slider").elements().unwrap();
if !s.is_empty() {
if let Some(v) = &s[0].value {
let val: f64 = v.parse().unwrap_or(0.0);
let expected = (start_val + 10.0).min(100.0);
assert!(
(val - expected).abs() < 2.0,
"After 10 increments from {}, should be ~{}, got {}",
start_val,
expected,
val
);
}
}
}
#[test]
#[ignore]
fn thrash_expand_collapse_cycle() {
let app = h::app_root();
let has_expander = !app
.locator(r#"[name*="Expander"]"#)
.elements()
.unwrap()
.is_empty();
if has_expander {
let mut ct = app;
for action in ["expand", "collapse", "expand", "collapse"] {
let node = ct
.locator(r#"[name*="Expander"]"#)
.elements()
.unwrap()
.into_iter()
.next()
.expect("Expander node should exist");
ct = h::act(&node, action);
}
let final_node = ct
.locator(r#"[name*="Expander"]"#)
.elements()
.unwrap()
.into_iter()
.next();
if let Some(n) = final_node {
if n.states.expanded.is_some() {
assert_eq!(n.states.expanded, Some(false), "Should end collapsed");
}
}
}
}
#[test]
#[ignore]
fn error_app_not_found() {
let result = App::by_name("nonexistent_app_12345");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
Error::SelectorNotMatched { .. }
));
}
#[test]
#[ignore]
fn error_selector_not_matched() {
let app = h::app_root();
let result = app
.locator(r#"button[name="nonexistent_element_12345"]"#)
.elements();
assert!(result.unwrap().is_empty());
}
#[test]
#[ignore]
fn error_invalid_selector() {
let app = h::app_root();
let result = app.locator("$$$invalid!!!").elements();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidSelector { .. }));
}
#[test]
#[ignore]
fn action_on_default_tree() {
let app = h::app_root();
let buttons = app.locator(r#"[name*="Submit"]"#).elements().unwrap();
assert!(!buttons.is_empty());
let result = h::try_act(&buttons[0], "press");
match result {
Ok(()) => {}
Err(e) => assert!(
matches!(e, Error::Platform { .. } | Error::ElementStale { .. }),
"Unexpected error: {}",
e
),
}
}
#[test]
#[ignore]
fn error_press_on_non_interactive_element() {
let app = h::app_root();
let labels = app.locator("static_text").elements().unwrap_or_default();
if let Some(label) = labels
.into_iter()
.find(|e| !e.data().actions.iter().any(|a| a == "press"))
{
let result = h::try_act(&label, "press");
assert!(
result.is_err(),
"Press on static_text should fail: {:?}",
result
);
#[cfg(target_os = "macos")]
assert!(
matches!(result, Err(Error::ActionNotSupported { .. })),
"Expected ActionNotSupported on macOS, got: {:?}",
result
);
}
}
#[test]
#[ignore]
fn error_expand_on_non_expandable_element() {
let app = h::app_root();
let button = h::named(&app, "Submit");
let result = h::try_act(&button, "expand");
assert!(
result.is_err(),
"Expand on a non-expandable button should fail, not silently succeed"
);
}
#[test]
#[ignore]
fn error_collapse_on_non_expandable_element() {
let app = h::app_root();
let button = h::named(&app, "Submit");
let result = h::try_act(&button, "collapse");
assert!(
result.is_err(),
"Collapse on a non-expandable button should fail, not silently succeed"
);
}
#[test]
#[ignore]
fn json_roundtrip_real_element() {
let app = h::app_root();
let json = serde_json::to_string(&app.data).unwrap();
let deser: ElementData = serde_json::from_str(&json).unwrap();
assert_eq!(deser.role, app.data.role);
assert_eq!(deser.name, app.data.name);
}
#[test]
#[ignore]
fn action_blur_text_entry() {
let app = h::app_root();
let text = find_text_entry(&app);
let result = h::try_act(&text, "focus");
assert!(result.is_ok(), "Focus should succeed: {:?}", result.err());
let app2 = h::app_root();
let text2 = find_text_entry(&app2);
let result = h::try_act(&text2, "blur");
assert!(result.is_ok(), "Blur should succeed: {:?}", result.err());
}
#[test]
#[ignore]
fn action_scroll_direction() {
let app = h::app_root();
let scrollbars = app.locator("scroll_bar").elements().unwrap();
let windows = app.locator("window").elements().unwrap();
let target = scrollbars
.into_iter()
.next()
.or_else(|| windows.into_iter().next())
.expect("No scrollable element found");
let result = target.provider().scroll_down(&target, 3.0);
match result {
Ok(()) => println!("Scroll succeeded"),
Err(e) => println!(
"Scroll result: {} (OK — not all elements support scroll)",
e
),
}
}
#[test]
#[ignore]
fn action_set_text_selection() {
let app = h::app_root();
let text = find_text_entry(&app);
let _ = h::try_act(&text, "focus");
let result = text.provider().set_text_selection(&text, 0, 4);
match result {
Ok(()) => println!("SetTextSelection succeeded"),
Err(e) => println!("SetTextSelection result: {}", e),
}
}
#[test]
#[ignore]
fn action_type_text() {
let app = h::app_root();
let text = find_text_entry(&app);
let _ = h::try_act(&text, "focus");
let result = text.provider().type_text(&text, "hi");
match result {
Ok(()) => println!("TypeText succeeded"),
Err(e) => println!("TypeText result: {}", e),
}
}
#[test]
#[ignore]
fn event_subscribe_try_recv() {
use std::time::Duration;
let app = h::app_root();
let sub = app.subscribe().unwrap();
assert!(sub.try_recv().is_none(), "Expected no events initially");
let text = find_text_entry(&app);
let _ = text.provider().focus(&text);
std::thread::sleep(Duration::from_millis(500));
if let Some(event) = sub.try_recv() {
assert_eq!(event.event_type, EventType::FocusChanged);
} else {
println!("No event received — may depend on platform event delivery");
}
}
#[test]
#[ignore]
fn event_recv_timeout() {
let app = h::app_root();
let sub = app.subscribe().unwrap();
let result = sub.recv(std::time::Duration::from_millis(100));
assert!(
matches!(result, Err(Error::Timeout { .. })),
"Expected Timeout, got: {:?}",
result
);
}
#[test]
#[ignore]
fn event_recv_receives_event() {
use std::time::Duration;
let app = h::app_root();
let sub = app.subscribe().unwrap();
let text = find_text_entry(&app);
let _ = text.provider().focus(&text);
match sub.recv(Duration::from_secs(2)) {
Ok(event) => {
assert_eq!(event.event_type, EventType::FocusChanged);
}
Err(Error::Timeout { .. }) => {
println!("No event received — may depend on platform event delivery");
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
#[ignore]
fn event_wait_for_timeout() {
let app = h::app_root();
let sub = app.subscribe().unwrap();
let result = sub.wait_for(
|e| e.event_type == EventType::Alert,
std::time::Duration::from_millis(100),
);
assert!(
matches!(result, Err(Error::Timeout { .. })),
"Expected Timeout, got: {:?}",
result
);
}
#[test]
#[ignore]
fn event_wait_for_predicate_filters() {
use std::time::Duration;
let app = h::app_root();
let sub = app.subscribe().unwrap();
let text = find_text_entry(&app);
let _ = text.provider().focus(&text);
match sub.wait_for(
|e| e.event_type == EventType::FocusChanged,
Duration::from_secs(2),
) {
Ok(event) => {
assert_eq!(event.event_type, EventType::FocusChanged);
}
Err(Error::Timeout { .. }) => {
println!("No event received — may depend on platform event delivery");
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
#[ignore]
fn event_metadata_populated() {
use std::time::Duration;
let app = h::app_root();
let expected_pid = app.pid;
let sub = app.subscribe().unwrap();
let text = find_text_entry(&app);
let _ = text.provider().focus(&text);
std::thread::sleep(Duration::from_millis(500));
if let Some(event) = sub.try_recv() {
if let Some(pid) = expected_pid {
assert_eq!(event.app_pid, pid);
}
assert!(!event.app_name.is_empty(), "app_name should be populated");
} else {
println!("No event received — may depend on platform event delivery");
}
}
#[test]
#[ignore]
fn event_iter_yields_events() {
use std::time::Duration;
let app = h::app_root();
let sub = app.subscribe().unwrap();
let text = find_text_entry(&app);
let _ = text.provider().focus(&text);
match sub.recv(Duration::from_secs(2)) {
Ok(event) => {
assert_eq!(event.event_type, EventType::FocusChanged);
println!("Iterator yielded event: {:?}", event.event_type);
}
Err(Error::Timeout { .. }) => {
println!("No event received — may depend on platform event delivery");
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
#[ignore]
fn event_drop_unsubscribes() {
let app = h::app_root();
{
let _sub = app.subscribe().unwrap();
}
let sub2 = app.subscribe().unwrap();
assert!(sub2.try_recv().is_none());
}
#[test]
#[ignore]
fn event_target_element_present() {
use std::time::Duration;
let app = h::app_root();
let sub = app.subscribe().unwrap();
let text = find_text_entry(&app);
let _ = text.provider().focus(&text);
std::thread::sleep(Duration::from_millis(500));
if let Some(event) = sub.try_recv() {
assert_eq!(event.event_type, EventType::FocusChanged);
if let Some(ref target) = event.target {
println!(
"Event target: role={:?}, name={:?}",
target.role, target.name
);
} else {
println!("Event target is None — acceptable for polling backends");
}
} else {
println!("No event received — may depend on platform event delivery");
}
}
#[test]
#[ignore]
fn event_structure_changed() {
use std::time::Duration;
let app = h::app_root();
let sub = app.subscribe().unwrap();
let add_btn = app
.locator(r#"[name="Add Item"]"#)
.element()
.expect("Add Item button not found");
let _ = add_btn.provider().press(&add_btn);
match sub.wait_for(
|e| e.event_type == EventType::StructureChanged,
Duration::from_secs(3),
) {
Ok(event) => {
assert_eq!(event.event_type, EventType::StructureChanged);
println!("StructureChanged event received");
}
Err(Error::Timeout { .. }) => {
println!("No StructureChanged event — may depend on platform event delivery");
}
Err(e) => panic!("Unexpected error: {:?}", e),
}
}
#[test]
#[ignore]
fn event_iter_next_threaded() {
use std::time::Duration;
let app = h::app_root();
let sub = app.subscribe().unwrap();
let handle = std::thread::spawn(move || -> Option<Event> {
sub.recv(Duration::from_secs(5)).ok()
});
std::thread::sleep(Duration::from_millis(50));
let text = find_text_entry(&app);
let _ = text.provider().focus(&text);
let result = handle.join().expect("Thread panicked");
match result {
Some(event) => {
assert_eq!(event.event_type, EventType::FocusChanged);
println!("Threaded recv received: {:?}", event.event_type);
}
None => {
println!("No event received on thread — may depend on platform event delivery");
}
}
}
fn find_text_entry(app: &App) -> Element {
let by_name = app
.locator(r#"[name="Name"]"#)
.elements()
.unwrap_or_default();
if let Some(el) = by_name.into_iter().next() {
return el;
}
let fields = app.locator("text_field").elements().unwrap_or_default();
if let Some(el) = fields.into_iter().next() {
return el;
}
let areas = app.locator("text_area").elements().unwrap_or_default();
areas
.into_iter()
.next()
.expect("Text entry not found in app")
}
}