feat: add Mermaid diagrams and test automation agents

- Convert 10 ASCII decision trees to Mermaid flowcharts across docs
- Add vitepress-plugin-mermaid for diagram rendering
- Add test-runner agent for xcodebuild test execution
- Add test-debugger agent for closed-loop test debugging
- Add run-tests command
- Add axiom-axe-ref skill for AXe CLI reference
- Add axiom-xctest-automation skill for XCUITest patterns
- Add axiom-ui-recording skill for Xcode 26 recording workflow
- Update simulator-tester and build-fixer agents with JSON patterns
- Bump version to 2.17.0
This commit is contained in:
Charles Wiltgen
2026-01-11 12:46:18 -08:00
parent 686caf354c
commit 0e147eae14
27 changed files with 4070 additions and 190 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"plugins": [
{
"name": "axiom",
"version": "2.16.0",
"version": "2.17.0",
"source": "./.claude-plugin/plugins/axiom",
"description": "Battle-tested Claude Code agents, skills, and references for modern xOS (iOS, iPadOS, watchOS, tvOS) development",
"author": {
@@ -91,8 +91,8 @@ ps -eo pid,etime,command | grep -E "xcodebuild|Simulator" | grep -v grep
# 2. Check Derived Data size (>10GB = stale)
du -sh ~/Library/Developer/Xcode/DerivedData
# 3. Check simulator states (stuck Booting?)
xcrun simctl list devices | grep -E "Booted|Booting|Shutting Down"
# 3. Check simulator states (stuck Booting?) - JSON for reliable parsing
xcrun simctl list devices -j | jq '.devices | to_entries[] | .value[] | select(.state == "Booted" or .state == "Booting" or .state == "Shutting Down") | {name, udid, state}'
```
### Interpreting Results
@@ -164,6 +164,42 @@ xcodebuild clean build -scheme <ACTUAL_SCHEME_NAME> \
-allowProvisioningUpdates
```
**Downloading Simulator Runtimes (CI/CD Setup):**
For CI/CD environments that need specific simulator runtimes:
```bash
# Download iOS simulator runtime for current Xcode
xcodebuild -downloadPlatform iOS
# Download specific iOS version
xcodebuild -downloadPlatform iOS -buildVersion 18.0
# Download to specific location (for caching/sharing)
xcodebuild -downloadPlatform iOS -exportPath ~/Downloads
# Download universal variant (works on Intel + Apple Silicon)
xcodebuild -downloadPlatform iOS -architectureVariant universal
# Download all platforms at once
xcodebuild -downloadAllPlatforms
# After downloading, install with three steps:
# 1. Select Xcode version
xcode-select -s /Applications/Xcode.app
# 2. Run first launch setup
xcodebuild -runFirstLaunch
# 3. Import platform (if downloaded to custom location)
xcodebuild -importPlatform "~/Downloads/iOS 18 Simulator Runtime.dmg"
# Check for newer components between releases
xcodebuild -runFirstLaunch -checkForNewerComponents
```
**Use for**: CI/CD initial setup, missing simulator errors, version-specific testing
**Red Flags for CI/CD:**
- "Works locally but fails in CI" → Usually SPM cache or Xcode version mismatch
- "Intermittent CI failures" → Network issues downloading packages
@@ -263,18 +299,26 @@ If user reports "Unable to boot simulator" or simulators stuck:
# Shutdown all simulators
xcrun simctl shutdown all
# List devices to verify
xcrun simctl list devices
# List devices with JSON for reliable parsing
xcrun simctl list devices -j | jq '.devices | to_entries[] | .value[] | select(.isAvailable == true) | {name, udid, state}'
# If specific simulator is stuck, get its UUID from the list above
# Example output: iPhone 16 (12345678-ABCD-EFGH-IJKL-123456789ABC) (Booted)
# The UUID is the part in first parentheses: 12345678-ABCD-EFGH-IJKL-123456789ABC
# Get UUID for a specific device (e.g., iPhone 16) using JSON
UDID=$(xcrun simctl list devices -j | jq -r '.devices | to_entries[] | .value[] | select(.name | contains("iPhone 16")) | select(.isAvailable == true) | .udid' | head -1)
# Extract UUID for a specific device (e.g., iPhone 16)
xcrun simctl list devices | grep "iPhone 16" | grep -o -E '([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})'
if [ -z "$UDID" ]; then
echo "No iPhone 16 simulator found. Available simulators:"
xcrun simctl list devices -j | jq '.devices | to_entries[] | .value[] | select(.isAvailable == true) | {name, udid}'
else
echo "iPhone 16 UUID: $UDID"
# Erase the stuck simulator using the extracted UUID
xcrun simctl erase "$UDID"
fi
# Or manually copy UUID from the list, then erase it
xcrun simctl erase <UUID>
# Find and erase all simulators stuck in Booting state
xcrun simctl list devices -j | jq -r '.devices | to_entries[] | .value[] | select(.state == "Booting") | .udid' | while read UDID; do
echo "Erasing stuck simulator: $UDID"
xcrun simctl erase "$UDID"
done
# Nuclear option if nothing works
killall -9 Simulator
@@ -419,3 +463,18 @@ Common errors and their fixes:
- Skip the verification step
- Leave user without clear next steps
- Use placeholder scheme names in commands
## Resources
**WWDC**: 2019-413 (Testing in Xcode)
**Docs**: /xcode/downloading-and-installing-additional-xcode-components, /xcode/troubleshooting-simulator
**Tech Notes**: TN2339 (Building from Command Line with Xcode)
## Related
For test execution: `test-runner` agent
For test debugging: `test-debugger` agent
For simulator testing: `simulator-tester` agent
For SPM conflicts: `spm-conflict-resolver` agent
@@ -23,6 +23,7 @@ tools:
- Read
skills:
- axiom-ios-testing
- axiom-axe-ref
hooks:
PreToolUse:
- matcher: Bash
@@ -44,21 +45,35 @@ You are an expert at using the iOS Simulator for automated testing and closed-lo
## Mandatory First Steps
**ALWAYS run these checks FIRST**:
**ALWAYS run these checks FIRST** (using JSON for reliable parsing):
```bash
# List available simulators
xcrun simctl list devices available | grep -E "iPhone|iPad"
# List available simulators with structured output
xcrun simctl list devices -j | jq '.devices | to_entries[] | .value[] | select(.isAvailable == true) | {name, udid, state}'
# Check booted simulators
xcrun simctl list devices | grep Booted
xcrun simctl list devices -j | jq '.devices | to_entries[] | .value[] | select(.state == "Booted") | {name, udid}'
# Boot if needed
# Get specific device UDID for commands
UDID=$(xcrun simctl list devices -j | jq -r '.devices | to_entries[] | .value[] | select(.state == "Booted") | .udid' | head -1)
# Boot if needed (get UDID first, then boot)
xcrun simctl boot "iPhone 16 Pro"
# Check for AXe (enables UI automation if available)
if command -v axe &> /dev/null; then
echo "AXe available - UI automation enabled (tap, swipe, type, describe-ui)"
AXE_AVAILABLE=true
else
echo "AXe not installed - using simctl only (install: brew install cameroncooke/axe/axe)"
AXE_AVAILABLE=false
fi
```
**Common fix**: "Unable to boot" → `xcrun simctl shutdown all && killall -9 Simulator`
**Why JSON?** Text parsing with grep is fragile and breaks when Apple changes output format. JSON output (`-j`) is stable and machine-readable.
## Capabilities
### 1. Screenshot Capture
@@ -140,6 +155,102 @@ xcrun simctl spawn booted log stream --predicate 'subsystem == "com.example.Your
ls -lt "$HOME/Library/Logs/DiagnosticReports/"*.crash 2>/dev/null | head -5
```
### 10. App Inventory & Diagnostics
```bash
# List all installed apps on booted simulator
xcrun simctl listapps booted
# Get app container path (useful for inspecting sandbox)
xcrun simctl get_app_container booted com.example.YourApp data
xcrun simctl get_app_container booted com.example.YourApp app
# Get detailed app info
xcrun simctl appinfo booted com.example.YourApp
# Comprehensive system diagnostics (no archive = faster)
xcrun simctl diagnose --no-archive
```
**Use for**: Verifying app installation, inspecting app data, deep debugging
### 11. Simulator Management
```bash
# Clone simulator for test variants
xcrun simctl clone <source-udid> "Test Variant - Dark Mode"
# List available runtimes
xcrun simctl list runtimes -j | jq '.runtimes[] | {name, identifier, isAvailable}'
# Add CA certificate for proxy testing
xcrun simctl keychain booted add-root-cert /path/to/ca.pem
```
### 12. UI Automation with AXe (Optional)
**Installation:**
```bash
# Install AXe via Homebrew
brew install cameroncooke/axe/axe
# Verify installation
axe --version
```
**Check availability:** `command -v axe`
```bash
# Discover UI elements first (get accessibility identifiers)
axe describe-ui --udid $UDID
# Tap by accessibility identifier (RECOMMENDED - stable)
axe tap --id "loginButton" --udid $UDID
# Tap by label
axe tap --label "Submit" --udid $UDID
# Tap at coordinates (less stable)
axe tap -x 200 -y 400 --udid $UDID
# Long press
axe tap -x 200 -y 400 --duration 1.0 --udid $UDID
# Gesture presets
axe gesture scroll-down --udid $UDID # Scroll content down
axe gesture scroll-up --udid $UDID # Scroll content up
axe gesture swipe-from-left-edge --udid $UDID # Back navigation
# Custom swipe
axe swipe --start-x 200 --start-y 600 --end-x 200 --end-y 200 --udid $UDID
# Type text (field must be focused first)
axe tap --id "emailTextField" --udid $UDID
axe type "user@example.com" --udid $UDID
# Press Return key
axe key 40 --udid $UDID
# Hardware buttons
axe button home --udid $UDID
axe button lock --udid $UDID
axe button siri --udid $UDID
```
**Use for**: Automated UI flows when XCUITest not available, quick manual automation
### 13. Video Streaming with AXe (Optional)
```bash
# Stream video at 10 FPS (for monitoring)
axe stream-video --fps 10 --udid $UDID
# Record video (H.264)
axe record-video --output /tmp/recording.mp4 --udid $UDID
# Press Ctrl+C to stop
# Screenshot (alternative to simctl)
axe screenshot --output /tmp/screenshot.png --udid $UDID
```
**Use for**: Live monitoring, recording test flows, capturing evidence
## Test Workflow
1. **Setup**: Check simulator state, boot if needed
@@ -184,6 +295,49 @@ ls -lt "$HOME/Library/Logs/DiagnosticReports/"*.crash 2>/dev/null | head -5
5. Read and analyze screenshots (you're multimodal)
6. Ask for bundle ID if not provided
## Comprehensive Diagnostics (simctl diagnose)
For deep troubleshooting and bug reports, use `simctl diagnose` to collect logs and system state.
```bash
# Basic diagnostic collection (opens archive in Finder when done)
xcrun simctl diagnose
# Faster collection without archive (useful for quick inspection)
xcrun simctl diagnose --no-archive --output /tmp/sim-diag
# Collect from specific device only
xcrun simctl diagnose --udid $UDID
# Include app data containers (warning: may include private data)
xcrun simctl diagnose --data-container
# Full collection with no timeout (for complex issues)
xcrun simctl diagnose -X --all-logs
```
### Best Practices for Diagnostic Collection
1. **Leave affected simulator booted** — More information collected from booted devices
2. **Enable verbose logging first** — For hard-to-reproduce issues:
```bash
xcrun simctl logverbose booted enable
# Reboot simulator, reproduce issue, then run diagnose
xcrun simctl diagnose
```
3. **Collect right after reproducing** — Logs rotate, so capture immediately
4. **Use --no-archive for quick inspection** — Faster when you just need to check logs
### What's Collected
- System logs and crash reports
- Simulator configuration and state
- Device logs from booted simulators
- CoreSimulator service logs
- Optionally: app data containers (--data-container)
**Use for**: Filing Apple bug reports, debugging simulator infrastructure issues, investigating crashes that happen before your code runs
## Error Quick Reference
| Symptom | Fix |
@@ -194,7 +348,34 @@ ls -lt "$HOME/Library/Logs/DiagnosticReports/"*.crash 2>/dev/null | head -5
| Deep link doesn't work | Check URL scheme in Info.plist |
| Push fails | Validate JSON: `python -m json.tool < push.json` |
## Example Interaction
**User**: "Take a screenshot to verify my login fix works"
**Your response**:
1. Check simulator state: `xcrun simctl list devices -j | jq '...'`
2. Boot if needed or confirm booted simulator
3. Wait for UI to stabilize: `sleep 2`
4. Capture screenshot: `xcrun simctl io booted screenshot /tmp/login-verify-$(date +%s).png`
5. Read and analyze the screenshot (you're multimodal)
6. Report findings:
- Screenshot shows login screen loaded correctly
- "Login" button is visible and enabled
- No error messages displayed
- Result: ✅ Fix verified
## Resources
**WWDC**: 2020-10647 (Become a Simulator expert)
**Docs**: /xcode/running-your-app-in-simulator-or-on-a-device
## Related
**Optional Tools:**
- **AXe**: `brew install cameroncooke/axe/axe` — UI automation CLI
For deep link debugging: `axiom-deep-link-debugging` skill
For build issues: `build-fixer` agent
For AXe reference: `axiom-axe-ref` skill
For running tests: `test-runner` agent
@@ -0,0 +1,338 @@
---
name: test-debugger
description: |
Use this agent for closed-loop test debugging - automatically analyzes test failures, suggests fixes, and re-runs tests until passing. Combines test-runner with intelligent failure analysis using screenshots, logs, and pattern recognition.
<example>
user: "My LoginTests are failing, help me fix them"
assistant: [Launches test-debugger agent]
</example>
<example>
user: "Debug why testCheckout keeps timing out"
assistant: [Launches test-debugger agent]
</example>
<example>
user: "Fix my flaky UI tests"
assistant: [Launches test-debugger agent]
</example>
Explicit command: Users can also invoke this agent directly with `/axiom:run-tests` (for debugging, specify the failing test)
model: sonnet
color: magenta
tools:
- Bash
- Read
- Grep
- Glob
- Edit
skills:
- axiom-ios-testing
- axiom-xctest-automation
hooks:
PreToolUse:
- matcher: Bash
hooks:
- type: command
command: "bash -c 'if echo \"$TOOL_INPUT_COMMAND\" | grep -qE \"rm -rf.*xcresult\"; then echo \"Warning: About to delete test results.\"; fi; exit 0'"
---
# Test Debugger Agent
You are an expert at closed-loop test debugging - running tests, analyzing failures, applying fixes, and iterating until tests pass.
## Core Principle
**Closed-loop debugging flow:**
```
RUN → CAPTURE → ANALYZE → SUGGEST → FIX → VERIFY → REPORT
↑ |
└──────────────── (if still failing) ─────────┘
```
## Your Mission
1. Run the failing test(s)
2. Capture failure evidence (screenshots, logs)
3. Analyze failures using pattern recognition
4. Suggest specific fixes
5. Apply fixes (with user confirmation)
6. Re-run to verify
7. Report final status
## Phase 1: Run Tests
```bash
# Get booted simulator
BOOTED_UDID=$(xcrun simctl list devices -j | jq -r '.devices | to_entries[] | .value[] | select(.state == "Booted") | .udid' | head -1)
# Create result bundle
RESULT_PATH="/tmp/debug-test-$(date +%s).xcresult"
# Run specific failing tests
xcodebuild test \
-scheme "<SCHEME_NAME>UITests" \
-destination "platform=iOS Simulator,id=$BOOTED_UDID" \
-resultBundlePath "$RESULT_PATH" \
-only-testing:"<TARGET>/<TestClass>/<testMethod>" \
2>&1 | tee /tmp/xcodebuild-debug.log
echo "Results: $RESULT_PATH"
```
## Phase 2: Capture Evidence
```bash
# Export failure attachments
ATTACHMENTS_DIR="/tmp/debug-failures-$(date +%s)"
mkdir -p "$ATTACHMENTS_DIR"
xcrun xcresulttool export attachments \
--path "$RESULT_PATH" \
--output-path "$ATTACHMENTS_DIR" \
--only-failures
# Read manifest
cat "$ATTACHMENTS_DIR/manifest.json" | jq '.attachments[] | {name, testName, uniformTypeIdentifier}'
# Get console logs
xcrun xcresulttool get log --path "$RESULT_PATH" --type console > "$ATTACHMENTS_DIR/console.log"
# Get detailed test results
xcrun xcresulttool get test-results tests --path "$RESULT_PATH" > "$ATTACHMENTS_DIR/test-results.txt"
```
## Phase 3: Analyze Failures
### Failure Pattern Recognition
| Pattern | Error Message | Root Cause | Fix |
|---------|---------------|------------|-----|
| **Element Not Found** | `Failed to find element` | Missing accessibilityIdentifier | Add identifier to element |
| **Timeout** | `Timed out waiting for element` | Slow app, short timeout | Increase timeout, optimize app |
| **State Mismatch** | `Expected X, got Y` | Race condition | Add explicit wait |
| **Not Hittable** | `Element exists but not hittable` | Element obscured | Dismiss keyboard/sheet, scroll |
| **Stale Element** | `Element no longer attached` | View refreshed | Re-query element |
| **Wrong Query** | `Multiple matches found` | Ambiguous query | Use more specific identifier |
### Analysis Workflow
```bash
# 1. Check error message
grep -A5 "Failure:" /tmp/xcodebuild-debug.log
# 2. Find file and line
grep -E "\.swift:[0-9]+" /tmp/xcodebuild-debug.log
# 3. Read the test code
# (Use Read tool on the file:line from above)
# 4. Analyze screenshot
# (Read the exported screenshot - you're multimodal)
```
## Phase 4: Suggest Fixes
Based on pattern analysis, suggest specific code changes:
### Element Not Found Fix
```swift
// BEFORE (missing identifier)
Button("Login") { ... }
// AFTER (with identifier)
Button("Login") { ... }
.accessibilityIdentifier("loginButton")
```
### Timeout Fix
```swift
// BEFORE (might timeout)
XCTAssertTrue(element.exists)
// AFTER (explicit wait)
XCTAssertTrue(element.waitForExistence(timeout: 10))
```
### Not Hittable Fix
```swift
// BEFORE (might be obscured)
button.tap()
// AFTER (wait for hittable)
let predicate = NSPredicate(format: "isHittable == true")
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: button)
_ = XCTWaiter.wait(for: [expectation], timeout: 5)
button.tap()
// Or dismiss keyboard first
if app.keyboards.count > 0 {
app.toolbars.buttons["Done"].tap()
}
```
### Race Condition Fix
```swift
// BEFORE (race condition)
button.tap()
XCTAssertTrue(resultLabel.exists)
// AFTER (wait for result)
button.tap()
XCTAssertTrue(resultLabel.waitForExistence(timeout: 5))
```
## Phase 5: Apply Fixes
1. **Show proposed change** to user
2. **Get confirmation** before editing
3. **Apply edit** using Edit tool
4. **Log the change** for verification
```markdown
## Proposed Fix
**File**: `LoginTests.swift:47`
**Issue**: Missing waitForExistence before tap
**Change**:
```diff
- loginButton.tap()
+ XCTAssertTrue(loginButton.waitForExistence(timeout: 5))
+ loginButton.tap()
```
Shall I apply this fix?
```
## Phase 6: Verify Fix
```bash
# Re-run ONLY the failing test
xcodebuild test \
-scheme "<SCHEME_NAME>UITests" \
-destination "platform=iOS Simulator,id=$BOOTED_UDID" \
-resultBundlePath "/tmp/verify-$(date +%s).xcresult" \
-only-testing:"<TARGET>/<TestClass>/<testMethod>"
# Check result
xcrun xcresulttool get test-results summary --path /tmp/verify-*.xcresult
```
## Phase 7: Report
```markdown
## Test Debugging Complete
### Original Failures
- [TestClass/testMethod]: [original error]
### Fixes Applied
1. **LoginTests.swift:47** — Added waitForExistence before tap
2. **ProfileTests.swift:23** — Added accessibilityIdentifier "profileButton"
### Verification
- **Rerun Result**: ✅ PASS (2/2 tests)
- **Duration**: 45s (was 60s with failures)
### Remaining Issues
- None (all tests passing)
### Recommendations
1. Add accessibilityIdentifier to all interactive elements
2. Always use waitForExistence before interactions
3. Consider adding test helpers for common patterns
```
## Decision Tree
```
User reports test failure
Run test with result bundle
Check result:
├─ Build failed → Delegate to build-fixer agent
├─ Tests passed → Report success
└─ Tests failed:
├─ Export failure attachments
├─ Analyze error pattern:
│ ├─ Element not found → Check for accessibilityIdentifier
│ ├─ Timeout → Check wait/timeout values
│ ├─ Not hittable → Check for obscuring elements
│ └─ State mismatch → Check for race conditions
├─ Read failure screenshot (multimodal analysis)
├─ Read test source code
├─ Suggest specific fix
├─ Get user approval
├─ Apply fix
└─ Re-run test (loop back if still failing)
```
## Integration with Other Skills
When analyzing failures, consider:
- **axiom-xctest-automation**: Best practices for element queries, waiting
- **axiom-ui-testing**: Condition-based waiting patterns
- **axiom-swift-concurrency**: Async test patterns, race conditions
- **axiom-swiftui-debugging**: View update issues in UI tests
## Guidelines
1. **Always export attachments** - Screenshots are invaluable
2. **Read screenshots** - You're multimodal, analyze them
3. **One fix at a time** - Don't batch multiple changes
4. **Verify each fix** - Re-run after each change
5. **Get user confirmation** - Before editing code
6. **Max 3 iterations** - If still failing, escalate to user
7. **Log all changes** - For audit trail
**Never**:
- Apply fixes without analyzing the failure first
- Edit code without user confirmation
- Skip the verification re-run after a fix
- Batch multiple fixes before verifying each one works
- Continue beyond 3 failed iterations without escalating
## Error Quick Reference
| Symptom | Quick Check | Likely Fix |
|---------|-------------|------------|
| "Failed to find element" | Screenshot shows element? | Add accessibilityIdentifier |
| "Timed out" | Check app loading | Increase timeout or optimize |
| "Not hittable" | Keyboard visible? | Dismiss keyboard |
| "Multiple matches" | Generic query? | Use specific identifier |
| "Test hangs" | Infinite wait? | Add timeout, check deadlock |
## Example Interaction
**User**: "My testLoginWithValidCredentials keeps timing out"
**Your response**:
1. Run the specific test with result bundle
2. Export failure screenshot
3. Read screenshot - see if login form loaded
4. Read test code - find the timeout line
5. Analyze: timeout is 5s but app loads slowly
6. Suggest: Increase timeout to 15s or add loading indicator check
7. Get user confirmation
8. Apply fix
9. Re-run test
10. Report pass/fail
## Resources
**WWDC**: 2019-413 (Testing in Xcode), 2025-344 (Record, replay, and review)
**Skills**: axiom-ios-testing, axiom-xctest-automation
## Related
For test execution: `test-runner` agent
For simulator issues: `simulator-tester` agent
For build issues: `build-fixer` agent
@@ -0,0 +1,364 @@
---
name: test-runner
description: |
Use this agent when the user wants to run XCUITests, parse test results, view test failures, or export test attachments. Runs xcodebuild test and parses .xcresult bundles using xcresulttool for structured test results, failure analysis, and attachment export.
<example>
user: "Run my UI tests and show me what failed"
assistant: [Launches test-runner agent]
</example>
<example>
user: "Run tests for the LoginTests scheme"
assistant: [Launches test-runner agent]
</example>
<example>
user: "Export the failure screenshots from my last test run"
assistant: [Launches test-runner agent]
</example>
<example>
user: "What tests failed and why?"
assistant: [Launches test-runner agent]
</example>
Explicit command: Users can also invoke this agent directly with `/axiom:run-tests`
model: sonnet
color: cyan
tools:
- Bash
- Read
- Grep
- Glob
skills:
- axiom-ios-testing
- axiom-xctest-automation
hooks:
PreToolUse:
- matcher: Bash
hooks:
- type: command
command: "bash -c 'if echo \"$TOOL_INPUT_COMMAND\" | grep -qE \"rm -rf.*xcresult\"; then echo \"Warning: About to delete test results.\"; fi; exit 0'"
---
# Test Runner Agent
You are an expert at running XCUITests and analyzing test results using xcodebuild and xcresulttool.
## Your Mission
1. Discover available test schemes and targets
2. Run tests with proper result bundle configuration
3. Parse test results for failures
4. Export failure attachments (screenshots, videos)
5. Provide actionable analysis
## Mandatory First Steps
**ALWAYS run these checks FIRST** to understand the project:
```bash
# 1. Verify project directory
ls -la | grep -E "\.xcodeproj|\.xcworkspace"
# 2. Discover schemes and test targets (JSON for reliable parsing)
xcodebuild -list -json | jq '{schemes: .project.schemes, targets: .project.targets}'
# 3. Check for booted simulator
BOOTED_UDID=$(xcrun simctl list devices -j | jq -r '.devices | to_entries[] | .value[] | select(.state == "Booted") | .udid' | head -1)
if [ -z "$BOOTED_UDID" ]; then
echo "No simulator booted. Boot one first:"
xcrun simctl list devices -j | jq '.devices | to_entries[] | .value[] | select(.isAvailable == true) | {name, udid}' | head -20
else
echo "Using booted simulator: $BOOTED_UDID"
fi
```
## Running Tests
### Basic Test Execution
```bash
# Get the booted simulator UDID
BOOTED_UDID=$(xcrun simctl list devices -j | jq -r '.devices | to_entries[] | .value[] | select(.state == "Booted") | .udid' | head -1)
# Create timestamped result bundle path
RESULT_PATH="/tmp/test-$(date +%s).xcresult"
# Run tests with result bundle
xcodebuild test \
-scheme "<SCHEME_NAME>UITests" \
-destination "platform=iOS Simulator,id=$BOOTED_UDID" \
-resultBundlePath "$RESULT_PATH" \
-enableCodeCoverage YES \
2>&1 | tee /tmp/xcodebuild-test.log
echo "Results saved to: $RESULT_PATH"
```
### Running Specific Tests
```bash
# Run a single test class
xcodebuild test \
-scheme "<SCHEME_NAME>UITests" \
-destination "platform=iOS Simulator,id=$BOOTED_UDID" \
-resultBundlePath "$RESULT_PATH" \
-only-testing:"<TARGET>/LoginTests"
# Run a single test method
xcodebuild test \
-scheme "<SCHEME_NAME>UITests" \
-destination "platform=iOS Simulator,id=$BOOTED_UDID" \
-resultBundlePath "$RESULT_PATH" \
-only-testing:"<TARGET>/LoginTests/testLoginWithValidCredentials"
# Skip specific tests
xcodebuild test \
-scheme "<SCHEME_NAME>UITests" \
-destination "platform=iOS Simulator,id=$BOOTED_UDID" \
-resultBundlePath "$RESULT_PATH" \
-skip-testing:"<TARGET>/SlowTests"
```
## Parsing Test Results with xcresulttool
### Get Test Summary
```bash
# Overall summary (pass/fail counts, duration)
xcrun xcresulttool get test-results summary --path "$RESULT_PATH"
```
Output format:
```
Test Results Summary:
Start Time: 2026-01-11 10:30:00
End Time: 2026-01-11 10:35:00
Tests: 42
Passed: 39
Failed: 3
Skipped: 0
```
### Get All Test Details
```bash
# Detailed test information (all tests with status)
xcrun xcresulttool get test-results tests --path "$RESULT_PATH"
```
### Get Specific Test Details
```bash
# First, get test IDs from the tests list
xcrun xcresulttool get test-results tests --path "$RESULT_PATH" | grep -E "testId|name"
# Then get details for a specific test
xcrun xcresulttool get test-results test-details \
--test-id "<TEST_ID>" \
--path "$RESULT_PATH"
```
### Export Failure Attachments
```bash
# Create output directory
ATTACHMENTS_DIR="/tmp/test-failures-$(date +%s)"
mkdir -p "$ATTACHMENTS_DIR"
# Export only failure attachments (screenshots, videos)
xcrun xcresulttool export attachments \
--path "$RESULT_PATH" \
--output-path "$ATTACHMENTS_DIR" \
--only-failures
# Read the manifest to understand what was exported
cat "$ATTACHMENTS_DIR/manifest.json" | jq '.attachments[] | {name, testName, uniformTypeIdentifier}'
echo "Failure attachments exported to: $ATTACHMENTS_DIR"
```
### Export All Attachments
```bash
# Export all attachments (not just failures)
xcrun xcresulttool export attachments \
--path "$RESULT_PATH" \
--output-path "$ATTACHMENTS_DIR"
```
### Export Code Coverage
```bash
COVERAGE_DIR="/tmp/coverage-$(date +%s)"
mkdir -p "$COVERAGE_DIR"
xcrun xcresulttool export coverage \
--path "$RESULT_PATH" \
--output-path "$COVERAGE_DIR"
echo "Coverage data exported to: $COVERAGE_DIR"
```
### Get Console Logs
```bash
# Get console output from tests
xcrun xcresulttool get log --path "$RESULT_PATH" --type console
```
## Common Failure Patterns
### Element Not Found
**Symptom**: `Failed to find element: Button with identifier 'loginButton'`
**Diagnosis**:
1. Missing accessibilityIdentifier
2. Element not visible (off-screen, hidden)
3. Wrong query (label changed, localization)
**Quick Fix**: Add accessibilityIdentifier to the element in code
### Timeout Waiting for Element
**Symptom**: `Timed out waiting for element to exist`
**Diagnosis**:
1. App is slow (network, animation)
2. Element appears conditionally
3. waitForExistence timeout too short
**Quick Fix**: Increase timeout or add explicit wait
### State Mismatch
**Symptom**: `Expected true, got false` or `Element exists but not hittable`
**Diagnosis**:
1. Race condition (UI updated between check and action)
2. Element behind another element
3. Keyboard covering element
**Quick Fix**: Wait for UI to stabilize, dismiss keyboard
## Output Format
Provide structured test results:
```markdown
## Test Run Results
### Configuration
- **Scheme**: [scheme name]
- **Destination**: [simulator name] ([iOS version])
- **Result Bundle**: [path]
- **Duration**: [time]
### Summary
- **Total**: [count]
- **Passed**: [count] ✅
- **Failed**: [count] ❌
- **Skipped**: [count] ⏭️
### Failures
#### 1. [TestClass/testMethod]
- **File**: [file:line]
- **Error**: [error message]
- **Screenshot**: [path to failure screenshot]
- **Analysis**: [what likely went wrong]
- **Suggested Fix**: [actionable fix]
#### 2. [TestClass/testMethod]
...
### Attachments Exported
- Screenshots: [count]
- Videos: [count]
- Location: [directory path]
### Next Steps
1. [Specific action to fix first failure]
2. [How to rerun just the failing tests]
```
## Decision Tree
```
User wants to run tests
├─ No scheme specified → Discover schemes with xcodebuild -list -json
├─ No simulator booted → List available simulators, suggest boot command
├─ Scheme found + simulator ready → Run xcodebuild test
Tests complete
├─ All passed → Report success summary
├─ Failures detected:
│ ├─ Export failure attachments
│ ├─ Analyze each failure
│ ├─ Categorize by pattern (element not found, timeout, state)
│ └─ Provide specific fix suggestions
└─ Build failed before tests → Delegate to build-fixer agent
```
## Guidelines
1. **ALWAYS use JSON output** for xcodebuild -list and simctl commands
2. **ALWAYS create timestamped result bundles** to preserve history
3. **Export attachments on failure** - screenshots are invaluable for diagnosis
4. **Read failure screenshots** - you're multimodal, analyze them
5. **Provide actionable fixes** - don't just report failures
6. **Suggest rerun commands** - make it easy to verify fixes
**Never**:
- Skip the mandatory first steps (scheme discovery, simulator check)
- Delete xcresult bundles without user permission
- Report "tests failed" without analyzing WHY
- Assume the scheme name - always discover it first
## Integration with Other Agents
- **build-fixer**: If tests fail to build, delegate to build-fixer
- **simulator-tester**: For visual verification and manual testing scenarios
- **test-debugger**: For closed-loop debugging of persistent failures
## Error Quick Reference
| Error | Cause | Fix |
|-------|-------|-----|
| `xcodebuild: error: Could not find scheme` | Wrong scheme name | Run `xcodebuild -list -json` |
| `Unable to boot simulator` | Simulator stuck | Shutdown all, try again |
| `Test target not found` | Missing test target | Check scheme has test action |
| `Code signing error` | Provisioning issue | Use automatic signing |
| `xcresulttool: error: Invalid result bundle` | Corrupt or incomplete | Rerun tests |
## Example Interaction
**User**: "Run the UI tests and tell me what failed"
**Your response**:
1. Discover schemes: `xcodebuild -list -json`
2. Check for booted simulator
3. Run tests: `xcodebuild test -scheme "AppUITests" -resultBundlePath /tmp/test-xxx.xcresult`
4. Parse results: `xcrun xcresulttool get test-results summary`
5. Export failures: `xcrun xcresulttool export attachments --only-failures`
6. Read and analyze failure screenshots
7. Report structured results with fixes
## Resources
**WWDC**: 2019-413 (Testing in Xcode)
**Docs**: /xcode/xcresulttool
**Skills**: axiom-ios-testing, axiom-xctest-automation
## Related
For build issues: `build-fixer` agent
For visual verification: `simulator-tester` agent
For closed-loop debugging: `test-debugger` agent
@@ -1,6 +1,6 @@
{
"name": "axiom",
"version": "2.16.0",
"version": "2.17.0",
"description": "Battle-tested Claude Code skills for modern xOS (iOS, iPadOS, watchOS, tvOS) development",
"author": "Charles Wiltgen",
"license": "MIT",
@@ -9,6 +9,7 @@
"./commands/audit.md",
"./commands/fix-build.md",
"./commands/optimize-build.md",
"./commands/run-tests.md",
"./commands/screenshot.md",
"./commands/status.md",
"./commands/test-simulator.md"
@@ -0,0 +1,58 @@
---
name: run-tests
description: Run XCUITests and parse results using the test-runner agent
arguments:
- name: scheme
description: Test scheme name (optional - will discover available schemes if not provided)
required: false
- name: target
description: Specific test class or method to run (optional)
required: false
allowed_tools:
- Task
---
# Run Tests Command
Runs XCUITests using the test-runner agent.
## Usage
```
/axiom:run-tests [scheme] [target]
```
## Examples
```
/axiom:run-tests
/axiom:run-tests MyAppUITests
/axiom:run-tests MyAppUITests LoginTests
/axiom:run-tests MyAppUITests LoginTests/testLoginWithValidCredentials
```
## Instructions
Launch the test-runner agent to:
1. **Discover schemes** if not provided
2. **Run tests** with the specified scheme/target
3. **Parse results** using xcresulttool
4. **Export failure attachments** (screenshots, videos)
5. **Provide analysis** with actionable fixes
<Task>
subagent_type: axiom:test-runner
prompt: |
{{#if args.scheme}}
Run the UI tests for scheme "{{args.scheme}}"{{#if args.target}} targeting {{args.target}}{{/if}}.
{{else}}
Discover available test schemes and run UI tests. Ask which scheme to use if multiple are available.
{{/if}}
After running tests:
1. Parse results with xcresulttool
2. Export failure attachments
3. Analyze failures and provide specific fixes
4. Show how to rerun just the failing tests
</Task>
+18 -10
View File
@@ -16,8 +16,12 @@ pgrep -f xcodebuild | wc -l
# Derived Data size
du -sh ~/Library/Developer/Xcode/DerivedData 2>/dev/null
# Simulator status
xcrun simctl list devices booted 2>/dev/null | head -5
# Simulator status (JSON for reliable parsing)
xcrun simctl list devices -j | jq '.devices | to_entries[] | .value[] | select(.state == "Booted") | {name, udid}'
# Tool availability
echo "jq: $(command -v jq &>/dev/null && echo 'installed' || echo 'NOT INSTALLED')"
echo "axe: $(command -v axe &>/dev/null && echo 'installed (UI automation available)' || echo 'not installed (optional)')"
```
### Project Analysis
@@ -35,19 +39,23 @@ grep -r "IPHONEOS_DEPLOYMENT_TARGET" *.xcodeproj/project.pbxproj 2>/dev/null | h
### Format as Dashboard
```
📊 Axiom Project Status
═══════════════════════
Axiom Project Status
=====================
🔧 Environment
Xcodebuild processes: [count] [⚠️ if > 3]
Derived Data: [size] [⚠️ if > 10GB]
Environment
Xcodebuild processes: [count] [warning if > 3]
Derived Data: [size] [warning if > 10GB]
Simulators running: [count]
jq: [installed/NOT INSTALLED]
axe: [installed/not installed (optional)]
📱 Project Analysis
Project Analysis
SwiftUI views: [count]
Potential memory patterns: [count] [⚠️ if > 0]
Potential memory patterns: [count] [warning if > 0]
Deployment target: iOS [version]
💡 Suggested Actions
Suggested Actions
[Based on findings, suggest 2-3 most relevant audits or skills]
[If jq not installed: "Install jq for reliable simulator control: brew install jq"]
[If axe installed: "AXe UI automation available for simulator-tester agent"]
```
@@ -1,4 +1,4 @@
2.16.0
120
26
7
2.17.0
123
28
8
@@ -0,0 +1,409 @@
---
name: axiom-axe-ref
description: Use when automating iOS Simulator UI interactions beyond simctl capabilities. Reference for AXe CLI covering accessibility-based tapping, gestures, text input, screenshots, video recording, and UI tree inspection.
version: 1.0.0
category: reference
user-invocable: false
---
# AXe Reference (iOS Simulator UI Automation)
AXe is a CLI tool for interacting with iOS Simulators using Apple's Accessibility APIs and HID functionality. Single binary, no daemon required.
## Installation
```bash
brew install cameroncooke/axe/axe
# Verify installation
axe --version
```
## Critical Best Practice: describe_ui First
**ALWAYS run `describe_ui` before UI interactions.** Never guess coordinates from screenshots.
**Best practice:** Use describe-ui to get precise element coordinates prior to using x/y parameters (don't guess from screenshots).
```bash
# 1. FIRST: Get the UI tree with frame coordinates
axe describe-ui --udid $UDID
# 2. THEN: Tap by accessibility ID (preferred)
axe tap --id "loginButton" --udid $UDID
# 3. OR: Tap by label
axe tap --label "Login" --udid $UDID
# 4. LAST RESORT: Tap by coordinates from describe-ui output
axe tap -x 200 -y 400 --udid $UDID
```
**Priority order for targeting elements:**
1. `--id` (accessibilityIdentifier) - most stable
2. `--label` (accessibility label) - stable but may change with localization
3. `-x -y` coordinates from `describe-ui` - fragile, use only when no identifier
## Core Concept: Accessibility-First
**AXe's key advantage**: Tap elements by accessibility identifier or label, not just coordinates.
```bash
# Coordinate-based (fragile - breaks with layout changes)
axe tap -x 200 -y 400 --udid $UDID
# Accessibility-based (stable - survives UI changes)
axe tap --id "loginButton" --udid $UDID
axe tap --label "Login" --udid $UDID
```
**Always prefer `--id` or `--label` over coordinates.**
## Getting the Simulator UDID
AXe requires the simulator UDID for most commands:
```bash
# Get booted simulator UDID
UDID=$(xcrun simctl list devices -j | jq -r '.devices | to_entries[] | .value[] | select(.state == "Booted") | .udid' | head -1)
# List all simulators
axe list-simulators
```
## Touch & Tap Commands
### Tap by Accessibility Identifier (Recommended)
```bash
# Tap element with accessibilityIdentifier
axe tap --id "loginButton" --udid $UDID
# Tap element with accessibility label
axe tap --label "Submit" --udid $UDID
```
### Tap by Coordinates
```bash
# Basic tap
axe tap -x 200 -y 400 --udid $UDID
# Tap with timing controls
axe tap -x 200 -y 400 --pre-delay 0.5 --post-delay 0.3 --udid $UDID
# Long press (hold duration in seconds)
axe tap -x 200 -y 400 --duration 1.0 --udid $UDID
```
### Low-Level Touch Events
```bash
# Touch down (finger press)
axe touch down -x 200 -y 400 --udid $UDID
# Touch up (finger release)
axe touch up -x 200 -y 400 --udid $UDID
```
## Swipe & Gesture Commands
### Custom Swipe
```bash
# Swipe from point A to point B
axe swipe --start-x 200 --start-y 600 --end-x 200 --end-y 200 --udid $UDID
# Swipe with duration (slower = more visible)
axe swipe --start-x 200 --start-y 600 --end-x 200 --end-y 200 --duration 0.5 --udid $UDID
```
### Gesture Presets
```bash
# Scrolling
axe gesture scroll-up --udid $UDID # Scroll content up (swipe down)
axe gesture scroll-down --udid $UDID # Scroll content down (swipe up)
axe gesture scroll-left --udid $UDID
axe gesture scroll-right --udid $UDID
# Edge swipes (navigation)
axe gesture swipe-from-left-edge --udid $UDID # Back navigation
axe gesture swipe-from-right-edge --udid $UDID
axe gesture swipe-from-top-edge --udid $UDID # Notification Center
axe gesture swipe-from-bottom-edge --udid $UDID # Home indicator/Control Center
```
## Text Input
### Type Text
```bash
# Type text (element must be focused)
axe type "user@example.com" --udid $UDID
# Type with delay between characters
axe type "password123" --char-delay 0.1 --udid $UDID
# Type from stdin
echo "Hello World" | axe type --stdin --udid $UDID
# Type from file
axe type --file /tmp/input.txt --udid $UDID
```
### Keyboard Keys
```bash
# Press specific key by HID keycode
axe key 40 --udid $UDID # Return/Enter
# Common keycodes:
# 40 = Return/Enter
# 41 = Escape
# 42 = Backspace/Delete
# 43 = Tab
# 44 = Space
# 79 = Right Arrow
# 80 = Left Arrow
# 81 = Down Arrow
# 82 = Up Arrow
# Key sequence with timing
axe key-sequence 40 43 40 --delay 0.2 --udid $UDID
```
## Hardware Buttons
```bash
# Home button
axe button home --udid $UDID
# Lock/Power button
axe button lock --udid $UDID
# Long press power (shutdown dialog)
axe button lock --duration 3.0 --udid $UDID
# Side button (iPhone X+)
axe button side-button --udid $UDID
# Siri
axe button siri --udid $UDID
# Apple Pay
axe button apple-pay --udid $UDID
```
## Screenshots
```bash
# Screenshot to auto-named file
axe screenshot --udid $UDID
# Output: screenshot_2026-01-11_143052.png
# Screenshot to specific file
axe screenshot --output /tmp/my-screenshot.png --udid $UDID
# Screenshot to stdout (for piping)
axe screenshot --stdout --udid $UDID > screenshot.png
```
## Video Recording & Streaming
### Record Video
```bash
# Start recording (Ctrl+C to stop)
axe record-video --output /tmp/recording.mp4 --udid $UDID
# Record with quality settings
axe record-video --output /tmp/recording.mp4 --quality high --udid $UDID
# Record with scale (reduce file size)
axe record-video --output /tmp/recording.mp4 --scale 0.5 --udid $UDID
```
### Stream Video
```bash
# Stream at 10 FPS (default)
axe stream-video --udid $UDID
# Stream at specific framerate (1-30 FPS)
axe stream-video --fps 30 --udid $UDID
# Stream formats
axe stream-video --format mjpeg --udid $UDID # MJPEG (default)
axe stream-video --format jpeg --udid $UDID # Individual JPEGs
axe stream-video --format ffmpeg --udid $UDID # FFmpeg compatible
axe stream-video --format bgra --udid $UDID # Raw BGRA
```
## UI Inspection (describe-ui)
**Critical for finding accessibility identifiers and labels.**
### Full Screen UI Tree
```bash
# Get complete accessibility tree
axe describe-ui --udid $UDID
# Output includes:
# - Element type (Button, TextField, StaticText, etc.)
# - Accessibility identifier
# - Accessibility label
# - Frame (position and size)
# - Enabled/disabled state
```
### Point-Specific UI Info
```bash
# Get element at specific coordinates
axe describe-ui --point 200,400 --udid $UDID
```
### Example Output
```json
{
"type": "Button",
"identifier": "loginButton",
"label": "Login",
"frame": {"x": 150, "y": 380, "width": 100, "height": 44},
"enabled": true,
"focused": false
}
```
## Common Workflows
### Login Flow
```bash
UDID=$(xcrun simctl list devices -j | jq -r '.devices | to_entries[] | .value[] | select(.state == "Booted") | .udid' | head -1)
# Tap email field and type
axe tap --id "emailTextField" --udid $UDID
axe type "user@example.com" --udid $UDID
# Tap password field and type
axe tap --id "passwordTextField" --udid $UDID
axe type "password123" --udid $UDID
# Tap login button
axe tap --id "loginButton" --udid $UDID
# Wait and screenshot
sleep 2
axe screenshot --output /tmp/login-result.png --udid $UDID
```
### Discover Elements Before Automating
```bash
# 1. Get the UI tree
axe describe-ui --udid $UDID > /tmp/ui-tree.json
# 2. Find elements (search for identifiers)
cat /tmp/ui-tree.json | jq '.[] | select(.identifier != null) | {identifier, label, type}'
# 3. Use discovered identifiers in automation
axe tap --id "discoveredIdentifier" --udid $UDID
```
### Scroll to Find Element
```bash
# Scroll down until element appears (pseudo-code pattern)
for i in {1..5}; do
if axe describe-ui --udid $UDID | grep -q "targetElement"; then
axe tap --id "targetElement" --udid $UDID
break
fi
axe gesture scroll-down --udid $UDID
sleep 0.5
done
```
### Screenshot on Error
```bash
# Automation with error capture
if ! axe tap --id "submitButton" --udid $UDID; then
axe screenshot --output /tmp/error-state.png --udid $UDID
axe describe-ui --udid $UDID > /tmp/error-ui-tree.json
echo "Failed to tap submitButton - see error-state.png"
fi
```
## Timing Controls
Most commands support timing options:
| Option | Description |
|--------|-------------|
| `--pre-delay` | Wait before action (seconds) |
| `--post-delay` | Wait after action (seconds) |
| `--duration` | Action duration (for taps, button presses) |
| `--char-delay` | Delay between characters (for type) |
```bash
# Example with full timing control
axe tap --id "button" --pre-delay 0.5 --post-delay 0.3 --udid $UDID
```
## AXe vs simctl
| Capability | simctl | AXe |
|------------|--------|-----|
| Device lifecycle | ✅ | ❌ |
| Permissions | ✅ | ❌ |
| Push notifications | ✅ | ❌ |
| Status bar | ✅ | ❌ |
| Deep links | ✅ | ❌ |
| Screenshots | ✅ | ✅ (PNG) |
| Video recording | ✅ | ✅ (H.264) |
| Video streaming | ❌ | ✅ |
| UI tap/swipe | ❌ | ✅ |
| Type text | ❌ | ✅ |
| Hardware buttons | ❌ | ✅ |
| Accessibility tree | ❌ | ✅ |
**Use both together**: simctl for device control, AXe for UI automation.
## Troubleshooting
### Element Not Found
1. Run `axe describe-ui` to see available elements
2. Check element has `accessibilityIdentifier` set in code
3. Ensure element is visible (not off-screen)
### Tap Doesn't Work
1. Check element is enabled (`"enabled": true` in describe-ui)
2. Try adding `--pre-delay 0.5` for slow-loading UI
3. Verify correct UDID with `axe list-simulators`
### Type Not Working
1. Ensure text field is focused first: `axe tap --id "textField"`
2. Check keyboard is visible
3. Try `--char-delay 0.05` for reliability
### Permission Denied
AXe uses private APIs - ensure you're running on a Mac with Xcode installed and proper entitlements.
## Resources
**GitHub**: https://github.com/cameroncooke/AXe
**Related**: xcsentinel (build orchestration)
**Skills**: axiom-xctest-automation, axiom-ui-testing
**Agents**: simulator-tester, test-runner
@@ -113,6 +113,66 @@ This router invokes specialized skills based on the specific testing need:
---
### 6. Running XCUITests from Command Line → **test-runner** (Agent)
**Triggers**:
- Run tests with xcodebuild
- Parse xcresult bundles
- Export failure screenshots/videos
- Code coverage reports
- CI/CD test execution
**Why test-runner**: Specialized agent for command-line test execution with xcresulttool parsing.
**Invoke**: Launch `test-runner` agent
---
### 7. Closed-Loop Test Debugging → **test-debugger** (Agent)
**Triggers**:
- Fix failing tests automatically
- Debug persistent test failures
- Run → analyze → fix → verify cycle
- Need to iterate until tests pass
- Analyze failure screenshots
**Why test-debugger**: Automated cycle of running tests, analyzing failures, suggesting fixes, and re-running.
**Invoke**: Launch `test-debugger` agent
---
### 8. Recording UI Automation (Xcode 26) → **ui-recording**
**Triggers**:
- Record user interactions in Xcode
- Test plans for multi-config replay
- Video review of test runs
- Xcode 26 recording workflow
- Enhancing recorded test code
**Why ui-recording**: Focused guide for Xcode 26's Record/Replay/Review workflow.
**Invoke**: Read the `axiom-ui-recording` skill
---
### 9. UI Automation Without XCUITest → **simulator-tester** + **axe-ref**
**Triggers**:
- Automate app without test target
- AXe CLI usage (tap, swipe, type)
- describe-ui for accessibility tree
- Quick automation outside XCUITest
- Scripted simulator interactions
**Why simulator-tester + axe-ref**: AXe provides accessibility-based UI automation when XCUITest isn't available.
**Invoke**: Launch `simulator-tester` agent (uses axiom-axe-ref)
---
## Decision Tree
```
@@ -135,8 +195,20 @@ User has testing question
├─ Tests crash or environment seems wrong?
│ └─ YES → xcode-debugging (via ios-build)
─ Tests are slow, want to speed them up?
└─ YES → swift-testing (Fast Tests section)
─ Tests are slow, want to speed them up?
└─ YES → swift-testing (Fast Tests section)
├─ Run tests from command line / CI / parse results?
│ └─ YES → test-runner (Agent)
├─ Fix failing tests automatically / closed-loop debugging?
│ └─ YES → test-debugger (Agent)
├─ Record UI interactions in Xcode 26?
│ └─ YES → ui-recording
└─ Automate without XCUITest / use AXe CLI?
└─ YES → simulator-tester + axe-ref
```
## Swift Testing vs XCTest Quick Guide
@@ -194,3 +266,27 @@ User: "Should I use Swift Testing or XCTest?"
User: "Tests crash before any assertions"
→ Invoke: axiom-xcode-debugging
User: "Run my tests and show me what failed"
→ Invoke: test-runner (Agent)
User: "Help me fix these failing tests"
→ Invoke: test-debugger (Agent)
User: "Parse the xcresult from my last test run"
→ Invoke: test-runner (Agent)
User: "Export failure screenshots from my tests"
→ Invoke: test-runner (Agent)
User: "How do I record UI automation in Xcode 26?"
→ Invoke: axiom-ui-recording
User: "How do I use test plans for multi-language testing?"
→ Invoke: axiom-ui-recording
User: "Can I automate my app without writing XCUITests?"
→ Invoke: simulator-tester (Agent) + axiom-axe-ref
User: "How do I tap a button using AXe?"
→ Invoke: axiom-axe-ref (via simulator-tester)
@@ -0,0 +1,431 @@
---
name: axiom-ui-recording
description: Use when setting up UI test recording in Xcode 26, enhancing recorded tests for stability, or configuring test plans for multi-configuration replay. Based on WWDC 2025-344 "Record, replay, and review".
version: 1.0.0
category: testing
user-invocable: false
---
# Recording UI Automation (Xcode 26+)
Guide to Xcode 26's Recording UI Automation feature for creating UI tests through user interaction recording.
## The Three-Phase Workflow
From WWDC 2025-344:
```
┌─────────────────────────────────────────────────────────────┐
│ UI Automation Workflow │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. RECORD ──────► Interact with app in Simulator │
│ Xcode captures as Swift test code │
│ │
│ 2. REPLAY ──────► Run across devices, languages, configs │
│ Using test plans for multi-config │
│ │
│ 3. REVIEW ──────► Watch video recordings in test report │
│ Analyze failures with screenshots │
│ │
└─────────────────────────────────────────────────────────────┘
```
## Phase 1: Recording
### Starting a Recording
1. Open your UI test file in Xcode
2. Place cursor inside a test method
3. **Debug → Record UI Automation** (or use the record button)
4. App launches in Simulator
5. Perform interactions - Xcode generates code
6. Stop recording when done
### What Gets Recorded
- **Taps** on buttons, cells, controls
- **Text input** into text fields
- **Swipes** and scrolling
- **Gestures** (pinch, rotate)
- **Hardware button presses** (Home, volume)
### Generated Code Example
```swift
// Xcode generates this from your interactions
func testLoginFlow() {
let app = XCUIApplication()
app.launch()
// Recorded: Tap email field, type email
app.textFields["Email"].tap()
app.textFields["Email"].typeText("user@example.com")
// Recorded: Tap password field, type password
app.secureTextFields["Password"].tap()
app.secureTextFields["Password"].typeText("password123")
// Recorded: Tap login button
app.buttons["Login"].tap()
}
```
## Enhancing Recorded Code
**Critical**: Recorded code is often fragile. Always enhance it for stability.
### 1. Add Accessibility Identifiers
Recorded code uses labels which break with localization:
```swift
// RECORDED (fragile - breaks with localization)
app.buttons["Login"].tap()
// ENHANCED (stable - uses identifier)
app.buttons["loginButton"].tap()
```
**Add identifiers in your app code:**
```swift
// SwiftUI
Button("Login") { ... }
.accessibilityIdentifier("loginButton")
// UIKit
loginButton.accessibilityIdentifier = "loginButton"
```
### 2. Add waitForExistence
Recorded code assumes elements exist immediately:
```swift
// RECORDED (may fail if app is slow)
app.buttons["Login"].tap()
// ENHANCED (waits for element)
let loginButton = app.buttons["loginButton"]
XCTAssertTrue(loginButton.waitForExistence(timeout: 5))
loginButton.tap()
```
### 3. Add Assertions
Recorded code just performs actions without verification:
```swift
// RECORDED (no verification)
app.buttons["Login"].tap()
// ENHANCED (with assertion)
app.buttons["loginButton"].tap()
let welcomeLabel = app.staticTexts["welcomeLabel"]
XCTAssertTrue(welcomeLabel.waitForExistence(timeout: 10),
"Welcome screen should appear after login")
```
### 4. Use Shorter Queries
Recorded code may have overly specific queries:
```swift
// RECORDED (too specific)
app.tables.cells.element(boundBy: 0).buttons["Action"].tap()
// ENHANCED (simpler)
app.buttons["actionButton"].tap()
```
## Query Selection Guidelines
From WWDC 2025-344:
| Scenario | Problem | Solution |
|----------|---------|----------|
| Localized strings | "Login" changes by language | Use accessibilityIdentifier |
| Deeply nested views | Long query chains break easily | Use shortest possible query |
| Dynamic content | Cell content changes | Use identifier or generic query |
| Multiple matches | Query returns many elements | Add unique identifier |
### Best Practices
1. **Prefer identifiers over labels**
2. **Use the shortest query that works**
3. **Avoid index-based queries** (`element(boundBy: 0)`)
4. **Add identifiers to dynamic content**
## Phase 2: Replay with Test Plans
Test plans allow running the same tests across multiple configurations.
### Creating a Test Plan
1. **File → New → File → Test Plan**
2. Add test targets
3. Configure configurations
### Test Plan Structure
```json
{
"configurations": [
{
"name": "iPhone - English",
"options": {
"targetForVariableExpansion": {
"containerPath": "container:MyApp.xcodeproj",
"identifier": "MyApp"
},
"language": "en",
"region": "US"
}
},
{
"name": "iPhone - Spanish",
"options": {
"language": "es",
"region": "ES"
}
},
{
"name": "iPhone - Dark Mode",
"options": {
"userInterfaceStyle": "dark"
}
},
{
"name": "iPad - Landscape",
"options": {
"defaultTestExecutionTimeAllowance": 120,
"testTimeoutsEnabled": true
}
}
],
"defaultOptions": {
"targetForVariableExpansion": {
"containerPath": "container:MyApp.xcodeproj",
"identifier": "MyApp"
}
},
"testTargets": [
{
"target": {
"containerPath": "container:MyApp.xcodeproj",
"identifier": "MyAppUITests",
"name": "MyAppUITests"
}
}
],
"version": 1
}
```
### Configuration Options
| Option | Purpose |
|--------|---------|
| `language` | Test localization |
| `region` | Test regional formatting |
| `userInterfaceStyle` | Test dark/light mode |
| `targetForVariableExpansion` | App target for configuration |
| `testTimeoutsEnabled` | Enable timeout enforcement |
| `defaultTestExecutionTimeAllowance` | Timeout in seconds |
### Running with Test Plan
```bash
# Command line
xcodebuild test \
-scheme "MyApp" \
-testPlan "MyTestPlan" \
-destination "platform=iOS Simulator,name=iPhone 16" \
-resultBundlePath /tmp/results.xcresult
# In Xcode
# Product → Test Plan → Select your plan
# Then Cmd+U to run tests
```
## Phase 3: Review
### Test Report Features
After tests complete:
1. **View test results** in Report Navigator
2. **Watch video recordings** of each test
3. **See screenshots** at failure points
4. **Analyze timeline** of actions
### Enabling Attachments
In test plan or scheme:
```json
"options": {
"systemAttachmentLifetime": "keepAlways",
"userAttachmentLifetime": "keepAlways"
}
```
### Capturing Custom Screenshots
```swift
func testCheckout() {
// ... actions ...
// Manual screenshot at specific point
let screenshot = app.screenshot()
let attachment = XCTAttachment(screenshot: screenshot)
attachment.name = "Checkout Confirmation"
attachment.lifetime = .keepAlways
add(attachment)
}
```
## Common Patterns
### Login Flow Template
```swift
func testLoginWithValidCredentials() throws {
let app = XCUIApplication()
app.launch()
// Navigate to login
let showLoginButton = app.buttons["showLoginButton"]
XCTAssertTrue(showLoginButton.waitForExistence(timeout: 5))
showLoginButton.tap()
// Enter credentials
let emailField = app.textFields["emailTextField"]
XCTAssertTrue(emailField.waitForExistence(timeout: 5))
emailField.tap()
emailField.typeText("test@example.com")
let passwordField = app.secureTextFields["passwordTextField"]
passwordField.tap()
passwordField.typeText("password123")
// Submit
app.buttons["loginButton"].tap()
// Verify success
let welcomeScreen = app.staticTexts["welcomeLabel"]
XCTAssertTrue(welcomeScreen.waitForExistence(timeout: 10))
}
```
### Navigation Flow Template
```swift
func testNavigateToSettings() throws {
let app = XCUIApplication()
app.launch()
// Open tab bar item
app.tabBars.buttons["Settings"].tap()
// Verify navigation
let settingsTitle = app.navigationBars["Settings"]
XCTAssertTrue(settingsTitle.waitForExistence(timeout: 5))
// Navigate deeper
app.tables.cells["Account"].tap()
XCTAssertTrue(app.navigationBars["Account"].exists)
}
```
### Form Validation Template
```swift
func testFormValidation() throws {
let app = XCUIApplication()
app.launch()
// Submit empty form
app.buttons["submitButton"].tap()
// Verify error appears
let errorAlert = app.alerts["Error"]
XCTAssertTrue(errorAlert.waitForExistence(timeout: 5))
XCTAssertTrue(errorAlert.staticTexts["Please fill all fields"].exists)
// Dismiss alert
errorAlert.buttons["OK"].tap()
}
```
## Troubleshooting
### Recording Doesn't Start
1. Ensure you're in a test method
2. Check simulator is available
3. Verify app builds and runs
4. Try restarting Xcode
### Recorded Code Doesn't Work
1. **Add waitForExistence** before interactions
2. **Check accessibility identifiers** are set
3. **Simplify queries** to shortest form
4. **Run app manually** to verify flow works
### Tests Pass Locally, Fail in CI
1. **Increase timeouts** for slower CI machines
2. **Add explicit waits** for animations
3. **Check simulator configuration** matches
4. **Disable animations** in test setup:
```swift
app.launchArguments = ["--disable-animations"]
```
## Anti-Patterns
### Don't Use Raw Recorded Code in CI
```swift
// BAD - Raw recorded code
app.buttons["Login"].tap()
app.textFields["Email"].typeText("user@example.com")
// GOOD - Enhanced for CI
let loginButton = app.buttons["loginButton"]
XCTAssertTrue(loginButton.waitForExistence(timeout: 10))
loginButton.tap()
```
### Don't Hardcode Coordinates
```swift
// BAD - Coordinates from recording
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
// GOOD - Use element queries
app.buttons["centerButton"].tap()
```
### Don't Skip Assertions
```swift
// BAD - Actions only
app.buttons["Login"].tap()
sleep(2) // Hope it works
// GOOD - Verify outcomes
app.buttons["loginButton"].tap()
XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 10))
```
## Resources
**WWDC**: 2025-344, 2024-10206, 2019-413
**Docs**: /xcode/testing/recording-ui-tests, /xctest/xcuiapplication
**Skills**: axiom-xctest-automation, axiom-ui-testing
@@ -0,0 +1,445 @@
---
name: axiom-xctest-automation
description: Use when writing, running, or debugging XCUITests. Covers element queries, waiting strategies, accessibility identifiers, test plans, and CI/CD test execution patterns.
version: 1.0.0
category: testing
user-invocable: false
---
# XCUITest Automation Patterns
Comprehensive guide to writing reliable, maintainable UI tests with XCUITest.
## Core Principle
**Reliable UI tests require three things**:
1. Stable element identification (accessibilityIdentifier)
2. Condition-based waiting (never hardcoded sleep)
3. Clean test isolation (no shared state)
## Element Identification
### The Accessibility Identifier Pattern
**ALWAYS use accessibilityIdentifier for test-critical elements.**
```swift
// SwiftUI
Button("Login") { ... }
.accessibilityIdentifier("loginButton")
TextField("Email", text: $email)
.accessibilityIdentifier("emailTextField")
// UIKit
loginButton.accessibilityIdentifier = "loginButton"
emailTextField.accessibilityIdentifier = "emailTextField"
```
### Query Selection Guidelines
From WWDC 2025-344 "Recording UI Automation":
1. **Localized strings change** → Use accessibilityIdentifier instead
2. **Deeply nested views** → Use shortest possible query
3. **Dynamic content** → Use generic query or identifier
```swift
// BAD - Fragile queries
app.buttons["Login"] // Breaks with localization
app.tables.cells.element(boundBy: 0).buttons.firstMatch // Too specific
// GOOD - Stable queries
app.buttons["loginButton"] // Uses identifier
app.tables.cells.containing(.staticText, identifier: "itemTitle").firstMatch
```
## Waiting Strategies
### Never Use sleep()
```swift
// BAD - Hardcoded wait
sleep(5)
XCTAssertTrue(app.buttons["submit"].exists)
// GOOD - Condition-based wait
let submitButton = app.buttons["submit"]
XCTAssertTrue(submitButton.waitForExistence(timeout: 5))
```
### Wait Patterns
```swift
// Wait for element to appear
func waitForElement(_ element: XCUIElement, timeout: TimeInterval = 10) -> Bool {
element.waitForExistence(timeout: timeout)
}
// Wait for element to disappear
func waitForElementToDisappear(_ element: XCUIElement, timeout: TimeInterval = 10) -> Bool {
let predicate = NSPredicate(format: "exists == false")
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element)
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
return result == .completed
}
// Wait for element to be hittable (visible AND enabled)
func waitForElementHittable(_ element: XCUIElement, timeout: TimeInterval = 10) -> Bool {
let predicate = NSPredicate(format: "isHittable == true")
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element)
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
return result == .completed
}
// Wait for text to appear anywhere
func waitForText(_ text: String, timeout: TimeInterval = 10) -> Bool {
app.staticTexts[text].waitForExistence(timeout: timeout)
}
```
### Async Operations
```swift
// Wait for network response
func waitForNetworkResponse() {
let loadingIndicator = app.activityIndicators["loadingIndicator"]
// Wait for loading to start
_ = loadingIndicator.waitForExistence(timeout: 5)
// Wait for loading to finish
_ = waitForElementToDisappear(loadingIndicator, timeout: 30)
}
```
## Test Structure
### Setup and Teardown
```swift
class LoginTests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
// Reset app state for clean test
app.launchArguments = ["--uitesting", "--reset-state"]
app.launchEnvironment = ["DISABLE_ANIMATIONS": "1"]
app.launch()
}
override func tearDownWithError() throws {
// Capture screenshot on failure
if testRun?.failureCount ?? 0 > 0 {
let screenshot = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: screenshot)
attachment.name = "Failure Screenshot"
attachment.lifetime = .keepAlways
add(attachment)
}
app.terminate()
}
}
```
### Test Method Pattern
```swift
func testLoginWithValidCredentials() throws {
// ARRANGE - Navigate to login screen
let loginButton = app.buttons["showLoginButton"]
XCTAssertTrue(loginButton.waitForExistence(timeout: 5))
loginButton.tap()
// ACT - Enter credentials and submit
let emailField = app.textFields["emailTextField"]
XCTAssertTrue(emailField.waitForExistence(timeout: 5))
emailField.tap()
emailField.typeText("user@example.com")
let passwordField = app.secureTextFields["passwordTextField"]
passwordField.tap()
passwordField.typeText("password123")
app.buttons["loginSubmitButton"].tap()
// ASSERT - Verify successful login
let welcomeLabel = app.staticTexts["welcomeLabel"]
XCTAssertTrue(welcomeLabel.waitForExistence(timeout: 10))
XCTAssertTrue(welcomeLabel.label.contains("Welcome"))
}
```
## Common Interactions
### Text Input
```swift
// Clear and type
let textField = app.textFields["emailTextField"]
textField.tap()
textField.clearText() // Custom extension
textField.typeText("new@email.com")
// Extension to clear text
extension XCUIElement {
func clearText() {
guard let stringValue = value as? String else { return }
tap()
let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)
typeText(deleteString)
}
}
```
### Scrolling
```swift
// Scroll until element is visible
func scrollToElement(_ element: XCUIElement, in scrollView: XCUIElement) {
while !element.isHittable {
scrollView.swipeUp()
}
}
// Scroll to specific element
let targetCell = app.tables.cells["targetItem"]
let table = app.tables.firstMatch
scrollToElement(targetCell, in: table)
targetCell.tap()
```
### Alerts and Sheets
```swift
// Handle system alert
addUIInterruptionMonitor(withDescription: "Permission Alert") { alert in
if alert.buttons["Allow"].exists {
alert.buttons["Allow"].tap()
return true
}
return false
}
app.tap() // Trigger the monitor
// Handle app alert
let alert = app.alerts["Error"]
if alert.waitForExistence(timeout: 5) {
alert.buttons["OK"].tap()
}
```
### Keyboard Dismissal
```swift
// Dismiss keyboard
if app.keyboards.count > 0 {
app.toolbars.buttons["Done"].tap()
// Or tap outside
// app.tap()
}
```
## Test Plans
### Multi-Configuration Testing
Test plans allow running the same tests with different configurations:
```xml
<!-- TestPlan.xctestplan -->
{
"configurations" : [
{
"name" : "English",
"options" : {
"language" : "en",
"region" : "US"
}
},
{
"name" : "Spanish",
"options" : {
"language" : "es",
"region" : "ES"
}
},
{
"name" : "Dark Mode",
"options" : {
"userInterfaceStyle" : "dark"
}
}
],
"testTargets" : [
{
"target" : {
"containerPath" : "container:MyApp.xcodeproj",
"identifier" : "MyAppUITests",
"name" : "MyAppUITests"
}
}
]
}
```
### Running with Test Plan
```bash
xcodebuild test \
-scheme "MyApp" \
-testPlan "MyTestPlan" \
-destination "platform=iOS Simulator,name=iPhone 16" \
-resultBundlePath /tmp/results.xcresult
```
## CI/CD Integration
### Parallel Test Execution
```bash
xcodebuild test \
-scheme "MyAppUITests" \
-destination "platform=iOS Simulator,name=iPhone 16" \
-parallel-testing-enabled YES \
-maximum-parallel-test-targets 4 \
-resultBundlePath /tmp/results.xcresult
```
### Retry Failed Tests
```bash
xcodebuild test \
-scheme "MyAppUITests" \
-destination "platform=iOS Simulator,name=iPhone 16" \
-retry-tests-on-failure \
-test-iterations 3 \
-resultBundlePath /tmp/results.xcresult
```
### Code Coverage
```bash
xcodebuild test \
-scheme "MyAppUITests" \
-destination "platform=iOS Simulator,name=iPhone 16" \
-enableCodeCoverage YES \
-resultBundlePath /tmp/results.xcresult
# Export coverage report
xcrun xcresulttool export coverage \
--path /tmp/results.xcresult \
--output-path /tmp/coverage
```
## Debugging Failed Tests
### Capture Screenshots
```swift
// Manual screenshot capture
let screenshot = app.screenshot()
let attachment = XCTAttachment(screenshot: screenshot)
attachment.name = "Before Login"
attachment.lifetime = .keepAlways
add(attachment)
```
### Capture Videos
Enable in test plan or scheme:
```xml
"systemAttachmentLifetime" : "keepAlways",
"userAttachmentLifetime" : "keepAlways"
```
### Print Element Hierarchy
```swift
// Debug: Print all elements
print(app.debugDescription)
// Debug: Print specific container
print(app.tables.firstMatch.debugDescription)
```
## Anti-Patterns to Avoid
### 1. Hardcoded Delays
```swift
// BAD
sleep(5)
button.tap()
// GOOD
XCTAssertTrue(button.waitForExistence(timeout: 5))
button.tap()
```
### 2. Index-Based Queries
```swift
// BAD - Breaks if order changes
app.tables.cells.element(boundBy: 0)
// GOOD - Uses identifier
app.tables.cells["firstItem"]
```
### 3. Shared State Between Tests
```swift
// BAD - Tests depend on order
func test1_CreateItem() { ... }
func test2_EditItem() { ... } // Depends on test1
// GOOD - Independent tests
func testCreateItem() {
// Creates own item
}
func testEditItem() {
// Creates item, then edits
}
```
### 4. Testing Implementation Details
```swift
// BAD - Tests internal structure
XCTAssertEqual(app.tables.cells.count, 10)
// GOOD - Tests user-visible behavior
XCTAssertTrue(app.staticTexts["10 items"].exists)
```
## Recording UI Automation (Xcode 26+)
From WWDC 2025-344:
1. **Record** — Record interactions in Xcode (Debug → Record UI Automation)
2. **Replay** — Run across devices/languages/configurations via test plans
3. **Review** — Watch video recordings in test report
### Enhancing Recorded Code
```swift
// RECORDED (may be fragile)
app.buttons["Login"].tap()
// ENHANCED (stable)
let loginButton = app.buttons["loginButton"]
XCTAssertTrue(loginButton.waitForExistence(timeout: 5))
loginButton.tap()
```
## Resources
**WWDC**: 2025-344, 2024-10206, 2023-10175, 2019-413
**Docs**: /xctest/xcuiapplication, /xctest/xcuielement, /xctest/xcuielementquery
**Skills**: axiom-ui-testing, axiom-swift-testing
+4 -3
View File
@@ -1,6 +1,7 @@
import { defineConfig } from 'vitepress'
import { withMermaid } from 'vitepress-plugin-mermaid'
export default defineConfig({
export default withMermaid(defineConfig({
title: 'Axiom',
description: 'Battle-tested Claude Code skills, autonomous agents, and references for Apple platform development',
base: '/Axiom/',
@@ -273,7 +274,7 @@ export default defineConfig({
footer: {
message: 'Released under the MIT License',
copyright: 'Copyright © 2026 Charles Wiltgen • v2.16.0'
copyright: 'Copyright © 2026 Charles Wiltgen • v2.17.0'
}
}
})
}))
+4 -4
View File
@@ -1,7 +1,7 @@
{
"disciplineSkills": 75,
"referenceSkills": 30,
"disciplineSkills": 77,
"referenceSkills": 31,
"diagnosticSkills": 15,
"commands": 7,
"agents": 26
"commands": 8,
"agents": 28
}
+12 -10
View File
@@ -63,16 +63,18 @@ Questions you can ask Claude that will draw from this skill:
### Decision Tree
```
Constraint error in console?
├─ Can't identify which views?
│ └─ Use Symbolic Breakpoint + Memory Address Identification
├─ Constraint conflicts shown?
│ └─ Use Constraint Priority Resolution
├─ Ambiguous layout (multiple solutions)?
│ └─ Use _autolayoutTrace to find missing constraints
└─ Views positioned incorrectly but no errors?
└─ Use Debug View Hierarchy + Show Constraints
```mermaid
flowchart TD
A[Constraint error in console?] --> B{Symptom}
B -->|Can't identify which views| C[Use Symbolic Breakpoint +<br/>Memory Address Identification]
B -->|Constraint conflicts shown| D[Use Constraint Priority Resolution]
B -->|Ambiguous layout<br/>multiple solutions| E[Use _autolayoutTrace to<br/>find missing constraints]
B -->|Views positioned incorrectly<br/>but no errors| F[Use Debug View Hierarchy +<br/>Show Constraints]
style C fill:#cce5ff
style D fill:#cce5ff
style E fill:#cce5ff
style F fill:#cce5ff
```
### Symbolic Breakpoint Setup (One-Time)
+21 -15
View File
@@ -59,21 +59,27 @@ Questions you can ask Claude that will draw from this skill:
### Decision Tree
```
Build failing?
├─ "No such module XYZ"?
│ ├─ After adding SPM package?
│ │ └─ Clean build folder + reset package caches
│ ├─ After pod install?
│ │ └─ Check Podfile.lock conflicts
│ └─ Framework not found?
│ └─ Check FRAMEWORK_SEARCH_PATHS
├─ "Multiple commands produce"?
│ └─ Duplicate files in target membership
├─ SPM resolution hangs?
│ └─ Clear package caches + derived data
└─ Version conflicts?
└─ Use dependency resolution strategies
```mermaid
flowchart TD
A[Build failing?] --> B{"No such module XYZ"?}
A --> C{"Multiple commands produce"?}
A --> D{SPM resolution hangs?}
A --> E{Version conflicts?}
B -->|After adding SPM package| F[Clean build folder +<br/>reset package caches]
B -->|After pod install| G[Check Podfile.lock conflicts]
B -->|Framework not found| H[Check FRAMEWORK_SEARCH_PATHS]
C --> I[Duplicate files in<br/>target membership]
D --> J[Clear package caches +<br/>derived data]
E --> K[Use dependency<br/>resolution strategies]
style F fill:#d4edda
style G fill:#d4edda
style H fill:#d4edda
style I fill:#d4edda
style J fill:#d4edda
style K fill:#d4edda
```
### SPM Package Not Found Fix
+40 -24
View File
@@ -74,30 +74,46 @@ Questions you can ask Claude that will draw from this skill:
### Energy Decision Tree
```
Power Profiler shows high impact in:
├─ CPU lane?
│ ├─ Continuous processing → Timer leak or polling loop
│ ├─ Spikes during actions → Eager loading or repeated parsing
└─ Background CPU → BGTasks running too long
├─ GPU lane?
│ ├─ Animations running → Check visibility, frame rate
│ ├─ Blur effects → Over dynamic content
│ └─ Shadows/masks → Complex compositing
├─ Network lane?
│ ├─ Frequent activity → Polling instead of push
│ ├─ Many small requests → Batching issue
│ └─ Background network → Missing discretionary flag
├─ Location lane?
│ ├─ Continuous updates → Use significant-change monitoring
│ └─ High accuracy always → Reduce when not needed
└─ Display lane?
├─ Light backgrounds on OLED → Consider dark mode
└─ Always-on features → Reduce refresh
```mermaid
flowchart TD
A[Power Profiler shows<br/>high impact in:] --> B{Lane}
B -->|CPU| C{Pattern}
C -->|Continuous processing| C1[Timer leak or<br/>polling loop]
C -->|Spikes during actions| C2[Eager loading or<br/>repeated parsing]
C -->|Background CPU| C3[BGTasks running<br/>too long]
B -->|GPU| D{Pattern}
D -->|Animations running| D1[Check visibility,<br/>frame rate]
D -->|Blur effects| D2[Over dynamic content]
D -->|Shadows/masks| D3[Complex compositing]
B -->|Network| E{Pattern}
E -->|Frequent activity| E1[Polling instead<br/>of push]
E -->|Many small requests| E2[Batching issue]
E -->|Background network| E3[Missing discretionary<br/>flag]
B -->|Location| F{Pattern}
F -->|Continuous updates| F1[Use significant-change<br/>monitoring]
F -->|High accuracy always| F2[Reduce when<br/>not needed]
B -->|Display| G{Pattern}
G -->|Light backgrounds on OLED| G1[Consider dark mode]
G -->|Always-on features| G2[Reduce refresh]
style C1 fill:#f8d7da
style C2 fill:#f8d7da
style C3 fill:#f8d7da
style D1 fill:#fff3cd
style D2 fill:#fff3cd
style D3 fill:#fff3cd
style E1 fill:#cce5ff
style E2 fill:#cce5ff
style E3 fill:#cce5ff
style F1 fill:#d4edda
style F2 fill:#d4edda
style G1 fill:#e2e3e5
style G2 fill:#e2e3e5
```
### Quick Power Profiler Workflow
+14 -12
View File
@@ -36,18 +36,20 @@ Questions you can ask Claude that will draw from this skill:
### Decision Tree
```
App performance problem?
├─ App feels slow or lags
│ └─ → Time Profiler (CPU usage)
├─ Memory grows over time
│ └─ → Allocations (object creation)
├─ Data loading is slow
│ └─ → Core Data instrument (if using Core Data)
├─ Battery drains fast
│ └─ → Energy Impact
└─ Scrolling stutters
└─ → Time Profiler + SwiftUI Instrument
```mermaid
flowchart TD
A[App performance problem?] --> B{Symptom}
B -->|App feels slow or lags| C[Time Profiler<br/>CPU usage]
B -->|Memory grows over time| D[Allocations<br/>object creation]
B -->|Data loading is slow| E[Core Data instrument<br/>if using Core Data]
B -->|Battery drains fast| F[Energy Impact]
B -->|Scrolling stutters| G[Time Profiler +<br/>SwiftUI Instrument]
style C fill:#cce5ff
style D fill:#cce5ff
style E fill:#cce5ff
style F fill:#cce5ff
style G fill:#cce5ff
```
### Time Profiler Deep Dive
+18 -12
View File
@@ -102,18 +102,24 @@ MPNowPlayingInfoCenter.default().nowPlayingInfo = [
### Decision Tree
```
Now Playing not working?
├─ Info never appears?
│ ├─ Category .ambient or .mixWithOthers? → Remove .mixWithOthers
│ ├─ No command handlers? → Add target + isEnabled
│ └─ Background mode missing? → Add "audio" to Info.plist
├─ Commands grayed out?
│ └─ isEnabled = false → Set to true
├─ Artwork missing/flickering?
│ └─ MPMediaItemArtwork block issues → Single source + cancellation
└─ State out of sync?
└─ Using playbackState? → Use playbackRate (iOS ignores playbackState)
```mermaid
flowchart TD
A[Now Playing not working?] --> B{Symptom}
B -->|Info never appears| C{Check}
B -->|Commands grayed out| D["isEnabled = false<br/>→ Set to true"]
B -->|Artwork missing/flickering| E["MPMediaItemArtwork block issues<br/>→ Single source + cancellation"]
B -->|State out of sync| F["Using playbackState?<br/>→ Use playbackRate"]
C -->|Category .ambient<br/>or .mixWithOthers| G[Remove .mixWithOthers]
C -->|No command handlers| H[Add target + isEnabled]
C -->|Background mode missing| I["Add 'audio' to Info.plist"]
style D fill:#d4edda
style E fill:#d4edda
style F fill:#fff3cd
style G fill:#d4edda
style H fill:#d4edda
style I fill:#d4edda
```
## Documentation Scope
+18 -9
View File
@@ -15,15 +15,24 @@ Use this skill when:
## Quick Decision Tree
```
Has your type...
├─ All properties Codable? → Automatic synthesis (add : Codable)
├─ Property names differ from JSON? → CodingKeys customization
├─ Needs to exclude properties? → CodingKeys customization
├─ Enum with associated values? → Check enum synthesis patterns
├─ Needs structural transformation? → Manual implementation + bridge types
├─ Needs data not in JSON? → DecodableWithConfiguration (iOS 15+)
└─ Complex nested JSON? → Manual implementation + nested containers
```mermaid
flowchart TD
A[Has your type...] --> B{Characteristic}
B -->|All properties Codable| C["Automatic synthesis<br/>add : Codable"]
B -->|Property names differ from JSON| D[CodingKeys customization]
B -->|Needs to exclude properties| E[CodingKeys customization]
B -->|Enum with associated values| F[Check enum synthesis patterns]
B -->|Needs structural transformation| G[Manual implementation +<br/>bridge types]
B -->|Needs data not in JSON| H["DecodableWithConfiguration<br/>(iOS 15+)"]
B -->|Complex nested JSON| I[Manual implementation +<br/>nested containers]
style C fill:#d4edda
style D fill:#cce5ff
style E fill:#cce5ff
style F fill:#fff3cd
style G fill:#f8d7da
style H fill:#fff3cd
style I fill:#f8d7da
```
## What This Skill Covers
+26 -20
View File
@@ -61,30 +61,36 @@ Questions you can ask Claude that will draw from this skill:
### Background Color Decision Tree
```
Is your app media-focused (photos, videos, music)?
├─ Yes → Consider permanent dark appearance
.preferredColorScheme(.dark) on root view
│ EXAMPLES: Apple Music, Photos, Clock
└─ No → Use system backgrounds (respects user preference)
systemBackground (adapts automatically)
systemGroupedBackground (iOS Settings-style lists)
```mermaid
flowchart TD
A["Is your app media-focused?<br/>(photos, videos, music)"] --> B{Answer}
B -->|Yes| C["Consider permanent dark appearance<br/>.preferredColorScheme(.dark)"]
B -->|No| D["Use system backgrounds<br/>(respects user preference)"]
C --> E["EXAMPLES:<br/>Apple Music, Photos, Clock"]
D --> F["systemBackground (adapts automatically)<br/>systemGroupedBackground (Settings-style)"]
style C fill:#1a1a2e,color:#fff
style D fill:#d4edda
style E fill:#2d2d44,color:#fff
style F fill:#d4edda
```
### Color Selection Decision Tree
```
Do you need a specific color value?
├─ No → Use semantic colors
│ label, secondaryLabel, tertiaryLabel
│ systemBackground, secondarySystemBackground
│ WHY: Adapts to light/dark/high contrast
└─ Yes → Create Color Set in asset catalog
1. Open Assets.xcassets
2. Add Color Set
3. Configure light/dark/high contrast variants
```mermaid
flowchart TD
A[Do you need a specific color value?] --> B{Answer}
B -->|No| C["Use semantic colors<br/>label, secondaryLabel, tertiaryLabel<br/>systemBackground, secondarySystemBackground"]
B -->|Yes| D["Create Color Set in asset catalog"]
C --> E["WHY: Adapts to<br/>light/dark/high contrast"]
D --> F["1. Open Assets.xcassets<br/>2. Add Color Set<br/>3. Configure variants"]
style C fill:#d4edda
style D fill:#cce5ff
style E fill:#d4edda
style F fill:#cce5ff
```
### Font Weight Decision
+27 -26
View File
@@ -105,36 +105,37 @@ struct ColorView: View {
### Architecture Decision Tree
```
How complex is your presentation logic?
├─ Simple (mostly data display)?
└─ Use Apple's vanilla @Observable patterns
├─ Medium (some formatting, validation)?
│ └─ Extract to @Observable model classes
├─ Complex (multiple async flows, complex state)?
│ ├─ Small team familiar with Swift?
│ │ └─ MVVM with @Observable
│ └─ Large team, need strict patterns?
│ └─ Consider TCA
└─ Complex navigation between features?
└─ Add Coordinator pattern
```mermaid
flowchart TD
A[How complex is your<br/>presentation logic?] --> B{Complexity}
B -->|Simple| C[Use Apple's vanilla<br/>@Observable patterns]
B -->|Medium| D[Extract to @Observable<br/>model classes]
B -->|Complex state| E{Team size?}
B -->|Complex navigation| F[Add Coordinator pattern]
E -->|Small team| G[MVVM with @Observable]
E -->|Large team| H[Consider TCA]
style C fill:#d4edda
style D fill:#d4edda
style G fill:#d4edda
style H fill:#fff3cd
style F fill:#d4edda
```
### Property Wrapper Decision Tree
```
Where does this data come from?
├─ View-local, temporary?
└─ @State
├─ Shared dependency (database, services)?
│ └─ @Environment
├─ Need two-way binding to @Observable?
│ └─ @Bindable
└─ Read-only data from parent?
└─ Plain property (no wrapper)
```mermaid
flowchart TD
A[Where does this<br/>data come from?] --> B{Source}
B -->|View-local, temporary| C["@State"]
B -->|Shared dependency| D["@Environment"]
B -->|Two-way binding<br/>to @Observable| E["@Bindable"]
B -->|Read-only from parent| F[Plain property<br/>no wrapper]
style C fill:#cce5ff
style D fill:#cce5ff
style E fill:#cce5ff
style F fill:#e2e3e5
```
## Documentation Scope
+17 -9
View File
@@ -64,15 +64,23 @@ Questions you can ask Claude that will draw from this skill:
### Diagnosing View Not Updating
```
View not updating?
├─ Can reproduce in preview?
├─ YES: Problem is in code
│ │ ├─ Modified struct directly? → Struct Mutation
│ │ ├─ Passed binding to child? → Lost Binding Identity
│ │ ├─ View inside conditional? → Accidental Recreation
│ │ └─ Object changed but view didn't? → Missing Observer
│ └─ NO: Likely cache/Xcode state → See Preview Crashes
```mermaid
flowchart TD
A[View not updating?] --> B{Can reproduce<br/>in preview?}
B -->|YES| C[Problem is in code]
B -->|NO| D[Likely cache/Xcode state<br/>See Preview Crashes]
C --> E{Check pattern}
E -->|Modified struct directly?| F[Struct Mutation]
E -->|Passed binding to child?| G[Lost Binding Identity]
E -->|View inside conditional?| H[Accidental Recreation]
E -->|Object changed but view didn't?| I[Missing Observer]
style F fill:#f8d7da
style G fill:#f8d7da
style H fill:#f8d7da
style I fill:#f8d7da
style D fill:#fff3cd
```
```swift
+12 -10
View File
@@ -103,16 +103,18 @@ XCTAssertTrue(submitButton.waitForExistence(timeout: 5))
### Decision Tree
```
Test failing?
├─ Element not found?
└─ Use waitForExistence(timeout:) not sleep()
├─ Passes locally, fails CI?
│ └─ Replace sleep() with condition polling
├─ Animation causing issues?
│ └─ Wait for animation completion, don't disable
└─ Network request timing?
└─ Use XCTestExpectation or waitForExistence
```mermaid
flowchart TD
A[Test failing?] --> B{Symptom}
B -->|Element not found| C["Use waitForExistence(timeout:)<br/>not sleep()"]
B -->|Passes locally, fails CI| D["Replace sleep() with<br/>condition polling"]
B -->|Animation causing issues| E["Wait for animation completion<br/>don't disable"]
B -->|Network request timing| F["Use XCTestExpectation<br/>or waitForExistence"]
style C fill:#d4edda
style D fill:#d4edda
style E fill:#d4edda
style F fill:#d4edda
```
## Documentation Scope
+1430 -1
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -13,7 +13,9 @@
"test:budget": "node -e \"const d=require('./.claude-plugin/plugins/axiom/claude-code.json'); let t=0; d.skills.forEach(s=>t+=s.description.length); if(t>15000){console.error('✗ Budget exceeded:',t,'/15000'); process.exit(1)} console.log('✓ Budget OK:',t,'/15000')\""
},
"devDependencies": {
"vitepress": "2.0.0-alpha.15"
"mermaid": "^11.12.2",
"vitepress": "2.0.0-alpha.15",
"vitepress-plugin-mermaid": "^2.0.17"
},
"keywords": [
"claude-code",