← back to blog

how to run Espresso tests against cloud phones

May 06, 2026

how to run Espresso tests against cloud phones

if you want to run Espresso tests on a cloudf.one cloud phone, the workflow is essentially “Android Studio without the USB cable.” Espresso is an instrumentation framework. it ships inside your test APK, runs in the same process as the app under test, and reports results back via the AndroidJUnitRunner. all of that is device-agnostic, which means any device reachable over ADB can run Espresso tests, including cloud phones connected via ADB-over-network.

cloud phones are particularly good for Espresso because Espresso is most useful on real-device behaviors: gestures, animations, scroll inertia, soft-keyboard interactions, accessibility events. these all emit on real arm64 hardware and behave inconsistently on emulators.

why Espresso on cloud phones beats Espresso on emulators

Espresso runs in-process with the app under test, which gives it sub-frame precision on view-hierarchy assertions. that precision is its strength when the device behaves predictably. emulators add unpredictability via x86 translation layers and host-OS scheduler noise. cloud phones eliminate that noise because they are real Samsung-class hardware running real Android kernels.

three categories benefit most. animation-heavy UIs (Espresso’s idling-resource model handles real animations cleanly). multi-window edge cases (split-screen, picture-in-picture). accessibility tests (TalkBack and Switch Access behave correctly only on real devices).

for context on the test-architecture choice, see real device cloud phones for mobile app testing and cloud phone vs physical Android device.

prerequisites

your Android project should already have the Espresso dependencies. if not, add them:

// app/build.gradle.kts
dependencies {
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    androidTestImplementation("androidx.test.espresso:espresso-contrib:3.5.1")
    androidTestImplementation("androidx.test:runner:1.5.2")
    androidTestImplementation("androidx.test:rules:1.5.0")
}

android {
    defaultConfig {
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
}

on your laptop or CI runner: Java 17, Android platform-tools (for adb), the Android SDK, and Gradle.

step 1: connect to your cloud phone

log in to cloudf.one and copy the ADB endpoint for your test phone.

adb connect adb-sg.cloudf.one:5555
adb devices

the device should show up with the network endpoint as its serial. our ADB setup guide covers troubleshooting.

step 2: run Espresso tests via Gradle

with one device connected, the simplest run is the standard Gradle command:

./gradlew :app:connectedAndroidTest

Gradle reads the connected device list, builds the test APK and the app APK, installs both on the cloud phone, and runs the tests. results land in app/build/reports/androidTests/connected/.

if you have multiple devices connected (cloud phones, USB devices, emulators), pin Espresso to the cloud phone:

./gradlew :app:connectedAndroidTest \
  -PandroidTestSerial=adb-sg.cloudf.one:5555

step 3: write your first Espresso test

create app/src/androidTest/java/com/example/SmokeTest.kt:

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
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 SmokeTest {
    @get:Rule
    val rule = ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun loginButton_isVisible() {
        onView(withId(R.id.loginButton)).check(matches(isDisplayed()))
    }

    @Test
    fun loginFlow_succeeds() {
        onView(withId(R.id.usernameField)).perform(typeText("test"))
        onView(withId(R.id.passwordField)).perform(typeText("test123"))
        onView(withId(R.id.loginButton)).perform(click())
        onView(withId(R.id.welcomeText)).check(matches(withText("Welcome, test")))
    }
}

run:

./gradlew :app:connectedAndroidTest -PandroidTestSerial=adb-sg.cloudf.one:5555

results appear in HTML form at app/build/reports/androidTests/connected/index.html.

step 4: handle idling resources for async work

Espresso’s killer feature is the idling-resource API. it lets your tests wait for async work without Thread.sleep.

import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.idling.CountingIdlingResource

class NetworkIdler {
    private val idler = CountingIdlingResource("network")
    fun start() { idler.increment() }
    fun stop() { idler.decrement() }
    fun register() { IdlingRegistry.getInstance().register(idler) }
}

call start() when the network call begins and stop() when it completes. Espresso blocks any UI assertion until the idler reports idle. this works reliably on cloud phones because real-device timing is consistent.

step 5: run instrumented tests against multiple cloud phones in parallel

Gradle by default runs against one device. for parallel execution across many cloud phones, use AndroidX ManagedDevices or third-party tools like Spoon or Composer.

with Gradle managed devices and cloud phones, the simplest pattern is to define multiple Gradle tasks, each pinned to a different device serial:

# in CI, parallelize via shell
adb connect adb-sg.cloudf.one:5555
adb connect adb-sg.cloudf.one:5556
adb connect adb-sg.cloudf.one:5557

# shard the test suite 3 ways
./gradlew connectedAndroidTest -PandroidTestSerial=adb-sg.cloudf.one:5555 \
  -Pshard=0 -PshardCount=3 &
./gradlew connectedAndroidTest -PandroidTestSerial=adb-sg.cloudf.one:5556 \
  -Pshard=1 -PshardCount=3 &
./gradlew connectedAndroidTest -PandroidTestSerial=adb-sg.cloudf.one:5557 \
  -Pshard=2 -PshardCount=3 &
wait

cloudf.one’s REST API gives you a list of locked devices. wire the lock loop into your CI step; see CI/CD integration for the full GitHub Actions example.

step 6: capture screenshots and screen recordings

for failure debugging, capture screenshots and a video.

@RunWith(AndroidJUnit4::class)
class ScreenshotTest {
    @get:Rule
    val screenshotRule = ScreenshotOnFailureRule()
    // your tests
}

or use the AndroidX androidx.test.runner.screenshot.Screenshot API in a JUnit TestWatcher rule. for video recordings, use ADB:

adb -s adb-sg.cloudf.one:5555 shell screenrecord --time-limit 180 /sdcard/test.mp4
adb -s adb-sg.cloudf.one:5555 pull /sdcard/test.mp4 artifacts/

Espresso vs UiAutomator on cloud phones

Espresso runs in-process with your app and only sees views inside your app’s process. UiAutomator runs out-of-process and sees the entire system UI. for app-internal tests, use Espresso. for cross-app flows (notification, settings, system dialogs), use UiAutomator. both work identically on cloud phones because both ride on ADB-over-network.

common pitfalls

three issues catch teams new to Espresso on cloud phones.

first, soft keyboard. Espresso typeText() does not auto-dismiss the keyboard. use closeSoftKeyboard() if subsequent assertions fail because the keyboard is covering the view.

second, animations. disable system animations on the cloud phone for stable runs:

adb -s adb-sg.cloudf.one:5555 shell settings put global window_animation_scale 0
adb -s adb-sg.cloudf.one:5555 shell settings put global transition_animation_scale 0
adb -s adb-sg.cloudf.one:5555 shell settings put global animator_duration_scale 0

third, the test orchestrator. for clean test isolation, use the AndroidX test orchestrator. it runs each test in its own process, preventing shared-state bugs.

the official Espresso docs cover edge cases.

try Espresso on a real Singapore cloud phone

register for a free trial for a one-hour cloud phone token. that’s enough to run a full smoke suite end to end. once confirmed, scale to a paid plan and add cloud phones to your test pool.

frequently asked questions

does Espresso work over a slow network?

Espresso runs in-process on the device, so the test logic itself does not depend on network latency. ADB-over-network only matters for the install and the result reporting, which happen at the start and end. the test loop itself is fast even from outside Singapore.

can I run Espresso tests on multiple Android versions in parallel?

yes. tag cloud phones in cloudf.one by Android version (android-13, android-14, android-15). lock one phone per version in CI and run the same test suite against all of them simultaneously. our Android version coverage post covers fleet sizing.

what about Robolectric vs Espresso?

Robolectric runs in a JVM with stubbed Android APIs. it is fast but does not catch real-device bugs. Espresso runs on a real device and catches them. use both: Robolectric for unit tests, Espresso for instrumented tests on cloud phones.

how do I debug a flaky Espresso test on a cloud phone?

enable Espresso’s verbose logging, capture a screen recording during the run, and inspect logcat. the cloud phone’s session is persistent during your lock, so you can keep the phone state and re-run interactively.

does Espresso work with Jetpack Compose?

yes. add the androidx.compose.ui:ui-test-junit4 dependency and use composeTestRule alongside Espresso rules. both work on cloud phones identically.