Espresso
Set up Espresso for Android UI testing and integrate test results with TestKase.
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("user@example.com"), 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("user@example.com"), 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})_", or use JUnit5's @DisplayName("[48271] ...").
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).
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 |
| Default bracket pattern | Requires JUnit5 @DisplayName("[48271] ...") | (default) | 48271 |
Generate the 5-digit ID in TestKase first, then embed it in your test method name or annotation.
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("user@example.com"), 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("user@example.com"), 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()