CLOUSD
Open account
Testing

Test an Android app online

One build, three Android versions, the install errors you will meet and the logcat commands that find the crash, without a drawer of test phones.

Testing an Android app online means the phone lives somewhere else. You reach it over the network, install your build, watch the screen in a browser and pull logs with adb as if a cable were plugged in. Done well, it replaces most of what a drawer of test phones gives you. Done badly, it is a slow emulator with extra steps.

This guide takes one build from APK to crash logs on three Android versions. It uses the commands I run every week, lists the install errors you will meet, and ends with the places where a physical phone is still the better tool.

Prepare the build

Start from a build you can install from the command line. A debug build is fine for a first pass:

./gradlew assembleDebug
ls app/build/outputs/apk/debug/

Two checks save time later. The first is the ABI. If your app ships native code, make sure the APK contains arm64-v8a libraries. Phones built from real models are arm64, and unlike the x86 emulator images on your laptop they do not translate code built for another architecture.

unzip -l app/build/outputs/apk/debug/app-debug.apk | grep "lib/"

The second is the testOnly flag. When you press Run in Android Studio, the IDE marks the APK with android:testOnly="true", and a plain adb install refuses it. Builds from assembleDebug do not carry the flag. If all you have is an IDE build, install it with adb install -t.

If your release artifact is an app bundle, turn it into an APK set for the connected phone with bundletool:

bundletool build-apks --bundle=app-release.aab --output=app.apks --connected-device
bundletool install-apks --apks=app.apks

Without --ks, bundletool signs with the debug key. That is fine for testing, but the result will not install over a copy signed with your release key.

Pick three Android versions, not one

A single phone hides most compatibility bugs, because Android changes behavior in two ways. Some changes hit every app on the new release. Others switch on only when you raise targetSdkVersion. Three versions catch most of both:

  • The oldest version you still support, or the closest you can get. Android 12 is a sensible floor for most apps in 2026.
  • The version with the biggest share in your own analytics, often 14 or 15.
  • The newest release, 16 or 17, where target-gated changes such as enforced edge-to-edge and predictive back show up first.

The full list of what changes between each pair is long. Testing an app on different Android versions has the version-by-version checklist.

Step 1: create the phones

On a cloud phone service you choose an Android version and a model, and the phone is ready in about a minute. Name each one after its version, for example qa-12, qa-14 and qa-16, so logs and screenshots stay readable a week later. If you script this from CI, your first device shows the same steps through the API.

Pick models your users actually own. A budget phone with a 720p panel and a flagship at 1440p break different layouts, so mixing screen densities across the three versions buys you more coverage for the same time.

Step 2: install the build

There are three ways to get the APK onto the phone: upload it in the browser dashboard, send it through the REST API, or run adb install over the ADB relay. The apps guide covers the first two. For adb, enable the relay for your IP address as described in the ADB docs, then connect:

adb connect HOST:PORT
adb -s HOST:PORT install -r app-debug.apk

Install failures are almost always one of five, and the error string tells you which:

ErrorCauseFix
INSTALL_FAILED_NO_MATCHING_ABISno native libraries for the phone's ABIadd arm64-v8a to abiFilters
INSTALL_FAILED_TEST_ONLYAPK built by the IDE Run buttonadb install -t, or build with Gradle
INSTALL_FAILED_UPDATE_INCOMPATIBLEa copy signed with another key is installedadb uninstall the old copy first
INSTALL_FAILED_DEPRECATED_SDK_VERSIONtarget below API 23 on Android 14, below 24 on 15 and laterraise targetSdkVersion
INSTALL_PARSE_FAILED_MANIFEST_MALFORMEDa component with an intent filter lacks android:exported on 12 and laterdeclare exported on every such component

Step 3: run a smoke pass you can repeat

Run the same pass the same way on every version, so the differences you see are differences in Android and not in how you tapped. Mine covers six things:

  1. Cold start. am start -W prints TotalTime in milliseconds. Compare it across versions, not against a physical flagship.
  2. First run. Install without -g, so every runtime permission prompt appears the way a new user sees it.
  3. Background and return. Press Home, open two other apps, come back.
  4. Process death. With the app in the background, am kill ends the process the way the system does under memory pressure. Returning to the app must restore the screen and its state.
  5. Configuration. Dark mode, a larger font, a different display size.
  6. Back navigation. Gesture back from every top-level screen.

The commands, with S holding the device address:

S=HOST:PORT
adb -s $S shell am force-stop com.example.app
adb -s $S shell am start -W -n com.example.app/.MainActivity
adb -s $S shell input keyevent KEYCODE_HOME
adb -s $S shell am kill com.example.app
adb -s $S shell cmd uimode night yes
adb -s $S shell settings put system font_scale 1.3
adb -s $S shell wm density 560
adb -s $S shell wm density reset

Undo the font scale with settings put system font_scale 1.0 and dark mode with cmd uimode night no before the next pass, or reset the phone.

Step 4: read the logs

Clear the buffer before each scenario, then read only your own process:

adb -s $S logcat -c
adb -s $S shell am start -W -n com.example.app/.MainActivity
adb -s $S logcat -d --pid=$(adb -s $S shell pidof -s com.example.app)

The pid changes every time the process restarts, so a crash during startup will not show under the new pid. Read the crash buffer instead. It keeps Java and native crashes for every process:

adb -s $S logcat -d -b crash
adb -s $S logcat -d AndroidRuntime:E "*:S"

For an ANR, logcat gives you only the headline. adb bugreport report.zip collects the thread traces from that moment along with system state. It takes a minute or two, and it is the file to attach to the ticket.

Keep the three logs side by side. A warning that appears only on the newest version is usually a target-gated change. A crash on all three is your code.

Step 5: reset between runs

pm clear wipes your app's data and leaves everything else in place: other apps, settings you changed, the Google account. For a clean comparison between suites, reset the phone to a fresh state, and keep pm clear for quick reruns inside one suite.

Once the pass is stable, it belongs in CI. The app testing page shows the same steps as API calls. Instrumentation tests are plain adb commands, so they run against any device adb can reach:

adb -s $S shell am instrument -w -e class com.example.LoginTest \
  com.example.app.test/androidx.test.runner.AndroidJUnitRunner

Where an online test stops

Be honest about what a phone in a data center cannot tell you:

  • Camera quality, autofocus, real GPS drift and biometric sensors.
  • Graphics performance. Virtualized Android renders through translated or software drivers, so frame times and jank counts do not transfer to a physical phone.
  • Battery drain and thermal throttling.
  • Vendor software. Battery optimizers and autostart settings on some brands stop background work in ways stock Android does not.
  • Radios: NFC payments, Bluetooth pairing, carrier features.

Keep two or three physical phones for those checks and run the wide matrix online.

Free ways to start

"Real device testing online free" usually means one of three things. The Android Emulator on your own machine costs nothing and is the right place for the edit and run loop. Public device clouds offer small free quotas of physical phones, mostly for automated runs. Cloud phone services usually offer a trial: Clousd gives the first 30 minutes free with no card, enough to install a build and run a first smoke pass, see the free trial.

Keep reading

All guides

Ready to launch
your first cloud phone?

Try free for 30 minutes

No card needed