skills/qa-react-native/SKILL.md
React Native device QA
npx skillsauth add laststance/skills qa-react-nativeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
When running this skill in Codex, translate Claude Code-only primitives before acting: AskUserQuestion -> chat/request_user_input, TodoWrite -> update_plan, Task/TaskCreate/TeamCreate/SendMessage -> spawn_agent/send_input/wait_agent when available and allowed, and EnterPlanMode/ExitPlanMode -> a concise chat plan plus explicit approval.
Resolve Read/Write/Edit/Bash/WebSearch/WebFetch to Codex file/shell/web tools, and map ~/.claude/... paths to ~/.agents/... or ~/.codex/... unless the task explicitly targets Claude Code.
When running this skill in Cursor Agent, translate Claude Code-only primitives before acting: AskUserQuestion -> AskQuestion; TodoWrite -> Cursor TodoWrite or an equivalent checklist; Task/TaskCreate/TeamCreate/SendMessage/multi-agent flows -> Cursor Task (subagents), parallel Tasks, or run_in_background when allowed (TeamCreate/SendMessage may have no exact match); EnterPlanMode/ExitPlanMode -> Plan mode (SwitchMode / CreatePlan) plus explicit user approval.
Resolve Read/Write/Edit/StrReplace/Bash/web/search/MCP via Cursor Composer or Agent equivalents. MCP names written as mcp__server__tool typically map to call_mcp_tool with configured server identifiers. Map ~/.claude/... to ~/.cursor/skills/, .cursor/skills/, and .cursor/rules/ unless the task explicitly targets Claude Code.
Drive a React Native app on iOS Simulator and/or Android Emulator, collect evidence (screenshots + AX dumps + native logs + Metro logs), grade issues, and produce a platform-aware report. Fixing is out of scope — this skill reports. If the user wants to fix bugs, hand the report to a session that has access to the JS/TS and native source.
React Native multiplies the surfaces a QA run needs to cover:
This skill codifies the systematic path that catches the most common classes of RN bugs across both platforms without reading the source.
In scope: Black-box testing on iOS Simulator + Android Emulator of a running RN app (bare or Expo), driving via accessibility APIs, capturing screenshots, parsing AX trees, reading native + JS logs, comparing platform behavior.
Out of scope:
If any of these are unknown, use AskUserQuestion to collect them:
com.example.myapp (for launch_app)com.example.myapp (for adb shell am start)npx react-native run-ios or manual Xcode build already
installednpx react-native run-android or adb installnpx expo start + open Expo Go / dev clienthttp://localhost:8081. Confirm
reachable: curl -s http://localhost:8081/status should return "packager-status:running".mcp__ios-simulator__get_booted_sim_id + adb devices.curl http://localhost:8081/status shows it, or the redbox header). Just
note in the report.Do not guess — missing context causes the skill to test the wrong app, the wrong platform, or skip a layer entirely.
Goal: both simulators ready, Metro reachable, app launched cold on each, log taps running, baseline screenshots + AX captured.
mcp__ios-simulator__get_booted_sim_id — UDID. If none booted, ask
the user which device + iOS version to boot.xcrun simctl list devices booted -j | head -50
mkdir -p /tmp/qa-rn-session
xcrun simctl spawn booted log stream \
--predicate 'subsystem == "<bundle-id>" OR eventMessage CONTAINS "RCTLog" OR eventMessage CONTAINS "ReactNativeJS"' \
--level debug > /tmp/qa-rn-session/ios.log 2>&1 &
echo $! > /tmp/qa-rn-session/ios-log.pid
mcp__ios-simulator__launch_app({ bundle_id, terminate_running: true })./tmp/qa-rn-session/00-ios-baseline.png/tmp/qa-rn-session/00-ios-ax.json (from ui_describe_all)adb devices → expect <serial> device.
If none, ask user to start one (emulator -avd <name> or Android
Studio's device manager).adb shell getprop ro.product.model
adb shell getprop ro.build.version.release
adb shell wm size # screen size
adb shell wm density
adb logcat -c # clear existing
adb logcat ReactNativeJS:* ReactNative:* *:E \
> /tmp/qa-rn-session/android.log 2>&1 &
echo $! > /tmp/qa-rn-session/android-log.pid
The ReactNativeJS tag carries JS console output. ReactNative is
the bridge. *:E catches any error-level log from anywhere else.adb shell am force-stop <package-name> # clean cold start
adb shell monkey -p <package-name> -c android.intent.category.LAUNCHER 1
(Or use an explicit activity:
adb shell am start -n <package>/.MainActivity.)adb exec-out screencap -p > /tmp/qa-rn-session/00-android-baseline.pngadb shell uiautomator dump /sdcard/ui.xml && adb pull /sdcard/ui.xml /tmp/qa-rn-session/00-android-ui.xmlConfirm Metro is alive:
curl -s http://localhost:8081/status
# expect: packager-status:running
If Metro is down, note it — the app may still work (bundle cached), but redbox navigation, fast refresh, and remote debugging won't.
If either sim fails to launch, STOP that side and continue with the other — a single-platform run is still valuable. Note the failure explicitly in the report.
For EACH platform, walk the app's top-level nav breadth-first and inventory screens. Use the same methodology as single-platform QA:
Per screen, record:
NN-<platform>-<screen-slug>.pngNN-<platform>-<screen-slug>-ax.json / .xmlNavigation-without-coordinates patterns:
iOS: ui_describe_all → find node by label → tap center:
node.frame.x + node.frame.width/2, node.frame.y + node.frame.height/2
Android: uiautomator dump returns XML with <node bounds="[x1,y1][x2,y2]" />
attributes. Parse the bounds, tap center:
adb shell input tap $(( (X1+X2)/2 )) $(( (Y1+Y2)/2 ))
Budget: 3–8 minutes (longer than single-platform because you walk each side). If the app has >15 top-level screens per platform, ask the user which matter most.
For every screen from Phase 1, run the checklist on BOTH platforms. Most bugs fall out of this loop — the point is systematic parity.
Per platform, enumerate every tappable element. Tap each in turn. After each tap:
ReactNativeJS errors? Unhandled promise
rejections?adb shell input keyevent KEYCODE_BACK)
AND any in-app back affordanceEvery text input (AXTextField on iOS, EditText on Android):
KeyboardAvoidingView is
often misconfiguredwindowSoftInputMode may be adjustPan (pushes up)
or adjustResize (rebuilds layout) — different behaviors, both can
hide fieldsForce non-default conditions on each platform:
| State | iOS how | Android how | What to check |
|-------|---------|-------------|---------------|
| Dark Mode | xcrun simctl ui booted appearance dark | adb shell "cmd uimode night yes" | Color inversions, missing dark assets |
| Light Mode | ...appearance light | ...night no | Restore |
| Font scale / Dynamic Type | xcrun simctl ui booted content_size extra-extra-extra-large | adb shell settings put system font_scale 1.3 | Text overflow, layout breaks |
| Restore font | ...content_size medium | adb shell settings put system font_scale 1.0 | |
| Landscape | Simulator → Device → Rotate Left (Cmd+←) | adb shell settings put system user_rotation 1 + adb shell content insert --uri content://settings/system --bind name:s:accelerometer_rotation --bind value:i:0 | Layout breaks; some RN apps lock portrait |
| Network off | Simulator → Features → Network Link Conditioner 100% loss | adb shell svc wifi disable && adb shell svc data disable | Offline states, error messages |
| Background | Cmd+Shift+H (home) → wait 3s → reopen | adb shell input keyevent KEYCODE_HOME → adb shell monkey -p <pkg> 1 | State preserved? |
At minimum: Dark Mode + Font scale XL + Landscape + Offline on each platform. These catch the state bugs that RN apps most commonly ship.
Walk the same primary flow on iOS AND Android back-to-back. Capture matching screenshots. Compare:
elevation, iOS uses shadow* props — often one works
and the other is flat)Haptics, Android has coarser vibrate;
parity is hard but absence on one side when present on the other is
usually wronguseNativeDriver: true vs false changes iOS vs Android
feel significantlyRecord each divergence as a separate issue, category parity, with
screenshots from both platforms side-by-side.
Per platform:
accessibilityLabel (or text content).
iOS AX tree: non-empty AXLabel. Android XML: non-empty
content-desc or text.accessibilityLabel or accessible={false} for decorative.
On Android: contentDescription or importantForAccessibility="no".See references/issue-taxonomy-react-native.md for the full category
list.
Dev-only affordances that reveal most RN bugs.
AXStaticText nodes with the error messageadb logcat ReactNativeJS logs the
error with stackClassify every issue:
redbox / logbox / parity / functional / visual
/ accessibility / state / content / performance / crash /
nativeDe-duplicate aggressively. A shared component with a missing
accessibilityLabel that appears on six screens is one issue with six
affected screens, not six issues. Same goes for platform-agnostic bugs —
one issue, two platforms affected.
Use templates/qa-report-template-react-native.md as the skeleton. Fill:
curl http://localhost:8081/status or package.json, Expo SDK version
if applicable, Hermes or JSC, dev mode flag)Save to ./qa-reports/rn-<date>-<app>.md. Screenshots and dumps into
./qa-reports/rn-<date>-<app>/ — organize into ios/ and android/
subdirs.
Don't embed full AX JSON / uiautomator XML in the report — they're big. Save alongside and link. Only embed the specific snippet relevant to an issue.
Always, even on partial runs:
# Kill log tails
[ -f /tmp/qa-rn-session/ios-log.pid ] && kill "$(cat /tmp/qa-rn-session/ios-log.pid)" 2>/dev/null
[ -f /tmp/qa-rn-session/android-log.pid ] && kill "$(cat /tmp/qa-rn-session/android-log.pid)" 2>/dev/null
# iOS state restore
xcrun simctl status_bar booted clear 2>/dev/null
xcrun simctl ui booted appearance light 2>/dev/null
xcrun simctl ui booted content_size medium 2>/dev/null
# Android state restore
adb shell "cmd uimode night no" 2>/dev/null
adb shell settings put system font_scale 1.0 2>/dev/null
adb shell svc wifi enable 2>/dev/null
adb shell svc data enable 2>/dev/null
Leaving either simulator in a non-default state wastes the user's time later.
Before telling the user "done":
qa-reports/rn-<date>-<app>.md exists and opens cleanlyreferences/issue-taxonomy-react-native.md — severity + category
definitions (RN-specific categories included)references/rn-driver-reference.md — simctl + adb + uiautomator +
Metro cheat sheetreferences/platform-parity-checklist.md — what should look/work
identically on iOS and Android, and what legitimately differstemplates/qa-report-template-react-native.md — fill-in report
skeletonLoad the references on demand. When you hit a category question during
triage, read the taxonomy. When you need an adb recipe you don't
remember, read the driver reference.
curl http://localhost:8081/status fails. App
may still run off cached bundle, but LogBox / Fast Refresh / remote
debugging won't. Note in coverage.npx expo start --tunnel or switch
to a dev client.adb devices shows "offline": try
adb kill-server && adb start-server. If still offline, user needs to
check Android Studio / emulator directly.uiautomator dump returns "ERROR: Could not get UiAutomation":
some emulator images don't ship it. Fall back to adb exec-out screencap -p and pixel-based tapping, noting reduced AX coverage.adb logcat | grep -i "bundle" for the error. If Metro is down,
restart it. If it's an actual JS error, redbox should show in dev mode.adb shell input keyevent 82) → Reload. If reload loops, you have a
bug that breaks at module load — file as critical, note that no
further testing was possible on that platform.ls -lt ~/Library/Logs/DiagnosticReports/ | head -5adb logcat -d | grep -i "FATAL EXCEPTION"
Quote first 20 lines + the crashed thread into the report.testing
Cited research briefs
development
Daily coding habit prompts JP
development
React core deep-dive JP
data-ai
Copy last agent reply