Appium is an open-source framework for automating tests on mobile apps. It lets teams write tests for Android, iOS, and mobile web applications using one automation approach instead of maintaining separate test suites for each platform.
Appium works by sending commands from a test script to the Appium server, which then passes them to platform-specific drivers such as UiAutomator2 for Android and XCUITest for iOS.
In this guide, I’ll explain how Appium works, how to set it up, how to write your first Appium test, how to configure capabilities, use stable locators, handle waits, manage Android and iOS execution, and debug common mobile test failures.
What Can You Automate With Appium?
Appium can automate mobile apps across different app types and platforms. Its main use case is end-to-end mobile UI testing, where the test interacts with the app the way a real user would: tapping buttons, entering text, scrolling screens, switching contexts, and validating results.
Appium is commonly used for three types of mobile applications.
Native mobile apps
Native apps are built specifically for Android or iOS using platform SDKs and frameworks. These are the apps users install from the Play Store or App Store.
With Appium, you can automate native app flows such as:
- Login and signup
- Product search
- Cart and checkout
- Form submission
- Push notification flows
- Permission dialogs
- Navigation between screens
- File upload or camera-based flows, depending on device support
For Android native apps, Appium usually works through the UiAutomator2 driver. For iOS native apps, it uses the XCUITest driver.
Hybrid mobile apps
Hybrid apps combine native app shells with web content inside a WebView. Many mobile apps use this model for screens such as checkout, help pages, payment flows, or content-heavy sections.
Appium can automate hybrid apps by switching between:
- Native context, where it interacts with native app elements
- WebView context, where it interacts with web elements inside the app
This is useful when a single user journey moves across both native and web-based screens. For example, a user may start on a native login screen, move to a WebView payment page, and return to a native confirmation screen.
Mobile web apps
and Safari on iOS.
This is useful when teams need to validate how a web application behaves on mobile browsers, especially for:
- Responsive layouts
- Touch interactions
- Mobile browser navigation
- Form behavior
- Cross-browser mobile issues
Mobile web testing with Appium is different from testing a desktop browser because the test still runs against a real or simulated mobile environment.
How Appium Works in 2026
Appium works as a bridge between your test script and the mobile device. The test script does not talk to the Android or iOS app directly. It sends commands to the Appium server, and the server passes those commands to the right platform driver.
The flow looks like this:
- The test script sends a command, such as tap, type, scroll, or find element.
- The Appium server receives the command through the WebDriver protocol.
- Appium checks which driver is being used for the session.
- The driver sends the command to the platform automation framework.
- The device performs the action on the app.
- The result is sent back to the test script.
For Android, Appium commonly uses the UiAutomator2 driver. For iOS, it uses the XCUITest driver. These drivers are important because Appium itself does not automate the device directly. It depends on platform-specific drivers to understand how Android and iOS expose their UI elements and actions.
A simple way to understand the architecture is:
For example, when a test clicks a login button on an Android app, the command starts in the test script. The Appium server receives it, sends it to the UiAutomator2 driver, and the driver uses Android’s automation framework to find and tap the button.
The same idea applies to iOS. The test script sends a command to Appium, Appium routes it to the XCUITest driver, and the driver works with Apple’s testing framework to perform the action on the iPhone simulator or real device.
This driver-based model is important in modern Appium. Older tutorials often describe Appium as one bundled tool where most things are already included. That is no longer the right way to think about it. In current Appium versions, you install Appium first, then install the drivers you need for the platforms you want to test.
For most mobile automation teams, this means:
- Use UiAutomator2 for Android app testing.
- Use XCUITest for iOS app testing.
- Use Espresso only when your Android tests need closer synchronization with app internals.
- Keep Appium, drivers, and client libraries updated together to avoid compatibility issues.
This structure gives Appium flexibility, but it also means setup matters. If the wrong driver is installed, the capabilities are outdated, or the device environment is not ready, the test may fail before the app even opens.
Appium 1 vs Appium 2 vs Appium 3: What Changed?
Many Appium issues happen because teams follow setup steps written for an older version. This matters because Appium has changed significantly over time.
Appium 1 installed the server and common drivers together. Appium 2 changed that model by making drivers and plugins separate from the core Appium server. This means drivers such as UiAutomator2 and XCUITest must be installed and managed separately. Appium 3 keeps the same driver-based architecture, but removes more legacy behavior and expects a more modern setup.
Note: For new projects, start with Appium 3. Existing Appium 1 or 2 projects do not need to migrate immediately unless the setup is blocked, outdated, or hard to maintain. Migrating from Appium 1 needs more work because Appium 2 changed the driver and server model, while Appium 2 to Appium 3 is usually a smaller upgrade.
| Aspect | Appium 1 | Appium 2 | Appium 3 | What testers should do now |
|---|---|---|---|---|
| Setup model | More bundled. Many drivers came with the main Appium installation. | Modular. Appium server and drivers are installed separately. | Still modular, with more old behavior removed. | Install Appium first, then install only the drivers your project needs. |
| Driver management | Drivers were mostly handled as part of Appium. | Drivers are managed using Appium CLI commands. | Same driver-based model, but version compatibility matters more. | Use commands like appium driver install uiautomator2, appium driver install xcuitest, and appium driver list –installed. |
| Android automation | Older guides may mention UiAutomator, Selendroid, or bundled Android support. | UiAutomator2 and Espresso are installed as separate drivers. | UiAutomator2 remains the default choice for most Android UI automation. | Use UiAutomator2 for most Android tests. Use Espresso only when the test needs closer synchronization with the app. |
| iOS automation | Older guides may mention UIAutomation or older bootstrap flows. | XCUITest is installed as a separate driver. | XCUITest remains the standard driver for iOS automation. | Use XCUITest for iOS. Avoid tutorials that mention UIAutomation for modern iOS testing. |
| Protocol support | Supported older protocols such as JSON Wire Protocol and Mobile JSON Wire Protocol. | Removed JSONWP/MJSONWP support and moved to W3C WebDriver only. | Continues the W3C-first model with stricter behavior. | Use current Appium and Selenium client libraries that support W3C WebDriver. |
| Capabilities | Many examples used “Desired Capabilities” without prefixes. | Appium-specific capabilities need the appium: vendor prefix. | Same approach, with stricter expectations around valid capabilities. | Use W3C-style capabilities such as appium:automationName, appium:deviceName, and appium:app. |
| Plugins | Limited plugin-based extension model. | Plugins became part of the Appium ecosystem. | Plugins remain optional and are used for specialized workflows. | Do not add plugins by default. Use them only when the project has a clear need. |
| Appium Desktop | Many tutorials used Appium Desktop for server start and inspection. | Appium Inspector became the preferred separate inspection tool. | Inspector and CLI-based setup are the safer current path. | Use Appium CLI for server and driver management. Use Appium Inspector for locating elements. |
| Migration effort | Older Appium 1 projects may need larger changes. | Appium 2 introduced the biggest setup shift. | Appium 3 migration is smaller than Appium 2, but still includes breaking changes. | Check Node, npm, client library, driver versions, and capability format before upgrading. |
| Main risk for testers | Copying old examples may still appear to work in some cases, but can fail in modern environments. | Missing drivers and unprefixed capabilities are common setup issues. | Old endpoints, old clients, and weak capabilities create more launch-time failures. | Treat old Appium examples carefully. Update setup, drivers, capabilities, and locators before debugging the test logic. |
For most teams, the practical takeaway is simple: do not treat Appium as one bundled mobile testing tool anymore. Install the server, install the right drivers, use W3C capabilities, and keep the Appium server, drivers, and client libraries aligned.
Appium 3 Setup Requirements
Before installing Appium, make sure the local machine is ready for mobile automation. Appium itself is lightweight, but Android and iOS testing depend on platform tools, drivers, SDKs, device access, and the right client libraries.
For Appium 3, the basic server requirements are:
- macOS, Windows, or Linux
- Node.js ^20.19.0, ^22.12.0, or >=24.0.0
- npm 10 or later
- Appium installed through npm
- At least one Appium driver installed, such as UiAutomator2 or XCUITest
Appium’s current documentation states that Appium 3 requires newer Node and npm versions, with Node 20.19.0+ and npm 10+ as the baseline.
Prerequisites
These are needed regardless of whether you are testing Android or iOS apps:
- Appium server: Receives commands from the test script and routes them to the correct driver.
- Appium client library: Lets you write tests in languages such as Java, JavaScript, Python, C#, or Ruby.
- Appium driver: Connects Appium to the target platform. Without a driver, Appium cannot automate anything.
- Appium Inspector: Helps inspect mobile elements and identify locators before using them in test scripts.
- Device environment: A real device, emulator, or simulator where the app will run.
Android setup requirements
For Android automation, you need:
- Android Studio
- Android SDK
- Android Platform Tools
- Android emulator or real Android device
- USB debugging enabled for real devices
- UiAutomator2 driver installed in Appium
- Java Development Kit if the test framework is written in Java
For most Android test automation, UiAutomator2 should be the default driver. Espresso can be used when the team needs closer synchronization with the app, but UiAutomator2 is the usual starting point for Appium-based Android UI testing.
iOS setup requirements
For iOS automation, you need:
- macOS
- Xcode
- Xcode command-line tools
- iOS simulator or real iOS device
- XCUITest driver installed in Appium
- Apple Developer account setup for real-device testing
- Code signing and provisioning configured for physical iPhones or iPads
The main difference between Android and iOS setup is that iOS automation depends heavily on Apple’s tooling. Simulators are easier to start with, but real-device iOS testing requires signing, provisioning, and device trust configuration.
What to check before writing tests
A working Appium setup is not just about installing Appium. Before creating the first test, check that:
- Node and npm versions match Appium 3 requirements.
- The required Appium driver is installed.
- The Android emulator, iOS simulator, or real device is visible to the machine.
- The app build is available as an .apk, .app, .ipa, or installed app package.
- The test framework has the correct Appium client library.
- Appium Inspector can detect the app’s UI elements.
How to Install Appium in 2026
Once Node.js, npm, and the platform tools are ready, install Appium from npm. Appium 3 supports macOS, Windows, and Linux, but it requires a recent Node.js and npm version. The current Appium docs list Node ^20.19.0 || ^22.12.0 || >=24.0.0 and npm >=10 as the basic server requirements.
Step 1: Install Appium
Use npm to install Appium globally:
npm install -g appium
This installs the Appium server so you can start it from the command line. Appium’s official install guide also notes that Appium is installed globally using npm.
Step 2: Check the installed Appium version
After installation, verify that Appium is available:
appium -v
This confirms that the Appium CLI is installed and accessible from the terminal.
Step 3: Install the Android driver
For Android automation, install the UiAutomator2 driver:
appium driver install uiautomator2
UiAutomator2 is the standard Appium driver for most Android UI automation. Appium does not come bundled with drivers, so installing Appium alone is not enough to automate Android or iOS apps.
Step 4: Install the iOS driver
For iOS automation, install the XCUITest driver:
appium driver install xcuitest
This driver is used for iOS automation through Apple’s XCUITest framework. You need macOS and Xcode for iOS testing.
Step 5: Check installed drivers
Use this command to confirm which drivers are installed:
appium driver list --installed
A working Android setup should show uiautomator2. A working iOS setup should show xcuitest.
Step 6: Validate the Android setup
For Android, run the driver doctor command:
appium driver doctor uiautomator2
This checks whether the required Android dependencies are configured correctly. Appium’s documentation notes that official drivers include Appium Doctor support to verify driver requirements.
Step 7: Start the Appium server
Start the Appium server from the terminal:
appium
When the server starts, it loads the installed drivers and waits for new session requests from your test scripts. The server log will also show the local URL that your client code can use, commonly:
http://127.0.0.1:4723/
Keep the server running while executing local tests. If a session fails, the Appium server log is one of the first places to check because it usually shows driver, capability, and device connection errors.
Understanding Appium Capabilities
Capabilities tell Appium what kind of session to create. They define the platform, driver, device, app, and other session-level settings before the test starts. Once the session is created, these values cannot be changed for that session. Appium follows the W3C WebDriver capabilities model, and Appium-specific capabilities use the appium: prefix.
This matters because many older examples still use unprefixed capabilities such as deviceName or app. In modern Appium, these should be written as appium:deviceName and appium:app.
Common Appium capabilities
| Capability | Used for | Example |
|---|---|---|
| platformName | Defines the target platform | Android, iOS |
| appium:automationName | Defines the Appium driver | UiAutomator2, XCUITest |
| appium:deviceName | Defines the device or simulator name | Android Emulator, iPhone 15 |
| appium:platformVersion | Defines the OS version when needed | 17.0, 14 |
| appium:app | Path to the app file | /path/to/app.apk, /path/to/app.app |
| appium:appPackage | Android app package when app is already installed | com.example.app |
| appium:appActivity | Android launch activity | .MainActivity |
| appium:bundleId | iOS bundle identifier when app is already installed | com.example.iosapp |
Android capabilities example
Use this when you want Appium to install and launch an APK:
{
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "Android Emulator",
"appium:app": "/path/to/app.apk"
}Use this when the Android app is already installed and you want to launch it by package and activity:
{
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "Android Emulator",
"appium:appPackage": "com.example.app",
"appium:appActivity": ".MainActivity"
}iOS capabilities example
Use this when testing an iOS app file on a simulator:
{
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:deviceName": "iPhone 15",
"appium:platformVersion": "17.0",
"appium:app": "/path/to/app.app"
}Use this when the iOS app is already installed and you want to launch it by bundle ID:
{
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:deviceName": "iPhone 15",
"appium:platformVersion": "17.0",
"appium:bundleId": "com.example.iosapp"
}app vs appPackage, appActivity, and bundleId
Use appium:app when Appium needs to install the app before starting the session. This is common when testing a fresh .apk, .app, or .ipa build.
Use appium:appPackage and appium:appActivity when the Android app is already installed and the test only needs to launch it.
Use appium:bundleId when the iOS app is already installed and the test only needs to launch it.
For most teams, this is the safest rule: use appium:app for fresh build validation, and use package, activity, or bundle ID when the app is already installed as part of the device setup.
Keep capability files separate for Android, iOS simulator, and iOS real-device runs so that one environment does not accidentally break another.
Write Your First Appium Test
Now that Appium, the Android driver, and the Appium server are ready, the next step is to create a basic test. This example uses Java + TestNG + Appium Java Client because it is a common stack for mobile automation teams.
The Appium Java Client is the official Java binding for writing Appium tests and supports driver-specific classes such as AndroidDriver for UiAutomator2 and IOSDriver for XCUITest. The current Maven Central version checked for this example is 10.1.1. TestNG 7.12.0 is used for test annotations and assertions.
Step 1: Add Maven dependencies
Create a Maven project and add the following dependencies in pom.xml.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>appium-mobile-tests</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<appium.java.client.version>10.1.1</appium.java.client.version>
<testng.version>7.12.0</testng.version>
</properties>
<dependencies>
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>${appium.java.client.version}</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>You do not need to add Selenium separately for this basic example. Appium Java Client already depends on Selenium libraries. The Appium Java Client compatibility matrix also shows which Selenium versions align with each Appium Java Client version, so pin Selenium separately only when your framework needs tighter dependency control.
Also Read: What is POM in Maven
Step 2: Start the Appium server
Before running the test, start the Appium server in a separate terminal:
appium
Keep this terminal open. The test will send commands to the local Appium server at:
http://127.0.0.1:4723
Also make sure an Android emulator is running, or a real Android device is connected and visible through:
adb devices
Step 3: Create a basic Android test
Create a test file at:
src/test/java/tests/FirstAndroidTest.java
Use this code:
package tests;
import io.appium.java_client.AppiumBy;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.net.URI;
import java.net.URL;
import java.time.Duration;
public class FirstAndroidTest {
private AndroidDriver driver;
private WebDriverWait wait;
@BeforeMethod
public void setUp() throws Exception {
UiAutomator2Options options = new UiAutomator2Options()
.setPlatformName("Android")
.setAutomationName("UiAutomator2")
.setDeviceName("Android Emulator")
.setApp(System.getProperty("user.dir") + "/apps/sample.apk");
URL appiumServerUrl = URI.create("http://127.0.0.1:4723").toURL();
driver = new AndroidDriver(appiumServerUrl, options);
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
@Test
public void userCanOpenLoginScreen() {
WebElement loginButton = wait.until(
ExpectedConditions.elementToBeClickable(
AppiumBy.accessibilityId("Login")
)
);
loginButton.click();
WebElement loginTitle = wait.until(
ExpectedConditions.visibilityOfElementLocated(
AppiumBy.accessibilityId("Login Screen")
)
);
Assert.assertTrue(
loginTitle.isDisplayed(),
"Login screen should be visible after tapping Login."
);
}
@AfterMethod(alwaysRun = true)
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}This example assumes the app has accessibility IDs named Login and Login Screen. In a real project, inspect your app with Appium Inspector and replace these values with locators from your application.
Step 4: Run the test
From the project root, run:
mvn test
If the setup is correct, Maven will compile the test, TestNG will start the test method, and Appium will create a new Android session through the UiAutomator2 driver.
What this test does
The test follows the basic Appium execution flow:
- UiAutomator2Options defines the Android session.
- setAutomationName(“UiAutomator2”) tells Appium to use the Android UiAutomator2 driver.
- setApp() points to the APK that should be installed and launched.
- AndroidDriver starts the session with the Appium server.
- WebDriverWait waits until the login button is actually clickable.
- AppiumBy.accessibilityId() locates the mobile element using a stable accessibility identifier.
- driver.quit() closes the session after the test finishes.
The important part is not just that the test clicks a button. The important part is the structure: setup, session creation, wait, action, assertion, and cleanup. Most Appium tests should follow this shape so failures are easier to debug.
Appium Locator Strategies
Locators decide how Appium finds elements inside the app. A weak locator can make a good test fail even when the app is working correctly. This usually happens when the locator depends on screen position, changing text, or a long XPath tied to the UI hierarchy.
A good Appium locator should be:
- stable across small UI changes
- easy for another tester to understand
- specific enough to avoid matching the wrong element
- supported by the platform driver being used
1. Accessibility ID
Accessibility ID is usually the best locator for Appium tests. It works across Android and iOS, and it is less dependent on the visual structure of the screen.
driver.findElement(AppiumBy.accessibilityId("Login"));Use Accessibility ID for important user actions such as login buttons, search fields, menu items, and checkout actions.
For long-term test stability, ask developers to add meaningful accessibility labels or content descriptions to key elements. This helps both automation and app accessibility.
2. Android resource ID
For Android apps, resource ID is another strong option. It points to the element ID defined in the Android app code.
driver.findElement(AppiumBy.id("com.example:id/login_button"));This is useful when the app exposes stable Android IDs. Avoid IDs that are generated dynamically or change between builds.
3. iOS Predicate String
For iOS apps, predicate strings are often more reliable than XPath. They let you locate elements using properties such as label, name, value, type, or visibility.
driver.findElement(
AppiumBy.iOSNsPredicateString("label == 'Login'")
);You can also combine conditions:
driver.findElement(
AppiumBy.iOSNsPredicateString("label == 'Login' AND visible == true")
);This is useful when the iOS screen has multiple elements with similar labels or when you need a more specific condition.
4. iOS Class Chain
iOS Class Chain is another iOS-specific locator strategy. It is faster and cleaner than XPath for many hierarchy-based lookups.
driver.findElement(
AppiumBy.iOSClassChain("**/XCUIElementTypeButton[`label == 'Login'`]")
);Use it when the element cannot be located by Accessibility ID or predicate alone, but avoid making the chain too dependent on exact screen hierarchy.
5. Android UiAutomator selector
Android UiAutomator selectors are useful when you need Android-specific matching logic.
driver.findElement(
AppiumBy.androidUIAutomator("new UiSelector().text(\"Login\")")
);You can also use it for scrolling:
driver.findElement( AppiumBy.androidUIAutomator( "new UiScrollable(new UiSelector().scrollable(true))" + ".scrollIntoView(new UiSelector().text(\"Settings\"))" ) );
This is useful for Android screens where the element is not immediately visible and needs to be scrolled into view.
6. XPath
XPath should be treated as a fallback in Appium. It can work, but it is often slower and more fragile because it depends on the UI tree.
driver.findElement(
AppiumBy.xpath("//android.widget.Button[@text='Login']")
);Avoid XPath like this:
driver.findElement(
AppiumBy.xpath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.Button[2]")
);This type of XPath breaks easily when the layout changes, even if the user-facing screen still looks the same.
Recommended locator order
For most Appium projects, use this order when choosing locators:
| Priority | Locator strategy | When to use |
|---|---|---|
| 1 | Accessibility ID | Best default choice for stable cross-platform tests |
| 2 | Android resource ID | Strong option for Android apps with stable IDs |
| 3 | iOS Predicate String | Strong option for iOS when Accessibility ID is not available |
| 4 | iOS Class Chain | Useful for iOS hierarchy-based lookup |
| 5 | Android UiAutomator | Useful for Android-specific selection and scrolling |
| 6 | XPath | Use only when better locators are not available |
The best locator is not always the shortest one. It is the one that keeps working after small UI changes, app updates, and device differences. For production-level Appium tests, locator stability matters more than quick test authoring.
Handling Waits in Appium
Mobile screens rarely load at the same speed every time. An element may appear after an animation, a network call, a permission dialog, or a WebView load. If the test tries to interact with the element too early, the app may be working correctly, but the test still fails.
This is why waits are important in Appium. They help the test wait for a real app condition before performing the next action.
Why hard waits are a problem
A hard wait pauses the test for a fixed time.
Thread.sleep(5000);
This looks simple, but it creates two problems:
- If the element appears in one second, the test still waits five seconds.
- If the element appears after six seconds, the test still fails.
Hard waits make the suite slower and do not solve timing issues reliably. They should be avoided except for rare debugging situations.
Use explicit waits instead
An explicit wait waits until a specific condition is true. For example, wait until a button is clickable before tapping it.
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement loginButton = wait.until(
ExpectedConditions.elementToBeClickable(
AppiumBy.accessibilityId("Login")
)
);
loginButton.click();In this example, Appium does not blindly wait for 10 seconds. It waits up to 10 seconds for the login button to become clickable. If the button is ready earlier, the test continues immediately.
Common Appium wait conditions
Use wait conditions based on what the test needs next.
| Condition | Use when |
|---|---|
| visibilityOfElementLocated | The element should be visible before validation or reading text |
| elementToBeClickable | The element should be ready for tap or click |
| presenceOfElementLocated | The element should exist in the UI tree, even if not visible yet |
| invisibilityOfElementLocated | A loader, toast, or blocking overlay should disappear |
| textToBePresentInElementLocated | A label, message, or status should update before assertion |
Example for waiting until a loader disappears:
wait.until(
ExpectedConditions.invisibilityOfElementLocated(
AppiumBy.accessibilityId("Loading")
)
);This is useful after login, payment, search, or any screen that depends on a backend response.
Wait for screen state, not just elements
A common Appium mistake is waiting for one element and assuming the whole screen is ready. In mobile apps, one button may appear before data, images, or dynamic content finishes loading.
Instead, wait for the state that proves the screen is usable.
For example, after tapping login, do not wait only for the next screen container. Wait for a user-specific element that confirms the login completed.
WebElement accountHeader = wait.until(
ExpectedConditions.visibilityOfElementLocated(
AppiumBy.accessibilityId("Account Home")
)
);
Assert.assertTrue(accountHeader.isDisplayed());This makes the test closer to how a real user would judge whether the action succeeded.
Handle permission dialogs carefully
Permission dialogs are common in mobile testing. They may appear on a fresh install, but not on later runs. A stable test should handle them only when they appear.
try {
WebElement allowButton = new WebDriverWait(driver, Duration.ofSeconds(3))
.until(ExpectedConditions.elementToBeClickable(
AppiumBy.id("com.android.permissioncontroller:id/permission_allow_button")
));
allowButton.click();
} catch (Exception ignored) {
// Permission dialog did not appear. Continue the test.
}Do not add long waits for permission popups in every test. Keep the timeout short and continue if the dialog is not present.
Avoid mixing implicit and explicit waits
Implicit waits apply globally to element searches. Explicit waits are condition-based. Mixing both can make failures harder to understand because Appium may wait longer than expected before throwing an error.
For Appium test suites, a safer approach is:
- keep implicit waits low or avoid them
- use explicit waits for important UI conditions
- create reusable wait helper methods for common screens
Example helper method:
public WebElement waitForVisible(By locator) {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
}Then use it like this:
WebElement loginButton = waitForVisible(AppiumBy.accessibilityId("Login"));
loginButton.click();Debugging Common Appium Failures
Appium failures usually fall into three areas: setup, locators, or timing. Setup failures happen before the app opens. Locator failures happen when Appium cannot find the expected element. Timing failures happen when the app is working, but the test acts before the screen is ready.
Start debugging from the failure point. If the session does not start, do not inspect test logic yet. Check the driver, device, app path, and capabilities first. If the app opens but the test fails midway, check locators, waits, app state, and screen transitions.
| Error | Common cause | How to fix |
|---|---|---|
| SessionNotCreatedException | Wrong capabilities, missing driver, unavailable device, incorrect app path | Check installed drivers, device connection, app file path, and platform version |
| NoSuchElementException | Locator is wrong, element is not visible, screen has not loaded | Recheck the element in Appium Inspector and add an explicit wait |
| StaleElementReferenceException | The screen refreshed and the old element reference is no longer valid | Locate the element again after navigation or screen update |
| InvalidElementStateException | Element is present but cannot receive the action | Check whether it is enabled, covered by another element, or blocked by the keyboard |
| WebDriverAgent error | iOS signing, provisioning, Xcode, or device trust issue | Check Xcode setup, signing team, provisioning profile, and WDA logs |
| adb device offline | Android device connection is unstable | Restart adb, reconnect the device, and authorize USB debugging |
| App launches but test fails immediately | Wrong app state or unexpected permission dialog | Reset app state or handle permission dialogs before test actions |
| Test passes locally but fails in CI | Different device, OS version, timing, or environment data | Capture logs, screenshots, and device details from CI runs |
After identifying the error type, check the Appium server logs first. They usually show whether the failure came from driver loading, capability validation, app launch, device connection, or WebDriverAgent setup.
For element-related failures, open the same screen in Appium Inspector and confirm that the locator matches the current UI tree. If the issue is intermittent, capture screenshots and page source on failure so you can compare what the user saw with what Appium could actually detect.
Best Practices for Running Appium Tests on Android and iOS
Running Appium tests on Android and iOS follows the same basic flow, but the platform details are different. A stable Appium suite should keep those differences clear instead of forcing both platforms into one shared setup.
| Area | Android | iOS |
|---|---|---|
| Main driver | UiAutomator2 | XCUITest |
| Local tooling | Android SDK, adb, emulator | macOS, Xcode, simctl |
| Real-device setup | USB debugging and device authorization | Signing, provisioning, device trust |
| App file | .apk | .app for simulator, .ipa for real device |
| Installed app launch | appPackage and appActivity | bundleId |
| Common setup issue | Device not visible in adb | WebDriverAgent or signing failure |
Before running Android tests, confirm that the device or emulator is visible:
adb devices
Before running iOS simulator tests, check the available simulators:
xcrun simctl list devices
Keep Android and iOS configuration separate. Even when the user journey is the same, the drivers, app files, device names, and launch capabilities are different.
A clean framework usually stores platform configuration separately:
config/ android.properties ios.properties
or:
config/ android.json ios.json
The test should describe the user flow. The configuration should describe where and how that test runs.
A few practices make Appium tests more stable across both platforms:
- Prefer Accessibility IDs for important elements because they are less tied to layout changes.
- Avoid XPath unless the app does not expose better identifiers.
- Use explicit waits based on visible app state, not hard-coded sleeps.
- Reset app state deliberately between tests instead of depending on leftovers from the previous session.
- Keep test data setup outside the UI when possible.
- Capture Appium logs, device logs, screenshots, and videos for CI failures.
- Run smoke tests on emulators or simulators, but validate release-critical flows on real devices.
- Keep Appium, driver, client library, OS, and device versions visible in CI logs.
- Avoid putting too many user journeys inside one test. One test should verify one clear flow.
When Not to Use Appium
Appium is best for testing important mobile user journeys where the UI, device, and platform behavior matter. It should not be used for every validation because UI tests are slower and more expensive to maintain than lower-level tests.
Avoid using Appium when:
- Faster test layers can cover the check: Use unit, API, component, or integration tests when the validation does not need the mobile UI.
- The test needs internal app access: Appium works from the outside like a user, so it is not ideal for private methods, memory state, or internal event handling.
- The goal is visual comparison: Use a visual testing tool for pixel-level layout, spacing, color, or screenshot diff checks.
- The screen is still changing often: Wait until the UI has stable identifiers before adding Appium coverage.
- Speed is the main priority: Appium is useful for end-to-end validation, but it is too heavy for small checks that need fast feedback.
- The test only validates backend logic: Business rules, data calculations, and API behavior should be tested below the UI.
- Only emulators or simulators are available for device-heavy flows: Hardware-dependent features should be validated on real devices before release.
Conclusion
Appium in 2026 works best when the setup is current: Appium 3, platform drivers, W3C capabilities, stable locators, and explicit waits. Most early failures come from missing drivers, outdated capability formats, weak locators, or device setup gaps.
Use Appium for mobile flows where the UI, device, and platform behavior affect release quality. Keep Android and iOS configuration separate, capture logs and screenshots for failures, and validate critical flows on real devices before shipping.
















