Skip to content

feat: iOS and Android simulator capture mode (v0.20260317.0)#1

Merged
ranaroussi merged 10 commits intomainfrom
feature/mobile-app-testing
Mar 17, 2026
Merged

feat: iOS and Android simulator capture mode (v0.20260317.0)#1
ranaroussi merged 10 commits intomainfrom
feature/mobile-app-testing

Conversation

@ranaroussi
Copy link
Copy Markdown
Member

What

Adds a new simulator capture mode for recording iOS Simulator and Android emulator screens while running UI tests, with automatic tap indicator overlays.

iOS

  • Records via xcrun simctl io recordVideo (SIGINT to stop)
  • Auto-detects booted simulator or boots by --device-name + --os
  • Warns when xcodebuild test is missing clone-prevention flags
  • ProofTapLogger.swift -- drop-in XCUITest extension: replace element.tap() with element.proofTap() to log element coordinates to proof-taps.json in the app container
  • Post-processes with ffmpeg to overlay pixel-accurate red dot + ripple ring at each tapped element's center, scaled from UIKit points to video pixels

Android

  • Records via adb emu screenrecord (QEMU monitor protocol) -- works on Apple Silicon where adb shell screenrecord produces 0 frames due to the gfxstream graphics backend
  • Auto-detects running emulator or boots AVD by name
  • Converts .webm output to .mp4 via ffmpeg
  • Reads tap coordinates from $PROOF_TAP_LOG (default /tmp/proof-android-taps.json) and overlays indicators at native device pixel coordinates (scale=1)

CLI

# iOS
proof capture --app my-app --mode simulator --platform ios \
  --device-name "iPhone 17 Pro" \
  --command "xcodebuild test -project ... -parallel-testing-enabled NO ..."

# Android  
proof capture --app my-app --mode simulator --platform android \
  --command "./run-tests.sh"

Fixes

  • Exclude test files from tsc compilation (stale dist/*.test.js were failing with missing cli.ts)
  • Scope bun test to src/ and test-app/cli/ to skip Playwright .spec.ts files

Tests

51 pass, 0 fail

ranaroussi and others added 10 commits March 16, 2026 23:40
- mode: 'simulator' with platform: 'ios' records the simulator screen
- Uses xcrun simctl io recordVideo, SIGINT to stop
- Auto-detects booted simulator or boots by name
- CLI: --mode simulator --platform ios [--device-name, --os, --codec]
- iOS test app: SwiftUI Hello World with button + UI tests
- 48 tests passing (3 new: command required, platform required, android stub)
- Smoke tested: real 5.3s recording of Safari on iPhone 17 Pro sim
xcodebuild test clones the simulator by default, making the recording
capture an idle screen. The clone is invisible to simctl list, so
clone detection isn't viable.

Instead: detect xcodebuild test commands and warn users to add
-parallel-testing-enabled NO -disable-concurrent-destination-testing
flags, which prevent cloning and run tests on the original sim.

Also provide a helpful error message if the recording file ends up
empty (common symptom of cloned sim recording).
Post-processes xcodebuild test recordings with red circle + ripple
indicators at tap timestamps, matching the Playwright cursor style.

- xcresult.ts: parses xcresult bundle for tap activity summaries
- touch-overlay.ts: ffmpeg filter to overlay red dot + ring at tap times
- simulator.ts: auto-detects xcodebuild tests and applies overlay

Uses geq filter to generate proper circular overlays (not drawbox).
Tap timestamps extracted from xcresult ActivitySummary entries.
Currently places indicators at screen center (element coordinate
extraction can be added later).
Add ProofTapLogger.swift -- a XCUITest extension that logs element
frames (x, y, width, height) to proof-taps.json on the simulator.
Tests use element.proofTap() instead of element.tap().

After recording, simulator.ts reads the tap log from the simulator's
app container and passes coordinates to the ffmpeg overlay filter.
Each tap's red dot + ripple ring is now positioned exactly on the
tapped element center, scaled from UIKit points to video pixels.

Falls back to screen center if no tap log is found.
Add simulator capture to architecture diagram, directory structure,
data flow (full recording + tap overlay pipeline), CLI flags,
gotchas (xcodebuild cloning, XCUITest HID injection, simctl
framebuffer, tap log location, point-to-pixel scaling), and
lessons learned (post-processing vs runtime, SIGINT for simctl,
xcresult limitations).
Add simulator-android.ts with:
- assertAndroidReady() -- checks adb is available, finds SDK path
- resolveAndroidDevice() -- finds running emulator or boots AVD by name
- startAndroidRecording() -- adb shell screenrecord with 175s segment
  chaining for recordings >3 minutes
- pullAndMergeRecording() -- adb pull + ffmpeg concat for multi-segment
- enableTouchIndicators() / disableTouchIndicators() -- toggle
  show_touches (Android renders tap dots in screenrecord natively)

Update simulator.ts to route platform=android to captureAndroid()
instead of throwing a stub error.

Tap indicators work automatically via Android's show_touches developer
option -- no ProofTapLogger equivalent needed.
Switch Android recording from adb shell screenrecord to adb emu
screenrecord which saves directly to the host filesystem. The shell
screenrecord approach produces 0 frames on Apple Silicon emulators
using the gfxstream graphics backend.

Add ffmpeg tap overlay for Android: captureAndroid reads
/tmp/proof-android-taps.json (or $PROOF_TAP_LOG) and overlays red
dot + ripple at each tap position. Overlay uses scaleFactor=1 since
adb emu screenrecord outputs at native device resolution.

Add scaleFactor param to overlayTouchIndicators so Android (scale=1)
and iOS (auto-detected 2x/3x from video width) can share the same
overlay pipeline.

Fix test runner: exclude test files from tsc compilation to stop
dist/*.test.js from landing in dist/ and failing with missing cli.ts.
Scope bun test to src/ and test-app/cli/ to exclude Playwright spec
files that bun cannot run.
@ranaroussi ranaroussi merged commit 7d2b7bb into main Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant