use super::windows::*;
use crate::platforms::AccessibilityEngine;
use std::process;
use std::time::Instant;
#[test]
fn test_get_process_name_by_pid_current_process() {
let current_pid = process::id() as i32;
let result = get_process_name_by_pid(current_pid);
assert!(result.is_ok(), "Should be able to get current process name");
let process_name = result.unwrap();
assert!(!process_name.is_empty(), "Process name should not be empty");
assert!(
!process_name.ends_with(".exe"),
"Process name should not contain .exe extension"
);
assert!(
!process_name.ends_with(".EXE"),
"Process name should not contain .EXE extension"
);
assert!(
process_name
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_'),
"Process name should contain only alphanumeric characters, hyphens, and underscores: {process_name}"
);
println!("Current process name: {process_name}");
}
#[test]
fn test_tree_building_performance_stress_test() {
let engine = match WindowsEngine::new(false, false) {
Ok(engine) => engine,
Err(_) => {
println!("Cannot create WindowsEngine, skipping stress test");
return;
}
};
let applications = match engine.get_applications() {
Ok(apps) => apps,
Err(_) => {
println!("Cannot get applications, using root element");
return;
}
};
if applications.is_empty() {
println!("No applications available, using root element for stress test");
return;
}
let app = &applications[0];
println!(
"Starting stress test with application: {:?}",
app.attributes().name
);
let start_time = Instant::now();
let config = crate::platforms::TreeBuildConfig::default();
match engine.get_window_tree(
app.process_id().unwrap_or(0),
app.attributes().name.as_deref(),
config,
) {
Ok(tree) => {
let total_time = start_time.elapsed();
let element_count = count_tree_elements(&tree);
let tree_depth = calculate_tree_depth(&tree);
println!("=== Stress Test Results ===");
println!("Tree building time: {total_time:?}");
println!("Total elements in tree: {element_count}");
println!("Tree depth: {tree_depth}");
println!(
"Elements per second: {:.2}",
element_count as f64 / total_time.as_secs_f64()
);
if total_time > std::time::Duration::from_secs(30) {
println!("Warning: Tree building took longer than expected: {total_time:?}");
}
}
Err(e) => {
println!("Tree building failed in stress test: {e}");
}
}
}
fn count_tree_elements(node: &crate::UINode) -> usize {
1 + node.children.iter().map(count_tree_elements).sum::<usize>()
}
fn calculate_tree_depth(node: &crate::UINode) -> usize {
if node.children.is_empty() {
1
} else {
1 + node
.children
.iter()
.map(calculate_tree_depth)
.max()
.unwrap_or(0)
}
}
#[test]
fn test_get_process_name_by_pid_invalid_pid() {
let result = get_process_name_by_pid(-1);
assert!(result.is_err(), "Should fail for invalid PID");
let result = get_process_name_by_pid(999999);
assert!(result.is_err(), "Should fail for non-existent PID");
}
#[test]
fn test_get_process_name_by_pid_system_process() {
let system_pids = vec![0, 4];
for pid in system_pids {
match get_process_name_by_pid(pid) {
Ok(name) => {
println!("System process {pid}: {name}");
assert!(!name.is_empty(), "System process name should not be empty");
}
Err(e) => {
println!("Could not get name for system process {pid}: {e}");
}
}
}
}
#[test]
fn test_open_regular_application() {
let engine = match WindowsEngine::new(false, false) {
Ok(engine) => engine,
Err(_) => {
println!("Cannot create WindowsEngine, skipping application test");
return;
}
};
let test_apps = vec!["notepad", "calc", "mspaint"];
for app_name in test_apps {
println!("Testing application opening: {app_name}");
match engine.open_application(app_name) {
Ok(app_element) => {
println!("Successfully opened {app_name}");
let attrs = app_element.attributes();
println!(
"App attributes - Role: {}, Name: {:?}",
attrs.role, attrs.name
);
assert!(!attrs.role.is_empty(), "Application should have a role");
let _ = app_element.press_key("Alt+F4");
}
Err(e) => {
println!("Could not open {app_name}: {e} (this might be expected)");
}
}
}
}
#[test]
#[ignore]
fn test_open_uwp_application() {
let engine = match WindowsEngine::new(false, false) {
Ok(engine) => engine,
Err(_) => {
println!("Cannot create WindowsEngine, skipping UWP test");
return;
}
};
let test_apps = vec!["Microsoft Store", "Settings", "Photos"];
for app_name in test_apps {
println!("Testing UWP application opening: {app_name}");
match engine.open_application(app_name) {
Ok(app_element) => {
println!("Successfully opened UWP app {app_name}");
let attrs = app_element.attributes();
println!(
"UWP app attributes - Role: {}, Name: {:?}",
attrs.role, attrs.name
);
assert!(!attrs.role.is_empty(), "UWP application should have a role");
let _ = app_element.press_key("Alt+F4");
}
Err(e) => {
println!("Could not open UWP app {app_name}: {e} (this might be expected)");
}
}
}
}
#[test]
fn test_browser_title_matching() {
let (is_browser, parts) = WindowsEngine::extract_browser_info(
"MailTracker: Email tracker for Gmail - Chrome Web Store - Google Chrome",
);
assert!(is_browser, "Should detect as browser title");
assert!(
parts.len() >= 2,
"Should split browser title into parts: {parts:?}"
);
let parts_str = parts.join(" ");
assert!(
parts_str.to_lowercase().contains("mailtracker"),
"Should contain page title"
);
assert!(
parts_str.to_lowercase().contains("chrome"),
"Should contain browser name"
);
let similarity = WindowsEngine::calculate_similarity(
"Chrome Web Store - Google Chrome",
"MailTracker: Email tracker for Gmail - Chrome Web Store - Google Chrome",
);
assert!(
similarity > 0.3,
"Should have reasonable similarity: {similarity}"
);
println!("Browser title parts: {parts:?}");
println!("Similarity score: {similarity:.2}");
}
#[test]
fn test_browser_title_matching_edge_cases() {
let test_cases = vec![
("Tab Title - Google Chrome", true),
("Mozilla Firefox", true),
("Microsoft Edge", true),
("Some App - Not Application", false), ("Chrome Web Store - Google Chrome", true),
("GitHub - Google Chrome", true),
("Random Window Title", false),
];
for (title, expected_is_browser) in test_cases {
let (is_browser, parts) = WindowsEngine::extract_browser_info(title);
assert_eq!(
is_browser, expected_is_browser,
"Browser detection failed for: '{title}', expected: {expected_is_browser}, got: {is_browser}"
);
if is_browser {
assert!(
!parts.is_empty(),
"Browser title should have parts: '{title}'"
);
}
}
}
#[test]
fn test_similarity_calculation_edge_cases() {
let test_cases = vec![
("identical", "identical", 1.0),
("Longer String", "Long", 0.3), ("Chrome Web Store", "MailTracker Chrome Web Store", 0.4), ("completely different", "nothing similar", 0.0),
("", "empty test", 0.0),
("single", "", 0.0),
];
for (text1, text2, min_expected) in test_cases {
let similarity = WindowsEngine::calculate_similarity(text1, text2);
if min_expected == 1.0 {
assert_eq!(
similarity, 1.0,
"Identical strings should have similarity 1.0"
);
} else if min_expected == 0.0 {
assert_eq!(
similarity, 0.0,
"Completely different strings should have similarity 0.0"
);
} else {
assert!(
similarity >= min_expected - 0.2 && similarity <= 1.0,
"Similarity for '{text1}' vs '{text2}' should be around {min_expected}, got: {similarity:.2}"
);
}
println!("'{text1}' vs '{text2}' = {similarity:.2}");
}
}
#[test]
fn test_find_best_title_match_browser_scenario() {
let target_title = "MailTracker: Email tracker for Gmail - Chrome Web Store - Google Chrome";
let available_window_name = "Chrome Web Store - Google Chrome";
let (is_target_browser, target_parts) = WindowsEngine::extract_browser_info(target_title);
let (is_window_browser, window_parts) =
WindowsEngine::extract_browser_info(available_window_name);
assert!(is_target_browser, "Target should be detected as browser");
assert!(is_window_browser, "Window should be detected as browser");
println!("Target parts: {target_parts:?}");
println!("Window parts: {window_parts:?}");
let mut max_similarity = 0.0f64;
for target_part in &target_parts {
for window_part in &window_parts {
let similarity = WindowsEngine::calculate_similarity(target_part, window_part);
max_similarity = max_similarity.max(similarity);
println!("'{target_part}' vs '{window_part}' = {similarity:.2}");
}
}
assert!(
max_similarity > 0.6,
"Should find good similarity between browser titles, got: {max_similarity:.2}"
);
}
#[test]
fn test_enhanced_error_messages() {
let target_title = "MailTracker: Email tracker for Gmail - Chrome Web Store - Google Chrome";
let available_windows = [
"Taskbar".to_string(),
"Chrome Web Store - Google Chrome".to_string(),
"Firefox - Mozilla Firefox".to_string(),
"Random Application".to_string(),
];
let (is_target_browser, _) = WindowsEngine::extract_browser_info(target_title);
assert!(is_target_browser, "Target should be browser");
let browser_windows: Vec<&String> = available_windows
.iter()
.filter(|name| {
let (is_browser, _) = WindowsEngine::extract_browser_info(name);
is_browser
})
.collect();
assert!(
!browser_windows.is_empty(),
"Should find browser windows in the list"
);
assert!(
browser_windows.len() >= 2,
"Should find multiple browser windows: {browser_windows:?}"
);
assert!(
browser_windows.iter().any(|w| w.contains("Chrome")),
"Should find Chrome window"
);
assert!(
browser_windows.iter().any(|w| w.contains("Firefox")),
"Should find Firefox window"
);
}
#[test]
fn test_unified_tree_api_with_config() {
let engine = match WindowsEngine::new(false, false) {
Ok(engine) => engine,
Err(_) => {
println!("Cannot create WindowsEngine, skipping unified API test");
return;
}
};
let app = match engine.open_application("calc") {
Ok(app) => app,
Err(e) => {
println!("Cannot open Calculator: {e}, skipping test");
return;
}
};
let pid = app.process_id().unwrap_or(0);
let title_string = app.attributes().name;
let title = title_string.as_deref();
let fast_config = crate::platforms::TreeBuildConfig {
property_mode: crate::platforms::PropertyLoadingMode::Fast,
timeout_per_operation_ms: Some(50),
yield_every_n_elements: Some(50),
batch_size: Some(50),
max_depth: None,
include_all_bounds: false,
ui_settle_delay_ms: None,
format_output: false,
show_overlay: false,
overlay_display_mode: None,
from_selector: None,
};
let start_fast = std::time::Instant::now();
let fast_result = engine.get_window_tree(pid, title, fast_config);
let fast_duration = start_fast.elapsed();
let full_config = crate::platforms::TreeBuildConfig {
property_mode: crate::platforms::PropertyLoadingMode::Complete,
timeout_per_operation_ms: Some(100),
yield_every_n_elements: Some(25),
batch_size: Some(25),
max_depth: None,
include_all_bounds: false,
ui_settle_delay_ms: None,
format_output: false,
show_overlay: false,
overlay_display_mode: None,
from_selector: None,
};
let start_full = std::time::Instant::now();
let full_result = engine.get_window_tree(pid, title, full_config);
let full_duration = start_full.elapsed();
let default_config = crate::platforms::TreeBuildConfig::default();
let start_default = std::time::Instant::now();
let default_result = engine.get_window_tree(pid, title, default_config);
let default_duration = start_default.elapsed();
assert!(
fast_result.is_ok(),
"Fast config should work: {:?}",
fast_result.err()
);
assert!(
full_result.is_ok(),
"Full config should work: {:?}",
full_result.err()
);
assert!(
default_result.is_ok(),
"Default config should work: {:?}",
default_result.err()
);
println!("Fast mode: {fast_duration:?}");
println!("Full mode: {full_duration:?}");
println!("Default mode: {default_duration:?}");
if let (Ok(fast_tree), Ok(full_tree), Ok(default_tree)) =
(&fast_result, &full_result, &default_result)
{
let fast_count = count_tree_elements(fast_tree);
let full_count = count_tree_elements(full_tree);
let default_count = count_tree_elements(default_tree);
println!("Fast mode elements: {fast_count}");
println!("Full mode elements: {full_count}");
println!("Default mode elements: {default_count}");
assert_eq!(
fast_count, full_count,
"Fast and full modes should find same number of elements"
);
assert_eq!(
fast_count, default_count,
"Fast and default modes should find same number of elements"
);
assert!(fast_count > 0, "Should find at least some elements");
}
let _ = app.close();
}
#[test]
#[ignore] fn test_window_transparency() {
let engine = WindowsEngine::new(false, true).unwrap();
let notepad = engine.open_application("notepad.exe").unwrap();
for percentage in (0..=100).rev().step_by(5) {
notepad.set_transparency(percentage as u8).unwrap();
std::thread::sleep(std::time::Duration::from_millis(20));
}
for percentage in (0..=100).step_by(5) {
notepad.set_transparency(percentage as u8).unwrap();
std::thread::sleep(std::time::Duration::from_millis(20));
}
notepad.set_transparency(100).unwrap();
notepad.close().unwrap();
}