* feat(cua-driver): sync vendored cua-driver from 0.6.8 to 0.7.0 Upstream 0.7.0 brings action-time modality (get_window_state always returns AX tree + screenshot, capture_mode retired), honest verification (every action returns verified/path/effect/escalation), browser page (CDP) tools, desktop-scope (capture_scope=window|desktop, get_desktop_state, screen-absolute click/scroll), and Rust-only consolidation (Swift backend retired). Cherry-pick patch changes: - #2025 (X11 synthetic click): retired — superseded by 0.7.0 honest-verification - #2035 (session revive): retired — superseded by 0.7.0 revive_session/loud-reject/is_session_lifecycle_tool - #2036 (EAGAIN socket write retry): re-injected — merged upstream post-0.7.0 tag, EAGAIN os error 35 is not acceptable - #2021 (null/empty-title windows + resolve_uwp_host_window): retained, adapted to 0.7.0's new is_listable_top_level and window_title helpers Shim extensions: - coord_norm: desktop-scope support — denormalize_args falls back to screen basis when no window cache (desktop-scope click), normalize_result handles get_desktop_state, ingest_screen_size reads get_desktop_state's screen_width/height - serve.rs: rewrite_coord_desc re-injected at both tools/list sites Install scripts simplified to Rust-only (Swift backend retired upstream). Fix platform-linux cross-compilation: cfg-gate wayland::PORTAL_LIBEI_ENABLED reference in health_report.rs for non-linux hosts. * fix(cua-driver): distinguish desktop-scope from window cache miss in denormalize_args When screenshot_w == 0 (no cached window size), check whether the call has a pid/window_id target before falling back to screen-size basis. Window-scoped clicks with a cache miss now correctly leave coordinates as-is instead of mapping them against the screen dimensions. * fix(cua-driver): use screenshot pixels not logical points for desktop-scope coord basis ingest_screen_size was reading screen_width/height (logical display points from CGDisplayBounds) for get_desktop_state, but the model operates on the screenshot PNG whose dimensions are physical pixels. On Retina displays (2x) this caused every desktop-scope click to land at half the intended position. Read screenshot_width/height instead — the same basis normalize_result rewrites to 1000. Updated test to use realistic Retina values (screen 1920, screenshot 3840) to catch 1:1 masking. * fix(cua-driver): split screen cache into logical (move_cursor) and physical (desktop-scope) The single screen_cache was being written by both get_screen_size (logical points, e.g. 1920x1080) and get_desktop_state (physical pixels, e.g. 3840x2160). Since move_cursor operates in CGEvent screen points (logical) while desktop-scope clicks operate in screenshot pixels (physical), whichever tool ran last would corrupt the other's denormalization basis — causing 2x overshoot or 1/2 offset on Retina. Split into: - SCREEN_SIZE: logical points, written by get_screen_size, consumed by move_cursor (screen_basis=true) - DESKTOP_SCREENSHOT_SIZE: physical pixels, written by get_desktop_state, consumed by desktop-scope click fallback (screenshot_w==0, no pid) Added cross-contamination isolation tests. --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|---|---|---|
| .. | ||
| apps | ||
| build | ||
| linux-container | ||
| modality-recordings | ||
| shared | ||
| smoke | ||
| vision-agent-test | ||
| .gitignore | ||
| CuaTestHarness.sln | ||
| README.md | ||
| TEST_HARNESS_STRUCTURE.md | ||
| TEST_SUITE.md | ||
cua-driver test harness
Deterministic host apps that present a fixed scenario set for cua-driver
to drive. One layout, three buckets: cross-OS reusable assets in
shared/, OS-specific (or cross-platform) target apps in apps/,
host-OS build scripts in build/, host-OS CLI smoke runners in
smoke/.
Layout
test-harness/
├── shared/ # cross-OS reusable
│ ├── scenarios.json # single SoT for AutomationIds / window titles
│ └── web/index.html # WebView2 + Electron load this same file
├── apps/ # the test-target host apps
│ ├── cross-platform/
│ │ └── electron/ # CuaTestHarness.Electron (CDP host, runs on any OS)
│ ├── macos/
│ │ ├── appkit/ # CuaTestHarness.AppKit (single-file Swift)
│ │ └── swiftui/ # CuaTestHarness.SwiftUI (single-file Swift)
│ └── windows/
│ ├── wpf/ # CuaTestHarness.Wpf (.NET 8 WPF)
│ ├── winui3/ # CuaTestHarness.WinUI3 (.NET 8 WinUI3 unpackaged)
│ └── webview2/ # CuaTestHarness.WebView (WPF + Microsoft.Edge.WebView2)
├── build/ # host-OS build scripts (publish into ../rust/test-apps/)
│ ├── macos.sh # builds appkit + swiftui
│ └── windows.ps1 # builds wpf + winui3 + webview2 + electron
└── smoke/ # host-OS per-tool CLI smoke runners
├── macos.sh # spawns appkit harness, iterates every tool
└── results/macos.txt # checked-in baseline for diff-based regression detection
shared/scenarios.json is the single source of truth — both the host
apps and the Rust integration tests read it so AutomationIds, AX
identifiers, and window titles never drift between the two halves.
Published build outputs land in ../rust/test-apps/harness-{wpf,winui3,webview,electron,appkit,swiftui}/
so the existing sandbox runner and Rust integration tests pick them up
without further wiring.
What's exercised
WPF (apps/windows/wpf):
- Plain XAML controls + AutomationIds (
counter,text_body) - TextBox + mirrored label (
text_input) - Right/double-click target (
click_target) - Scroll body with VerticalOffset label + WM_VSCROLL hook (
scroll_target) - Modal
MessageBox(message_box) - Save/Cancel bottom strip — regression guard for PR #1696 (
bottom_strip) - Native Win32 child HWNDs via
HwndHost(child_hwnd) - Owned secondary
Window(owned_popup) - Layered transparent window (
layered_popup) - Keyboard accelerators — F5 + Ctrl+Shift+H (
accelerator)
WinUI3 (apps/windows/winui3):
counter/text_body/exit(parity with WPF)- TextBox + mirror (
text_input— UIAValuePattern) CommandBarFlyout— popup in same HWND, DComp-rendered- XAML
Popup— primitive, not a separate HWND
WebView2 (apps/windows/webview2):
- Loads
shared/web/index.html— same page as the Electron harness. - Exposes CDP on
default_cdp_port=9222so cua-driver'spagetool can drive it.
Electron (apps/cross-platform/electron):
- Loads the same
shared/web/index.html. CDP ondefault_cdp_port=9223.
AppKit (apps/macos/appkit):
- Plain NSButton + NSTextField counter (
counter) - NSTextField label with shared marker (
text_body) - Editable NSTextField + mirror label (
text_input) - Custom NSView click-target (
click_target) - NSScrollView with tall NSTextView body + offset label (
scroll_target) - Top NSMenu item with known title (
ns_menubar) - Exit button
SwiftUI (apps/macos/swiftui):
- Counter / text_body / text_input parity with AppKit
- SwiftUI
.popover()— Mac analogue of WinUI3 CommandBarFlyout / XAML Popup - Exit button
Coverage matrices (Rust integration tests)
Windows:
| Capability | WPF | WinUI3 |
|---|---|---|
| Smoke (UIA tree + AutomationIds) | yes | yes |
| UIA Invoke click | yes | (implicit) |
right_click |
yes | — |
double_click |
yes | — |
type_text (PostMessage / UIA) |
yes | yes |
set_value (UIA ValuePattern) |
yes | (covered) |
scroll (WM_VSCROLL via hook) |
yes | — |
press_key accelerator (F5) |
yes | — |
Modal MessageBox open + dismiss |
yes | — |
| Owned popup open + parse | yes | — |
| Layered popup capture | yes | — |
| XAML Popup open + parse | — | yes |
macOS:
| Capability | AppKit | SwiftUI |
|---|---|---|
| Smoke (AX tree + identifiers) | yes | yes |
| Counter click via element_index | yes | (implicit) |
set_value (AXSetAttribute) |
yes | (covered) |
| Popover open + body assert | — | yes |
| Per-tool CLI smoke | smoke/macos.sh |
— |
Build
macOS (requires Xcode CLT for xcrun swiftc):
libs/cua-driver/test-harness/build/macos.sh # both apps
libs/cua-driver/test-harness/build/macos.sh --skip swiftui # AppKit only
libs/cua-driver/test-harness/build/macos.sh --clean # nuke stage dirs first
Windows (requires .NET 8 SDK on PATH; Node.js + npm for the Electron app):
cd libs\cua-driver\test-harness
.\build\windows.ps1 # all four apps
.\build\windows.ps1 -Skip winui3 # skip a specific one
Run the integration tests
# macOS
cd libs/cua-driver/rust
cargo test --release --test harness_appkit_test -- --ignored --nocapture
cargo test --release --test harness_swiftui_test -- --ignored --nocapture
# Windows (locally)
cd libs\cua-driver\rust
cargo test --test harness_wpf_test -- --ignored --nocapture
cargo test --test harness_winui3_test -- --ignored --nocapture
# Windows Sandbox (matches CI)
.\rust\sandbox\run-tests-in-sandbox.ps1 harness_wpf
.\rust\sandbox\run-tests-in-sandbox.ps1 harness_winui3
Tests are #[ignore] so they don't run in plain cargo test (they
require the harness apps to be built and, on macOS, TCC Accessibility
granted to the test runner).
Per-tool CLI smoke
Companion to the MCP integration tests. Spawns the AppKit harness as a victim, then iterates every cua-driver tool with sensible JSON args and reports PASS / FAIL / SKIP in a sorted table. Runs in ~25 seconds.
libs/cua-driver/test-harness/smoke/macos.sh
A baseline result file (smoke/results/macos.txt) is checked in for the
current driver version — diff against it to catch regressions in tool
coverage.
AppKit / SwiftUI AX quirks captured in the tests
accessibilityIdentifieron AXStaticText doesn't propagate. NSTextField label-mode + SwiftUITextviews render as AXStaticText leaves and the OS drops the identifier slot. Tests assert AX-id only on actionable controls and assert on text-content for labels.- SwiftUI popovers may live in a separate AXWindow. The popover test
walks
list_windowsfor any new window owned by the harness pid if the trigger window doesn't contain the marker. - AX element budget caps tree depth + width. The scroll-body is capped at 30 lines so later scenarios don't get truncated.