mod integ;
#[cfg(test)]
mod tests {
use super::integ as h;
use xa11y::*;
#[test]
#[ignore]
fn check_permissions_granted() {
let status = xa11y::check_permissions().unwrap();
assert!(
matches!(status, PermissionStatus::Granted),
"Expected Granted, got: {:?}",
status
);
}
#[test]
#[ignore]
fn apps_returns_nonempty() {
let root = xa11y::apps().unwrap();
assert!(!root.tree().is_empty(), "apps should return nodes");
assert_eq!(root.tree().app_name, "Desktop");
assert!(
root.tree().pid.is_none(),
"Multi-app tree should have no PID"
);
let has_test_app = root
.subtree()
.iter()
.any(|n| n.name.as_deref().is_some_and(|name| name.contains("xa11y")));
assert!(
has_test_app,
"apps should include the test app. Apps: {:?}",
root.subtree()
.iter()
.filter(|n| n.parent_index.is_none_or(|p| p == 0))
.map(|n| &n.name)
.collect::<Vec<_>>()
);
}
#[test]
#[ignore]
fn app_target_by_pid() {
let by_name = h::app_tree();
let pid = by_name.tree().pid.expect("app tree should have a PID");
assert!(pid > 0);
let root = xa11y::app(&AppTarget::ByPid(pid)).unwrap();
assert!(!root.tree().is_empty());
assert_eq!(root.tree().pid, Some(pid));
}
#[test]
#[ignore]
fn app_target_by_name() {
let root = h::app_tree();
assert!(!root.tree().is_empty());
assert!(root.tree().app_name.contains("xa11y"));
}
#[test]
#[ignore]
fn tree_has_root_node() {
let root = h::app_tree();
assert!(
root.role == Role::Application || root.role == Role::Window,
"Root role: {:?}",
root.role
);
}
#[test]
#[ignore]
fn tree_has_window() {
let root = h::app_tree();
let windows = root.query("window").unwrap();
assert!(!windows.is_empty(), "No windows found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn tree_has_buttons() {
let root = h::app_tree();
let buttons = root.query("button").unwrap();
assert!(
buttons.len() >= 2,
"Expected >=2 buttons, found {}. Tree:\n{}",
buttons.len(),
root
);
}
#[test]
#[ignore]
fn tree_has_submit_button() {
let root = h::app_tree();
let submit = h::named(&root, "Submit");
assert_eq!(submit.role, Role::Button);
}
#[test]
#[ignore]
fn tree_has_cancel_button_disabled() {
let root = h::app_tree();
let cancel = h::named(&root, "Cancel");
assert_eq!(cancel.role, Role::Button);
let _ = cancel.states.enabled;
}
#[test]
#[ignore]
fn tree_has_checkbox_unchecked() {
let root = h::app_tree();
let cb = h::named(&root, "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 root = h::app_tree();
let text_nodes: Vec<Node> = root
.subtree()
.into_iter()
.filter(|n| {
(n.role == Role::TextField || n.role == Role::TextArea)
&& (n.value.is_some() || n.name.as_deref() == Some("Name"))
})
.collect();
assert!(
!text_nodes.is_empty(),
"Text entry not found. Tree:\n{}",
root
);
}
#[test]
#[ignore]
fn tree_has_welcome_label() {
let root = h::app_tree();
let welcome = root.query(r#"[name*="Welcome"]"#).unwrap();
if welcome.is_empty() {
let labels = root.query("static_text").unwrap();
assert!(
!labels.is_empty(),
"No StaticText/label nodes found. Tree:\n{}",
root
);
} 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 root = h::app_tree();
let sliders = root.query("slider").unwrap();
assert!(!sliders.is_empty(), "No sliders found. Tree:\n{}", root);
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 root = h::app_tree();
let progress = root.query("progress_bar").unwrap();
assert!(
!progress.is_empty(),
"No progress bars found. Tree:\n{}",
root
);
}
#[test]
#[ignore]
fn tree_has_radio_buttons() {
let root = h::app_tree();
let radios = root.query("radio_button").unwrap();
assert!(
radios.len() >= 2,
"Expected >=2 radio buttons, found {}. Tree:\n{}",
radios.len(),
root
);
}
#[test]
#[ignore]
fn tree_has_combo_box() {
let root = h::app_tree();
let combos = root.query("combo_box").unwrap();
assert!(!combos.is_empty(), "ComboBox not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn tree_has_list_with_items() {
let root = h::app_tree();
let lists = root.query("list").unwrap();
let items = root.query("list_item").unwrap();
assert!(
!lists.is_empty() || !items.is_empty(),
"Neither List nor ListItem found. Tree:\n{}",
root
);
}
#[test]
#[ignore]
fn tree_has_table_with_cells() {
let root = h::app_tree();
let tables = root.query("table").unwrap();
let cells = root.query("table_cell").unwrap();
assert!(
!tables.is_empty() || !cells.is_empty(),
"Neither Table nor TableCell found. Tree:\n{}",
root
);
}
#[test]
#[ignore]
fn role_menu_bar() {
let root = h::app_tree();
let nodes = root.query("menu_bar").unwrap();
assert!(!nodes.is_empty(), "MenuBar not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn role_menu_item() {
let root = h::app_tree();
let nodes = root.query("menu_item").unwrap();
assert!(!nodes.is_empty(), "MenuItem not found. Tree:\n{}", root);
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 root = h::app_tree();
let nodes = root.query("toolbar").unwrap();
assert!(!nodes.is_empty(), "Toolbar not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn role_tab_and_tab_group() {
let root = h::app_tree();
let tab_groups = root.query("tab_group").unwrap();
let tabs = root.query("tab").unwrap();
assert!(
!tab_groups.is_empty() || !tabs.is_empty(),
"Neither TabGroup nor Tab found. Tree:\n{}",
root
);
}
#[test]
#[ignore]
fn role_separator() {
let root = h::app_tree();
let seps = root.query("separator").unwrap();
assert!(!seps.is_empty(), "Separator not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn role_image() {
let root = h::app_tree();
let images = root.query("image").unwrap();
assert!(!images.is_empty(), "Image not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn role_link() {
let root = h::app_tree();
let links = root.query("link").unwrap();
assert!(!links.is_empty(), "Link not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn role_tree_item() {
let root = h::app_tree();
let items = root.query("tree_item").unwrap();
assert!(!items.is_empty(), "TreeItem not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn role_dialog() {
let root = h::app_tree();
let dialogs = root.query("dialog").unwrap();
assert!(!dialogs.is_empty(), "Dialog not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn role_alert() {
let root = h::app_tree();
let alerts = root.query("alert").unwrap();
assert!(!alerts.is_empty(), "Alert not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn role_heading() {
let root = h::app_tree();
let headings = root.query("heading").unwrap();
assert!(!headings.is_empty(), "Heading not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn role_scroll_bar() {
let root = h::app_tree();
let scrollbars = root.query("scroll_bar").unwrap();
assert!(
!scrollbars.is_empty(),
"ScrollBar not found. Tree:\n{}",
root
);
}
#[test]
#[ignore]
fn role_split_group() {
let root = h::app_tree();
let node = root.query(r#"[name*="SplitGroup"]"#).unwrap();
assert!(
!node.is_empty(),
"SplitGroup node not found. Tree:\n{}",
root
);
}
#[test]
#[ignore]
fn role_static_text() {
let root = h::app_tree();
let labels = root.query("static_text").unwrap();
assert!(!labels.is_empty(), "StaticText not found. Tree:\n{}", root);
}
#[test]
#[ignore]
fn tree_get_by_id() {
let root = h::app_tree();
let got = root.tree().get_data(0);
assert!(got.is_some());
assert_eq!(got.unwrap().role, root.role);
}
#[test]
#[ignore]
fn tree_get_invalid_returns_none() {
let root = h::app_tree();
assert!(root
.tree()
.get_data(root.tree().len() as u32 + 999)
.is_none());
}
#[test]
#[ignore]
fn tree_iter_all_nodes() {
let root = h::app_tree();
assert_eq!(root.subtree().len(), root.tree().len());
assert!(root.tree().len() > 1);
}
#[test]
#[ignore]
fn tree_children_of_root() {
let root = h::app_tree();
let children = root.children();
assert!(!children.is_empty(), "Root should have children");
for child in &children {
let parent = child.parent();
assert!(parent.is_some(), "Child should have a parent");
}
}
#[test]
#[ignore]
fn tree_subtree_from_root() {
let root = h::app_tree();
let subtree = root.subtree();
assert_eq!(subtree.len(), root.tree().len());
}
#[test]
#[ignore]
fn tree_subtree_of_leaf() {
let root = h::app_tree();
let leaf = root.subtree().into_iter().find(|n| n.children().is_empty());
if let Some(leaf) = leaf {
let st = leaf.subtree();
assert_eq!(st.len(), 1);
}
}
#[test]
#[ignore]
fn tree_is_not_empty() {
let root = h::app_tree();
assert!(!root.tree().is_empty());
}
#[test]
#[ignore]
fn tree_display_readable() {
let root = h::app_tree();
let display = root.to_string();
assert!(!display.is_empty());
assert!(display.contains("[0]"), "Display should start with [0]");
}
#[test]
#[ignore]
fn node_description_on_image() {
let root = h::app_tree();
let images = root.query("image").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 node_bounds_present() {
let root = h::app_tree();
let submit = h::named(&root, "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 node_bounds_none_for_non_component_nodes() {
let root = h::app_tree();
assert!(
root.bounds.is_none(),
"Application root should not have bounds (no Component interface)"
);
let submit = h::named(&root, "Submit");
assert!(submit.bounds.is_some(), "Submit button should have bounds");
}
#[test]
#[ignore]
fn node_actions_list_on_button() {
let root = h::app_tree();
let submit = h::named(&root, "Submit");
assert!(!submit.actions.is_empty());
assert!(
submit.actions.contains(&Action::Press),
"Submit should support Press, got: {:?}",
submit.actions
);
}
#[test]
#[ignore]
fn node_children_ids_valid() {
let root = h::app_tree();
let children = root.children();
assert!(!children.is_empty());
for child in &children {
let _ = child.role;
}
}
#[test]
#[ignore]
fn node_parent_field() {
let root = h::app_tree();
assert!(root.parent().is_none(), "Root should have no parent");
let non_root = root
.subtree()
.into_iter()
.find(|n| n.parent_index.is_some());
if let Some(n) = non_root {
assert!(n.parent().is_some(), "Non-root should have parent");
}
}
#[test]
#[ignore]
fn state_enabled_default() {
let root = h::app_tree();
let submit = h::named(&root, "Submit");
assert!(submit.states.enabled, "Submit should be enabled");
}
#[test]
#[ignore]
fn state_disabled_on_cancel() {
let root = h::app_tree();
let cancel = h::named(&root, "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 root = h::app_tree();
let submit = h::named(&root, "Submit");
assert!(submit.states.visible, "Submit should be visible");
}
#[test]
#[ignore]
fn state_focused_after_focus_action() {
let root = h::app_tree();
let submit = h::named(&root, "Submit");
let result = h::try_act(&submit, Action::Focus);
if result.is_ok() {
std::thread::sleep(std::time::Duration::from_millis(100));
let root2 = h::app_tree();
let submit2 = h::named(&root2, "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 root = h::app_tree();
let cb = h::named(&root, "I agree to terms");
assert_eq!(cb.states.checked, Some(Toggled::Off));
}
#[test]
#[ignore]
fn state_checked_on_radio() {
let root = h::app_tree();
let radios = root.query("radio_button").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 root = h::app_tree();
let expandable: Vec<Node> = root
.subtree()
.into_iter()
.filter(|n| n.states.expanded.is_some())
.collect();
let expander_by_name = root.query(r#"[name*="Expander"]"#).unwrap();
if expandable.is_empty() && expander_by_name.is_empty() {
println!(
"No expandable nodes found (tree has {} nodes). \
Expand/collapse actions tested separately.",
root.tree().len()
);
}
}
#[test]
#[ignore]
fn state_editable_on_text_field() {
let root = h::app_tree();
let text: Vec<Node> = root
.subtree()
.into_iter()
.filter(|n| {
(n.role == Role::TextField || n.role == Role::TextArea)
&& (n.value.is_some() || n.name.as_deref() == Some("Name"))
})
.collect();
assert!(!text.is_empty(), "Text entry not found. Tree:\n{}", root);
assert!(text[0].states.editable, "Text entry should be editable");
}
#[test]
#[ignore]
fn state_selected_on_list_item() {
let root = h::app_tree();
let apple = h::named(&root, "Apple");
let root2 = h::act(&apple, Action::Press);
let apple2 = h::named(&root2, "Apple");
println!(
"Apple selected state after Click: {:?}",
apple2.states.selected
);
}
#[test]
#[ignore]
fn sel_by_role() {
let root = h::app_tree();
let buttons = root.query("button").unwrap();
assert!(buttons.len() >= 2);
for b in &buttons {
assert_eq!(b.role, Role::Button);
}
}
#[test]
#[ignore]
fn sel_by_exact_name() {
let root = h::app_tree();
let submit = h::one(&root, r#"button[name="Submit"]"#);
assert_eq!(submit.role, Role::Button);
}
#[test]
#[ignore]
fn sel_by_role_and_name() {
let root = h::app_tree();
let results = root.query(r#"button[name="Cancel"]"#).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
#[ignore]
fn sel_name_contains() {
let root = h::app_tree();
let results = root.query(r#"[name*="agree"]"#).unwrap();
assert!(
!results.is_empty(),
"Should find element with 'agree' in name"
);
}
#[test]
#[ignore]
fn sel_name_starts_with() {
let root = h::app_tree();
let results = root.query(r#"[name^="Welc"]"#).unwrap();
if results.is_empty() {
let results = root.query(r#"[name^="Sub"]"#).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 root = h::app_tree();
let results = root.query(r#"[name$="xa11y"]"#).unwrap();
if results.is_empty() {
let results = root.query(r#"[name$="App"]"#).unwrap();
assert!(
!results.is_empty(),
"Should find at least one element with name ending in 'App'"
);
}
}
#[test]
#[ignore]
fn sel_value_attribute() {
let root = h::app_tree();
let results = root.query(r#"[value*="Red"]"#).unwrap();
if results.is_empty() {
let has_value = root.subtree().iter().any(|n| n.value.is_some());
assert!(has_value, "At least one node should have a value");
let results = root.query(r#"[value*="0.75"]"#).unwrap();
assert!(
!results.is_empty(),
"Should find element with value containing '0.75' (ProgressBar)"
);
}
}
#[test]
#[ignore]
fn sel_descendant_combinator() {
let root = h::app_tree();
let results = root.query("window button").unwrap();
assert!(!results.is_empty());
for r in &results {
assert_eq!(r.role, Role::Button);
}
}
#[test]
#[ignore]
fn sel_child_combinator() {
let root = h::app_tree();
let results = root.query("application > window").unwrap();
for r in &results {
assert_eq!(r.role, Role::Window);
}
}
#[test]
#[ignore]
fn sel_nth_pseudo() {
let root = h::app_tree();
let first = root.query("button:nth(1)").unwrap();
assert_eq!(first.len(), 1);
}
#[test]
#[ignore]
fn sel_role_attribute() {
let root = h::app_tree();
let results = root.query(r#"[role="button"]"#).unwrap();
assert!(!results.is_empty());
for r in &results {
assert_eq!(r.role, Role::Button);
}
}
#[test]
#[ignore]
fn sel_complex_chain() {
let root = h::app_tree();
let results = root.query(r#"window button[name*="Sub"]"#).unwrap();
assert!(!results.is_empty());
assert_eq!(results[0].role, Role::Button);
assert!(results[0].name.as_deref().unwrap().contains("Sub"));
}
#[test]
#[ignore]
fn raw_data_always_present() {
let _root = h::app_tree();
#[cfg(target_os = "linux")]
match &_root.raw {
RawPlatformData::Linux { atspi_role, .. } => {
assert!(!atspi_role.is_empty());
}
_ => panic!("Expected Linux raw data"),
}
#[cfg(target_os = "macos")]
match &_root.raw {
RawPlatformData::MacOS { ax_role, .. } => {
assert!(!ax_role.is_empty());
}
_ => panic!("Expected macOS raw data"),
}
}
#[test]
#[ignore]
fn action_press_button() {
let root = h::app_tree();
let submit = h::named(&root, "Submit");
let result = h::try_act(&submit, Action::Press);
match result {
Ok(()) => println!("Submit pressed"),
Err(e) => println!("Submit press result: {}", e),
}
}
#[test]
#[ignore]
fn action_toggle_checkbox() {
let root = h::app_tree();
let cbs = root.query("check_box").unwrap();
assert!(!cbs.is_empty(), "No checkbox");
let initial = cbs[0].states.checked;
let root2 = h::act(&cbs[0], Action::Press);
let cb2 = root2.query("check_box").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 root = h::app_tree();
let was_enabled = h::named(&root, "Cancel").states.enabled;
let cbs = root.query("check_box").unwrap();
assert!(!cbs.is_empty(), "No checkbox");
let root2 = h::act(&cbs[0], Action::Press);
let cancel2 = h::named(&root2, "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 root = h::app_tree();
let text = root
.subtree()
.into_iter()
.find(|n| {
(n.role == Role::TextField || n.role == Role::TextArea)
&& (n.value.as_deref() == Some("John Doe") || n.name.as_deref() == Some("Name"))
})
.expect("Text entry not found");
let result = h::try_act(&text, Action::Focus);
assert!(result.is_ok(), "Focus should succeed: {:?}", result.err());
}
#[test]
#[ignore]
fn action_set_value_text() {
let root = h::app_tree();
let text = root
.subtree()
.into_iter()
.find(|n| {
(n.role == Role::TextField || n.role == Role::TextArea)
&& (n.value.as_deref() == Some("John Doe") || n.name.as_deref() == Some("Name"))
})
.expect("Text entry not found");
match h::try_act_with(
&text,
Action::SetValue,
Some(ActionData::Value("Jane Smith".to_string())),
) {
Ok(()) => {
std::thread::sleep(std::time::Duration::from_millis(300));
let root2 = h::app_tree();
let updated = root2
.subtree()
.into_iter()
.find(|n| n.value.as_deref() == Some("Jane Smith"));
if updated.is_none() {
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 root = h::app_tree();
let sliders = root.query("slider").unwrap();
assert!(!sliders.is_empty());
let result = h::try_act_with(
&sliders[0],
Action::SetValue,
Some(ActionData::NumericValue(75.0)),
);
assert!(result.is_ok(), "SetValue numeric: {:?}", result.err());
std::thread::sleep(std::time::Duration::from_millis(300));
let root2 = h::app_tree();
let s2 = root2.query("slider").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 root = h::app_tree();
let spin = root
.subtree()
.into_iter()
.find(|n| {
n.role == Role::TextField
&& n.value.is_some()
&& n.value.as_deref().unwrap_or("").parse::<f64>().is_ok()
})
.or_else(|| root.query("slider").unwrap().first().cloned());
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, Action::Increment);
if result.is_ok() {
std::thread::sleep(std::time::Duration::from_millis(300));
let root2 = h::app_tree();
if let Some(s2) = root2.query("slider").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 root = h::app_tree();
let spin = root
.subtree()
.into_iter()
.find(|n| {
n.role == Role::TextField
&& n.value.is_some()
&& n.value.as_deref().unwrap_or("").parse::<f64>().is_ok()
})
.or_else(|| root.query("slider").unwrap().first().cloned());
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, Action::Decrement);
if result.is_ok() {
std::thread::sleep(std::time::Duration::from_millis(300));
let root2 = h::app_tree();
if let Some(s2) = root2.query("slider").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 root = h::app_tree();
let expander = root
.subtree()
.into_iter()
.find(|n| n.states.expanded.is_some())
.or_else(|| {
root.query(r#"[name*="Expander"]"#)
.unwrap()
.first()
.cloned()
})
.or_else(|| {
root.query(r#"[name*="More Details"]"#)
.unwrap()
.first()
.cloned()
});
if let Some(node) = expander {
if let Ok(()) = h::try_act(&node, Action::Expand) {
std::thread::sleep(std::time::Duration::from_millis(300));
let root2 = h::app_tree();
let n2 = root2
.query(r#"[name*="Expander"]"#)
.unwrap()
.first()
.cloned()
.or_else(|| {
root2
.subtree()
.into_iter()
.find(|n| n.states.expanded.is_some())
});
if let Some(n) = n2 {
if n.states.expanded == Some(true) {
if let Ok(()) = h::try_act(&n, Action::Collapse) {
std::thread::sleep(std::time::Duration::from_millis(300));
let root3 = h::app_tree();
let n3 = root3
.query(r#"[name*="Expander"]"#)
.unwrap()
.first()
.cloned()
.or_else(|| {
root3
.subtree()
.into_iter()
.find(|n| n.states.expanded.is_some())
});
if let Some(n) = n3 {
assert_eq!(n.states.expanded, Some(false));
}
}
}
}
}
}
}
#[test]
#[ignore]
fn action_select_list_item() {
let root = h::app_tree();
let apple = root.query(r#"[name*="Apple"]"#).unwrap();
if !apple.is_empty() {
let _ = h::try_act(&apple[0], Action::Press);
}
}
#[test]
#[ignore]
fn nesting_deep_tree_traversal() {
let root = h::app_tree();
let cells = root.query(r#"[name*="Alice"]"#).unwrap();
assert!(!cells.is_empty(), "Alice cell not found. Tree:\n{}", root);
let parent = cells[0].parent();
assert!(parent.is_some());
}
#[test]
#[ignore]
fn nesting_subtree_of_table() {
let root = h::app_tree();
let tables = root.query("table").unwrap();
if !tables.is_empty() {
let subtree = tables[0].subtree();
assert!(
subtree.len() >= 3,
"Table subtree too small: {}",
subtree.len()
);
}
}
#[test]
#[ignore]
fn thrash_toggle_checkbox_5_times() {
let root = h::app_tree();
let cbs = root.query("check_box").unwrap();
assert!(!cbs.is_empty());
let mut current_root = root;
for _ in 0..5 {
let cbs = current_root.query("check_box").unwrap();
assert!(!cbs.is_empty());
current_root = h::act(&cbs[0], Action::Press);
}
let final_cb = current_root.query("check_box").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 root = h::app_tree();
let sliders = root.query("slider").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_root = root;
for _ in 0..10 {
let sliders = current_root.query("slider").unwrap();
let slider = sliders.first().expect("No slider");
current_root = h::act(slider, Action::Increment);
}
let s = current_root.query("slider").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 root = h::app_tree();
let has_expander = root
.subtree()
.into_iter()
.find(|n| n.states.expanded.is_some())
.or_else(|| {
root.query(r#"[name*="Expander"]"#)
.unwrap()
.first()
.cloned()
})
.is_some();
if has_expander {
let mut ct = root;
for action in [
Action::Expand,
Action::Collapse,
Action::Expand,
Action::Collapse,
] {
let node = ct
.subtree()
.into_iter()
.find(|n| n.states.expanded.is_some())
.or_else(|| ct.query(r#"[name*="Expander"]"#).unwrap().first().cloned())
.expect("Expander node should exist");
ct = h::act(&node, action);
}
let final_node = ct
.query(r#"[name*="Expander"]"#)
.unwrap()
.first()
.cloned()
.or_else(|| {
ct.subtree()
.into_iter()
.find(|n| n.states.expanded.is_some())
});
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 = xa11y::app(&AppTarget::ByName("nonexistent_app_12345".to_string()));
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::AppNotFound { .. }));
}
#[test]
#[ignore]
fn error_selector_not_matched() {
let root = h::app_tree();
let result = root.query(r#"button[name="nonexistent_element_12345"]"#);
assert!(result.unwrap().is_empty());
}
#[test]
#[ignore]
fn error_invalid_selector() {
let root = h::app_tree();
let result = root.query("$$$invalid!!!");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidSelector { .. }));
}
#[test]
#[ignore]
fn action_on_default_tree() {
let root = h::app_tree();
let buttons = root.query(r#"[name*="Submit"]"#).unwrap();
assert!(!buttons.is_empty());
let result = h::try_act(&buttons[0], Action::Press);
match result {
Ok(()) => {}
Err(e) => assert!(
matches!(e, Error::Platform { .. } | Error::ElementStale { .. }),
"Unexpected error: {}",
e
),
}
}
#[test]
#[ignore]
fn json_roundtrip_real_tree() {
let root = h::app_tree();
let json = serde_json::to_string(root.tree().as_ref()).unwrap();
let deser: Tree = serde_json::from_str(&json).unwrap();
assert_eq!(deser.len(), root.tree().len());
assert_eq!(deser.app_name, root.tree().app_name);
}
#[test]
#[ignore]
fn action_blur_text_entry() {
let root = h::app_tree();
let text = root
.subtree()
.into_iter()
.find(|n| {
(n.role == Role::TextField || n.role == Role::TextArea)
&& (n.value.as_deref() == Some("John Doe") || n.name.as_deref() == Some("Name"))
})
.expect("Text entry not found");
let result = h::try_act(&text, Action::Focus);
assert!(result.is_ok(), "Focus should succeed: {:?}", result.err());
let root2 = h::app_tree();
let text2 = root2
.subtree()
.into_iter()
.find(|n| {
(n.role == Role::TextField || n.role == Role::TextArea)
&& (n.value.as_deref() == Some("John Doe") || n.name.as_deref() == Some("Name"))
})
.expect("Text entry not found after focus");
let result = h::try_act(&text2, Action::Blur);
assert!(result.is_ok(), "Blur should succeed: {:?}", result.err());
}
#[test]
#[ignore]
fn action_scroll_direction() {
let root = h::app_tree();
let target = root
.subtree()
.into_iter()
.find(|n| n.role == Role::ScrollBar)
.or_else(|| root.query("window").unwrap().first().cloned())
.expect("No scrollable element found");
let result = h::try_act_with(
&target,
Action::ScrollDown,
Some(ActionData::ScrollAmount(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 root = h::app_tree();
let text = root
.subtree()
.into_iter()
.find(|n| {
(n.role == Role::TextField || n.role == Role::TextArea)
&& (n.value.as_deref() == Some("John Doe") || n.name.as_deref() == Some("Name"))
})
.expect("Text entry not found");
let _ = h::try_act(&text, Action::Focus);
let result = h::try_act_with(
&text,
Action::SetTextSelection,
Some(ActionData::TextSelection { start: 0, end: 4 }),
);
match result {
Ok(()) => println!("SetTextSelection succeeded"),
Err(e) => println!("SetTextSelection result: {}", e),
}
}
#[test]
#[ignore]
fn action_type_text() {
let root = h::app_tree();
let text = root
.subtree()
.into_iter()
.find(|n| {
(n.role == Role::TextField || n.role == Role::TextArea)
&& (n.value.as_deref() == Some("John Doe") || n.name.as_deref() == Some("Name"))
})
.expect("Text entry not found");
let _ = h::try_act(&text, Action::Focus);
let result = h::try_act_with(
&text,
Action::TypeText,
Some(ActionData::Value("hi".to_string())),
);
match result {
Ok(()) => println!("TypeText succeeded"),
Err(e) => println!("TypeText result: {}", e),
}
}
#[test]
#[ignore]
fn event_subscribe_receives_focus_event() {
use std::time::Duration;
let ep = xa11y::create_event_provider().expect("EventProvider unavailable");
let root = h::app_tree();
let sub = ep
.subscribe(
&AppTarget::ByName("xa11y".to_string()),
EventFilter::kinds(&[EventKind::FocusChanged]),
)
.unwrap();
let text = root
.subtree()
.into_iter()
.find(|n| n.role == Role::TextField || n.role == Role::TextArea)
.expect("Text entry not found");
let _ = ep.perform_action(text.tree(), &text, Action::Focus, None);
std::thread::sleep(Duration::from_millis(500));
if let Some(event) = sub.try_recv() {
assert_eq!(event.kind, EventKind::FocusChanged);
println!("Received focus event: {:?}", event.kind);
} else {
println!("No event received within timeout — may depend on platform event delivery");
}
}
#[test]
#[ignore]
fn event_wait_for_event_timeout() {
use std::time::Duration;
let ep = xa11y::create_event_provider().expect("EventProvider unavailable");
let result = ep.wait_for_event(
&AppTarget::ByName("xa11y".to_string()),
EventFilter::kinds(&[EventKind::Alert]),
Duration::from_millis(100),
);
assert!(
matches!(result, Err(Error::Timeout { .. })),
"Expected Timeout, got: {:?}",
result
);
}
#[test]
#[ignore]
fn event_wait_for_attached() {
use std::time::Duration;
let ep = xa11y::create_event_provider().expect("EventProvider unavailable");
let result = ep.wait_for(
&AppTarget::ByName("xa11y".to_string()),
"button[name=\"Submit\"]",
ElementState::Attached,
Duration::from_secs(2),
);
assert!(
result.is_ok(),
"wait_for attached should succeed: {:?}",
result.err()
);
let node = result.unwrap().expect("attached node should exist");
assert_eq!(node.role, Role::Button);
}
}