Skip to content

AXTerminator

World's Most Superior macOS GUI Testing Framework

AXTerminator enables background GUI testing on macOS without stealing focus, with ~250µs element access times and self-healing locators.

Features

  • 🎯 Background Testing - Run tests without stealing focus or interrupting your work
  • ⚡ Ultra-Fast - ~250µs element access (4000× faster than typical UI frameworks)
  • 🔧 Self-Healing Locators - 7-strategy healing system survives UI changes
  • 🧠 Visual VLM Detection - AI-powered element detection as ultimate fallback
  • 🦀 Rust Core - Native performance with Python bindings

Quick Start

pip install axterminator
import axterminator

# Check accessibility permissions
if not axterminator.is_accessibility_enabled():
    print("Enable accessibility in System Preferences")
    exit(1)

# Connect to app and interact
app = axterminator.app(name="Calculator")
app.find("5").click()
app.find("+").click()
app.find("3").click()
app.find("=").click()

Installation

Basic

pip install axterminator

With VLM Support

# Local MLX (recommended - fast, private)
pip install axterminator[vlm]

# Cloud APIs
pip install axterminator[vlm-anthropic]  # Claude Vision
pip install axterminator[vlm-openai]     # GPT-4V
pip install axterminator[vlm-gemini]     # Gemini Vision

# All backends
pip install axterminator[vlm-all]

Requirements

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

API Reference

Core Functions

axterminator.app(name=None, bundle_id=None, pid=None)

Connect to a running application.

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

# By bundle ID
app = axterminator.app(bundle_id="com.apple.Safari")

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

axterminator.is_accessibility_enabled()

Check if accessibility permissions are granted.

if not axterminator.is_accessibility_enabled():
    print("Grant accessibility permissions")

AXApp Class

app.find(query, timeout_ms=5000)

Find an element by query.

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

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

# Combined query
save_btn = app.find("role:AXButton title:Save")

app.find_all(query)

Find all matching elements.

buttons = app.find_all("role:AXButton")

app.pid

Get the process ID.

AXElement Class

element.click(mode=BACKGROUND)

Click the element.

# Background click (default) - won't steal focus
element.click()

# Foreground click - brings app to front
element.click(mode=axterminator.FOCUS)

element.type_text(text)

Type text into the element.

text_field.type_text("Hello, World!")

element.value

Get the element's value attribute.

element.title

Get the element's title.

element.role

Get the element's accessibility role.

VLM Configuration

axterminator.configure_vlm(backend, model=None, api_key=None)

Configure visual element detection.

# Local MLX (default)
axterminator.configure_vlm(backend="mlx")

# Cloud APIs
axterminator.configure_vlm(backend="anthropic", api_key="sk-...")
axterminator.configure_vlm(backend="openai", api_key="sk-...")
axterminator.configure_vlm(backend="gemini", api_key="...")

Self-Healing Locators

When an element is found, AXTerminator stores multiple locator strategies:

  1. data_testid - Custom test identifiers (most stable)
  2. aria_label - ARIA accessibility labels
  3. identifier - macOS accessibility identifiers
  4. title - Element title/text
  5. xpath - Structural path in accessibility tree
  6. position - Relative position within parent
  7. visual_vlm - AI-powered visual detection

If the primary locator fails, the system automatically tries alternatives.

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

Operation Time
Element access ~250µs
Click ~1ms
Type text ~5ms
Find element ~10-50ms

License

MIT OR Apache-2.0