Skip to content

Espresso test reporting

Espresso is the easiest of Qualflare’s framework integrations to set up, because there’s nothing to configure: ./gradlew connectedAndroidTest writes JUnit-XML automatically. This guide covers how that output works, CI patterns for running it against emulators, and how to add hosted, historical analysis — AI failure clustering, flaky-test scoring, and per-launch risk — on top with Qualflare.

Espresso’s native JUnit-XML output, explained

Pipeline diagram: Gradle's connectedAndroidTest task produces JUnit-XML automatically, uploaded with qf collect --format espresso, landing in Qualflare for AI clustering, flaky scoring, and risk

Espresso is a Gradle-based, in-process UI testing framework — part of AndroidX Test, scoped to testing within your own app (an espresso-remote extension covers multi-process scenarios inside that app). It complements, rather than competes with, UI Automator, which tests from outside the app’s process and can drive other apps and system UI — Espresso stays in-app.

Running the instrumented test task uses AndroidJUnitRunner, which writes results as a documented, automatic side effect of the task — no reporter to add, no config to edit:

# Run the instrumented suite via Gradle — writes results automatically
./gradlew connectedAndroidTest

# JUnit-XML lands at:
# app/build/outputs/androidTest-results/connected/*.xml
# A matching HTML report lands at:
# app/build/reports/androidTests/connected/index.html

Both paths are documented behavior of the Android Gradle plugin’s command-line test tasks — not a side effect you have to opt into. Any CI job that runs connectedAndroidTest already has a JUnit-XML file sitting in build/outputs when the task finishes. That’s the whole selling point of Espresso reporting: there’s no format to configure and no conversion step, unlike iOS, where Xcode’s .xcresult bundles need a converter before they become JUnit-XML (see our XCTest reporting guide).

Why the raw XML (and its HTML report) isn’t enough

The auto-generated HTML report is genuinely useful — open index.html and you get a browsable summary of the run with zero setup. But like Playwright’s HTML reporter, it’s built for inspecting one run. It’s written to a local build/reports folder and overwritten the next time the task runs. There’s no history across CI builds, no aggregation when a suite is split across multiple emulators, and no way to see whether a test has gotten flakier over the last two weeks. You can archive each run’s XML and HTML as CI artifacts, but a pile of zip files per build is storage, not reporting — nothing connects build #1481 to build #1482, and nobody opens them.

For anything beyond a single run, you need a place that collects results over time and analyzes them: trend lines, flakiness scores, failure grouping. That’s the gap the rest of this guide fills — first the CI plumbing, then the analysis layer.

CI patterns: GitHub Actions, GitLab, Jenkins

GitHub Actions. Android instrumented tests need a running emulator, so most GitHub Actions setups use a community action like reactivecircus/android-emulator-runner to boot one, then run connectedAndroidTest inside it. Upload results even when tests fail — a reporting step that only runs on green builds is useless on the day you need it:

# .github/workflows/android.yml
- name: Run Espresso tests on an emulator
  uses: reactivecircus/android-emulator-runner@v2
  with:
    api-level: 34
    script: ./gradlew connectedAndroidTest

- name: Upload results to Qualflare
  if: always() # upload even when tests fail — that's the point
  run: qf myapp collect app/build/outputs/androidTest-results/connected/*.xml --format espresso

GitLab CI. GitLab renders JUnit XML natively in the pipeline’s Tests tab, the same way it does for any framework — run the task on a runner with an SDK, emulator (or hardware-accelerated KVM), and Gradle already set up, then declare the XML as a JUnit report:

# .gitlab-ci.yml — JUnit XML doubles as GitLab's native test report
espresso:
  script:
    - ./gradlew connectedAndroidTest
  artifacts:
    when: always
    reports:
      junit: app/build/outputs/androidTest-results/connected/*.xml
    paths:
      - app/build/reports/androidTests/connected/

Jenkins. Same idea: run ./gradlew connectedAndroidTest, hand the resulting XML to the classic junit 'app/build/outputs/androidTest-results/connected/*.xml' pipeline step for Jenkins’ own build-level pass/fail tracking, then upload the same file with qf collect for the analysis layer Jenkins doesn’t provide.

Whatever the platform, the principle is the same as every other framework Qualflare supports: run the suite, upload the results file with if: always() semantics — one extra step, not a rewrite.

Common Espresso reporting problems (and fixes)

Mobile CI flakiness is a measured, growing problem, not just a feeling: Bitrise’s 2025 Mobile Insights Report found the share of teams experiencing test flakiness grew from 10% in 2022 to 26% in 2025. Most of the causes below are specific to running a UI test against a virtual device rather than a plain JVM.

  • Emulator instability in CI. CI-hosted emulators are often headless and, without hardware acceleration (KVM on Linux runners), meaningfully slower than a developer’s local machine — timing-sensitive assertions that pass locally can fail intermittently in CI simply because the device is slower, not because the app is broken.
  • Animations causing intermittent failures. Google’s own Espresso setup guide is direct about this: “To avoid flakiness, we highly recommend that you turn off system animations” — Window animation scale, Transition animation scale, and Animator duration scale, all under Developer options — on any device or emulator used for testing (developer.android.com).
  • Async operations without an IdlingResource. Espresso’s synchronization only covers operations posted to the app’s MessageQueue — network calls, database writes, and background work outside that queue need an IdlingResource registered, or Espresso can act before the async work finishes. Google’s docs warn that the usual workaround — Thread.sleep() — “might still fail sometimes when executed on slower devices,” which describes a CI emulator almost exactly.
  • Sharding across multiple emulators. AndroidJUnitRunner supports splitting a suite with -e numShards / -e shardIndex instrumentation arguments, so teams commonly run N emulators in parallel, each executing one shard. Unlike Playwright, there’s no built-in merge tool — each shard writes its own separate XML file:
# AndroidJUnitRunner shards natively — split into 4, run each on its own emulator/job
adb shell am instrument -w -e numShards 4 -e shardIndex 0 \
  com.myapp.test/androidx.test.runner.AndroidJUnitRunner
# (repeat with shardIndex 1, 2, 3 on parallel CI jobs)
# No merge-reports equivalent for Espresso — upload each shard's XML separately
qf myapp collect shard-0-results.xml --format espresso
qf myapp collect shard-1-results.xml --format espresso
# ...repeat per shard; all land in the same project
  • The results file is missing after a timed-out run. AndroidJUnitRunner writes its XML when the task finishes — if the CI job is killed by a timeout (a common failure mode when a cold-booting emulator eats into the test budget) or runs out of memory, the file may never be written. Set the CI job timeout comfortably above how long the suite plus emulator boot actually takes, so the task ends and reports rather than getting killed mid-run.

Send Espresso results to Qualflare

There’s nothing to configure on the Espresso side — the XML is already there once the task finishes. Point the Qualflare CLI at it:

# Upload the XML — --format labels it Espresso for detection
qf myapp collect app/build/outputs/androidTest-results/connected/*.xml --format espresso

The general shape is qf <project> collect [files...] [flags]. Useful flags: --environment, --branch, --commit, and --dry-run to preview what would be uploaded. In CI it’s the one extra step shown in the GitHub Actions snippet above — and identical in GitLab CI, Bitbucket Pipelines, and Jenkins. Authenticate the CLI once with your Qualflare access token, stored as a CI secret — see the CLI docs.

What you get on top of raw XML

  • AI failure clustering. When a backend change breaks 30 Espresso tests across a dozen activities and fragments, Qualflare groups them by root cause so you triage a handful of clusters instead of 30 stack traces.
  • Flaky-test scoring from launch history. Standard JUnit-XML doesn’t carry retry or flaky-status metadata the way some frameworks’ native reporters do, so Qualflare scores flakiness by tracking each test’s pass/fail pattern across launches over time — surfacing the tests that fail intermittently even when no single run flags them.
  • Per-launch risk. Every CI run becomes a “launch” with a risk rating, the failing areas, and recommended next steps — a ship / don’t-ship signal that arrives with the results, across every emulator, API level, or shard you test against.
  • History & trends. Pass rate, slowest tests, and flakiness over time across branches and shards — the aggregation the local HTML report can’t do.
  • Context & defects. Failures keep their JUnit stack trace and logcat context where captured, and you can spin up a defect straight from a failing run.

Espresso’s HTML report vs Qualflare

  Espresso HTML report Qualflare
History across CI runsYes
Aggregates sharded / multi-emulator runsManual (no built-in merge)Yes
AI failure clustering (root cause)Yes
Flaky-test scoring over timeYes
Local, zero-setup, offlineYes
Human-readable without extra toolingYesYes

They’re complementary: keep the auto-generated HTML report for local debugging, add Qualflare for hosted, historical CI observability.

Get AI analysis on your Espresso runs

Start free — run connectedAndroidTest, upload with qf collect, and get your first AI analysis in minutes.

Get Started Free

Building out the rest of your mobile stack? See our guides to XCTest reporting for iOS and Maestro reporting for cross-platform E2E flows, or the full list of 23+ supported frameworks. For the bigger picture on getting Android and iOS results into one place, read unifying Android and iOS test reporting, fixing flaky mobile tests, or the complete guide to mobile test management. Weighing tools? See how Qualflare compares to other test management platforms, or browse all framework reporting guides.

Frequently asked questions

Does Qualflare run my Espresso tests on real devices or emulators?

No. Qualflare is a results-management and observability layer, not a device-execution cloud — it never provisions or touches emulators, simulators, or real devices, and it doesn’t schedule your test run. It only needs the JUnit-XML file your run already produced, so it works identically whether that run happened on a local emulator, a CI-hosted emulator (GitHub Actions, GitLab), a self-hosted device farm, or a third-party device cloud like Firebase Test Lab, BrowserStack, or Sauce Labs.

Which Android test frameworks does Qualflare support?

Espresso and UI Automator — both part of AndroidX Test, both written through AndroidJUnitRunner into the same JUnit-XML format — plus Maestro for cross-platform Android/iOS E2E flows, which writes JUnit-XML natively via --format junit. Any other Android test runner that emits JUnit-compatible XML also works through Qualflare’s generic catch-all, even without a named integration. See the full list on the frameworks page.

Do I need to configure a reporter to get JUnit-XML out of Espresso?

No — this is what makes Espresso the simplest of Qualflare’s framework integrations to set up. Running ./gradlew connectedAndroidTest uses AndroidX Test’s AndroidJUnitRunner, which writes JUnit-XML (and a matching HTML report) automatically as part of the task. There’s no reporter package to install and no config file to edit, unlike Playwright or Jest, where you explicitly enable a JSON or JUnit reporter.

Does Qualflare have Espresso-specific parsing logic?

Honestly: no, not beyond format detection. The --format espresso flag labels the upload so the CLI and dashboard identify it correctly, but the file itself routes through the same JUnit-XML-compatible ingestion path as every other JUnit-XML framework Qualflare supports. There’s no bespoke Espresso parser doing anything Espresso-specific with the XML — the value Qualflare adds (clustering, flaky scoring, risk) comes from analysis across your history, not from special parsing of this one format.

How do I send Espresso results to Qualflare?

Run ./gradlew connectedAndroidTest as usual, then point the CLI at the XML file it writes: qf <project> collect app/build/outputs/androidTest-results/connected/*.xml --format espresso. The CLI attaches your Git branch and commit automatically (or pass --branch/--commit explicitly), turning the run into a tracked launch.

Does Qualflare detect flaky Espresso tests?

Yes, but the mechanism is different from a framework like Playwright, whose JSON output carries native retry and flaky-status fields. Standard JUnit-XML — what Espresso writes — doesn’t include that metadata, so Qualflare scores flakiness by watching each test’s pass/fail pattern across launches over time: a test that alternates between green and red across otherwise-identical runs gets flagged, even though no single XML file says “flaky.”

Setup reflects the Qualflare CLI (docs.qualflare.com) and Android/AndroidX Test documentation as of August 2026. Published 14 August 2026. Written by İbrahim Süren, founder of Qualflare.