Appium Tutorial: Mobile App Testing from Setup to First Test

Appium Tutorial: Mobile App Testing from Setup to First Test

Appium is the most widely used framework for mobile app automation. It lets you write tests once and run them on Android and iOS using the same API — whether your app is native, hybrid, or a mobile web app.

This tutorial walks you through everything: installing Appium, setting up your first test, and running it on a real device or emulator.

What Is Appium?

Appium is an open-source test automation framework for mobile apps. It uses the WebDriver protocol (the same standard as Selenium) and talks to mobile platforms through vendor-specific drivers:

  • UiAutomator2 — Android native apps
  • XCUITest — iOS native apps
  • Espresso — Android (faster, in-process)
  • Gecko — Mobile browsers

The key benefit: your test code doesn't care whether it's running on Android or iOS. You interact with elements, tap buttons, fill forms, and assert states using the same commands.

Appium runs as a server process. Your test code is a client that sends WebDriver commands to the Appium server, which forwards them to the device.

Test code → Appium Server → UiAutomator2/XCUITest → Device/Emulator

Prerequisites

Before installing Appium, you need:

For Android:

  • Java 11+ (JDK)
  • Android Studio + Android SDK
  • ANDROID_HOME environment variable set
  • An AVD (Android Virtual Device) or physical device with USB debugging enabled

For iOS:

  • macOS (required — no iOS testing on Windows/Linux)
  • Xcode 14+
  • Xcode Command Line Tools
  • A Simulator or physical device

Installing Appium

Appium 2.x is installed via npm:

npm install -g appium

Install the driver for your target platform:

# Android
appium driver install uiautomator2

<span class="hljs-comment"># iOS
appium driver install xcuitest

Verify everything is correct with the Appium doctor:

npm install -g appium-doctor
appium-doctor --android
appium-doctor --ios

Fix any issues appium-doctor reports before continuing. Missing SDK paths and unsigned certificates are the most common blockers.

Start the Appium server:

appium --port 4723

You'll see output confirming the server is listening. Leave this running while your tests execute.

Your First Android Test (JavaScript)

We'll use WebdriverIO — the most popular Appium client for JavaScript/TypeScript:

mkdir appium-demo && <span class="hljs-built_in">cd appium-demo
npm init -y
npm install --save-dev webdriverio @wdio/cli

Create wdio.conf.js:

exports.config = {
  runner: 'local',
  port: 4723,
  specs: ['./test/specs/**/*.js'],
  capabilities: [{
    platformName: 'Android',
    'appium:deviceName': 'Pixel_4_API_30',
    'appium:app': '/path/to/your/app.apk',
    'appium:automationName': 'UiAutomator2',
    'appium:newCommandTimeout': 240,
  }],
  framework: 'mocha',
  reporters: ['spec'],
  mochaOpts: {
    timeout: 60000,
  },
};

Create your first test at test/specs/login.spec.js:

describe('Login flow', () => {
  it('should login with valid credentials', async () => {
    // Find the username field by accessibility ID
    const usernameField = await $('~username');
    await usernameField.setValue('testuser@example.com');

    // Find the password field
    const passwordField = await $('~password');
    await passwordField.setValue('securepassword');

    // Tap the login button
    const loginButton = await $('~loginButton');
    await loginButton.click();

    // Assert the home screen is visible
    const homeHeader = await $('~homeHeader');
    await expect(homeHeader).toBeDisplayed();
  });

  it('should show error for invalid credentials', async () => {
    const usernameField = await $('~username');
    await usernameField.setValue('wrong@example.com');

    const passwordField = await $('~password');
    await passwordField.setValue('wrongpassword');

    const loginButton = await $('~loginButton');
    await loginButton.click();

    const errorMessage = await $('~errorMessage');
    await expect(errorMessage).toBeDisplayed();
    await expect(errorMessage).toHaveText('Invalid credentials');
  });
});

Run your tests:

npx wdio run wdio.conf.js

Finding Elements

Appium supports several element locator strategies:

// Accessibility ID (recommended — works on both platforms)
const element = await $('~myAccessibilityId');

// XPath (flexible but slow)
const element = await $('//android.widget.TextView[@text="Submit"]');

// Class name
const element = await $('.android.widget.Button');

// Android UiAutomator
const element = await $('android=new UiSelector().text("Login")');

// iOS NSPredicate string
const element = await $('ios=label == "Login"');

// ID
const element = await $('#com.example.app:id/login_button');

Use the Appium Inspector to visually browse your app's element hierarchy:

npm install -g appium-inspector
appium-inspector

Connect to your running Appium server and launch your app. You'll see the full UI tree and can click elements to get their locators.

Gestures and Interactions

Mobile testing often requires touch gestures beyond simple taps:

// Scroll down
await driver.execute('mobile: scroll', {
  direction: 'down',
});

// Swipe left on an element
await driver.execute('mobile: swipe', {
  direction: 'left',
  element: (await $('~cardElement')).elementId,
});

// Long press
await driver.execute('mobile: longClickGesture', {
  element: (await $('~holdButton')).elementId,
  duration: 2000,
});

// Pinch to zoom
await driver.execute('mobile: pinchOpenGesture', {
  elementId: (await $('~mapView')).elementId,
  scale: 2.0,
  velocity: 2.2,
});

// Type text (alternative to setValue)
await driver.execute('mobile: type', { text: 'Hello World' });

Cross-Platform Test Strategy

The real power of Appium is writing tests that run on both Android and iOS. Here's a pattern for managing platform differences:

// helpers/platform.js
const isIOS = () => driver.isIOS;
const isAndroid = () => driver.isAndroid;

async function findElement(androidSelector, iosSelector) {
  if (isIOS()) {
    return $(`ios=${iosSelector}`);
  }
  return $(`android=${androidSelector}`);
}

module.exports = { isIOS, isAndroid, findElement };

Use accessibility IDs consistently in your app code — they work across platforms with the same locator:

// Both Android and iOS
const submitButton = await $('~submitButton');
await submitButton.click();

Work with your mobile developers to add accessibilityLabel (iOS) and contentDescription (Android) to interactive elements from the start. Retrofitting them later is painful.

Handling Async Wait

Mobile apps load data asynchronously. Use waitForDisplayed instead of assuming elements are immediately present:

// Wait up to 10 seconds for an element
const dashboard = await $('~dashboardScreen');
await dashboard.waitForDisplayed({ timeout: 10000 });

// Wait for an element to disappear (e.g., loading spinner)
const spinner = await $('~loadingSpinner');
await spinner.waitForDisplayed({ timeout: 5000, reverse: true });

// Custom polling
await browser.waitUntil(
  async () => {
    const items = await $$('~listItem');
    return items.length > 0;
  },
  { timeout: 15000, interval: 500, timeoutMsg: 'List items never appeared' }
);

Running on Real Devices

For Android, enable USB debugging and connect via USB:

adb devices  # Confirm device appears

Update your capabilities:

capabilities: [{
  platformName: 'Android',
  'appium:deviceName': 'your-device-serial',
  'appium:udid': 'your-device-serial',
  'appium:app': '/path/to/app.apk',
  'appium:automationName': 'UiAutomator2',
}]

For iOS real devices, you need:

  • A valid signing certificate and provisioning profile
  • The UDID of your device (find it in Xcode → Devices)
  • xcrun xctrace list devices to list connected devices

Common Appium Issues

App not found: Use absolute paths for the app capability. Relative paths frequently fail depending on how you launch Appium.

Element not found: Add waits. Appium doesn't automatically wait for elements — they must exist in the DOM at the time of the selector call.

Session creation failed: Check that appium-doctor shows all green. The most common cause is a missing ANDROID_HOME or unsigned iOS certificate.

Tests are slow: Appium starts a new session for each test suite by default. Use noReset: true and session reuse patterns to avoid reinstalling the app on every run.

Flaky tests: Add implicitWait as a global fallback, but rely on explicit waits for specific elements that take time to appear.

Scaling with CI

Add Appium tests to your CI pipeline with GitHub Actions:

# .github/workflows/appium.yml
name: Appium Android Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '11'
      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Start Android emulator
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 30
          script: |
            npm install
            appium &
            sleep 5
            npx wdio run wdio.conf.js

Beyond Manual Automation

Appium handles functional regression well — verifying that user flows work correctly after each release. But mobile apps also need monitoring after deployment: do real users on real devices experience crashes or broken flows?

HelpMeTest lets you write mobile test scenarios in plain English, run them on schedule, and get alerts when something breaks. No Appium server to maintain, no device farm to configure. It complements your Appium suite by catching production regressions between releases.

Summary

  • Install: npm install -g appium, then appium driver install uiautomator2
  • Start server: appium --port 4723
  • Write tests: WebdriverIO or any WebDriver client
  • Find elements: Prefer accessibility IDs for cross-platform compatibility
  • Handle async: Use waitForDisplayed and waitUntil — never assume elements are instantly present
  • CI: Use reactivecircus/android-emulator-runner for Android emulator in GitHub Actions

Appium has a learning curve, but once it's running, you have a powerful cross-platform mobile test suite that grows with your app.

Read more