Skip to main content

macOS GUI testing framework with background testing, sub-millisecond element access, and self-healing locators

Reason this release was yanked:

Deprecated. Use: cargo install axterminator --features cli

Project description

AXTerminator

PyPI version Downloads Tests Python macOS License Rust Discussions

macOS GUI testing framework with background testing, sub-millisecond element access, and self-healing locators.

Quick Start · Features · API · Examples · Docs · Benchmarks


Background Testing

Test macOS apps without stealing focus. AXTerminator uses the macOS Accessibility API (AXUIElement) to interact with applications in the background. Your active window stays focused while tests run behind it.

No other macOS GUI testing framework offers this -- XCUITest, Appium, PyAutoGUI, and Maestro all require the application under test to be in the foreground.

import axterminator as ax

# Tests run in the background -- your active window stays focused
calculator = ax.app(name="Calculator")
calculator.find("5").click()    # No focus stealing
calculator.find("+").click()    # Continue your work
calculator.find("3").click()    # Tests just work
calculator.find("=").click()

Quick Start

1. Install

pip install axterminator

2. Grant accessibility permissions

Open System Settings > Privacy & Security > Accessibility and add your terminal app (Terminal, iTerm2, VS Code, etc.).

3. Run your first test

import axterminator as ax

# Check permissions
if not ax.is_accessibility_enabled():
    print("Enable in System Settings > Privacy & Security > Accessibility")
    exit(1)

# Connect to any running app
app = ax.app(name="Calculator")

# Interact -- background mode by default
app.find("7").click()
app.find("+").click()
app.find("3").click()
app.find("=").click()

That's it. Three lines to connect and interact.

Why AXTerminator?

Capability AXTerminator XCUITest Appium PyAutoGUI Maestro
Background testing Yes No No No No
Element access 379 us ~200 ms ~500 ms ~100 ms ~300 ms
Cross-app testing Yes No Limited Yes Limited
Self-healing 7 strategies No Basic No Yes
AI vision fallback Yes No No No No
Python API Yes No Yes Yes No
No Xcode required Yes No No Yes Yes

Element access benchmarked on M1 MacBook Pro, macOS 14.2. See full benchmarks.

Features

Background Testing

# User continues working while tests run
for i in range(100):
    app.find("Refresh").click()  # All in background

Self-Healing Locators (7 Strategies)

# Element survives UI changes via fallback strategies:
# 1. data_testid  - Developer-set stable IDs
# 2. aria_label   - Accessibility labels
# 3. identifier   - AX identifier
# 4. title        - Element title (fuzzy matching)
# 5. xpath        - Structural path
# 6. position     - Relative position
# 7. visual_vlm   - AI vision fallback

AI Vision Detection (VLM)

# When all else fails, use AI to find elements visually
ax.configure_vlm(backend="mlx")      # Local (fast, private)
ax.configure_vlm(backend="anthropic") # Claude Vision
ax.configure_vlm(backend="openai")    # OpenAI Vision
ax.configure_vlm(backend="gemini")    # Gemini Vision
ax.configure_vlm(backend="ollama")    # Local Ollama

# Natural language element description
app.find("the blue Save button in the toolbar")

pytest Integration

import pytest

@pytest.mark.ax_requires_app("Calculator")
def test_addition(ax_app, ax_wait):
    app = ax_app("Calculator")
    app.find("7").click()
    app.find("+").click()
    app.find("3").click()
    app.find("=").click()
    ax_wait(0.1)

Recording Mode

from axterminator import Recorder

recorder = Recorder(app)
recorder.start()
# ... perform actions ...
recorder.stop()

# Generate test code
print(recorder.generate_test())

API Reference

App Connection

# By name
app = ax.app(name="Safari")

# By bundle ID (recommended -- locale-independent)
app = ax.app(bundle_id="com.apple.Safari")

# By PID
app = ax.app(pid=12345)

# Launch if not running
app = ax.app(name="Notes", launch=True)

Finding Elements

# By text/title
button = app.find("Save")

# With timeout
button = app.find("Save", timeout_ms=5000)

# By role
text_field = app.find("", role="AXTextField")

# Find all matching
buttons = app.find_all("role:AXButton")

Actions

# Clicks (background by default)
element.click()
element.double_click()
element.right_click()

# Focused mode (for text input)
element.click(mode=ax.FOCUS)
element.type_text("Hello World!")

# Get properties
print(element.title)
print(element.value)
print(element.role)

Synchronization

from axterminator.sync import wait_for_idle, wait_for_element

# Wait for app to settle
wait_for_idle(app, timeout_ms=5000)

# Wait for element to appear
button = wait_for_element(app, "Done", timeout_ms=3000)

Performance

Measured on Apple M1 MacBook Pro, macOS 14.2, using Criterion benchmarks against Finder.app:

Operation Time Method
Single attribute read 54 us Criterion (get_ax_role)
Element access (window -> child) 379 us Criterion (search_first_button)
Perform action 20 us Criterion (perform_action_overhead)
Find element (Python, incl. overhead) ~0.5-1 ms time.perf_counter() loop

The speedup over Appium (~500 ms per element access) comes from eliminating HTTP/WebDriver/JSON overhead. AXTerminator calls the Accessibility API directly: Python -> PyO3 FFI -> Rust -> AXUIElement.

Reproduce: cargo bench or compile and run benches/bench_quick.rs. See full benchmarks.

Known Limitations

Background testing works for most interactions via AXUIElementPerformAction(), but some operations still require or steal window focus:

Operation Background? Why
Click, press, pick Yes AX actions work on unfocused windows
Read attributes/values Yes AX queries don't need focus
Screenshots Yes CGWindowListCreateImage captures any window
Text input Partial Some apps accept AX value setting; others require focused text field + CGEvent keystrokes
Drag operations No Mouse events are global — requires cursor control
System dialogs No Authentication prompts and file pickers always grab focus
Some Electron apps Partial May not respond to background AX actions; use CDP fallback

Workarounds:

  • For text input: prefer ax_set_value (AX-based) over ax_type (CGEvent-based) when possible
  • For Electron apps: enable CDP integration (ax_connect with bundle ID detection)
  • For unavoidable focus stealing: use virtual desktop isolation (planned — #23)

Examples

See examples/ for real-world automation:

Script Description
basic_usage.py Calculator automation
system_preferences.py System Settings navigation
finder_automation.py Finder file operations
notes_app.py Notes app automation
textedit_automation.py Document creation
pytest_example.py pytest integration
self_healing_locators.py Locator strategies
vlm_visual_detection.py VLM fallback demo

Browser Extension

Record browser interactions and generate axterminator code:

  1. Load browser-extension/ in Chrome (Developer mode)
  2. Click extension icon, then Start Recording
  3. Interact with web pages
  4. Copy generated Python code

Installation Options

# Basic
pip install axterminator

# With VLM backends
pip install axterminator[vlm]           # Local MLX
pip install axterminator[vlm-anthropic] # Claude Vision
pip install axterminator[vlm-openai]    # OpenAI Vision
pip install axterminator[vlm-gemini]    # Gemini Vision
pip install axterminator[vlm-ollama]    # Ollama
pip install axterminator[vlm-all]       # All backends

Requirements

  • macOS 12+ (Monterey or later)
  • Python 3.9+
  • Accessibility permissions granted to terminal/IDE

Building from Source

git clone https://github.com/MikkoParkkola/axterminator
cd axterminator

# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Build
pip install maturin
maturin develop

# Run tests
cargo test
pytest python/tests/

Community

Acknowledgements

AXTerminator was inspired by Terminator by mediar-ai, which pioneered accessible desktop GUI automation on Windows. AXTerminator brings similar capabilities to macOS, with the addition of background testing -- the ability to test applications without stealing window focus -- made possible by leveraging undocumented behavior of Apple's Accessibility API.

License

MIT OR Apache-2.0


Built with Rust + Python | Report Bug · Request Feature

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

axterminator-0.5.0-cp39-abi3-macosx_11_0_arm64.whl (423.2 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

axterminator-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl (435.6 kB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file axterminator-0.5.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for axterminator-0.5.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c3cb17ddc3c133a69be8c34cf571ae1d6b0f47f28adb5d1b92cae3e480782024
MD5 a2eee2731fc0e69b65667c51a88bd77f
BLAKE2b-256 39af0432aab88bdc6bfd883dbc10eb95915dcfd2542eb263dcbcf50273db437f

See more details on using hashes here.

Provenance

The following attestation bundles were made for axterminator-0.5.0-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on MikkoParkkola/axterminator

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file axterminator-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for axterminator-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 04136be87f4c169ab4e23318256f261b4d56cb89bc77d73bc75585785200bad3
MD5 8ea8b7707aa95576075a1ade11b823da
BLAKE2b-256 425b0bb7b1951568cb3fd011e911f501a436d9758378939cfd08f9fbc24fbde2

See more details on using hashes here.

Provenance

The following attestation bundles were made for axterminator-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on MikkoParkkola/axterminator

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page