Test Expo Router features on Android emulators using ADB. Use after implementing native Android features or when verifying UI behavior on Android.
Use adb to manually test Expo Router screens and components on Android emulators.
@expo/ui/jetpack-compose)native-navigation or other E2E apps on AndroidAn Android emulator must be running. Prefer Pixel emulators over tablet ones for standard phone-sized testing.
# Verify emulator is connected
adb devices
# Check which emulator is running
adb -s DEVICE_ID emu avd name
# Check screen resolution (important for coordinate calculations)
adb -s DEVICE_ID shell wm size
Package name: dev.expo.routere2e
Each app in apps/router-e2e/__e2e__/ has a corresponding yarn android:<name> script. Check apps/router-e2e/package.json for available scripts.
cd apps/router-e2e
yarn android:[APP_NAME] # e.g. yarn android:native-navigation
If the app is already built, relaunch it:
adb shell monkey -p dev.expo.routere2e -c android.intent.category.LAUNCHER 1
To find the package name of any installed app:
adb shell pm list packages | grep -i <keyword>
CRITICAL: Always use uiautomator dump for element coordinates. Screenshot pixel coordinates have display scaling factors that make them unreliable for adb shell input tap. The UI dump provides actual device coordinates.
adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml
This returns XML with every UI element including:
text — displayed textcontent-desc — accessibility description (useful for icon buttons)bounds — position as [left,top][right,bottom]clickable — whether the element responds to tapsclass — Android view classtext or content-descbounds attribute: bounds="[left,top][right,bottom]"x = (left + right) / 2, y = (top + bottom) / 2adb shell input tap <x> <y>
Example: For bounds="[367,498][714,633]":
adb shell input tap 540 565
After tapping a navigation element, wait before verifying:
sleep 1
For slow transitions or heavy screens, use sleep 2.
adb shell input tap <x> <y>
# Scroll down
adb shell input swipe 540 1500 540 500 300
# Scroll up
adb shell input swipe 540 500 540 1500 300
# Scroll further (larger distance)
adb shell input swipe 540 1500 540 200 500
adb shell input text "hello%sworld" # %s = space
adb shell input keyevent 4 # Back
adb shell input keyevent 3 # Home
adb shell input keyevent 82 # Menu / React Native dev menu
adb shell input swipe <x> <y> <x> <y> 1000
adb shell screencap -p /sdcard/screenshot.png && adb pull /sdcard/screenshot.png /tmp/screenshot.png
Then use the Read tool to view /tmp/screenshot.png. Screenshots are useful for:
Note: Use screenshots for visual verification only. For element positions and tapping, always use uiautomator dump.
adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml
Search the XML for expected content:
content-desc for accessibility labels# React Native JS errors
adb logcat -d -s ReactNativeJS:E | tail -20
# Crash logs
adb logcat -b crash -d
# All recent errors
adb logcat -d *:E | tail -30
After testing, summarize results in a table:
| Test | Result |
|---|---|
| Navigation to screen | PASS/FAIL |
| Component renders correctly | PASS/FAIL |
| Interaction works | PASS/FAIL |
| No JS errors in logcat | PASS/FAIL |
Include details for any failures: what was expected vs what happened, relevant logcat output, and screenshots.
Preferably attach screenshots for features you tested.
Components from @expo/ui/jetpack-compose (like HorizontalFloatingToolbar, IconButton, Host) render as Compose views inside React Native. In UI dumps they appear as:
androidx.compose.ui.platform.ComposeView — the Compose containerandroid.widget.HorizontalScrollView — inside toolbar layoutsandroid.widget.Button — Compose buttonsandroid.view.View with content-desc — icon buttons with accessibility labelsWhen testing Compose components:
content-desc attributes to identify buttons (e.g., content-desc="Clear selection")ComposeView wrapper may have different bounds than the inner interactive elementsThis can happen during animations or transitions. Wait and retry:
sleep 2 && adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml
clickable="true" elementkeyevent 4) can exit the app entirely if on the root screenmonkey command to relaunch: adb shell monkey -p dev.expo.routere2e -c android.intent.category.LAUNCHER 1adb reverse tcp:8081 tcp:8081
# Check crash buffer
adb logcat -b crash -d
# Look for fatal exceptions
adb logcat -d | grep -A 10 "FATAL EXCEPTION"
# Open React Native dev menu and tap Reload
adb shell input keyevent 82
# Or force-stop and relaunch
adb shell am force-stop dev.expo.routere2e && adb shell monkey -p dev.expo.routere2e -c android.intent.category.LAUNCHER 1
Disabling animations prevents flaky UI dumps and makes testing more reliable:
adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global animator_duration_scale 0
Re-enable when done:
adb shell settings put global window_animation_scale 1
adb shell settings put global transition_animation_scale 1
adb shell settings put global animator_duration_scale 1
# 1. Launch app
adb shell monkey -p dev.expo.routere2e -c android.intent.category.LAUNCHER 1
sleep 2
# 2. Dump UI to find navigation button
adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml
# Find: content-desc="Android Toolbar" bounds="[367,498][714,633]"
# Center: (540, 565)
# 3. Navigate to screen
adb shell input tap 540 565
sleep 1
# 4. Take screenshot to verify visual appearance
adb shell screencap -p /sdcard/screenshot.png && adb pull /sdcard/screenshot.png /tmp/screenshot.png
# 5. Dump UI to find toolbar buttons
adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml
# Find buttons by content-desc: "Clear selection", "Select all", "Delete", "Add"
# 6. Test toolbar interactions
adb shell input tap 457 2233 # "Select all" button center
sleep 1
# 7. Verify state changed
adb shell screencap -p /sdcard/screenshot.png && adb pull /sdcard/screenshot.png /tmp/screenshot.png
# 8. Check for errors
adb logcat -d -s ReactNativeJS:E | tail -20