Reference
Device Control
The device control skill provides comprehensive reference for controlling simulators and emulators, capturing screenshots, reading UI state, managing permissions, and handling Expo/EAS builds.
When loaded
Section titled “When loaded”Activates when Claude encounters tasks involving simulator/emulator interaction: taking screenshots, booting devices, installing apps, reading UI hierarchy, opening deep links, granting permissions, streaming native logs, or managing device settings.
Key Knowledge Areas
Section titled “Key Knowledge Areas”iOS Simulator (simctl)
Section titled “iOS Simulator (simctl)”Commands for boot/shutdown, app install/launch/terminate, deep links, screenshots, native log streaming, and permissions. Key recommendations:
- Prefer JPEG screenshots (80ms, 200KB) over PNG (150ms, 800KB)
- Use
processImagePath ENDSWITH "/BinaryName"for precise native log filtering - Use
xcrun simctl privacyfor permission grants/revokes
Android Emulator (adb)
Section titled “Android Emulator (adb)”Commands for device management, deep links, screenshots, UI hierarchy extraction, animation control, native logs, permissions, and locale. Key recommendations:
- Use
adb exec-out screencap -pfor direct-pipe screenshots (skips device storage round-trip) uiautomator dump --compressedprovides full structured accessibility tree- Disable animations before test runs (window, transition, animator scales to 0)
Device tools
Section titled “Device tools”Cross-platform unified device interaction that eliminates platform-specific branching. The device_* tools route through the in-tree runners (iOS: rn-fast-runner; Android: rn-android-runner) — the sole device backend:
| Task | Tool |
|---|---|
| List devices | device_list |
| Screenshot | device_screenshot |
| UI element tree | device_snapshot (returns @refs) |
| Tap by text | device_find text="Sign In" action=click |
| Tap by ref | device_press ref=@e3 |
| Fill input | device_fill ref=@e5 text="hello" |
| Scroll/swipe | device_swipe direction=up |
| System back | device_back |
Concurrent State Snapshot
Section titled “Concurrent State Snapshot”scripts/snapshot_state.sh captures screenshot + UI hierarchy simultaneously, cutting state-check time by ~40%.
Benchmark Reference
Section titled “Benchmark Reference”| Operation | Time | Size |
|---|---|---|
| iOS screenshot (JPEG) | 80ms | 200KB |
| iOS screenshot (PNG) | 150ms | 800KB |
| Android screenshot (exec-out) | 300ms | 800KB |
| Android UI hierarchy (raw) | 300-500ms | 15-30KB XML |
| Android UI hierarchy (parsed) | 350-550ms | 2-3KB JSON |
Expo/EAS Build Integration
Section titled “Expo/EAS Build Integration”Decision table for build workflows:
| Situation | Action |
|---|---|
| App running + Metro connected | Skip, proceed to testing |
| Metro not running, app missing | expo_ensure_running.sh ios or android |
| Test a specific EAS build | eas_resolve_artifact.sh then expo_ensure_running.sh --artifact |
Troubleshooting
Section titled “Troubleshooting”| Symptom | Fix |
|---|---|
| Simulator not booting | xcrun simctl shutdown all && xcrun simctl erase all |
| adb shows “unauthorized” | Revoke USB debugging, reconnect, tap Allow |
| Screenshot hangs | Wait for home screen, verify boot completed |
| uiautomator dump fails | Wake screen with keyevent WAKEUP |
Keyboard-occlusion guard
Section titled “Keyboard-occlusion guard”device_press and device_longpress guard against tapping through a software keyboard by default. Before the tap lands, the runner checks whether a visible keyboard’s frame contains the tap point (a sanity-checked rect — min height 120pt iOS / 150px Android, so a predictive-text accessory bar doesn’t count) and dismisses it first if so.
meta.keyboardGuard | Meaning |
|---|---|
off | Guard disabled (RN_KEYBOARD_GUARD=0) |
no_keyboard | No keyboard was visible |
not_occluded | Keyboard visible but the tap point was clear of it |
dismissed | Keyboard was occluding the tap point and was dismissed first |
Platform behavior differs because the safe dismissal mechanism differs:
- Android —
pressBack+ a bounded wait for the UI to settle (~3.6s measured). Only fires when a real input-method window with sane bounds contains the tap point, so it never triggers an unrelated back-navigation. - iOS — verify-or-refuse. The runner only ever taps a genuine dismiss control (“Hide keyboard” / “Dismiss keyboard” / “Done”) and re-verifies the keyboard is gone. On the standard iPhone QWERTY keyboard, which has no such control, the tap is refused with a
KEYBOARD_OCCLUDEDerror (keyboardGuard=dismiss_failed) instead of swiping the keyboard closed — a swipe-based dismiss triggers QuickPath slide-typing and corrupts whatever the keyboard was over.
If you hit KEYBOARD_OCCLUDED on iOS: dismiss the keyboard yourself first (device_fill/cdp_interact type through the JS path and don’t require the keyboard to be down, or tap a non-input area of the screen) and then retry the tap.
Opt out entirely with RN_KEYBOARD_GUARD=0 (or false) if the guard gets in the way of a flow you control precisely.
Only device_press/device_longpress-style taps are guarded — tapSeries, text-based taps, swipes/scrolls/drags, and doubleTap are unaffected.