From agent-skills-android
Use when writing or debugging instrumented tests (Espresso, UI Automator, Compose test rules), using ADB, managing emulators, or inspecting UI with Layout Inspector.
How this skill is triggered — by the user, by Claude, or both
Slash command
/agent-skills-android:android-device-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Test your app on real or emulated Android devices. This skill covers instrumented testing frameworks (Espresso, UI Automator, Compose testing), device management (ADB, emulators), UI inspection (Layout Inspector), and debugging tools for on-device behavior.
Test your app on real or emulated Android devices. This skill covers instrumented testing frameworks (Espresso, UI Automator, Compose testing), device management (ADB, emulators), UI inspection (Layout Inspector), and debugging tools for on-device behavior.
Skip when: Writing unit tests that don't need a device (use JUnit5 + MockK instead).
// build.gradle.kts
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest")
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun taskItem_displaysTitle() {
composeTestRule.setContent {
AppTheme {
TaskItem(
task = Task("1", "Buy groceries", false),
onToggle = {},
onDelete = {},
)
}
}
composeTestRule
.onNodeWithText("Buy groceries")
.assertIsDisplayed()
}
@Test
fun taskItem_toggleCallsCallback() {
var toggledId: String? = null
composeTestRule.setContent {
AppTheme {
TaskItem(
task = Task("1", "Buy groceries", false),
onToggle = { toggledId = it },
onDelete = {},
)
}
}
composeTestRule
.onNodeWithContentDescription("Buy groceries")
.performClick()
assertEquals("1", toggledId)
}
@Test
fun taskList_showsEmptyState_whenNoTasks() {
composeTestRule.setContent {
AppTheme {
TaskListContent(
uiState = TaskListUiState.Success(tasks = emptyList()),
onToggle = {},
onDelete = {},
)
}
}
composeTestRule
.onNodeWithText("No tasks yet")
.assertIsDisplayed()
}
onNodeWithText("visible text") — most readableonNodeWithContentDescription("description") — for icons, imagesonNodeWithTag("test_tag") — last resort, add Modifier.testTag("tag")@Test
fun settingsScreen_displaysVersionNumber() {
onView(withId(R.id.version_text))
.check(matches(withText(containsString("1.0"))))
}
@Test
fun loginButton_disabled_whenFieldsEmpty() {
onView(withId(R.id.login_button))
.check(matches(not(isEnabled())))
}
onView(withId(R.id.recycler_view))
.perform(
RecyclerViewActions.actionOnItemAtPosition<ViewHolder>(
0, click()
)
)
@Test
fun notification_opensApp_whenTapped() {
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// Open notification shade
device.openNotification()
// Find and click notification
val notification = device.findObject(
UiSelector().textContains("New task")
)
notification.click()
// Verify app screen
composeTestRule
.onNodeWithText("Task Detail")
.assertIsDisplayed()
}
# List connected devices
adb devices
# Install APK
adb install -r app/build/outputs/apk/debug/app-debug.apk
# Run instrumented tests
./gradlew connectedAndroidTest
# Run specific test class
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.TaskListTest
# Take screenshot
adb shell screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png
# Clear app data
adb shell pm clear com.example.app
# View logs
adb logcat -s TAG_NAME:D
# Simulate process death
adb shell am kill com.example.app
# Simulate low memory
adb shell am send-trim-memory com.example.app RUNNING_CRITICAL
# Grant/revoke permissions
adb shell pm grant com.example.app android.permission.CAMERA
adb shell pm revoke com.example.app android.permission.CAMERA
# Simulate no network
adb shell svc wifi disable
adb shell svc data disable
# List available system images
sdkmanager --list | grep system-images
# Create emulator
avdmanager create avd \
--name "Pixel_7_API_35" \
--package "system-images;android-35;google_apis;x86_64" \
--device "pixel_7"
# Start emulator
emulator -avd Pixel_7_API_35 -no-snapshot-load
# Start headless (for CI)
emulator -avd Pixel_7_API_35 -no-window -no-audio -gpu swiftshader_indirect
android CLI for Deploy and Layout AssertionsUse android CLI as a thin wrapper around adb + avdmanager + emulator when available (probe with android --version; fall back to step 4/5 commands if absent):
# Lifecycle: emulator → install → run → inspect
android emulator list
android emulator start --name Pixel_7_API_35
android run --apks=app/build/outputs/apk/debug/app-debug.apk
android describe # locate built artifacts (JSON)
android run accepts a comma-separated APK list and an --activity flag; preferable to adb install -r + am start when you want a single deploy step that resolves the launcher activity automatically.
Use android layout for state-change assertions (faster than re-running an instrumented test for one-off checks):
# Snapshot before action, then diff after to see only what changed
android layout --pretty --output=before.json
adb shell input tap 540 1200 # perform the action
android layout --diff # only nodes added/changed/removed since last snapshot
Useful during test authoring: lets you discover the exact resource-id, text, and bounds of the elements your test should assert on, without guessing from a screenshot. See references/android-cli-reference.md.
/‾‾‾‾‾‾‾‾‾\
/ UI Tests \ ~5% — Espresso, UI Automator, Compose
/ (connected) \ (slow, flaky, but catch integration bugs)
/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\
/ Integration Tests \ ~15% — Robolectric, Room in-memory,
| (local or device) | MockWebServer
|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|
| Unit Tests | ~80% — JUnit5 + MockK
| (local, fast) | (ViewModels, UseCases, Repos)
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
| Shortcut | Why It Fails |
|---|---|
| "I tested manually, that's enough" | Manual testing doesn't catch regressions. Automated tests do. |
| "Instrumented tests are too slow" | Slow tests that catch real bugs beat fast tests that miss them. Run in CI. |
| "I'll just test on my device" | Your device is one API level, one screen size. The matrix matters. |
| "Compose tests are flaky" | Flaky tests usually have timing issues. Use waitUntil and proper assertions. |
Thread.sleep) instead of idling resources or waitUntil./gradlew connectedAndroidTest passesThread.sleep)Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub guillemroca/agent-skills-android --plugin agent-skills-android