Skip to content

Appium Flaky Tests: Root Causes and Stable Mobile Automation (2026)

Appium adds a WebDriver-protocol layer on top of UiAutomator2 and XCUITest, and that layer is its own flake source. Session/capability mismatches, wait anti-patterns, cross-platform driver differences, and stale sessions — plus the honest story on how Qualflare ingests Appium results.

İbrahim Süren
Founder · Aug 14, 2026 · 10 min read
Appium Flaky Tests: Root Causes and Stable Mobile Automation (2026)
Get Qualflare updates

Product news and testing tips.

Appium sits between your test code and the platform's real automation engine — UiAutomator2 on Android, XCUITest on iOS — as a WebDriver-protocol server, and that extra hop is its own source of flakiness on top of whatever UiAutomator2 or XCUITest already contribute. The biggest Appium-specific causes are capability/session mismatches, mixing implicit and explicit waits, cross-platform driver differences, and stale sessions left behind by crashed runs. Bitrise's analysis of 10M+ mobile builds found flakiness climbed from 10% of teams in 2022 to 26% in 2025.

Key takeaways

  • Appium is a WebDriver-protocol server, not a testing framework itself — it drives UiAutomator2 on Android and XCUITest on iOS, and every command makes a network round trip through that protocol layer.
  • Capability mismatches (platformVersion, deviceName, automationName not matching the actual device) cause intermittent session-creation failures, especially on device farms with rotating device pools.
  • Mixing implicit and explicit waits is a documented Selenium/Appium anti-pattern: Selenium's own docs warn it 'can cause unpredictable wait times' — a 10s implicit + 15s explicit wait can time out at 20s, not 15.
  • The same Appium test code can behave differently on UiAutomator2 vs. XCUITest because the two drivers have different synchronization and element-resolution semantics underneath.
  • Appium itself has no official JUnit reporter. If your suite runs on JUnit/TestNG via Surefire, or on pytest, those tools already produce the JUnit-XML Qualflare ingests — Qualflare never sees 'Appium', only the report format.
  • Bitrise's analysis of 10M+ mobile builds found the share of teams hitting flakiness rose from 10% in 2022 to 26% in 2025.

Appium tests flake for a reason Espresso and XCUITest tests don’t have to deal with: an extra network hop. Where Espresso talks to Android directly and XCUITest talks to iOS directly, Appium sits in between as a WebDriver-protocol server — your test code talks to Appium, Appium talks to a platform driver (UiAutomator2 on Android, XCUITest itself on iOS), and the driver talks to the device. Every one of those hops is a place for timing to go wrong, on top of whatever flakiness UiAutomator2 or XCUITest already contribute on their own. Bitrise’s analysis of 10M+ mobile builds found the share of teams hitting flakiness climbed from 10% in 2022 to 26% in 2025 — and Appium’s protocol layer is a real contributor to that trend for teams running it.

This is the Appium-specific companion to Flaky Mobile Tests: Why Android & iOS Tests Fail Randomly, which covers causes shared across every mobile framework — this post goes deep on what’s unique to Appium’s own architecture. It’s one spoke in the complete guide to mobile testing.

Why is Appium more flaky than Espresso or XCUITest alone?

Appium’s own docs describe its architecture as split into Appium Core, Drivers, Clients, and Plugins — a protocol server mediating between your test code and a platform-specific driver. On Android, that driver is UiAutomator2; on iOS, it’s the XCUITest driver, which means Appium’s iOS automation is built directly on top of XCUITest, not a competing implementation. Every command — tap, type, query — travels from the test script to the Appium server, gets translated and forwarded to the platform driver, executes on the device, and the result travels back the same path.

That means Appium inherits every native flake source UiAutomator2 and XCUITest already have on their own, and adds a protocol-layer surface on top that neither of those frameworks carries when used directly. A network hiccup between client and server, a slow translation step, or server-side queueing under load can all produce a flake that has nothing to do with the app being tested.

Session and capability mismatches

Starting an Appium session requires desired capabilities — platformVersion, deviceName, automationName, udid, and others — that Appium’s own capabilities guide describes as “the core parameters used to start an Appium session.” When a capability doesn’t match what’s actually available — a platformVersion that isn’t installed on the assigned device, a deviceName that doesn’t exist in the current pool — session creation fails.

This is rarely a hardcoded, always-fails mismatch; those get caught immediately in development. The flaky version shows up on device farms with rotating device pools: a capability set that matched yesterday’s assigned device doesn’t match today’s, because the farm assigned a different physical device or emulator image to the same test run. The fix is defensive, not clever — pin capabilities to what the farm’s pool actually guarantees rather than what one specific device happened to provide, and log the resolved session capabilities on every run so a mismatch is visible immediately instead of discovered after the fact.

Explicit waits vs. implicit waits in Appium

Appium’s Java and Python clients inherit Selenium’s WebDriver wait APIs directly, and Selenium’s own documentation is explicit about the trap: mixing implicit and explicit waits “can cause unpredictable wait times.” A 10-second implicit wait combined with a 15-second explicit wait doesn’t cap out at either number — it can produce a 20-second timeout, because the implicit wait fires on every internal findElement call inside the explicit wait’s own polling loop, and the two durations stack instead of one overriding the other.

The fix is to pick one strategy for the whole suite, not a clever workaround:

import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;
import io.appium.java_client.AppiumBy;

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
WebElement loginButton = wait.until(
    ExpectedConditions.elementToBeClickable(
        AppiumBy.accessibilityId("login-button")
    )
);
loginButton.click();
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from appium.webdriver.common.appiumby import AppiumBy

wait = WebDriverWait(driver, 15)
login_button = wait.until(
    EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "login-button"))
)
login_button.click()

Use explicit WebDriverWait with ExpectedConditions for the elements that actually need a wait, and leave the driver’s implicit wait at its default of zero everywhere else. A suite that’s inconsistent about this — explicit waits in some files, an implicit wait set once in a base class and forgotten — is exactly the setup that produces intermittent, hard-to-reproduce timeouts.

Cross-platform driver differences: UiAutomator2 vs. XCUITest

The same Appium test code, run against both platforms, can pass reliably on one and flake on the other — and that’s not automatically an app bug. UiAutomator2 and the XCUITest driver are different automation engines underneath a shared WebDriver interface, with real differences: element-resolution behavior across the two platforms’ accessibility trees isn’t identical, each driver has its own notion of when the app is “idle” before the next command proceeds, and gesture primitives (swipes, long-presses, multi-touch) are implemented differently on each side. A suite that assumes uniform timing across both drivers is assuming something Appium doesn’t actually guarantee — a flake that only appears on one platform is often exposing that difference, not a defect in the app itself.

Appium server and session cleanup

A crashed test run can leave a session the Appium server still considers active, and on iOS specifically, the underlying WebDriverAgent process can keep running on the device after the client disconnects. The next test run that tries to start a fresh session on the same device can then fail or behave unpredictably because a stale session or lingering process is still holding resources.

The fix is disciplined teardown, not just a happy-path driver.quit():

@AfterEach
void tearDown() {
    if (driver != null) {
        driver.quit(); // run even when the test failed, not just on success
    }
}

For CI specifically, checking the Appium server’s GET /sessions endpoint before starting a new run — and clearing any orphaned sessions found — catches the cases a single test’s teardown can’t, like a job that was killed by a CI timeout before its own cleanup ran.

Network and device-farm latency: infrastructure flakiness vs. test-code flakiness

Not every red Appium run is the same kind of problem, and treating them identically wastes triage time. The pattern is visible in where and how a failure shows up, not in any single run:

SignalPoints to test-code flakinessPoints to infrastructure flakiness
Where failures happenSame assertion or wait, every timeDifferent tests, different steps, no shared assertion
Device/platform spreadConcentrated on one platform or device profileSpread across devices, farms, or time of day
CorrelationFollows an app or test-code changeFollows farm queue depth, network conditions, or CI load
Fix that resolves itChanging the wait/locator/assertionRetrying, or nothing — it clears on its own

Spotting this requires historical pass/fail data across runs, not a judgment call on one red build — the same detection problem flaky mobile tests covers generally, applied to Appium’s specific failure surface.

How does Qualflare handle Appium test results?

Here’s the honest version, stated plainly rather than implied: Espresso and Maestro write JUnit-XML natively and are named, directly parsed frameworks in Qualflare’s CLI. XCTest/XCUITest needs a .xcresult-to-JUnit-XML conversion step but is still a named, parsed framework. Appium has neither — no official JUnit reporter of its own, and no dedicated Appium parser in Qualflare’s CLI, because there’s no single Appium output format for one to parse.

What Appium suites actually produce is whatever their client harness emits. A Java suite running on JUnit or TestNG via Maven Surefire produces standard JUnit-XML at target/surefire-reports/ — Qualflare’s existing JUnit/TestNG parser reads that file the same way it reads any other JUnit/TestNG output. A Python suite running pytest --junitxml=results.xml produces the same shape of file — Qualflare’s existing pytest parser reads it identically. Qualflare’s CLI auto-detects the file format, not the framework that generated it: it’s reading JUnit, TestNG, or pytest output, and that output happening to come from an Appium-driven UI test underneath is invisible to the parser. See Qualflare’s framework support for the full list of what’s named directly.

Once that JUnit-XML lands in Qualflare, the same flaky-scoring approach applies as everywhere else: pass/fail history across runs, not a single result, with the same root-cause workflow for narrowing down what’s actually wrong.

Start free with Qualflare — if your Appium suite already writes JUnit-XML via JUnit, TestNG, or pytest, it’s already something Qualflare can ingest.

Frequently asked questions

Does Qualflare support Appium?

Not as a named framework — Appium has no official JUnit reporter, so there’s nothing for a dedicated Appium parser to read. But if your Appium suite is written with JUnit or TestNG via Surefire, or with pytest, Qualflare already ingests that output, because those tools produce standard JUnit-XML regardless of Appium being underneath. This is different from Espresso, XCTest, and Maestro, which Qualflare supports as explicitly named frameworks.

Why is Appium flakier than Espresso or XCUITest used directly?

Appium doesn’t replace those frameworks — on Android it drives UiAutomator2, and on iOS it drives XCUITest itself — so every flake source native to those drivers is still present. Appium adds a WebDriver-protocol server on top: each command travels from the test script to the Appium server to the platform driver to the device and back, and that round trip is an additional failure surface that doesn’t exist when you call UiAutomator2 or XCUITest directly.

What causes intermittent Appium session-creation failures?

Usually a mismatch between the desired capabilities your test requests (platformVersion, deviceName, automationName, udid) and what the actual connected device or emulator provides. This is especially common on device farms with rotating device pools, where a capability that matched yesterday’s assigned device doesn’t match today’s.

Should I use implicit or explicit waits in Appium?

Pick one, not both. Appium’s Java and Python clients inherit Selenium’s WebDriver wait APIs, and Selenium’s own documentation warns against mixing implicit and explicit waits because it “can cause unpredictable wait times” — a 10-second implicit wait combined with a 15-second explicit wait can produce a 20-second timeout instead of either configured value. Use explicit WebDriverWait with ExpectedConditions and leave the implicit wait at its default of zero.

How do I tell if an Appium test failure is a real bug or infrastructure flakiness?

Look at where the failure clusters. A failure that always happens at the same assertion or wait, tied to a specific app state, is a test-code or app bug. A failure that happens on different tests, different devices, or different times, with no shared assertion, and correlates with device-provisioning or queue delays, is infrastructure flakiness. Historical pass/fail tracking across runs makes this pattern visible; a single red build doesn’t.

What’s different between the UiAutomator2 and XCUITest drivers in Appium?

They’re different automation engines with different synchronization behavior, so identical Appium test code can pass reliably on one platform and flake on the other. UiAutomator2 talks to Android’s accessibility and instrumentation layers; XCUITest talks to Apple’s native UI-testing framework through WebDriverAgent. Element-resolution timing, gesture handling, and what counts as “the app is idle” all differ between them.

Ready to ship with confidence?

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