Skip to content

One Dashboard for Espresso, XCTest & Maestro: Unifying Android + iOS Test Results (2026)

Android runs Espresso, iOS runs XCTest, and Maestro covers both — three CI jobs, three formats, no combined pass-rate. Here's exactly how each reaches JUnit-XML, and how to get all three into one dashboard.

İbrahim Süren
Founder · Aug 14, 2026 · 10 min read
One Dashboard for Espresso, XCTest & Maestro: Unifying Android + iOS Test Results (2026)
Get Qualflare updates

Product news and testing tips.

A typical mobile team runs Espresso for Android, XCTest/XCUITest for iOS, and often Maestro for cross-platform E2E flows — three CI jobs, three result formats, no combined pass-rate or flaky picture across the whole app. All three can reach JUnit-XML: Espresso writes it natively via Gradle, Maestro writes it natively via `--format junit`, and XCTest's `.xcresult` bundles need conversion — the currently maintained tool is `a7ex/xcresultparser`, since `fastlane-community/trainer` stopped shipping releases in 2019. Qualflare's CLI ingests all three, plus Appium's JUnit output, into one dashboard alongside web and API tests.

Key takeaways

  • Android, iOS, and cross-platform E2E tests usually run as three disconnected CI jobs — Espresso, XCTest/XCUITest, and Maestro — each producing its own result format with no combined pass-rate or flaky picture.
  • Espresso and Maestro both write JUnit-XML natively; only XCTest/XCUITest's .xcresult bundles need conversion.
  • The once-standard XCTest converter, fastlane-community/trainer, hasn't shipped a release since 2019 and hasn't been pushed to since late 2021 — the actively maintained tool in 2026 is a7ex/xcresultparser (latest release 2.2.0, July 2026).
  • Appium has no official JUnit reporter of its own — the client framework produces it (JUnit/TestNG via Surefire, pytest's --junit-xml, or WebdriverIO's @wdio/junit-reporter) — and Qualflare already ingests that output.
  • Mobile build flakiness is a growing, measured problem: Bitrise's 2025 Mobile Insights Report found the share of teams experiencing flakiness grew from 10% to 26% between 2022 and 2025.
  • Qualflare's CLI (qf <project> collect <results-file>) auto-detects all of these JUnit-XML-compatible formats into one dashboard, whether the tests ran on a local emulator, a self-hosted device farm, or a third-party device cloud — it never touches execution.

A mobile team running Espresso for Android and XCTest/XCUITest for iOS has two CI jobs and two result formats that can’t see each other — add Maestro for cross-platform E2E flows and it’s three. Each produces a report nobody combines, so there’s no single pass rate, no cross-platform flaky picture, and no way to answer “is this release healthy?” without opening three dashboards. The good news: all three can reach JUnit-XML. Espresso and Maestro write it natively; XCTest’s .xcresult bundles need one conversion step, currently best handled by a7ex/xcresultparser. Once every framework speaks JUnit-XML, Qualflare’s CLI merges them into one dashboard.

Why mobile teams end up with three disconnected test reports

Android and iOS are built on different toolchains by design, so their test runners were never going to share a reporter. Espresso tests run through Gradle and AndroidX Test. XCTest and XCUITest run through Xcode’s own build system. Maestro, adopted specifically to write one E2E flow that drives both platforms, runs as a third, separate CI step with its own report. Add an Appium suite for legacy device coverage and that’s four independent pipelines, four independent artifacts, and — this is the part that actually costs teams — no combined view of whether the app, as a whole, is shipping safely.

This isn’t a hypothetical fragmentation problem. Mobile build instability is measurably rising: Bitrise’s 2025 Mobile Insights Report found the share of teams experiencing any test flakiness grew from 10% to 26% between 2022 and 2025 — and the same report found teams using monitoring tools see 25% fewer flaky reruns. A flaky Espresso test and a flaky XCUITest test that share a root cause — a shared backend staging environment, say — look like two unrelated problems when nobody’s watching both platforms at once. This post covers the reporting layer specifically; for the broader picture of Android/iOS test management, see the mobile testing complete guide.

How Espresso results reach JUnit-XML

Espresso needs no conversion step at all. Running ./gradlew connectedAndroidTest uses AndroidX Test’s AndroidJUnitRunner, which writes JUnit-XML directly as part of the test run:

./gradlew connectedAndroidTest
# XML results land at:
# module/build/outputs/androidTest-results/connected/*.xml

That path is documented behavior of the Android Gradle plugin’s command-line test tasks, not a side effect you have to configure. Any CI job that runs connectedAndroidTest (or connectedCheck) already has a JUnit-XML file sitting in build/outputs when the task finishes — zero extra tooling.

How XCTest and XCUITest results reach JUnit-XML

iOS is the one platform in this stack with a real conversion step. Xcode writes test results as an .xcresult bundle — a binary, database-like package, not XML — via xcodebuild test -resultBundlePath TestResults.xcresult. To get JUnit-XML out of it, you need a converter.

For years the default answer was fastlane-community/trainer. It isn’t anymore: its last release shipped in 2019, and its GitHub history shows no real commits since November 2021 — a single dependency bump, then nothing. Xcode 16 also changed xcresulttool itself, deprecating the old get object subcommand in favor of a new get test-results family (summary, tests, activities) — which is exactly the kind of change an unmaintained wrapper won’t track. Recommending trainer in 2026 means recommending a tool built against a CLI interface Apple has since replaced.

The actively maintained alternative is a7ex/xcresultparser — latest release 2.2.0, shipped July 2026, with releases landing every few months. It parses an .xcresult bundle directly and writes JUnit-XML:

xcodebuild test -scheme MyApp -resultBundlePath TestResults.xcresult
xcresultparser -o junit TestResults.xcresult > junit.xml

That junit.xml is what you hand to any JUnit-XML-consuming platform, Qualflare included.

How Maestro results reach JUnit-XML

Maestro, like Espresso, needs no conversion. Its own documentation on test reports states plainly that “JUnit is the standard for CI/CD integration and for test reporting” — you get it with one flag:

maestro test --format junit --output report.xml .maestro/

That’s a single command producing a standard JUnit-XML file for a Maestro flow suite that already exercises both Android and iOS builds of the same app — genuinely useful for a cross-platform E2E layer sitting above the two native unit/UI suites.

What about Appium?

Appium is a different case: it’s a WebDriver-protocol automation server, not a test framework, so it has no official JUnit reporter of its own. Reporting comes from whatever client library and test framework drive the Appium session:

  • Java + JUnit/TestNG — the Maven Surefire plugin writes JUnit-XML to target/surefire-reports/ the same way it would for any JUnit suite.
  • Python + pytestpytest --junit-xml=report.xml produces standard JUnit-XML.
  • JavaScript + WebdriverIO — the official @wdio/junit-reporter package writes it.

If your Appium suite already emits JUnit-XML through any of these — and most CI-integrated Appium suites do, since Surefire, pytest, and wdio’s reporter are the default choice for their ecosystems — Qualflare already ingests it. There’s no Appium-specific integration to build.

Android, iOS, and Maestro: native JUnit-XML vs. conversion required

FrameworkNative JUnit-XML?Recommended pathTool / command
Espresso (Android)Yes — native./gradlew connectedAndroidTest writes XML directlyAndroidX Test’s AndroidJUnitRunner
XCTest / XCUITest (iOS)No — needs conversion from .xcresultConvert the .xcresult bundlea7ex/xcresultparser (xcresultparser -o junit)
MaestroYes — nativemaestro test --format junit writes XML directlyBuilt into the Maestro CLI
Appium (Java)Indirect — via the client frameworkJUnit/TestNG through Maven Surefiretarget/surefire-reports/*.xml
Appium (Python)Indirect — via the client frameworkpytestpytest --junit-xml=report.xml
Appium (JavaScript)Indirect — via the client frameworkWebdriverIO@wdio/junit-reporter

Wiring it into one CI pipeline

Once every job produces a JUnit-XML file, the CI wiring is the same four steps regardless of platform:

  1. Run each suite as it already runs today./gradlew connectedAndroidTest for Android, xcodebuild test -resultBundlePath ... for iOS, maestro test for cross-platform flows.
  2. Convert only where needed — XCTest’s .xcresult output through xcresultparser; Espresso and Maestro already need nothing.
  3. Point the CLI at each results fileqf myapp collect <results-file>, once per job, using the same project name across all three.
  4. View the combined dashboard — pass rate, flaky tests, and failures across Android, iOS, and your E2E layer land in the same Qualflare project instead of three separate reporters.

In a GitHub Actions pipeline, that’s three parallel jobs each ending in the same qf collect step:

jobs:
  android:
    runs-on: ubuntu-latest
    steps:
      - run: ./gradlew connectedAndroidTest
      - run: qf myapp collect app/build/outputs/androidTest-results/connected/*.xml

  ios:
    runs-on: macos-14
    steps:
      - run: xcodebuild test -scheme MyApp -resultBundlePath TestResults.xcresult
      - run: xcresultparser -o junit TestResults.xcresult > junit.xml
      - run: qf myapp collect junit.xml

  maestro-e2e:
    runs-on: macos-14
    steps:
      - run: maestro test --format junit --output maestro-report.xml .maestro/
      - run: qf myapp collect maestro-report.xml

Three CI jobs, three commands, one myapp project on the other end.

One dashboard for Android + iOS + everything else

This is the same multi-framework aggregation problem this blog has covered for Playwright, Jest, and pytest — mobile just adds two more formats to the pile. The CLI auto-detects JUnit-XML-compatible results with no per-framework adapter to write, the same zero-config approach documented for Qualflare’s 23+ supported frameworks, which already lists Espresso, XCTest, and Maestro by name alongside Playwright, Jest, and pytest. That’s what turns per-framework reporting into real test observability — analysis across results, not just a shared viewer for them.

The payoff is the single pane of glass argument applied to mobile specifically: a flaky pattern that shows up in both the Espresso suite and the XCUITest suite — a shared staging API, a shared test-account pool — is invisible if nobody ever looks at both reports side by side, and obvious the moment flaky scoring runs across the combined data. The same project can hold your web and API JUnit-XML results too, so “release health” stops meaning four dashboards and starts meaning one pass rate, one flaky list, and one set of defects for the whole product — not just the mobile half of it. For the fuller case on what that observability layer is and isn’t, see what mobile test observability actually means.

Qualflare doesn’t run your tests — on any device, anywhere

Worth stating plainly, because it’s easy to conflate with device-execution tools: Qualflare never touches test execution. It doesn’t provision emulators, simulators, or real devices, and it doesn’t schedule or run your test suite. It only needs the results file a run already produced — which means it works identically whether that run happened on a developer’s local emulator, a self-hosted device lab, or a third-party device cloud like BrowserStack, Sauce Labs, or Firebase Test Lab. Swap execution environments and nothing about the ingestion step changes: same file, same qf collect command, same dashboard.

Start free with Qualflare — collect Espresso, XCTest, and Maestro results into one project and see Android, iOS, and everything else on one pass-rate and flaky-test dashboard.

Frequently asked questions

Does Espresso need any conversion to produce JUnit-XML?

No. Running ./gradlew connectedAndroidTest uses AndroidX Test’s AndroidJUnitRunner, which writes JUnit-XML directly to module/build/outputs/androidTest-results/connected/ — no converter, plugin, or extra step required.

Why do XCTest and XCUITest results need converting, and what should I use in 2026?

Xcode writes XCTest/XCUITest results as .xcresult bundles, a binary format, not XML — so they need a converter to become JUnit-XML. The once-standard tool, fastlane-community/trainer, hasn’t shipped a release since 2019. The actively maintained alternative is a7ex/xcresultparser (latest release 2.2.0, July 2026), which parses an .xcresult bundle and outputs JUnit-XML directly: xcresultparser -o junit TestResults.xcresult > junit.xml.

Does Maestro produce JUnit-XML natively?

Yes. Running maestro test --format junit --output report.xml <flow> writes a JUnit-XML report directly — no conversion step, the same as Espresso.

Does Qualflare support Appium test results?

Yes, indirectly through whichever client framework your Appium suite already uses. Appium itself has no official JUnit reporter — a Java+JUnit/TestNG suite gets one via Surefire (target/surefire-reports/), a Python+pytest suite via pytest --junit-xml=, and a JS+WebdriverIO suite via the official @wdio/junit-reporter. If your suite already emits JUnit-XML through one of these, Qualflare’s CLI already ingests it.

Does Qualflare run my mobile tests or execute them on real devices?

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

Can I see Android, iOS, and web/API test results in one dashboard?

Yes. Point the CLI at each framework’s results file — Espresso’s XML, the converted XCTest JUnit-XML, Maestro’s JUnit-XML, and any web/API JUnit-XML you already collect — and they land in the same Qualflare project, giving one pass-rate, flaky-test, and defect picture across the whole app instead of one dashboard per platform.

Ready to ship with confidence?

Start free with Qualflare's AI-powered test management.