Testland
Browse all skills & agents

appium-testing

Wires Appium for cross-platform mobile UI automation - uses the WebDriver protocol, picks a driver per platform (XCUITest for iOS, UiAutomator2 / Espresso for Android, Mac2 for macOS, Windows for desktop), authors tests in JS / Python / Java / Ruby / .NET, configures `desiredCapabilities`, runs against simulators / emulators / device farms. Use when a single test suite must cover both iOS and Android, or when the team's stack is multi-platform (iOS + Android + Mac + Windows).

Install with skills.sh (any agent)

npx skills add testland/qa --skill appium-testing
View source

appium-testing

The driver model is the load-bearing concept: Appium itself is the WebDriver protocol; per-platform automation is a driver (XCUITest driver talks to iOS, UiAutomator2 / Espresso to Android, Mac2 to macOS, Windows to Windows desktop). Client libraries exist for JavaScript, Python, Java, Ruby, and .NET (per appium-docs (opens in new window)).

When to use

  • A team needs one test suite to cover iOS + Android.
  • The product is multi-platform (mobile + desktop + TV) and one framework should drive all of them.
  • Existing Selenium expertise transfers (same WebDriver mental model).
  • Device-farm integration matters (BrowserStack / Sauce Labs / AWS Device Farm all support Appium natively).

If the app is iOS-only, xcuitest-suite is lighter (no external server). For React Native specifically, detox-testing is faster.

Step 1 - Install Appium server + driver

npm install -g appium

# Install per-platform drivers:
appium driver install xcuitest         # iOS
appium driver install uiautomator2     # Android
appium driver install espresso         # Android (alternative; gray-box)
appium driver install mac2             # macOS desktop
appium driver install windows          # Windows desktop

# Verify:
appium driver list

Step 2 - Start the server

appium server -p 4723

The default port is 4723; client tests connect to http://localhost:4723 (no /wd/hub suffix in Appium 2.x).

Step 3 - Author a test (JavaScript example)

import { remote } from 'webdriverio';

const capabilities = {
  platformName: 'Android',
  'appium:automationName': 'UiAutomator2',
  'appium:deviceName': 'Android Emulator',
  'appium:app': '/path/to/app.apk',
};

describe('Cart flow', () => {
  let driver;

  before(async () => {
    driver = await remote({
      hostname: 'localhost',
      port: 4723,
      capabilities,
    });
  });

  after(async () => {
    await driver.deleteSession();
  });

  it('adds an item to cart', async () => {
    const addButton = await driver.$('id=add_to_cart_button');
    await addButton.click();

    const cartCount = await driver.$('id=cart_count');
    const text = await cartCount.getText();
    expect(text).toBe('1');
  });
});

The automationName capability picks the driver: UiAutomator2 (Android), XCUITest (iOS), Espresso (Android gray-box), Mac2, Windows.

Step 4 - Capabilities catalog

CapabilityUse
platformNameiOS / Android / Mac / Windows
appium:automationNameDriver name (XCUITest / UiAutomator2 / Espresso etc.)
appium:deviceNameLogical device name (Simulator / Emulator / specific)
appium:platformVersionOS version (e.g. 14.0)
appium:appPath to .apk / .ipa
appium:bundleId / appium:appPackagePre-installed app
appium:noResetDon't reinstall app between sessions
appium:newCommandTimeoutServer idle timeout (default 60s)

For device-farm runs, capabilities also include the farm's authentication tokens / device-pool selection.

Step 5 - Selector strategies (cross-platform)

// By accessibility ID - the most cross-platform
await driver.$('~login-button').click();   // ~ prefix in WebdriverIO

// By id - works for both platforms with platform-prefixed IDs
await driver.$('id=add_to_cart_button');     // Android
await driver.$('id=add-to-cart-button');     // iOS uses accessibility ID

// XPath - works everywhere; brittle (selector-quality anti-pattern)
await driver.$('//XCUIElementTypeButton[@name="Submit"]');

// Image-based locator (Appium-specific) - fallback when no IDs
await driver.findElementByImage('./assets/login-button.png');

The cross-platform strategy: production code sets the same accessibility ID on both iOS (accessibilityIdentifier) and Android (contentDescription or resource-id). Tests use one selector for both.

Step 6 - Run

# Local with one platform
WDIO_OS=android wdio run wdio.conf.js

# Multi-platform: WebdriverIO multi-capability config
# wdio.conf.js
exports.config = {
    capabilities: [
        { platformName: 'iOS', 'appium:platformVersion': '17.4', ...iosCaps },
        { platformName: 'Android', 'appium:platformVersion': '14', ...androidCaps },
    ],
    runner: 'local',
    services: ['appium'],
};

The services: ['appium'] line auto-starts the Appium server inside the test runner - no separate server step.

Step 7 - CI integration

# Multi-platform matrix
jobs:
  ui-tests:
    strategy:
      matrix:
        platform: [iOS, Android]
    runs-on: ${{ matrix.platform == 'iOS' && 'macos-15' || 'ubuntu-latest' }}
    steps:
      - uses: actions/checkout@v5

      - if: matrix.platform == 'Android'
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 34
          script: WDIO_OS=android npx wdio run wdio.conf.js

      - if: matrix.platform == 'iOS'
        run: |
          xcrun simctl boot 'iPhone 15'
          WDIO_OS=ios npx wdio run wdio.conf.js

Anti-patterns

Anti-patternWhy it failsFix
Per-platform separate test codeDefeats Appium's reuse value.One test, multi-capability config (Step 6).
XPath selectors as defaultSlowest selector strategy; brittle to UI tree changes.Accessibility IDs first (Step 5).
Hard-coded device name ('iPhone 15')Test fails when sim doesn't exist.Use genericDevice: true or generated capabilities.
Ignoring noReset between testsApp state leaks; flaky.noReset: false (default) reinstalls per session.
One mega-test using before for the whole suiteSuite-long flake; one tiny issue restarts everything.Per-test beforeEach reset; small focused tests.
Mixing Appium 1.x and 2.x docsServer URL / capability format differ; failures cryptic.Pin Appium 2.x; use Appium 2.x docs only.

Limitations

  • External server. Appium's HTTP-protocol design adds latency vs in-process frameworks (Espresso, XCUITest) - typical 100-300ms per command.
  • Per-driver feature gaps. Some features are XCUITest-driver only or UiAutomator2-driver only; cross-platform tests must avoid them.
  • Device-farm cost. Real devices on cloud farms charge per-minute; matrix runs add up.
  • Setup complexity. Server + drivers + Android SDK + Xcode command-line tools = many dependencies. Containerize where possible.

References

  • app (opens in new window) - Appium overview: WebDriver protocol, driver architecture, supported platforms (iOS, Android, Tizen, browser, desktop, TV), client libraries (JS / Python / Java / Ruby / .NET).
  • xcuitest-suite, espresso-suite - per-platform native alternatives.
  • detox-testing, maestro-flows - cross-platform alternatives with different trade-offs.
  • mobile-device-matrix-toolkit - orchestrates Appium-driven matrix runs across simulators / emulators / farms.

Related skills

detox-testing

Authors React Native E2E tests with Detox (Wix) - gray-box architecture (runs in-process with the app), `element(by.id|by.text|by.label)` matchers, `waitFor()` for explicit sync beyond Detox's automatic async tracking, Jest runner. Use when the app is React Native and speed matters. For Flutter use flutter-testing; for black-box cross-platform use appium-testing; for YAML-declarative flows use maestro-flows; for non-RN native use xcuitest-suite or espresso-suite.

espresso-suite

Authors Espresso UI tests for Android - uses `onView(withId(...)).perform(...).check(matches(...))`, leans on Espresso's automatic synchronization (no `Thread.sleep`), wires `IdlingResource` for app-specific async, runs via `./gradlew connectedAndroidTest` and parses the JUnit XML output. Use when an Android app needs UI tests in Google's first-party framework.

flutter-testing

Authors Flutter tests across the three-layer pyramid - unit (`flutter test`), widget (`testWidgets` + `WidgetTester`), integration (`integration_test` on simulator/emulator/device). Picks the right layer per change, mocks via `mockito` + `build_runner`, LCOV coverage, CI with the Flutter Action. Use when the app is Flutter and the team wants its first-party stack. For React Native use detox-testing; for black-box cross-platform use appium-testing; for YAML-declarative flows use maestro-flows.

maestro-flows

Authors Maestro YAML flow files (`.maestro/*.yaml`) for mobile + web UI automation: declarative `tapOn`, `inputText`, `assertVisible`, `swipe`, supported targets (iOS, Android, Flutter, React Native, web), nested flow imports, JavaScript hooks for complex conditions. Use when the team has already chosen Maestro, is coming from an existing `.maestro/` directory, or explicitly wants YAML-declarative tests readable by non-engineers without a compile step. For framework selection or authoring tests in XCUITest / Espresso / Detox / Appium / Flutter, use a mobile driver-selection or per-flow mobile test-authoring step instead.

mobile-a11y-test-author

Authors native mobile accessibility tests covering iOS (Accessibility Inspector, XCUITest `performAccessibilityAudit()` introduced in iOS 17, VoiceOver label/trait/hint verification) and Android (Espresso `AccessibilityChecks.enable()`, Accessibility Scanner, TalkBack traversal, `contentDescription` labelling) with WCAG-aligned checks for element labels, 44pt/48dp touch targets, contrast ratios, and focus order. Use when an iOS or Android app needs automated and manual accessibility test coverage beyond what `xcuitest-suite` or `espresso-suite` provide.

mobile-device-matrix-toolkit

Dispatches mobile UI test runs across a 3-tier device matrix (smoke per-PR, regression per-merge, full farm at release) to control CI cost: generates per-target Appium capability configs from a central YAML, parallelises via GitHub Actions matrix strategy, and aggregates JUnit XML into a cross-device pass/fail table. Use when deciding which iOS / Android devices and OS versions to run tests on and at which stage (smoke / regression / full farm), not how to configure a specific test framework (for that, use xcuitest-suite, espresso-suite, etc.).

mobile-web-emulation-runner

Builds a workflow to run web E2E tests under mobile viewports + DPRs (device pixel ratios): Playwright's `devices` catalog (iPhone 15, Pixel 7), suite run per-device as matrix shards, per-device screenshots, mobile assertions (`.tap()`, viewport-conditional layout). Use when a responsive web app needs mobile-breakpoint regression without a real-device farm. Mobile WEB only - for native apps use appium-testing, detox-testing, or flutter-testing; for cross-shard aggregation use mobile-device-matrix-toolkit; for gesture sequences use touch-gesture-tester.

mobile-web-perf-budget

Pure-reference skill for mobile-web performance budgets - Core Web Vitals at the 75th percentile mobile (LCP ≤2.5s, INP ≤200ms, CLS ≤0.1; FID retired March 2024 in favor of INP), Lighthouse mobile profile config, per-route resource budgets (JS bundle, image weight, font load). Use as the team's reference for "what should the mobile perf gate enforce" - paired with `lighthouse-perf` (the runner) and `lighthouse-budget-author` (the per-route author).

touch-gesture-tester

Verifies touch-gesture handlers (tap, double-tap, long-press, swipe, pinch, rotate, pan) work as expected under both mobile-emulation (Playwright) and native (XCUITest / Espresso / Detox) - distinguishes "mouse click handler also fires on tap" from "real touch event fired with correct properties." Use when the app has bespoke gesture handlers (custom carousels, sliders, drag-drop, pull-to-refresh) and the team needs targeted gesture verification beyond generic UI assertions.

xcuitest-suite

Authors XCUIest UI tests for iOS / iPadOS / tvOS - uses the three-class XCUIApplication / XCUIElement / XCUIElementQuery pattern, sets accessibility identifiers on production code, runs via `xcodebuild test` with destination, parses the `xcresult` bundle. Use when an iOS app needs UI tests in Apple's first-party framework (no external runtime; native to Xcode).