Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add alpharigel/ios-e2e-skill --skill "ios-e2e"
Install specific skill from multi-skill repository
# Description
End-to-end iOS app testing in the Simulator. Use when the user asks to test iOS UI, interact with the simulator, verify app behavior, inspect accessibility trees, take screenshots, or automate swipe/tap/type gestures.
# SKILL.md
name: ios-e2e
description: End-to-end iOS app testing in the Simulator. Use when the user asks to test iOS UI, interact with the simulator, verify app behavior, inspect accessibility trees, take screenshots, or automate swipe/tap/type gestures.
iOS Simulator Testing with AXe
Build, test, and automate iOS applications running in the Simulator using AXe β a fast, modern CLI for accessibility-based UI automation.
Why AXe
AXe is a Swift CLI binary that talks directly to the Accessibility API. No Python version issues, no daemon, no Facebook idb dependency. Install with:
brew install cameroncooke/axe/axe
Prerequisites
Before testing, ensure:
1. Xcode is installed with iOS Simulator runtimes
2. AXe is installed (axe --version)
3. A simulator is booted (xcrun simctl boot "iPhone 16 Pro")
4. Your app is built and installed on the simulator
Finding the Simulator UDID
# List booted simulators
xcrun simctl list devices booted
# List all available simulators
xcrun simctl list devices available
Store the UDID for use in all subsequent commands.
Core AXe Commands
Inspect the UI (Accessibility Tree)
axe describe-ui --udid <UDID>
Returns a JSON array β always unwrap with [0] to get the root element.
Key JSON fields:
- AXLabel β the element's accessibility label (NOT label)
- AXValue β the element's current value (NOT value)
- children β child elements (NOT AXChildren)
- frame β {x, y, width, height} in points
Tap
# By accessibility label
axe tap --label "Save" --udid <UDID>
# By coordinates (points)
axe tap -x 200 -y 400 --udid <UDID>
Swipe
axe swipe --start-x 100 --start-y 400 --end-x 300 --end-y 400 --duration 0.3 --udid <UDID>
Common patterns:
- Swipe right: start-x < end-x (same y)
- Swipe left: start-x > end-x (same y)
- Pull to refresh: long downward swipe from top, --duration 0.8
Type Text
# IMPORTANT: text is a POSITIONAL argument, NOT --text
axe type "Hello world" --udid <UDID>
Press Keys
# By HID usage code
axe key 40 --udid <UDID> # Return/Enter (0x28)
axe key 42 --udid <UDID> # Delete/Backspace (0x2A)
# Hardware buttons
axe button home --udid <UDID>
Common HID Key Codes
| Key | Code | Hex |
|---|---|---|
| Return/Enter | 40 | 0x28 |
| Escape | 41 | 0x29 |
| Delete/Backspace | 42 | 0x2A |
| Tab | 43 | 0x2B |
| Space | 44 | 0x2C |
| Arrow Right | 79 | 0x4F |
| Arrow Left | 80 | 0x50 |
| Arrow Down | 81 | 0x51 |
| Arrow Up | 82 | 0x52 |
Key combinations use + syntax: axe key "224+4" --udid <UDID> (Cmd+A / Select All)
Simulator Management (xcrun simctl)
# Boot
xcrun simctl boot <UDID>
# Launch app
xcrun simctl launch <UDID> com.example.app
# Terminate app
xcrun simctl terminate <UDID> com.example.app
# Install app
xcrun simctl install <UDID> /path/to/App.app
# Screenshot
xcrun simctl io <UDID> screenshot /tmp/screenshot.png
# Open Simulator.app window
open -a Simulator
# Clear app data (get container, then delete files)
xcrun simctl get_app_container <UDID> com.example.app data
# Then delete files in Library/Application Support/ or relevant paths
# Push notification
xcrun simctl push <UDID> com.example.app /path/to/payload.json
Helper Scripts
This skill includes Python helper scripts in the scripts/ subdirectory (located next to this SKILL.md file). They require Python 3.10+ and no external dependencies. Find the absolute path to the scripts by locating this skill's directory.
ui_driver.py β AXe Wrapper
Provides a UIDriver class for programmatic UI automation:
# Describe current UI as formatted text
python3 scripts/ui_driver.py describe --udid <UDID>
# Tap by label
python3 scripts/ui_driver.py tap --label "Save" --udid <UDID>
# Tap by coordinates
python3 scripts/ui_driver.py tap --x 200 --y 400 --udid <UDID>
# Swipe right (e.g. to complete an item)
python3 scripts/ui_driver.py swipe --direction right --y 400 --udid <UDID>
# Swipe left (e.g. to dismiss or skip)
python3 scripts/ui_driver.py swipe --direction left --y 400 --udid <UDID>
# Pull to refresh
python3 scripts/ui_driver.py refresh --udid <UDID>
# Scroll down/up
python3 scripts/ui_driver.py scroll --direction down --udid <UDID>
python3 scripts/ui_driver.py scroll --direction up --distance 500 --udid <UDID>
# Find an element by label (returns coordinates)
python3 scripts/ui_driver.py find --label "Submit" --udid <UDID>
# Check if element exists
python3 scripts/ui_driver.py exists --label "Error" --udid <UDID>
# Type text into focused field
python3 scripts/ui_driver.py type-text --text "hello" --udid <UDID>
# Clear field and type new text
python3 scripts/ui_driver.py clear-and-type --text "new value" --udid <UDID>
# Dump all labels in the UI tree (useful for debugging)
python3 scripts/ui_driver.py dump-labels --udid <UDID>
# Get screen size
python3 scripts/ui_driver.py screen-size --udid <UDID>
Note: tap --label automatically falls back to coordinate-based tapping if AXe's native label tap fails.
sim_controller.py β Simulator Lifecycle
Provides a SimController class for app lifecycle management:
# Launch app
python3 scripts/sim_controller.py launch --bundle-id com.example.app --udid <UDID>
# Terminate app
python3 scripts/sim_controller.py terminate --bundle-id com.example.app --udid <UDID>
# Relaunch (terminate + launch)
python3 scripts/sim_controller.py relaunch --bundle-id com.example.app --udid <UDID>
# Screenshot
python3 scripts/sim_controller.py screenshot --path /tmp/screenshot.png --udid <UDID>
# Clear all app data (uninstall/reinstall β also wipes UserDefaults)
python3 scripts/sim_controller.py clear-data --bundle-id com.example.app --udid <UDID>
# Clear data with explicit app path for reinstall
python3 scripts/sim_controller.py clear-data --bundle-id com.example.app --app-path /path/to/App.app --udid <UDID>
# Read app debug log from container
python3 scripts/sim_controller.py get-log --filename debug.log --bundle-id com.example.app --udid <UDID>
# Boot simulator (if not already booted)
python3 scripts/sim_controller.py boot --udid <UDID>
wait_for.py β Polling Utilities
Wait for UI elements to appear or disappear before interacting:
# Wait for element to appear (default 8s timeout)
python3 scripts/wait_for.py element --label "Welcome" --udid <UDID>
# Wait with custom timeout
python3 scripts/wait_for.py element --label "Loaded" --timeout 15 --udid <UDID>
# Wait for element to disappear (e.g. loading spinner)
python3 scripts/wait_for.py gone --label "Loading..." --udid <UDID>
Important: All script paths above are relative to this skill's directory. When running them, use the full path to the scripts/ folder within this skill. For example, if this skill is installed at /path/to/skills/ios-e2e/, run python3 /path/to/skills/ios-e2e/scripts/ui_driver.py ....
Testing Workflow
A typical interactive testing session:
- Boot & launch: Boot the simulator, install and launch your app
- Inspect: Run
axe describe-uito see what's on screen - Interact: Tap buttons, type text, swipe β verify each step
- Screenshot: Capture screenshots to verify visual state
- Repeat: Navigate through flows, checking state at each step
Common Gotchas
- AXe returns a JSON array β
describe-uioutput is[{...}], not{...}. Always index[0]. - Field names are prefixed β
AXLabelandAXValue, notlabelandvalue. Children are underchildren, notAXChildren. axe typetext is positional βaxe type "hello" --udid X, NOTaxe type --text "hello" --udid X.- Tab bars may not be in the accessibility tree β some custom tab bars require coordinate-based taps instead of label-based.
- Apps don't always sync on launch β if your app uses server-side data, you may need to pull-to-refresh after launch to trigger a sync.
- Wait before asserting β UI updates are async. Use the
wait_for.pyscript or add short delays after interactions. - Terminate before data setup β if your app has background sync, terminate it before writing test data to the backend to avoid database lock contention.
- UserDefaults persists outside the app container β deleting container files won't clear UserDefaults on the simulator. Use uninstall/reinstall (
clear-datacommand) for a true clean slate. - Multiple elements with same label β buttons and headings often share labels (e.g. "Create Account"). Use
find_all_elements()and pick the right match (usually the last one is the button).
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.