Flaky Espresso tests on Android emulators are most often caused by asynchronous work that Espresso cannot see, such as network calls, database queries, or coroutine and RxJava streams that complete after the test's assertion already ran, which is exactly the gap idling resources and Espresso's IdlingRegistry exist to close. Animations and transitions are another major source, since Espresso's default synchronization does not always wait for a running animator or a RecyclerView layout pass to finish before interacting with a view, and this gets worse on slower emulator images or CI runners with limited CPU allocation. Emulator performance variability itself matters: an underpowered CI host running an emulator without hardware acceleration can make timing-dependent tests pass locally and fail in CI purely due to slower rendering. Shared mutable state, such as a singleton or a local database not reset between tests, and tests that depend on execution order rather than isolated setup, also produce intermittent failures. Turning off window animation scale, transition animation scale, and animator duration scale in the emulator's developer options is a standard mitigation. Nanobase AI configures Espresso environments inside its Mobile Test Lab to control these synchronization and performance variables so Android test runs stay deterministic.

Start by identifying which of two failure signatures you have

Espresso flakiness almost always falls into one of two signatures: an AppNotIdleException or an element-not-found error that only occurs intermittently. The first points to unregistered asynchronous work Espresso cannot see; the second usually points to animation timing or emulator performance. Diagnosing which one you have before applying a fix saves time, since the remedies are different and applying the wrong one, for example adding a sleep to fix an idling issue, just hides the symptom. Match the fix to the specific failure signature rather than applying a generic delay.

Implementing an IdlingResource correctly

Espresso only knows about work it can observe through its own IdlingRegistry. A network call made through a coroutine or RxJava stream is invisible to Espresso by default, which is why a test can query a view before the response updates it, even though the request completes a moment later. The fix is registering a custom IdlingResource around the specific async operation:

class DataLoadIdlingResource(private val repository: Repository) : IdlingResource {
    private var callback: IdlingResource.ResourceCallback? = null
    override fun getName() = "DataLoadIdlingResource"
    override fun isIdleNow(): Boolean {
        val idle = !repository.isLoading
        if (idle) callback?.onTransitionToIdle()
        return idle
    }
    override fun registerIdleTransitionCallback(cb: IdlingResource.ResourceCallback?) {
        callback = cb
    }
}

Register this in test setup and unregister it in teardown; leaving idling resources registered across tests is itself a common source of test pollution. An unregistered IdlingResource is the single most common root cause behind an intermittent element-not-found failure in Espresso.

Emulator configuration that removes a second failure class

Beyond code-level fixes, emulator configuration accounts for a large share of Espresso flakiness that looks like a test bug but is actually an environment issue.

SettingWhere to change itEffect
Window animation scaleDeveloper options, or adb shell settings put global window_animation_scale 0Removes transition-timing races
Transition animation scaleSame developer options menuRemoves screen-transition races
Animator duration scaleSame developer options menuRemoves custom animator races
Hardware acceleration (KVM)CI runner or host configurationFixes slow-rendering timing variance in CI specifically
Emulator API level/imageAVD configurationNewer images have more consistent Espresso synchronization behavior

Disabling all three animation scales via adb shell settings put global at the start of a CI job is a standard, low-effort mitigation that removes an entire category of intermittent failures. These three settings cost nothing to disable and eliminate an entire class of flakiness that no amount of code-level fixing addresses.

When the emulator itself is the bottleneck

A test that is stable locally but flaky in CI specifically, with no code changes, usually points to the CI host lacking hardware acceleration. An emulator without KVM (Linux) or without the Apple Hypervisor framework (macOS) falls back to software rendering, which is dramatically slower and introduces exactly the kind of timing variance that produces intermittent Espresso failures under load from parallel jobs on the same host. Verify KVM is actually active before spending time debugging a test that only fails in CI.

Frequently asked questions

Why does a test pass when run alone but fail in the full suite?

This is almost always a shared state issue: a singleton, static field, or local database not reset between tests. Run the specific test in isolation versus the full suite with logging around setup and teardown to confirm state leakage before assuming it's a timing problem.

Is Espresso's built-in synchronization enough, or do I always need custom idling resources?

Espresso automatically synchronizes with the main thread and standard Android widgets like RecyclerView, but any custom async work, coroutines, RxJava, WorkManager, or third-party SDKs, needs an explicit IdlingResource since Espresso cannot infer it.

Does increasing emulator RAM or CPU allocation reduce flakiness?

Often yes, if the CI host was under-provisioned relative to the number of parallel emulator instances running. Confirm actual CPU and memory allocation per emulator against what your AVD config requests before assuming code is the only variable.

Should we use UI Automator instead of Espresso to avoid these issues?

UI Automator trades Espresso's tight main-thread synchronization for out-of-process interaction, which avoids some in-process timing issues but introduces different flakiness sources around cross-process communication; it is not a strict upgrade for flakiness.

How Nanobase AI helps

Nanobase AI configures Espresso environments inside its Mobile Test Lab specifically to control these synchronization and hardware acceleration variables, verifying KVM configuration and animation settings as a standard part of every CI setup rather than debugging them reactively. For the broader CI-level flakiness process beyond Android specifics, see our flaky test reduction guide or our solutions.

Ready to discuss your project? Contact Nanobase AI or email hello@bumu.tech.