AXTerminator Design Document¶
Status: Implemented | Version: 0.6.1 | Last Updated: 2026-03-21
Overview¶
AXTerminator is a macOS GUI testing framework that provides background testing (interacting with applications without stealing window focus), sub-millisecond element access via direct Accessibility API calls, and self-healing locators with 7 fallback strategies.
Key Differentiators¶
| Capability | AXTerminator | XCUITest | Appium | PyAutoGUI |
|---|---|---|---|---|
| Background testing | Yes | No | No | No |
| Element access | ~379 us | ~200 ms | ~500 ms | ~100 ms |
| Cross-app testing | Yes | No | Limited | Yes |
| Self-healing | 7 strategies | No | Basic | No |
| Electron support | CDP | No | Via driver | No |
| WebView support | Auto-detect | Manual | Via context | No |
Measured Performance (M1 MacBook Pro, macOS 14.2)¶
Single attribute read: 54 us (Criterion, get_ax_role)
Element access: 379 us (Criterion, search_first_button)
Action overhead: 20 us (Criterion, perform_action_overhead)
Architecture¶
+-----------------------------------------------------------------+
| AXTerminator Framework |
+-----------------------------------------------------------------+
| |
| +-----------------------------------------------------------+ |
| | Python API (PyO3) | |
| | import axterminator as ax | |
| | app = ax.app("Safari") | |
| | app.find("Save").click() # Background by default | |
| +-----------------------------------------------------------+ |
| | |
| v |
| +-----------------------------------------------------------+ |
| | Rust Core Engine (~379 us/element) | |
| | +-----------+ +-----------+ +---------------------+ | |
| | | AXBridge | | CGEvent | | Element Cache (LRU) | | |
| | | Background| | Focus-req | | | | |
| | +-----------+ +-----------+ +---------------------+ | |
| +-----------------------------------------------------------+ |
| | |
| v |
| +------------------+---------------+----------------------+ |
| | EspressoMac | UnifiedTestOS | Self-Healing | |
| | Sync Engine | Cross-App | 7-Strategy | |
| | | Router | Fallback | |
| | +------------+ | +----------+ | +----------------+ | |
| | | XPC Sync | | | Native | | | 1. data-testid | | |
| | | (SDK apps) | | | AX | | | 2. aria-label | | |
| | +------------+ | +----------+ | | 3. identifier | | |
| | | Heuristic | | | Electron | | | 4. title | | |
| | | (non-SDK) | | | CDP | | | 5. xpath | | |
| | +------------+ | +----------+ | | 6. position | | |
| | | | WebView | | | 7. visual(VLM) | | |
| | | | Hybrid | | +----------------+ | |
| +------------------+---------------+----------------------+ |
| |
+-----------------------------------------------------------------+
|
+-------------------+-------------------+
v v v
+----------+ +----------+ +----------+
| Native | | Electron | | WebView |
| macOS | | Apps | | Content |
| Apps | | (CDP) | | (Hybrid) |
+----------+ +----------+ +----------+
Core Components¶
1. Background Action Engine¶
The ability to test apps without stealing focus. macOS AXUIElementPerformAction works on unfocused windows -- this is undocumented in Apple's developer documentation but verified working on macOS 12-15.
pub fn perform_background_action(
element: AXUIElementRef,
action: &str,
) -> Result<(), AXError> {
// AXUIElementPerformAction works on unfocused windows
unsafe {
let action_str = CFString::new(action);
AXUIElementPerformAction(element, action_str.as_concrete_TypeRef())
}
}
Supported in background (verified):
- kAXPressAction -- Button clicks, menu items
- kAXPickAction -- Selection in pickers/lists
- kAXIncrementAction / kAXDecrementAction -- Steppers, sliders
- kAXShowMenuAction -- Context menus
- kAXConfirmAction -- Dialog confirmation
Requires focus (falls back automatically):
- Text input (requires AXValue setting with focus)
- Drag operations (requires CGEvent)
- Multi-touch gestures
2. EspressoMac Sync Engine¶
Espresso-style synchronization for macOS, with two strategies:
- XPC Client: Direct communication with EspressoMac SDK-enabled apps (~1 ms latency)
- Heuristic Sync: Accessibility tree hashing for any app (~50 ms polling, ~95% accuracy)
The heuristic fallback watches for structural stability: 3 consecutive identical tree hashes indicates the UI has settled.
3. UnifiedTestOS Router¶
Automatic detection and routing for different app architectures:
- Native: Pure AXUIElement API
- Electron: Chrome DevTools Protocol (auto-detected by checking for Chromium helper processes)
- WebView Hybrid: Switches protocol at WebView boundaries
- Catalyst: iPad apps on Mac
4. Self-Healing System (7-Strategy)¶
Deterministic fallback chain with configurable time budget (default: 100 ms):
data_testid-- Developer-set stable IDs (most reliable)aria_label-- Accessibility labelsidentifier-- AX identifiertitle-- Fuzzy title matching (Levenshtein, 80% threshold)xpath-- Structural path in accessibility treeposition-- Relative spatial position (50px threshold)visual_vlm-- AI vision fallback (local MLX, Ollama, or cloud VLMs)
Successful heals are cached so subsequent lookups skip failed strategies.
Python API¶
Basic Usage¶
import axterminator as ax
# Connect by bundle ID (locale-independent, recommended)
app = ax.app(bundle_id="com.apple.Safari")
# Find elements
button = app.find("Save")
button = app.find(role="AXButton", title="Save")
# Background click (default)
button.click()
# Focus mode when needed
text_field = app.find(role="AXTextField")
text_field.type_text("Hello", mode=ax.FOCUS)
# Synchronization
app.wait_for_idle()
app.wait_for_element("Loading Complete", timeout=5.0)
Healing Configuration¶
config = ax.HealingConfig(
strategies=["data_testid", "aria_label", "title"],
max_heal_time_ms=200,
cache_healed=True,
)
ax.configure_healing(config)
Swift SDK (EspressoMac)¶
Optional SDK for deterministic synchronization in your own apps:
import EspressoMacSDK
@main
struct MyApp: App {
init() {
EspressoMac.install() // 1-line integration
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
Performance Budget¶
Measured on Apple M1 MacBook Pro, macOS 14.2 using Criterion benchmarks:
| Operation | Measured | Method |
|---|---|---|
| Single attribute read | 54 us | Criterion get_ax_role |
| Element access | 379 us | Criterion search_first_button |
| Background click | ~1 ms | Manual timing |
| Focus click | ~5 ms | Manual timing (includes app activation) |
| Healing (all 7 strategies) | <100 ms | Budget-enforced |
| VLM fallback | ~400 ms | Depends on backend |
Last updated: 2026-03-21