Performance - AXTerminator
Description: World's Most Superior macOS GUI Testing Framework

Skip to content

# Performance Guide

AXTerminator is designed for speed. This document covers performance characteristics, benchmarks, and optimization tips.

## Benchmarks

### Element Access

Operation | Time | Notes
--- | --- | ---
Get element attribute | ~250µs | Title, value, role, etc.
Find element by title | ~10-50ms | Depends on tree depth
Find with cached path | ~1-5ms | After first find
Click (background) | ~1ms | No focus change
Click (foreground) | ~5-10ms | Includes focus switch
Type text | ~5ms + 1ms/char | Character-by-character

### Comparison with Other Frameworks

Framework | Element Access | Find Element
--- | --- | ---
AXTerminator | ~250µs | ~10-50ms
Appium | ~50-100ms | ~500ms-2s
XCUITest | ~10-50ms | ~100-500ms
Playwright (Web) | ~1-5ms | ~50-200ms

AXTerminator is 4000× faster for element access than typical mobile testing frameworks.

## Why So Fast?

1. Direct Accessibility API Access
2. Uses native macOS AX APIs via Rust FFI

3. No intermediate process or network layers

4. Zero-Copy Where Possible

5. Rust core minimizes memory allocations

6. Python bindings use efficient PyO3 patterns

7. Smart Caching

8. Element paths are cached after first find

9. Accessibility tree structure is partially cached

10. Background Operation

11. No focus switching overhead
12. No window activation delays

## Profiling Your Tests

### Built-in Timing

    import time
    import axterminator

    app = axterminator.app(name="Calculator")

    # Time element finding
    start = time.perf_counter()
    element = app.find("5")
    find_time = (time.perf_counter() - start) * 1000
    print(f"Find took {find_time:.2f}ms")

    # Time clicking
    start = time.perf_counter()
    element.click()
    click_time = (time.perf_counter() - start) * 1000
    print(f"Click took {click_time:.2f}ms")

### Using pytest-benchmark

    def test_element_find_performance(benchmark, calculator_app):
        app = axterminator.app(name="Calculator")
        result = benchmark(lambda: app.find("5"))
        assert result is not None

## Optimization Tips

### 1. Reuse Element References

    # ❌ Slow: Re-finds element each time
    for i in range(10):
        app.find("button").click()

    # ✅ Fast: Find once, click multiple times
    button = app.find("button")
    for i in range(10):
        button.click()

### 2. Use Specific Queries

    # ❌ Slow: Searches entire tree
    app.find("Save")

    # ✅ Fast: More specific query
    app.find("role:AXButton title:Save")

    # ✅ Fastest: By identifier
    app.find("identifier:save_button")

### 3. Set Appropriate Timeouts

    # ❌ Wasteful: 5 second timeout for fast apps
    element = app.find("button", timeout_ms=5000)

    # ✅ Better: Adjust based on expected response time
    element = app.find("button", timeout_ms=500)

### 4. Batch Operations

    # ❌ Slow: Individual finds
    num1 = app.find("1")
    num2 = app.find("2")
    plus = app.find("+")

    # ✅ Faster: Find parent, then children
    keypad = app.find("role:AXGroup identifier:keypad")
    num1 = keypad.find("1")  # Searches smaller tree

### 5. Use Synchronization Wisely

    from axterminator.sync import wait_for_idle

    # ❌ Wasteful: Always wait
    wait_for_idle(app, timeout_ms=5000)
    element.click()
    wait_for_idle(app, timeout_ms=5000)

    # ✅ Better: Wait only when needed
    element.click()
    if expecting_animation:
        wait_for_idle(app, timeout_ms=1000)

## Memory Usage

AXTerminator has low memory overhead:

Component | Memory
--- | ---
Rust core | ~2-5 MB
Python bindings | ~1 MB
Per-element cache | ~1 KB
VLM model (MLX) | ~1-2 GB

The VLM model is only loaded when visual detection is actually used.

## CI/CD Considerations

### Headless Environments

AXTerminator requires a GUI session (not headless). In CI:

    # GitHub Actions
    jobs:
      test:
        runs-on: macos-latest  # Has GUI
        steps:
          - run: pytest tests/ --ignore=tests/test_integration.py

### Parallel Testing

Tests can run in parallel safely:

    # Different apps = safe
    app1 = axterminator.app(name="Calculator")
    app2 = axterminator.app(name="Notes")  # OK: Different process

    # Same app = be careful
    # Use locking or sequential execution for same-app tests

## Troubleshooting Performance

### Slow Element Finding

1. Check query specificity
2. Verify element exists (typos in query)
3. Consider app loading time
4. Use shorter timeouts with retries

### High Memory Usage

1. Don't store many element references
2. Clear caches if running long sessions
3. Avoid loading VLM for simple tests

### Flaky Tests

1. Add synchronization after actions
2. Use `wait_for_element()` instead of `find()` with long timeout
3. Check for race conditions in app state