Espresso
Set up Espresso for Android UI testing and integrate test results with TestKase.
Want to run your Espresso suite on our real Android devices instead? Upload your app and androidTest APKs and we run them on real phones and emulators. See Automate → Espresso on real devices.
Overview
Espresso is Android's native UI testing framework, part of the AndroidX Test library. Tests are written in Kotlin or Java and run directly on Android devices or emulators, providing fast and reliable UI interaction with automatic synchronization of the UI thread.
To integrate Espresso results with TestKase, use --format junit with the TestKase reporter. Gradle's
connectedAndroidTest task automatically generates JUnit XML output.
Prerequisites
- Android Studio (latest stable recommended)
- Android SDK with platform tools
- Java 11+
- Gradle (bundled with Android Studio)
- An Android emulator or physical device with USB debugging enabled
Installation
Add the Espresso dependencies to your app/build.gradle:
// app/build.gradle
android {
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:rules:1.5.0'
}Project Setup
build.gradle Configuration
A complete app/build.gradle for Espresso testing:
// app/build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.example.myapp'
compileSdk 34
defaultConfig {
applicationId "com.example.myapp"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
// Espresso and AndroidX Test
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:rules:1.5.0'
}Directory Structure
Instrumented test files go in the androidTest source set:
app/
src/
main/java/com/example/myapp/ # Application code
androidTest/java/com/example/myapp/ # Espresso testsWriting Tests
Create a test class in the androidTest directory:
// app/src/androidTest/java/com/example/myapp/LoginTest.kt
package com.example.myapp
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LoginTest {
@get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@Test
fun test_48271_ValidLogin() {
onView(withId(R.id.emailInput))
.perform(typeText("[email protected]"), closeSoftKeyboard())
onView(withId(R.id.passwordInput))
.perform(typeText("password123"), closeSoftKeyboard())
onView(withId(R.id.loginButton))
.perform(click())
onView(withId(R.id.dashboardTitle))
.check(matches(isDisplayed()))
}
@Test
fun test_48272_InvalidPassword() {
onView(withId(R.id.emailInput))
.perform(typeText("[email protected]"), closeSoftKeyboard())
onView(withId(R.id.passwordInput))
.perform(typeText("wrong"), closeSoftKeyboard())
onView(withId(R.id.loginButton))
.perform(click())
onView(withId(R.id.errorMessage))
.check(matches(withText("Invalid credentials")))
}
@Test
fun test_48273_EmptyEmailValidation() {
onView(withId(R.id.loginButton))
.perform(click())
onView(withId(R.id.emailError))
.check(matches(withText("Email is required")))
}
}Link 5-digit Automation IDs from TestKase to each test. Since Kotlin/Java method names cannot contain
brackets, embed the ID in the method name using underscores (e.g., test_48271_ValidLogin) and configure
the reporter with --automation-id-format "_(\\d{5})_".
Android instrumentation tests run on JUnit 4 (@RunWith(AndroidJUnit4::class)), so JUnit 5's
@DisplayName is not available here — the method name is the only place an ID can go.
For the tests above, generate IDs in TestKase and link them:
48271→ linked to the "valid login" test case in TestKase48272→ linked to the "invalid password" test case in TestKase48273→ linked to the "empty email" test case in TestKase
Generate Automation IDs in TestKase first. For JUnit4 frameworks like Espresso where method names
cannot contain brackets, use the --automation-id-format CLI flag to customize the extraction regex.
Running Tests
Run all instrumented tests with Gradle:
./gradlew connectedAndroidTestFor verbose output:
./gradlew connectedAndroidTest --infoGradle generates JUnit XML results in:
app/build/outputs/androidTest-results/connected/Each device produces a separate XML file (e.g., TEST-emulator-5554 - 14-com.example.myapp.LoginTest.xml).
The commands below use a recursive **/*.xml glob so the files are found whether or not your Android
Gradle Plugin version nests them in a per-variant subdirectory. Confirm the path with
ls -R app/build/outputs/androidTest-results/ after your first run.
To run tests for a specific class, use the -Pandroid.testInstrumentationRunnerArguments.class flag:
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.myapp.LoginTest
TestKase Integration
After tests complete and the JUnit XML files are generated, report results to TestKase:
npx @testkase/reporter report \
--token $TESTKASE_PAT \
--project-id PRJ-1 \
--org-id 1173 \
--cycle-id TCYCLE-5 \
--format junit \
--automation-id-format "_(\\d{5})_" \
--results-file "app/build/outputs/androidTest-results/connected/**/*.xml"--cycle-id is optional. If not provided, results are reported to TCYCLE-1 — the master test cycle for the project.
Automation ID Mapping
The reporter extracts Automation IDs based on the configured pattern. For Espresso, embed the 5-digit ID
in the method name and use a custom --automation-id-format:
| Approach | Test Method | CLI Flag | Extracted ID |
|---|---|---|---|
| Underscore pattern | fun test_48271_ValidLogin() | --automation-id-format "_(\\d{5})_" | 48271 |
The default [XXXXX] bracket pattern cannot be used with Espresso: instrumentation tests run on JUnit 4,
so there is no @DisplayName to carry the brackets, and Kotlin/Java method names cannot contain [ or
]. Generate the 5-digit ID in TestKase first, then embed it in your test method name.
Complete Example
1. Test File
// app/src/androidTest/java/com/example/myapp/LoginTest.kt
package com.example.myapp
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LoginTest {
@get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@Test
fun test_48271_ValidLogin() {
onView(withId(R.id.emailInput))
.perform(typeText("[email protected]"), closeSoftKeyboard())
onView(withId(R.id.passwordInput))
.perform(typeText("password123"), closeSoftKeyboard())
onView(withId(R.id.loginButton))
.perform(click())
onView(withId(R.id.dashboardTitle))
.check(matches(isDisplayed()))
}
@Test
fun test_48272_InvalidPassword() {
onView(withId(R.id.emailInput))
.perform(typeText("[email protected]"), closeSoftKeyboard())
onView(withId(R.id.passwordInput))
.perform(typeText("wrong"), closeSoftKeyboard())
onView(withId(R.id.loginButton))
.perform(click())
onView(withId(R.id.errorMessage))
.check(matches(withText("Invalid credentials")))
}
}2. Run Tests
# Ensure an emulator is running or a device is connected
./gradlew connectedAndroidTest3. Report Results to TestKase
npx @testkase/reporter report \
--token $TESTKASE_PAT \
--project-id PRJ-1 \
--org-id 1173 \
--cycle-id TCYCLE-5 \
--format junit \
--automation-id-format "_(\\d{5})_" \
--results-file "app/build/outputs/androidTest-results/connected/**/*.xml"Troubleshooting
No connected devices
Espresso tests require a running emulator or a connected physical device. Start an emulator from Android Studio or the command line:
emulator -avd Pixel_6_API_34For a physical device, enable USB debugging in Developer Options and verify the connection:
adb devicesYou should see your device listed with a device status (not unauthorized or offline).
Test runner not found
If you see No tests found or a runner-related error, verify that testInstrumentationRunner is set
correctly in your app/build.gradle:
android {
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}Also confirm that the test class is in the androidTest source set (not the test source set, which is
for local unit tests).
Espresso IdlingResource for async operations
If tests fail because the UI has not finished loading, you need to register an IdlingResource so
Espresso knows to wait for asynchronous work to complete:
// Register before tests
IdlingRegistry.getInstance().register(myIdlingResource)
// Unregister after tests
IdlingRegistry.getInstance().unregister(myIdlingResource)Common scenarios that require idling resources include network calls, database operations, and animations.
You can use CountingIdlingResource for tracking multiple concurrent async tasks:
val idlingResource = CountingIdlingResource("NetworkCalls")
// Increment when starting async work
idlingResource.increment()
// Decrement when async work completes
idlingResource.decrement()