car-desktop 0.15.1

OS-level screen capture, accessibility inspection, and input synthesis for Common Agent Runtime
Documentation
//! Cross-platform integration tests for the `car-desktop` scaffold.
//!
//! These tests run on every platform and verify that the public API
//! is well-shaped: errors surface as expected, the default backend
//! constructs cleanly, and the type re-exports wire up. Platform-
//! specific end-to-end tests (Calculator integration on macOS, etc.)
//! live in CD-07's dedicated test file.

use car_desktop::models::{
    ClickRequest, KeyPressRequest, MouseButton, TypeRequest, WindowFilter, WindowHandle,
};
use car_desktop::{default_backend, CarDesktopError};

#[tokio::test]
async fn list_windows_returns_expected_error_surface_on_unsupported() {
    // On non-macOS targets the default backend returns
    // PlatformUnsupported from list_windows. This test covers that
    // branch only — the real macOS implementation is exercised by
    // `list_windows_succeeds_on_macos` below.
    if cfg!(target_os = "macos") {
        return;
    }
    let backend = default_backend();
    let result = backend.list_windows(WindowFilter::default()).await;
    assert!(
        matches!(result, Err(CarDesktopError::PlatformUnsupported { .. })),
        "unexpected error variant on unsupported platform: {result:?}"
    );
}

/// macOS-only: list_windows must return Ok and at least one window
/// (the terminal / test runner's own session). Also re-runs the
/// call with a PID filter for the current process — the test
/// binary may or may not have a window, but the call must succeed.
#[cfg(target_os = "macos")]
#[tokio::test]
async fn list_windows_succeeds_on_macos() {
    let backend = default_backend();
    let all = backend
        .list_windows(WindowFilter::default())
        .await
        .expect("list_windows must succeed on macOS with the macos feature");
    assert!(
        !all.is_empty(),
        "expected at least one on-screen window (the test host's terminal or IDE)"
    );
    // Every returned entry carries a valid handle and positive
    // frame dimensions.
    for info in &all {
        assert!(
            info.handle.pid > 0,
            "pid must be populated for {}",
            info.owner_name
        );
        assert!(
            info.frame.width >= 0.0 && info.frame.height >= 0.0,
            "frame dims must be non-negative for {:?}",
            info
        );
    }
    // Filter-by-pid round trip: pick the first window, filter by
    // its pid, expect at least one result.
    let pid = all[0].handle.pid;
    let filtered = backend
        .list_windows(WindowFilter::by_pid(pid))
        .await
        .expect("pid-filtered list_windows must succeed");
    assert!(
        filtered.iter().any(|w| w.handle.pid == pid),
        "pid filter dropped every window for pid {pid}"
    );
}

/// macOS-only, opt-in (`--ignored`): capture the primary display.
/// Requires Screen Recording TCC permission granted to the test
/// runner. Skipped in CI until the permissions-dance workflow
/// lands in CD-13.
#[cfg(target_os = "macos")]
#[tokio::test]
#[ignore = "requires Screen Recording permission"]
async fn capture_primary_display_round_trips_bytes() {
    use car_desktop::models::DisplayId;
    let backend = default_backend();
    let frame = backend
        .capture_display(DisplayId::PRIMARY)
        .await
        .expect("capture_display must succeed on macOS with Screen Recording granted");
    assert!(frame.width > 0, "display width must be non-zero");
    assert!(frame.height > 0, "display height must be non-zero");
    assert_eq!(
        frame.rgba.len(),
        frame.width as usize * frame.height as usize * 4,
        "RGBA buffer must match declared dimensions",
    );
}

/// macOS-only: focus_window against a nonexistent pid reports
/// WindowNotFound rather than panicking or silently succeeding.
#[cfg(target_os = "macos")]
#[tokio::test]
async fn focus_nonexistent_window_reports_window_not_found() {
    use car_desktop::models::WindowHandle;
    let backend = default_backend();
    // PID 0 is never a valid user-space app on macOS (reserved for
    // kernel_task / the process table root); the lookup returns
    // None, our backend translates to WindowNotFound.
    let result = backend.focus_window(WindowHandle::new(0, 0)).await;
    assert!(
        matches!(result, Err(CarDesktopError::WindowNotFound { .. })),
        "expected WindowNotFound for bogus pid, got {result:?}"
    );
}

#[tokio::test]
async fn click_against_bogus_window_errors_cleanly() {
    // pid 1 is `launchd` — never a user-space app with a window.
    // click() is expected to fail somewhere in the pipeline
    // (rate limiter → target-window resolution → focus → actual
    // click) but every variant must be structured, not a panic.
    let backend = default_backend();
    let window = WindowHandle::new(1, 1);
    let result = backend
        .click(ClickRequest {
            window,
            element_id: None,
            point: Some((10.0, 10.0)),
            button: MouseButton::Left,
            modifiers: Vec::new(),
            unsafe_ok: false,
            dry_run: false,
        })
        .await;
    match result {
        Err(CarDesktopError::PlatformUnsupported { .. })
        | Err(CarDesktopError::NotYetImplemented { .. })
        | Err(CarDesktopError::WindowNotFound { .. })
        | Err(CarDesktopError::OutOfTargetWindow { .. })
        | Err(CarDesktopError::RateLimited)
        | Err(CarDesktopError::PermissionDenied { .. }) => {}
        other => panic!("unexpected click() result against bogus pid: {other:?}"),
    }
}

#[tokio::test]
async fn type_text_dry_run_succeeds_on_macos_errors_on_others() {
    let backend = default_backend();
    let result = backend
        .type_text(TypeRequest {
            window: WindowHandle::new(1, 1),
            text: "hello".into(),
            dry_run: true,
        })
        .await;
    if cfg!(target_os = "macos") {
        // Dry-run short-circuits before any OS event posting, so
        // focus_window of pid=1 is the likely error (or Ok if
        // activate happens to succeed).
        assert!(
            result.is_ok() || matches!(result, Err(CarDesktopError::WindowNotFound { .. })),
            "dry-run type_text: {result:?}"
        );
    } else {
        assert!(matches!(
            result,
            Err(CarDesktopError::PlatformUnsupported { .. })
        ));
    }
}

#[tokio::test]
async fn keypress_dry_run_unsupported_character_reports_clearly() {
    let backend = default_backend();
    // \u{FFFD} is a non-ASCII char; Key::Char(c) with non-ASCII
    // should fail with UnsupportedCharacter on macOS regardless
    // of dry_run (the keycode lookup runs before dry-run check).
    let result = backend
        .keypress(KeyPressRequest {
            window: WindowHandle::new(1, 1),
            key: car_desktop::models::Key::Char('\u{FFFD}'),
            modifiers: Vec::new(),
            dry_run: true,
        })
        .await;
    if cfg!(target_os = "macos") {
        assert!(
            matches!(result, Err(CarDesktopError::UnsupportedCharacter { .. })),
            "unexpected keypress result: {result:?}"
        );
    } else {
        assert!(matches!(
            result,
            Err(CarDesktopError::PlatformUnsupported { .. })
        ));
    }
}