Claude-skill-registry android-e2e-testing-setup
Setup UI Automator 2.4 smoke test for validating app launches (works with debug and release builds)
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/android-e2e-testing-setup" ~/.claude/skills/majiayu000-claude-skill-registry-android-e2e-testing-setup && rm -rf "$T"
skills/data/android-e2e-testing-setup/SKILL.mdAndroid E2E Testing Setup
Sets up a lightweight smoke test using UI Automator 2.4 to verify the app launches without crashing.
Works with BOTH debug and release builds because UI Automator interacts with apps externally (doesn't require debuggable builds).
Prerequisites
- Android project with Gradle
- Minimum SDK 21+
- Device or emulator available (MANDATORY)
Process
Step 1: Add Dependencies
Update
app/build.gradle.kts:
dependencies { // UI Automator 2.4 - Modern API with built-in waiting androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05") // AndroidX Test androidTestImplementation("androidx.test:core:1.5.0") androidTestImplementation("androidx.test:runner:1.5.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") // Truth assertions androidTestImplementation("com.google.truth:truth:1.1.5") }
Note: UI Automator 2.4 introduces a modern Kotlin DSL. See: https://developer.android.com/training/testing/other-components/ui-automator
Step 1b: Configure Test Build Type for Release Testing (Optional)
To run instrumented tests against the release build (for ProGuard validation), add this to
app/build.gradle.kts:
android { // ... existing config ... // Change test build type from "debug" to "release" // This makes connectedAndroidTest run against release builds // IMPORTANT: Both app and test APK will be signed with release key testBuildType = "release" }
What this does:
now runs against release build./gradlew connectedAndroidTest- Both app APK and test APK are signed with the same (release) key
- ProGuard/R8 runs on the app APK
- Tests validate the actual release build
When to use this:
- During release validation (before publishing)
- To catch ProGuard/R8 issues
- CI/CD release pipelines
When NOT to use this:
- Day-to-day development (debug builds are faster)
- When you don't have release signing configured locally
To toggle back to debug testing:
testBuildType = "debug" // Or just remove the line (debug is default)
Step 2: Create Smoke Test
Create
app/src/androidTest/kotlin/{package_path}/SmokeTest.kt:
package {PACKAGE_NAME} import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.uiautomator.uiAutomator import androidx.test.uiautomator.UiAutomatorTestScope import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith /** * Smoke test using modern UI Automator 2.4 API. * * This test works with BOTH debug and release APKs because UI Automator * interacts with the app externally (doesn't require debuggable build). * * Primary purpose: Validate app launches without crashing. * For release builds: Validates ProGuard/R8 didn't break critical code paths. * * @see https://developer.android.com/training/testing/other-components/ui-automator */ @RunWith(AndroidJUnit4::class) class SmokeTest { companion object { private const val PACKAGE_NAME = "{PACKAGE_NAME}" } @Test fun appLaunches_doesNotCrash() = uiAutomator { // Start the app startApp(PACKAGE_NAME) // Wait for app to be visible waitForAppToBeVisible(PACKAGE_NAME) // Handle HealthConnect permission dialogs if they appear handleHealthConnectPermissions() // Verify app is running by checking for any element with our package val appElement = onElementOrNull(5000) { packageName == PACKAGE_NAME } assertThat(appElement).isNotNull() } @Test fun appLaunches_hasVisibleContent() = uiAutomator { // Start the app startApp(PACKAGE_NAME) waitForAppToBeVisible(PACKAGE_NAME) // Handle permissions handleHealthConnectPermissions() // Verify app has UI content (didn't crash to blank screen) val appStillRunning = onElementOrNull(2000) { packageName == PACKAGE_NAME } assertThat(appStillRunning).isNotNull() } // ========================================================================= // HealthConnect Permission Handling // ========================================================================= /** * Navigate through HealthConnect permission UI. * * HealthConnect has a multi-screen permission flow: * 1. Data permissions screen - toggle "Allow all" then click "Allow" * 2. Background access screen - click "Allow" */ private fun UiAutomatorTestScope.handleHealthConnectPermissions() { // Screen 1: Data permissions ("fitness and wellness data") val dataPermScreen = onElementOrNull(3000) { text?.contains("fitness and wellness data") == true } if (dataPermScreen != null) { // Click "Allow all" toggle to enable all permissions onElementOrNull(1000) { text == "Allow all" }?.click() // Click "Allow" button at bottom onElement { text == "Allow" && className == "android.widget.Button" }.click() } // Screen 2: Background access ("access data in the background") val backgroundScreen = onElementOrNull(2000) { text?.contains("access data in the background") == true } if (backgroundScreen != null) { // Click "Allow" button onElement { text == "Allow" && className == "android.widget.Button" }.click() } } // ========================================================================= // Standard Runtime Permissions (Optional) // ========================================================================= /** * Handle standard Android runtime permission dialogs. */ private fun UiAutomatorTestScope.handleRuntimePermissions() { val allowButton = onElementOrNull(1000) { text?.matches(Regex("(?i)allow|while using the app")) == true } allowButton?.click() } }
Replace {PACKAGE_NAME} with the actual package name (e.g.,
).com.hitoshura25.healthsync
To find the package name:
grep "applicationId" app/build.gradle.kts # Or check AndroidManifest.xml grep "package=" app/src/main/AndroidManifest.xml
Note: The
UiAutomatorTestScope extension functions allow accessing the scope's methods like onElement from within helper functions.
Verification (MANDATORY)
⛔ DO NOT SKIP THIS STEP
Prerequisite: Device/Emulator Required
First, verify a device or emulator is available:
adb devices
If no devices listed:
- Start an emulator:
# List available AVDs emulator -list-avds # Start an emulator (replace with actual AVD name) emulator -avd Pixel_6_API_34 & # Wait for device to be ready adb wait-for-device - Or connect a physical device with USB debugging enabled
- Re-run
to confirmadb devices
If no device/emulator available, STOP. Inform user this skill cannot complete without a device.
Run Tests
# Run debug tests ./gradlew connectedDebugAndroidTest
If tests fail:
- Read the error message carefully
- Fix compilation errors (usually import issues)
- Fix test failures (adjust permission handling if needed)
- Re-run until tests pass
Only proceed to completion when tests pass.
Expected Output
> Task :app:connectedDebugAndroidTest Tests on Pixel_6_API_34 - 14 SmokeTest > appLaunches_doesNotCrash PASSED SmokeTest > appLaunches_hasVisibleContent PASSED 2 tests, 2 passed, 0 failed
Completion Criteria
Do NOT mark complete unless ALL are verified:
- UI Automator 2.4 dependency in
app/build.gradle.kts -
exists using modernSmokeTest.kt
APIuiAutomator { } -
executes successfully./gradlew connectedDebugAndroidTest - At least 2 tests pass
- Device/emulator was used (tests cannot run without one)
If tests fail, fix them before marking complete.
Troubleshooting
No device found
Cause: No connected device or running emulator Fix: Start emulator or connect physical device (see Verification section)
Tests fail to compile
Cause: Incorrect package name in SmokeTest.kt Fix: Verify package name matches AndroidManifest.xml
Permission dialog blocks test
Cause: App requires permissions not handled in handleHealthConnectPermissions() Fix: See
PERMISSION_DEBUGGING.md for guide on debugging UI Automator selectors
"waitForAppToBeVisible timed out"
Cause: App crashed or took too long to start Fix: Check logcat:
adb logcat -d | grep -i crash
UI Automator 2.4 API Benefits
The modern API provides several advantages over the old approach:
| Old API (deprecated) | New API (UI Automator 2.4) |
|---|---|
| scope |
| |
| |
| Manual timeout loops | Built-in timeout: |
+ click | |
| |
Key benefits:
- Cleaner Kotlin DSL
- Built-in waiting (no more
everywhere)waitForIdle() - More readable predicates
- Better null handling with
onElementOrNull - Works with non-debuggable APKs (can test actual release builds)
Next Steps
After smoke tests pass:
- For release testing: Install release APK and run tests against it (see
)android-release-validation - Add more comprehensive tests if needed (use
skill)android-additional-tests - Integrate with CI/CD pipeline