Espresso is a framework for automated UI testing of Android applications, developed by Google and included in AndroidX Test. Unlike instrumented tests that check isolated components, Espresso interacts with the real UI: it clicks buttons, enters text, and checks element visibility. According to Google Android Developers, Espresso provides automatic synchronization with the UI thread, eliminating the need for manual Thread.sleep().
Key Takeaways
Espresso is a library for writing automated UI tests for Android, part of Google AndroidX Test. It provides an API for finding View elements on screen, performing actions on them (click, input, swipe), and verifying their state (displayed, contains text, is enabled).
The key feature of Espresso is automatic synchronization with the application's main thread. The framework waits for all async tasks (coroutines, AsyncTask, Handler) to finish before executing the next check. This eliminates flaky tests caused by race conditions and makes UI tests stable and reliable — no test contains Thread.sleep() or polling loops.
Espresso follows the Three-Legged Dog principle — a test consists of three steps: find an element (ViewMatcher), perform an action (ViewAction), verify the result (ViewAssertion). All three steps are written in a chain of calls: onView().perform().check(). This concept makes tests predictable and easy to read — each test explicitly describes what it looks for, what it does, and what it checks.
Architecture of Espresso is based on three components: Espresso (entry point — static methods onView and onData), ViewMatchers (element search), ViewActions (actions) and ViewAssertions (checks). Internally, the framework uses Idling Resource for synchronization with the UI thread.
The simplest test finds a button by ID, performs a click, and checks that the text “Done” appears. All operations are synchronous from the test's perspective — Espresso guarantees that the UI thread has finished processing the event before the test continues. This is achieved through a built-in waiting mechanism: onView blocks the test execution until the UI becomes idle.
@Test
fun buttonClick_showsSuccessText() {
// Find button by ID and click
onView(withId(R.id.button_submit))
.perform(click())
// Verify that the text “Done” is displayed
onView(withText("Done"))
.check(matches(isDisplayed()))
}
To launch an Espresso test, ActivityScenario (AndroidX Test) is used, which creates an Activity in a specific state — running, paused, or destroyed. ActivityScenario allows testing the Activity lifecycle in addition to pure UI testing. For example, you can verify that data is preserved on screen rotation (Activity recreation) and restored after destruction.
ViewMatchers is a set of methods from the Espresso.onView class that allow finding Views on screen by various criteria: resource ID (R.id), text, hint, parent element, and hierarchy. If one matcher does not produce a unique result, matchers can be combined using allOf().
| Matcher | Purpose |
|---|---|
| withId(R.id.name) | Search by resource ID |
| withText(“text”) | Search by displayed text |
| withHint(“hint”) | Search by EditText hint attribute |
| isDisplayed() | Check that the element is visible on screen |
| hasSibling(matcher) | Search by sibling element |
| allOf(m1, m2) | Combine multiple matchers |
If there are multiple identical elements on screen (e.g., two TextViews with different text), it is convenient to combine matchers using allOf: onView(allOf(withId(R.id.title), withText(“Hello”))). This guarantees selecting a single element. The inverse operator — not() — excludes elements from the search, and hasSibling() finds an element next to a known one.
ViewActions are actions that Espresso performs on the found View: click(), typeText(), clearText(), scrollTo(), swipeLeft() and others. Actions are passed to the perform() method, which can accept multiple actions in sequence.
The perform() method accepts vararg ViewAction, allowing you to execute a sequence of actions on one element: clear the field, enter new text, close the keyboard, and click a button. All actions execute in the order they are listed, and Espresso guarantees that the previous action is complete before the next one starts.
// Enter text in EditText and click button
onView(withId(R.id.edit_email))
.perform(
clearText(),
typeText("user@example.com"),
closeSoftKeyboard()
)
onView(withId(R.id.button_login))
.perform(click())
For elements inside AdapterView (ListView, RecyclerView), the onData() method is used instead of onView. It works with adapter data rather than Views — it finds an element by model content and returns the corresponding View for further actions. onData uses hamcrest matchers to locate an element by model data fields.
ViewAssertions verify that a View is in a specific state. The basic method — matches(matcher) — checks that the element matches the given matcher. Additionally, Espresso offers doesNotExist() (element is absent) and selectedDescendantsMatch() (checking nested elements).
The most frequent checks in UI tests: element is displayed (isDisplayed), element contains specific text (withText), element is enabled (isEnabled), element is not checked (isNotChecked). Each check throws a detailed exception on failure — including the View hierarchy on screen. This simplifies debugging: the error message shows which elements were actually on screen at the time of the check.
If standard checks are insufficient, you can create a custom one through the ViewAssertion interface. A custom assertion receives a View and can check its state programmatically — for example, text color, padding, or the state of a custom component not exposed through standard matchers.
// Check: TextView is displayed and contains text
onView(withId(R.id.text_welcome))
.check(matches(isDisplayed()))
.check(matches(withText("Welcome")))
// Check: element is NOT displayed
onView(withId(R.id.progress_bar))
.check(doesNotExist())
Idling Resource is Espresso's mechanism for synchronizing the test with asynchronous operations. By default, Espresso waits for Handler, AsyncTask, and coroutines (via coroutinesIdlingResource). If the application performs background work through custom threads or callback services, you must register a custom Idling Resource.
Starting from AndroidX Test 1.4.0, Espresso supports coroutines via CoroutinesIdlingResource. The test automatically waits for all launched coroutines before performing UI checks. For more complex scenarios, CountingIdlingResource is used — a counter that increments on task start and decrements on completion.
// Register IdlingResource for OkHttp
class OkHttpIdlingResource(
private val client: OkHttpClient
) : IdlingResource {
private var isIdle = true
private var watcher: IdlingResource.ResourceCallback? = null
override fun getName() = "OkHttp"
override fun isIdleNow() = isIdle
override fun registerIdleTransitionCallback(
callback: IdlingResource.ResourceCallback
) {
watcher = callback
}
}
Integrating Espresso into an Android project is done by adding dependencies to the module-level build.gradle. Espresso is part of AndroidX Test, so it is enough to specify dependencies for the Espresso core, extensions, and JUnit integration. Tests are placed in the src/androidTest directory and run on a physical device or emulator via AndroidJUnitRunner.
The minimal dependency set includes espresso-core (core), espresso-contrib (additional matchers for RecyclerView, Drawer, Picker) and runner (AndroidX test runner). All tests run on an emulator or physical device via Android Test Orchestrator.
// build.gradle.kts (androidTest dependencies)
android {
defaultConfig {
testInstrumentationRunner =
"androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation("androidx.test.espresso:espresso-contrib:3.6.1")
androidTestImplementation("androidx.test:runner:1.6.1")
androidTestImplementation("androidx.test:rules:1.6.1")
}
Espresso tests can be run via Google Android Test Orchestrator, which isolates each test in a separate process and clears state between runs. This eliminates flaky tests caused by residual data from previous tests and improves stability on CI servers. For parallel execution, sharding is used — distributing tests across multiple emulators.
Frequently Asked Questions
Espresso works inside the application process and uses automatic synchronization with the UI thread. UI Automator works at the system level, can interact with other applications, but requires manual wait management.
This is a metaphor from a Google presentation: an Espresso test stands on three pillars — ViewMatcher (find), ViewAction (act), and ViewAssertion (verify). Removing any one makes the test unstable, like a three-legged dog.
For RecyclerView, the espresso-contrib library is used with methods like onView(withId(R.id.recycler)).perform(actionOnItemAtPosition(0, click())). An alternative is onData() for AdapterView or a custom ViewAction to find an element by text inside RecyclerView. Additionally, RecyclerViewActions from espresso-contrib can be used for scrolling to an element and performing actions on it.
A flaky test is a test that sometimes fails without code changes, due to race conditions or asynchronicity. Espresso solves this problem using Idling Resource — waiting for all background tasks to complete before performing checks.
Espresso itself is not designed for screenshot tests, but it can be combined with libraries like Shot or Paparazzi. Espresso prepares the UI in the desired state, and the comparison library takes a screenshot and compares it with a reference. This approach is called visual regression testing and helps find unexpected interface changes.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also