- Home
- Appium – Mobile Automation Tutorial
Appium
Mobile Automation Testing Tutorial
Master mobile test automation with Appium — from architecture and setup to writing real tests for Android and iOS, locators, gestures, waits, and professional best practices.
Key facts about Appium:
What makes Appium unique and popular:
- 1No app modification needed You do NOT need to add any special testing code inside your Android or iOS app. Appium tests the app as-is — just like a real user would interact with it. No recompilation, no special "test builds" required.
- 2Cross-platform with one API Write tests using the same Appium API whether you target Android or iOS. Switching platforms is mostly a matter of changing Desired Capabilities — the test commands stay the same.
- 3Multi-language support Your team can write Appium tests in Java, Python, JavaScript, Ruby, or C#. You are not locked into one language.
- 4Supports all app types Native apps (built in Swift/Kotlin/Java), Hybrid apps (web view inside native shell), and Mobile Web apps (browser testing on mobile) — Appium handles all three.
- 5Works on real devices AND emulators/simulators Run tests on physical Android/iOS devices connected via USB, or on Android Virtual Devices (AVD) and iOS Simulators — both fully supported.
The 4 layers of Appium architecture:
Your test script (Java, Python, etc.) — sends HTTP commands
Node.js HTTP server — receives commands & routes to correct driver
UiAutomator2 (Android) | XCUITest (iOS) — translates commands
Real Android/iOS device or AVD/Simulator — executes actions
Step-by-step communication flow:
- 1Test script sends a command Your Java/Python test calls a method like driver.findElement(By.id("login-btn")).click(). The Appium Client Library converts this into a JSON-formatted HTTP POST request.
- 2Appium Server receives the request The Appium Server (running at http://127.0.0.1:4723 by default) receives the HTTP request. It reads the session capabilities to decide which driver to use.
- 3Driver routes to the device For Android, the UiAutomator2 driver sends commands via ADB (Android Debug Bridge) to the device. For iOS, XCUITest uses WebDriverAgent (a WDA server running on the iOS device).
- 4Device executes the action The device performs the action (clicks the button, types text, etc.) and returns the result back up the chain to your test script.
| Feature | 📱 Native App | 🔀 Hybrid App | 🌐 Mobile Web |
|---|---|---|---|
| Technology | Kotlin/Java or Swift | HTML/CSS/JS in WebView | Website in browser |
| Examples | Gmail, WhatsApp | Ionic, React Native apps | m.amazon.in in Chrome |
| Locators | ID, AccessibilityId | Native + CSS/XPath | CSS, XPath |
| Context switch | Not needed | Must switch NATIVE↔WEBVIEW | Not needed |
| Appium capability | app: /path/to.apk | app: /path/to.apk | browserName: Chrome |
| Performance | Fastest | Medium | Depends on browser |
// Get all available contexts in the app Set<String> contexts = driver.getContextHandles(); // Prints: [NATIVE_APP, WEBVIEW_com.example.app] // Switch TO WebView to interact with web content driver.context("WEBVIEW_com.example.app"); driver.findElement(By.cssSelector("#login-btn")).click(); // Switch BACK to native context driver.context("NATIVE_APP"); driver.findElement(By.id("com.example:id/back_btn")).click();
Step-by-step setup for Android automation (most common):
- 1Install Java JDK 17+ Download from adoptium.net. After installing, set environment variable: JAVA_HOME = C:\Program Files\Java\jdk-17 and add %JAVA_HOME%\bin to PATH. Verify: java -version
- 2Install Android Studio Download from developer.android.com. This installs the Android SDK, ADB, and AVD Manager. Set environment variable: ANDROID_HOME = C:\Users\YourName\AppData\Local\Android\Sdk and add %ANDROID_HOME%\platform-tools to PATH.
- 3Install Node.js 18+ Download from nodejs.org. Appium Server is a Node.js application and requires npm to install. Verify: node --version and npm --version
- 4Install Appium globally Run: npm install -g appium — this installs the Appium server only (no drivers in Appium 2.x). Verify: appium --version
- 5Install UiAutomator2 driver (for Android) Run: appium driver install uiautomator2. This installs the Android automation driver separately. This is required in Appium 2.x.
- 6Run Appium Doctor to check setup Install: npm install -g appium-doctor, then run: appium-doctor --android. This checks all dependencies and tells you what is missing or misconfigured.
- 7Start the Appium Server Run: appium in terminal. You should see "Welcome to Appium v2.x" and "Appium REST http interface listener started on 0.0.0.0:4723". Keep this running while tests execute.
# Step 1: Install Appium server globally npm install -g appium # Step 2: Install Android driver (Appium 2.x — required!) appium driver install uiautomator2 # Step 3: Install iOS driver (macOS only) appium driver install xcuitest # Step 4: Check all drivers installed appium driver list # Step 5: Install and run Appium Doctor (check dependencies) npm install -g appium-doctor appium-doctor --android # Check Android setup appium-doctor --ios # Check iOS setup (macOS only) # Step 6: Start Appium Server appium # Step 7: Verify ADB can see your device/emulator adb devices
npm install -g appium, a tester tries to run an Android test but gets "No driver found for Android". What did they forget?Essential capabilities for Android (UiAutomator2):
import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.android.AndroidDriver; import java.net.URL; // Create UiAutomator2Options (recommended for Appium 2.x) UiAutomator2Options options = new UiAutomator2Options(); options.setPlatformName("Android"); options.setPlatformVersion("14"); options.setDeviceName("emulator-5554"); options.setAutomationName("UiAutomator2"); options.setApp("/path/to/MyApp.apk"); // install & open this APK options.setAppPackage("com.mycompany.myapp"); options.setAppActivity("com.mycompany.myapp.MainActivity"); options.setNoReset(true); // don't reinstall between sessions // Create driver — connects to Appium Server AndroidDriver driver = new AndroidDriver( new URL("http://127.0.0.1:4723"), // Appium server URL options );
import io.appium.java_client.ios.options.XCUITestOptions; import io.appium.java_client.ios.IOSDriver; XCUITestOptions options = new XCUITestOptions(); options.setPlatformName("iOS"); options.setPlatformVersion("17.0"); options.setDeviceName("iPhone 15"); options.setAutomationName("XCUITest"); options.setApp("/path/to/MyApp.app"); // .app for simulator, .ipa for real device options.setBundleId("com.mycompany.myapp"); // iOS app bundle identifier options.setNoReset(true); IOSDriver driver = new IOSDriver( new URL("http://127.0.0.1:4723"), options );
1. Connect device/emulator and open the app manually
2. Run: adb shell dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'
3. Output shows: com.example.app/com.example.app.MainActivity — the part before / is appPackage, after / is appActivity
How to use Appium Inspector:
- 1Download Appium Inspector From github.com/appium/appium-inspector — available for Windows, macOS, and Linux. Install like any desktop application.
- 2Start Appium Server first Run appium in terminal. Appium Inspector connects to this running server. Server must be running before you open Inspector.
- 3Fill in Remote Host settings In Inspector, set: Remote Host = 127.0.0.1, Remote Port = 4723, Remote Path = leave empty (Appium 2.x default).
- 4Enter Desired Capabilities as JSON In the "Desired Capabilities" section, paste your capabilities JSON — platform, device, app path etc. — then click "Start Session".
- 5Inspect elements Inspector shows a screenshot of the app on the left. Click any element → Inspector shows all its attributes on the right panel (accessibility id, resource-id, class, text, XPath). Copy the locator to use in your tests.
{
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "emulator-5554",
"appium:platformVersion": "14",
"appium:app": "/path/to/MyApp.apk",
"appium:noReset": true
}Locator strategy ranking — best to worst:
import org.openqa.selenium.By; import io.appium.java_client.AppiumBy; // ── 1. ID (resource-id) — ⭐ BEST for Android ───────────────── // Android: uses resource-id like "com.package:id/elementId" WebElement loginBtn = driver.findElement( By.id("com.myapp:id/login_button") ); // ── 2. Accessibility ID — ⭐ BEST (cross-platform) ──────────── // Android: content-desc attribute | iOS: accessibility-id label WebElement backBtn = driver.findElement( AppiumBy.accessibilityId("Go Back") ); // ── 3. Android UIAutomator — ✅ GOOD Android-specific ───────── // Use text(), resourceId(), className(), contentDesc() WebElement item = driver.findElement( AppiumBy.androidUIAutomator("new UiSelector().text(\"Login\")") ); // Find by resource ID using UIAutomator WebElement pwd = driver.findElement( AppiumBy.androidUIAutomator( "new UiSelector().resourceId(\"com.myapp:id/password\")") ); // ── 4. iOS Predicate String — ✅ GOOD iOS-specific ──────────── WebElement field = driver.findElement( AppiumBy.iOSNsPredicateString("type == 'XCUIElementTypeButton' AND label == 'Login'") ); // ── 5. Class Name — ⚠️ USE CAREFULLY ───────────────────────── // Android: "android.widget.Button", "android.widget.EditText" WebElement btn = driver.findElement( By.className("android.widget.Button") // finds FIRST button ); // ── 6. XPath — 🔴 LAST RESORT (slow, fragile) ──────────────── WebElement el = driver.findElement( By.xpath("//android.widget.Button[@text='Login']") ); // Find MULTIPLE elements (returns a list) List<WebElement> allItems = driver.findElements( By.id("com.myapp:id/list_item") ); System.out.println("Total items: " + allItems.size());
Maven pom.xml dependencies needed:
<dependencies> <!-- Appium Java Client --> <dependency> <groupId>io.appium</groupId> <artifactId>java-client</artifactId> <version>9.3.0</version> </dependency> <!-- TestNG for test structure --> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.10.2</version> </dependency> </dependencies>
Complete first test — Login flow on Android app:
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.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.testng.annotations.*; import java.net.URL; import java.time.Duration; public class LoginTest { private AndroidDriver driver; @BeforeMethod public void setUp() throws Exception { // Define capabilities UiAutomator2Options options = new UiAutomator2Options(); options.setPlatformName("Android"); options.setPlatformVersion("14"); options.setDeviceName("emulator-5554"); options.setAutomationName("UiAutomator2"); options.setApp(System.getProperty("user.dir") + "/apps/MyApp.apk"); options.setAppPackage("com.mycompany.myapp"); options.setAppActivity("com.mycompany.myapp.LoginActivity"); options.setNoReset(false); // Connect to Appium Server driver = new AndroidDriver( new URL("http://127.0.0.1:4723"), options ); } @Test public void testValidLogin() { // Wait for email field then type email WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); WebElement emailField = wait.until( ExpectedConditions.visibilityOfElementLocated( By.id("com.mycompany.myapp:id/et_email") ) ); emailField.sendKeys("[email protected]"); // Find and fill password field driver.findElement( By.id("com.mycompany.myapp:id/et_password") ).sendKeys("Test@123"); // Click Login button driver.findElement( AppiumBy.accessibilityId("Login Button") ).click(); // Verify dashboard screen appeared WebElement dashboardTitle = wait.until( ExpectedConditions.visibilityOfElementLocated( By.id("com.mycompany.myapp:id/tv_welcome") ) ); // Assert welcome message is displayed assert dashboardTitle.isDisplayed() : "Dashboard not loaded!"; System.out.println("Login test PASSED. Welcome: " + dashboardTitle.getText()); } @AfterMethod public void tearDown() { if (driver != null) { driver.quit(); // ends session, closes app } } }
driver.findElement(locator) — finds a single element
element.sendKeys("text") — types text into a field (like type() in Cypress)
element.click() — clicks the element
element.getText() — gets the visible text of the element
element.isDisplayed() — returns true/false if element is visible
driver.quit() — ends session and closes the app (always call in teardown)
// ── CLICK ──────────────────────────────────────────────────── driver.findElement(By.id("com.myapp:id/login_btn")).click(); // ── TYPE TEXT ──────────────────────────────────────────────── driver.findElement(By.id("com.myapp:id/et_email")) .sendKeys("[email protected]"); // ── CLEAR FIELD then TYPE ──────────────────────────────────── WebElement field = driver.findElement(By.id("com.myapp:id/et_email")); field.clear(); field.sendKeys("[email protected]"); // ── READ TEXT from element ─────────────────────────────────── String welcomeMsg = driver .findElement(By.id("com.myapp:id/tv_welcome")) .getText(); // returns "Welcome, Priya!" // ── CHECK if element is displayed ─────────────────────────── boolean isVisible = driver .findElement(By.id("com.myapp:id/error_msg")) .isDisplayed(); // ── CHECK if element is ENABLED (interactive) ─────────────── boolean isEnabled = driver .findElement(By.id("com.myapp:id/submit_btn")) .isEnabled(); // ── GET attribute value ───────────────────────────────────── String hint = driver .findElement(By.id("com.myapp:id/et_email")) .getAttribute("hint"); // gets placeholder text // ── NAVIGATE BACK (Android hardware back button) ───────────── driver.navigate().back(); // ── HIDE KEYBOARD ─────────────────────────────────────────── driver.hideKeyboard(); // ── TAKE SCREENSHOT ───────────────────────────────────────── import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.apache.commons.io.FileUtils; import java.io.File; File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); FileUtils.copyFile(screenshot, new File("screenshots/login_test.png"));
import java.util.HashMap; import java.util.Map; // ── SWIPE GESTURE (using mobile: swipeGesture) ─────────────── // Swipe UP on the screen (scroll down content) Map<String, Object> swipeArgs = new HashMap<>(); swipeArgs.put("left", 100); swipeArgs.put("top", 700); swipeArgs.put("width", 400); swipeArgs.put("height", 500); swipeArgs.put("direction", "up"); // up, down, left, right swipeArgs.put("percent", 0.75); // swipe 75% of the area driver.executeScript("mobile: swipeGesture", swipeArgs); // ── SCROLL TO ELEMENT (UIScrollable — Android only) ────────── // Scrolls the list until element with text "Settings" is found driver.findElement( AppiumBy.androidUIAutomator( "new UiScrollable(new UiSelector().scrollable(true))" + ".scrollIntoView(new UiSelector().text(\"Settings\"))" ) ); // ── LONG PRESS (using mobile: longClickGesture) ────────────── WebElement element = driver.findElement( By.id("com.myapp:id/item_card") ); Map<String, Object> longPressArgs = new HashMap<>(); longPressArgs.put("elementId", ((RemoteWebElement) element).getId()); longPressArgs.put("duration", 2000); // hold for 2 seconds driver.executeScript("mobile: longClickGesture", longPressArgs); // ── TAP at specific coordinates ────────────────────────────── Map<String, Object> tapArgs = new HashMap<>(); tapArgs.put("x", 540); tapArgs.put("y", 1200); driver.executeScript("mobile: clickGesture", tapArgs); // ── LEGACY TouchAction (older style, still used in many projects) ── import io.appium.java_client.TouchAction; import io.appium.java_client.touch.WaitOptions; import io.appium.java_client.touch.offset.PointOption; new TouchAction(driver) .press(PointOption.point(540, 1500)) // start point .waitAction(WaitOptions.waitOptions(Duration.ofMillis(500))) .moveTo(PointOption.point(540, 600)) // end point (scroll up) .release() .perform();
// ── 1. IMPLICIT WAIT — set once for whole session ───────────── driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); // Now every findElement waits up to 10 seconds before failing // ── 2. EXPLICIT WAIT — wait for specific condition ──────────── WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15)); // Wait until element is VISIBLE WebElement el = wait.until( ExpectedConditions.visibilityOfElementLocated( By.id("com.myapp:id/dashboard_title") ) ); // Wait until element is CLICKABLE WebElement btn = wait.until( ExpectedConditions.elementToBeClickable( AppiumBy.accessibilityId("Checkout Button") ) ); btn.click(); // Wait until text matches exactly wait.until( ExpectedConditions.textToBePresentInElementLocated( By.id("com.myapp:id/status_msg"), "Order Confirmed!" ) ); // Wait until element is NOT visible (e.g. loading spinner disappears) wait.until( ExpectedConditions.invisibilityOfElementLocated( By.id("com.myapp:id/loading_spinner") ) ); // ── 3. FLUENT WAIT — custom polling + ignore exceptions ─────── import org.openqa.selenium.support.ui.FluentWait; import org.openqa.selenium.NoSuchElementException; FluentWait<AndroidDriver> fluentWait = new FluentWait<>(driver) .withTimeout(Duration.ofSeconds(20)) .pollingEvery(Duration.ofMillis(500)) // check every 500ms .ignoring(NoSuchElementException.class); WebElement dynamicEl = fluentWait.until(d -> d.findElement(By.id("com.myapp:id/dynamic_content")) );
BAD: Thread.sleep(3000); — waits ALWAYS 3 seconds even if element loads in 0.5s
GOOD: wait.until(ExpectedConditions.visibilityOfElementLocated(...)) — waits ONLY until element is ready
Thread.sleep makes tests slow, flaky, and unprofessional. It will be called out in code reviews.
These are professional best practices followed by senior mobile automation engineers. Apply them to build a stable, maintainable, and fast mobile automation suite.
- 1Use Appium Inspector BEFORE writing any locator Never guess a locator. Always open Inspector, inspect the element, and use the suggested locator. This prevents "Element not found" errors and saves debugging time.
- 2Locator priority: ID → Accessibility ID → UIAutomator → XPath Always prefer ID first, then Accessibility ID. Ask developers to add accessibility IDs to important elements. Avoid XPath — it's slow and fragile in mobile apps.
- 3Never use Thread.sleep() — use Explicit Waits Thread.sleep is unpredictable and slows down tests. Use WebDriverWait + ExpectedConditions for all timing. This is the #1 sign of test quality.
- 4Always call driver.quit() in @AfterMethod Failing to quit the driver leaves sessions open on the Appium server, which causes "Max sessions reached" errors. Use try-finally in tearDown to guarantee it runs even if the test fails.
- 5Use Page Object Model (POM) pattern Create a separate class for each screen (LoginPage, HomePage, CartPage). Store locators and screen actions in these classes. Test methods just call page methods like loginPage.login("user", "pass"). This makes tests easier to maintain when the app UI changes.
- 6Test on real devices, not only emulators Emulators are good for quick development testing, but real devices reveal hardware-specific bugs — touch events, camera, GPS, network behavior. Always run final regression on real devices before release.
- 7Hide the keyboard after typing in input fields Always call driver.hideKeyboard() after filling input fields, especially on real Android devices. The keyboard covers buttons and causes click failures if not dismissed.
- 8Set noReset = true during development, false in CI During test development, set noReset: true to skip reinstalling the app every run — faster iteration. In CI/CD pipelines, set noReset: false for a clean, predictable test environment.
- 9Run appium-doctor before every fresh setup Run appium-doctor --android after any machine setup or dependency update. It checks all environment variables, SDK paths, ADB, Java version — preventing hours of confusing setup errors.
- 10Take screenshots on test failure Add a TestNG listener (using @AfterMethod(alwaysRun=true)) that captures a screenshot when a test fails. These screenshots are invaluable for debugging failures in CI where you cannot see the device screen.
"First, I install prerequisites: Java JDK 17+, Android Studio for the Android SDK and AVD Manager, Node.js, then Appium with npm install -g appium. In Appium 2.x, I install the UiAutomator2 driver separately with appium driver install uiautomator2. I run appium-doctor --android to verify the setup. I open Appium Inspector to inspect element locators. In my Java test, I create UiAutomator2Options with platformName, deviceName, automationName, appPackage, appActivity, then create an AndroidDriver connecting to http://127.0.0.1:4723. I use WebDriverWait for element visibility, findElement with By.id or AppiumBy.accessibilityId, sendKeys for input, click for buttons, and always call driver.quit() in @AfterMethod to end the session cleanly."
Ready to Master Appium in Real Projects?
STAD Solution's QA Automation course covers Appium end-to-end — Android & iOS, real devices, CI/CD integration, Page Object Model, and 100% placement support.
Explore Courses at STAD Solution →OUR ACCREDITATIONS
For Training Inquiry
For Business Inquiry
© 2025 STAD Solution. All rights reserved. Made by Dikshtech | SEO & Digital Marketing by ShoutnHike.
Our Accreditations
For Training Inquiry
For Business Inquiry
Location
Ahmede
© 2026 STAD Solution. All rights reserved. SEO by ShoutnHike. | llm Info