| name | simulator-test |
| description | Build, launch, and capture debug logs from the iOS simulator. Use when you need to verify runtime behavior, measure timing, or diagnose issues without asking the user to manually capture logs. |
Simulator Test — Build, Launch & Log Capture
Use this skill when the testing skill calls for runtime proof in the running app. The default path is:
- Build the app.
- Install and launch it in Simulator.
- Drive the relevant UI with the iOS Simulator MCP server.
- Capture screenshots, accessibility output, and logs as evidence.
This skill exists so the agent can iterate without asking the user to manually operate the app.
For repeatable cold-launch baselines, prefer scripts/capture_runtime_baseline.sh --capture-startup after the app is installed. It captures both the simulator OS log stream and the latest PersistentLogService session log, then prints a filtered summary.
Ensemble Automation Hooks
Use these hooks before falling back to coordinate tapping. They route through NavigationCoordinator, so ordinary USER_JOURNEY navigation/profile/download breadcrumbs still appear.
Launch arguments:
xcrun simctl launch booted com.videogorl.ensemble \
-EnsembleAutomationMode YES \
-EnsembleAutomationStartSurface profile-storage \
-EnsembleAutomationDisableAnimations YES
Environment alternatives:
ENSEMBLE_AUTOMATION_MODE=1
ENSEMBLE_AUTOMATION_START_SURFACE=profile-storage
ENSEMBLE_AUTOMATION_DISABLE_ANIMATIONS=1
Supported start surfaces: home, songs, artists, albums, genres, playlists, favorites, search, downloads, settings, profile, profile-storage.
Debug navigation deep links:
xcrun simctl openurl booted 'ensemble://debug/open?surface=profile-storage'
xcrun simctl openurl booted 'ensemble://debug/open?surface=playlists'
Media deep links:
xcrun simctl openurl booted 'ensemble://artist/<artist-id>?sourceKey=<url-encoded-source-key>'
xcrun simctl openurl booted 'ensemble://album/<album-id>?sourceKey=<url-encoded-source-key>'
xcrun simctl openurl booted 'ensemble://playlist/<playlist-id>?sourceKey=<url-encoded-source-key>'
Expected logs:
USER_JOURNEY context=automation event=launchOptions ...
USER_JOURNEY context=automation event=deepLinkAccepted ...
USER_JOURNEY context=automation event=routeRequested ...
USER_JOURNEY context=navigation event=tabChanged ...
USER_JOURNEY context=navigation event=auxiliaryPresentation ...
Stable accessibility identifiers to prefer in UI automation:
sidebar.search
sidebar.library.home
sidebar.library.songs
sidebar.library.artists
sidebar.library.albums
sidebar.library.genres
sidebar.library.favorites
sidebar.playlists.all
sidebar.toolbar.downloads
sidebar.toolbar.profile
profile.storage.clearArtworkCache
profile.storage.clearAllLibraryData
profile.reset.removeAllAccounts
Dynamic sidebar rows use sanitized identifiers:
sidebar.playlist.<playlist-id>.source.<source-key>
sidebar.smartPlaylist.<playlist-id>.source.<source-key>
sidebar.mergedPlaylist.<playlist-id>.source.<source-key>
sidebar.pin.<artist|album|playlist>.<id>.source.<source-key>
MCP-First Workflow
Use the iOS Simulator MCP server for interaction and state inspection:
open_simulator opens the Simulator app.
get_booted_sim_id returns the active device identifier.
install_app installs a built .app or .ipa.
launch_app launches the app by bundle identifier.
ui_describe_all dumps the accessibility tree so you can find tappable elements and current labels.
ui_tap, ui_type, and ui_swipe drive the UI directly.
ui_view gives a compressed screenshot-like view of the current screen.
screenshot saves a real screenshot when you need visual proof.
Use shell commands for build/log work, and MCP tools for interaction. That split keeps the iteration loop fast and agent-driven.
Physical Device Screenshots Via iPhone Mirroring
When validating on a real iPhone through iPhone Mirroring, do not use plain screencapture for evidence. It captures the full desktop display and can save the wrong window. Resolve the iPhone Mirroring window id first, then target that window explicitly:
swift -e 'import CoreGraphics; let opts = CGWindowListOption(arrayLiteral: [.optionOnScreenOnly, .excludeDesktopElements]); if let list = CGWindowListCopyWindowInfo(opts, kCGNullWindowID) as? [[String: Any]] { for w in list { let owner = w[kCGWindowOwnerName as String] as? String ?? ""; let name = w[kCGWindowName as String] as? String ?? ""; if owner.localizedCaseInsensitiveContains("iPhone") || name.localizedCaseInsensitiveContains("iPhone") { print("\\(w[kCGWindowNumber as String] ?? "?") owner=\\(owner) name=\\(name) bounds=\\(w[kCGWindowBounds as String] ?? [:])") } } }'
screencapture -x -l <window-id> /tmp/ensemble-device-profile/artifacts/iphone-mirroring-now-playing.png
Use the window-targeted artifact for before/after performance comparisons, especially when collecting Time Profiler or Instruments evidence from a physical device.
Quick Reference
xcodebuild -workspace Ensemble.xcworkspace -scheme Ensemble \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
build
xcrun simctl terminate booted com.videogorl.ensemble
xcrun simctl spawn booted log stream \
--level debug \
--predicate 'processImagePath CONTAINS "Ensemble" AND NOT processImagePath CONTAINS "Extension"' \
--style compact > /tmp/ensemble-test-log.txt 2>&1 &
LOG_PID=$!
sleep 1
sleep 10
kill $LOG_PID 2>/dev/null
grep -E '(pattern|you|care|about)' /tmp/ensemble-test-log.txt
Step-by-Step Guide
1. Boot Or Select The Target Simulator
xcrun simctl list devices | grep "Booted"
If no simulator is booted, boot one:
xcrun simctl boot "iPhone 17 Pro"
Use the device name (not UUID) with xcrun simctl commands, or use booted as a shortcut when exactly one simulator is running. Use the MCP open_simulator and get_booted_sim_id tools once the device is up.
2. Build the App
xcodebuild -workspace Ensemble.xcworkspace -scheme Ensemble \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
build 2>&1 | grep -E "error:|BUILD" | tail -5
Check for BUILD SUCCEEDED. If the build fails, fix errors before proceeding.
3. Install And Launch Through MCP
After building, install the generated .app bundle with install_app, then launch with launch_app(bundle_id: "com.videogorl.ensemble").
Once the app is running:
- Use
ui_describe_all to understand the current screen.
- Use
ui_tap, ui_type, and ui_swipe to navigate the target flow.
- Use
ui_view or screenshot to confirm the visible state.
This is the default validation path for bug fixes and UI work.
4. Terminate Any Running Instance
xcrun simctl terminate booted com.videogorl.ensemble 2>/dev/null
This ensures a clean cold launch. Ignore errors if no instance is running.
5. Start Debug Log Stream
xcrun simctl spawn booted log stream \
--level debug \
--predicate 'processImagePath CONTAINS "Ensemble" AND NOT processImagePath CONTAINS "Extension"' \
--style compact > /tmp/ensemble-test-log.txt 2>&1 &
LOG_PID=$!
sleep 1
Predicate notes:
--level debug captures ALL log levels (debug, info, default, error)
- The predicate filters to only the main app process (excludes Siri extension noise)
- To include the Siri extension, remove the
AND NOT clause
--style compact keeps lines concise
Alternative predicates for focused capture:
--predicate 'subsystem BEGINSWITH "com.videogorl.ensemble"'
--predicate 'subsystem == "com.videogorl.ensemble:core"'
--predicate 'processImagePath CONTAINS "Ensemble" AND messageType >= 1'
6. Launch the App
xcrun simctl launch booted com.videogorl.ensemble
For launches with specific arguments or environment variables:
xcrun simctl launch booted com.videogorl.ensemble --argument1 value1
Prefer the Ensemble automation arguments for repeatable surface entry:
xcrun simctl launch booted com.videogorl.ensemble \
-EnsembleAutomationMode YES \
-EnsembleAutomationStartSurface downloads
7. Wait for the Phase Under Test
Adjust the sleep duration based on what you're measuring:
| Phase | Suggested Wait |
|---|
| Health checks only | 5s |
| Full startup (health + sync) | 15s |
| Siri cold launch simulation | 20s |
| Background sync trigger | 30s |
8. Stop Log Stream & Analyze
kill $LOG_PID 2>/dev/null
9. Analyze Results
Common analysis patterns:
grep -E '(🏥|health check|ServerHealthChecker|ConnectionTest|✅ Server|❌ Server)' /tmp/ensemble-test-log.txt
grep -E '(📱 AppDelegate|didFinishLaunching|health check|Startup sync|network monitor)' /tmp/ensemble-test-log.txt
grep -E '(ConnectionTest|ConnectionFailover|⚡️|Early exit|Grace period|preferred)' /tmp/ensemble-test-log.txt
grep -E '(SIRI_APP|SIRI_EXT|InAppPlayMedia|coordinator|execute|AirPlay|route)' /tmp/ensemble-test-log.txt
grep -E '(🎵|Starting playback|AVPlayer|playing audio|player item|stream URL)' /tmp/ensemble-test-log.txt
grep -E '(🔄|sync|incremental|full sync|SyncCoordinator)' /tmp/ensemble-test-log.txt
grep -E '(📡|NetworkMonitor|network state|Restored cached)' /tmp/ensemble-test-log.txt
All-in-One Script
Copy-paste this block for a standard cold-launch capture:
xcrun simctl terminate booted com.videogorl.ensemble 2>/dev/null
xcrun simctl spawn booted log stream --level debug \
--predicate 'processImagePath CONTAINS "Ensemble" AND NOT processImagePath CONTAINS "Extension"' \
--style compact > /tmp/ensemble-test-log.txt 2>&1 &
LOG_PID=$!
sleep 1
xcrun simctl launch booted com.videogorl.ensemble
sleep 10
kill $LOG_PID 2>/dev/null
echo "=== Captured $(wc -l < /tmp/ensemble-test-log.txt) lines ==="
Evidence To Capture
Capture enough evidence to support the claim:
- A passing package test run for the affected package, if the change is non-trivial.
- Simulator confirmation of the relevant flow using MCP-driven interaction.
- A screenshot, accessibility dump, or log excerpt when the result would otherwise be ambiguous.
If the app cannot be fully validated because login, network state, or an external service is unavailable, stop short of "done" and report the blocker precisely.
Tips
- Log file location: Always use
/tmp/ensemble-test-log.txt (or similar) so it's easy to find and doesn't clutter the project.
- Multiple runs: Rename the log file between runs (e.g.,
/tmp/ensemble-test-log-v2.txt) to avoid confusion.
- Large logs: The full debug log can be 5000+ lines for a 10s capture. Use targeted grep patterns rather than reading the whole file.
- Simulator performance: Simulator probes are faster than real devices (local network latency is near-zero). Device logs will show longer probe times.
- Real device logs: For device testing, the user must capture logs via Console.app or
log stream on the device. This skill covers simulator-only workflows.