android-e2e-testing

bởi expo

Kiểm tra các tính năng Expo Router trên trình giả lập Android bằng ADB. Sử dụng sau khi triển khai các tính năng Android gốc hoặc khi xác minh hành vi giao diện người dùng trên Android.

npx skills add https://github.com/expo/expo --skill android-e2e-testing

Android E2E Testing for Expo Router

Use adb to manually test Expo Router screens and components on Android emulators.

When to Use

  • After implementing or modifying native Android UI components (toolbars, tabs, menus)
  • When verifying Jetpack Compose components (@expo/ui/jetpack-compose)
  • When running the native-navigation or other E2E apps on Android
  • Before opening a PR that touches Android-specific behavior

Prerequisites

An 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

Step 1: Build and Launch

Router E2E apps

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>

Step 2: Navigate Using UI Dump

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.

Dump the view hierarchy

adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml

This returns XML with every UI element including:

  • text — displayed text
  • content-desc — accessibility description (useful for icon buttons)
  • bounds — position as [left,top][right,bottom]
  • clickable — whether the element responds to taps
  • class — Android view class

Find and tap an element

  1. Search the XML for your target element by text or content-desc
  2. Extract the bounds attribute: bounds="[left,top][right,bottom]"
  3. Calculate center: x = (left + right) / 2, y = (top + bottom) / 2
  4. Tap:
adb shell input tap <x> <y>

Example: For bounds="[367,498][714,633]":

  • x = (367 + 714) / 2 = 540
  • y = (498 + 633) / 2 = 565
adb shell input tap 540 565

Wait for navigation to settle

After tapping a navigation element, wait before verifying:

sleep 1

For slow transitions or heavy screens, use sleep 2.

Step 3: Interact

Tap items

adb shell input tap <x> <y>

Scroll

# 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

Type text

adb shell input text "hello%sworld"   # %s = space

Press hardware buttons

adb shell input keyevent 4    # Back
adb shell input keyevent 3    # Home
adb shell input keyevent 82   # Menu / React Native dev menu

Long press

adb shell input swipe <x> <y> <x> <y> 1000

Step 4: Verify

Visual verification via screenshot

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:

  • Confirming visual appearance (colors, layout, styling)
  • Verifying toolbar/tab positioning
  • Checking selection states and visual feedback

Note: Use screenshots for visual verification only. For element positions and tapping, always use uiautomator dump.

Programmatic verification via UI dump

adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml

Search the XML for expected content:

  • Verify text content appears
  • Check content-desc for accessibility labels
  • Confirm element presence after navigation
  • Verify selection states (look for checkmarks, content-desc changes)

Check for errors

# 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

Step 5: Report Results

After testing, summarize results in a table:

TestResult
Navigation to screenPASS/FAIL
Component renders correctlyPASS/FAIL
Interaction worksPASS/FAIL
No JS errors in logcatPASS/FAIL

Include details for any failures: what was expected vs what happened, relevant logcat output, and screenshots.

Preferably attach screenshots for features you tested.

Testing Jetpack Compose Components

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 container
  • android.widget.HorizontalScrollView — inside toolbar layouts
  • android.widget.Button — Compose buttons
  • android.view.View with content-desc — icon buttons with accessibility labels

When testing Compose components:

  1. Look for content-desc attributes to identify buttons (e.g., content-desc="Clear selection")
  2. The ComposeView wrapper may have different bounds than the inner interactive elements
  3. Tap the interactive element's bounds, not the container's

Troubleshooting

uiautomator dump fails or returns empty

This can happen during animations or transitions. Wait and retry:

sleep 2 && adb shell uiautomator dump /sdcard/ui.xml && adb shell cat /sdcard/ui.xml

Tap doesn't register

  • Recalculate coordinates from a fresh UI dump — the layout may have shifted
  • Ensure you're tapping a clickable="true" element
  • Try tapping the parent element if the child isn't clickable

App navigated to wrong screen or went to home

  • The Back button (keyevent 4) can exit the app entirely if on the root screen
  • Use monkey command to relaunch: adb shell monkey -p dev.expo.routere2e -c android.intent.category.LAUNCHER 1
  • Wait 2 seconds after launch before interacting

Metro bundler not connecting

adb reverse tcp:8081 tcp:8081

App crashes on launch

# Check crash buffer
adb logcat -b crash -d

# Look for fatal exceptions
adb logcat -d | grep -A 10 "FATAL EXCEPTION"

Reload the app

# 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

Disable Animations (Recommended for Testing)

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

Complete Example: Testing a Toolbar Screen

# 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

Thêm skills từ expo

expo-upgrade
expo
Khung (Mã nguồn mở). Hướng dẫn nâng cấp phiên bản Expo SDK và sửa lỗi phụ thuộc
apidevelopmentofficial
expo-data-fetching
expo
Framework (OSS). Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, and Expo Router data loaders (`useLoaderData`).
official
expo-dev-client
expo
Xây dựng các bản phát triển Expo tùy chỉnh để kiểm tra mã gốc trên thiết bị vật lý qua EAS Build hoặc cục bộ. Chỉ cần thiết khi sử dụng các mô-đun gốc tùy chỉnh, mục tiêu Apple (widget, app clips) hoặc mã gốc của bên thứ ba không có trong Expo Go; hãy thử Expo Go trước với lệnh npx expo start. Hỗ trợ xây dựng đám mây với tính năng tự động gửi lên TestFlight hoặc xây dựng cục bộ trên máy của bạn, xuất ra tệp .ipa (iOS) hoặc .apk / .aab (Android). Yêu cầu cấu hình eas.json với một hồ sơ phát triển thiết lập...
official
android-jetpack-compose
expo
Use when building Android UIs with Jetpack Compose, managing state with remember/mutableStateOf, or implementing declarative UI patterns.
official
swiftui-expert-skill
expo
Write, review, or improve SwiftUI code following best practices for state management, view composition, performance, macOS-specific APIs, and iOS 26+ Liquid…
official
android-e2e-testing
expo
Test Expo Router features on Android emulators using ADB. Use after implementing native Android features or when verifying UI behavior on Android.
official
expo-api-docs
expo
Write TSDoc comments for Expo SDK APIs following official conventions. MUST USE when introducing new user-facing TypeScript APIs in expo-* packages - document…
official
expo-review
expo
Run Expo's configured AI code reviewer on local changes or an expo/expo pull request, summarize findings and reviewer coverage, retain PR previews by default…
official