Claude-skill-registry device-testing
Interact with iOS simulators and verify app behavior using xcobra
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/device-testing" ~/.claude/skills/majiayu000-claude-skill-registry-device-testing && rm -rf "$T"
skills/data/device-testing/SKILL.mdUse
bunx xcobra to interact with iOS simulators and debug Expo apps.
Inspecting the UI
Get the accessibility tree to understand current screen state:
bunx xcobra sim xml
This returns XML with all UI elements, their labels, identifiers, and positions. Use this to:
- Find element identifiers for tapping
- Verify UI state after actions
- Debug layout issues
Tapping Elements
Tap by accessibility label (preferred):
bunx xcobra sim tap --label "Submit"
Tap by accessibility identifier:
bunx xcobra sim tap --id "submit-button"
Tap by coordinates:
bunx xcobra sim tap --x 200 --y 400
Add delays for animations:
bunx xcobra sim tap --label "Next" --pre-delay 500 --post-delay 300
Typing Text
Type text into focused input:
bunx xcobra sim type "Hello World"
Type from stdin:
echo "test@example.com" | bunx xcobra sim type --stdin
Gestures
Preset gestures:
bunx xcobra sim gesture scroll-up bunx xcobra sim gesture scroll-down bunx xcobra sim gesture swipe-from-left-edge
Custom swipe:
bunx xcobra sim swipe --start-x 200 --start-y 400 --end-x 200 --end-y 100
Hardware Buttons
Press hardware buttons:
bunx xcobra sim button home bunx xcobra sim button lock bunx xcobra sim button siri
Screenshots
Capture screenshot:
bunx xcobra sim screenshot --output screenshot.png
Video Recording
Record simulator video:
bunx xcobra sim record-video --output recording.mp4
Evaluating JavaScript
Execute JS in the running Expo app:
bunx xcobra expo eval "Date.now()"
Get app state:
bunx xcobra expo eval "global.__REDUX_STORE__?.getState()"
Call exposed functions:
bunx xcobra expo eval "globalThis.testHelper?.getCurrentRoute()"
Console Logs
Stream console output:
bunx xcobra expo console
JSON format for parsing:
bunx xcobra expo console --json
Network Monitoring
Monitor network requests:
bunx xcobra expo network
Reloading the App
Trigger a reload to refresh the JavaScript bundle:
bunx xcobra expo reload
This is useful when:
- The Metro connection becomes stale
- Hot reload isn't picking up changes
- The app state needs a fresh start
- Deep links or navigation seem stuck
Crash Reports
View latest crash:
bunx xcobra crash latest
List recent crashes:
bunx xcobra crash list
Show specific crash:
bunx xcobra crash show <crash-id>
Source Inspection
List loaded scripts:
bunx xcobra expo src scripts
Get source code by script ID:
bunx xcobra expo src source <script-id>
List Metro modules:
bunx xcobra expo src modules
Simulator Management
List all simulators:
bunx xcobra sim list
Target specific simulator:
bunx xcobra sim tap --udid "DEVICE-UDID" --label "OK"
Testing Workflow
-
Get current UI state
bunx xcobra sim xml -
Perform action
bunx xcobra sim tap --label "Login" -
Wait and verify
sleep 1 bunx xcobra sim xml | grep "Welcome" -
Check for errors
bunx xcobra expo console --json | head -20
Verifying Screen Content
After navigating, verify you're on the expected screen:
# Check for expected text content bunx xcobra sim xml | grep -i "expected title" # Get full accessibility tree and search for elements bunx xcobra sim xml > /tmp/ui.xml && cat /tmp/ui.xml
Use JavaScript eval to check the current route:
bunx xcobra expo eval "window.location?.pathname"
Troubleshooting Unexpected Routes
If deep links navigate to the wrong screen or you see unexpected content:
1. Check the current route in the app:
bunx xcobra expo eval "globalThis.testHelper?.getCurrentRoute()"
2. Verify the app directory structure:
Look for unexpected index routes that may be intercepting navigation:
# List all index files - these define default routes find app -name "index.tsx" -o -name "index.ts" -o -name "index.js" # Check for index routes inside groups that may override expected behavior find app -path "*/(*)/*" -name "index.*"
3. Common issues:
- Unexpected index in a group: A file like
will be the default route for theapp/(tabs)/index.tsx
group, potentially overriding(tabs)app/index.tsx - Missing layout: Groups need a
to properly nest routes_layout.tsx - Conflicting routes: Two files resolving to the same URL path
4. Verify route structure matches expectations:
# List all route files find app -name "*.tsx" | grep -v "_layout" | sort # Check group structure find app -type d -name "(*)"`
5. Test deep link resolution:
# Open a deep link and immediately check the route xcrun simctl openurl booted "myapp://settings" && sleep 1 && bunx xcobra expo eval "window.location?.pathname"
Exposing Test Helpers
Add global helpers in your app for testing:
if (__DEV__) { globalThis.testHelper = { getCurrentRoute: () => navigationRef.current?.getCurrentRoute(), getState: () => store.getState(), resetApp: () => { /* reset logic */ }, }; }
Then call via eval:
bunx xcobra expo eval "testHelper.getCurrentRoute()"