Lesson 2 of 6

Lesson 02 — Sessions and Capabilities

Title: Starting your first Appium session with capabilities

Description: Learn what a "session" is, how capabilities work as configuration for your test, and how to write correct capability objects for both Android and iOS — including the most common mistakes and how to fix them.

Why it matters for QA: Capabilities are the very first thing Appium reads when you start a test. If they're wrong, the test never even starts. Understanding capabilities properly saves hours of "cannot find device" and "app not installed" debugging.


1. What is a session?

Before your test can do anything — tap a button, read text, fill a form — it needs to open a session with Appium.

Think of a session like logging into an app. When you log in, the server creates a record of you and gives you a token. Every action you take from that point belongs to your session. When you log out, the session ends.

An Appium session works the same way:

  1. Your test sends a "start session" request to Appium with your settings (called capabilities)
  2. Appium sets up the device, installs the app, and opens it
  3. Appium gives your test a unique session ID (like "a1b2c3d4-5678-...")
  4. Your test uses that session ID for every subsequent command
  5. When your test finishes, it sends a "close session" request and everything shuts down
Timeline of a session: Your test starts │ ▼ POST /session ──► Appium receives capabilities (with capabilities) │ ▼ Device boots / app launches │ ▼ ◄── session ID returned (e.g. "a1b2c3-...") │ ▼ Test runs (tap, read, swipe...) ── all using session ID │ ▼ DELETE /session ──► App closes, device session ends

What this means in practice: If your test crashes without ending the session, the device might stay locked in that state. Always use try/finally blocks to ensure the session is closed even when a test fails.


2. What are capabilities?

Capabilities are a JSON object of settings that you send when starting a session. They tell Appium:

  • What platform you're targeting (Android or iOS)
  • Which device or emulator to use
  • Which app to install and test
  • Which automation driver to use
  • Any extra settings for the test environment

Think of capabilities like a form you fill out when renting a car:

  • Type of car: Android / iOS
  • License plate (device ID): Pixel_6_API_33 / iPhone 14
  • Which car lot (app path): /path/to/app.apk
  • Any extras (reset app between tests, auto-grant permissions, etc.)

Every capability has a key (name) and a value.

In Appium 2, most capabilities need a vendor prefix: appium:. This is the W3C standard way of adding vendor-specific options. The only capability without the prefix is platformName — it's a standard W3C capability.

javascriptjavascript
// Correct Appium 2 format — note the "appium:" prefix
const capabilities = {
  platformName: "Android",          // No prefix — standard W3C capability
  "appium:deviceName": "Pixel_6",   // Appium-specific — needs "appium:" prefix
  "appium:automationName": "UiAutomator2",
};

// WRONG — Appium 1 style, will cause warnings or errors in Appium 2
const wrongCapabilities = {
  platformName: "Android",
  deviceName: "Pixel_6",           // Missing "appium:" prefix!
  automationName: "UiAutomator2",  // Missing "appium:" prefix!
};

3. Required vs optional capabilities

Some capabilities are required — without them, Appium cannot start a session. Others are optional and control additional behavior.

Required capabilities (always needed)

CapabilityWhat it doesExample value
platformNameWhich mobile OS"Android" or "iOS"
appium:automationNameWhich driver to use"UiAutomator2" or "XCUITest"
appium:deviceNameName of the device/emulator"Pixel_6_API_33"
appium:appPath to your app file"/path/to/app.apk"

Note: Instead of appium:app (a path to a file), you can use appium:appPackage + appium:appActivity (Android) or appium:bundleId (iOS) if the app is already installed on the device.

Optional but commonly used capabilities

CapabilityWhat it doesExample value
appium:platformVersionOS version on the device"13" or "16.4"
appium:noResetDon't reinstall the app between sessionstrue
appium:fullResetUninstall app and clear all datatrue
appium:autoGrantPermissionsAuto-accept Android permission dialogstrue
appium:newCommandTimeoutSeconds before idle session is killed60
appium:languageApp language"en"
appium:localeDevice locale"US"

4. Android capabilities — full example

Here is a complete, working capability object for Android testing using WebdriverIO:

javascriptjavascript
// capabilities for testing an Android app
const androidCapabilities = {
  // Which OS we're targeting
  platformName: "Android",

  // Which Appium driver to use for Android
  // UiAutomator2 is the standard Android driver
  "appium:automationName": "UiAutomator2",

  // Name of the emulator or physical device
  // Run "adb devices" in terminal to see connected devices
  "appium:deviceName": "Pixel_6_API_33",

  // Android version on the device (optional but recommended)
  "appium:platformVersion": "13",

  // Full path to your APK file
  // Use an absolute path to avoid confusion
  "appium:app": "/Users/qa/projects/shop-app/build/app-debug.apk",

  // Don't wipe app data between test runs
  // Set to false if you need a clean state each time
  "appium:noReset": false,

  // Android will automatically grant location/camera/mic permissions
  // This prevents permission dialogs from blocking your test
  "appium:autoGrantPermissions": true,

  // If Appium receives no command for 60 seconds, end the session
  // Prevents orphaned sessions from blocking the device
  "appium:newCommandTimeout": 60,
};

If the app is already installed on the device and you don't want to install it again:

javascriptjavascript
// Use appPackage + appActivity instead of app path
const androidCapabilitiesInstalled = {
  platformName: "Android",
  "appium:automationName": "UiAutomator2",
  "appium:deviceName": "Pixel_6_API_33",

  // The app's package name (find it in AndroidManifest.xml or Play Store URL)
  "appium:appPackage": "com.example.myshop",

  // The activity to launch (usually the main/launcher activity)
  "appium:appActivity": "com.example.myshop.MainActivity",

  // Since app is installed, no need to reinstall
  "appium:noReset": true,
};

5. iOS capabilities — full example

iOS testing requires macOS and Xcode. Here is a complete capability object for iOS:

javascriptjavascript
// Capabilities for testing an iOS app in a simulator
const iosCapabilities = {
  // Which OS we're targeting
  platformName: "iOS",

  // Which Appium driver to use for iOS
  // XCUITest is Apple's own UI testing framework, used by Appium
  "appium:automationName": "XCUITest",

  // Name of the simulator
  // Open Xcode > Devices and Simulators to see available simulators
  "appium:deviceName": "iPhone 14",

  // iOS version on the simulator
  "appium:platformVersion": "16.4",

  // Full path to your IPA file (built for simulator — not signed for real device)
  "appium:app": "/Users/qa/projects/shop-app/build/MyShop.app",

  // Don't reset app state between sessions
  "appium:noReset": false,

  // How long to wait before Appium times out an idle session
  "appium:newCommandTimeout": 60,
};

For testing on a real iOS device (not a simulator), you also need:

javascriptjavascript
// Additional capabilities for real iOS device
const iosRealDeviceCapabilities = {
  platformName: "iOS",
  "appium:automationName": "XCUITest",
  "appium:deviceName": "My iPhone",
  "appium:platformVersion": "16.4",

  // For real devices, use bundleId of an app already installed
  "appium:bundleId": "com.example.myshop",

  // The UDID of your specific device
  // Find it in Xcode > Window > Devices and Simulators
  "appium:udid": "00008101-001234AB5678901E",

  // Your Apple Developer Team ID (found in Apple Developer account)
  "appium:xcodeOrgId": "YOUR_TEAM_ID",

  // Your signing certificate name
  "appium:xcodeSigningId": "iPhone Developer",
};

Android vs iOS side-by-side

SettingAndroidiOS
automationNameUiAutomator2XCUITest
App file format.apk (or .aab built to APK).app (simulator) or .ipa (device)
Already-installed appappPackage + appActivitybundleId
Specific device targetingdeviceName (or udid from adb devices)udid from Xcode
Auto permissionsautoGrantPermissions: trueHandle in test (no equivalent)
macOS required?NoYes

6. Common capability mistakes

These are the most frequent errors beginners encounter:

Mistake 1: Missing the appium: prefix

javascriptjavascript
// WRONG — Appium 2 will reject or warn about this
{
  deviceName: "Pixel_6_API_33",   // Missing appium: prefix
  automationName: "UiAutomator2", // Missing appium: prefix
}

// CORRECT
{
  "appium:deviceName": "Pixel_6_API_33",
  "appium:automationName": "UiAutomator2",
}

Mistake 2: Wrong device name

The deviceName must exactly match what your system reports.

bashbash
# Android: see connected devices and emulators
adb devices

# iOS: see simulators (macOS only)
xcrun simctl list devices | grep Booted

Mistake 3: App path doesn't exist

javascriptjavascript
// WRONG — relative path is unreliable
"appium:app": "./app-debug.apk",

// CORRECT — use an absolute path
"appium:app": "/Users/qa/projects/shop-app/build/app-debug.apk",
// or in Windows:
"appium:app": "C:\\Users\\qa\\projects\\shop-app\\build\\app-debug.apk",

Mistake 4: Wrong automationName for the platform

javascriptjavascript
// WRONG — using Android driver for iOS test
{
  platformName: "iOS",
  "appium:automationName": "UiAutomator2", // This is Android-only!
}

// CORRECT
{
  platformName: "iOS",
  "appium:automationName": "XCUITest",
}

Mistake 5: Forgetting to install the driver

If you see "No driver found for..." errors:

bashbash
# Check what drivers are installed
appium driver list --installed

# Install the missing driver
appium driver install uiautomator2
appium driver install xcuitest

7. Implicit vs explicit waits — which to use and why

Mobile apps are not instant. Animations play, screens load, data fetches from the network. Your test needs to wait for elements to appear before interacting with them.

There are two ways to handle waiting in Appium:

Implicit waits — what they are

An implicit wait tells Appium: "If you can't find an element immediately, keep trying for X seconds before giving up."

javascriptjavascript
// Set a global implicit wait of 10 seconds
await driver.implicitWait(10000); // milliseconds

The problem with implicit waits: They apply to every single element lookup. If an element genuinely doesn't exist, your test waits 10 full seconds before failing — on every failed lookup. This makes your test suite slow.

Explicit waits — the better approach

An explicit wait tells Appium: "Wait for THIS specific condition to be true before continuing."

javascriptjavascript
// Wait for a specific element to be visible — up to 10 seconds
const loginButton = await driver.$('~loginButton');
await loginButton.waitForDisplayed({ timeout: 10000 });

// Wait for text to contain a specific value
const welcomeMessage = await driver.$('~welcomeText');
await welcomeMessage.waitForExist({ timeout: 5000 });

Why explicit waits are better:

  • You wait only for the elements that actually need waiting
  • Your tests run faster when elements appear quickly
  • The error message tells you exactly which element wasn't found and when
  • You can use different timeouts for different situations (a loading screen might need 30s; a simple button needs only 2s)

The recommended approach

javascriptjavascript
// Set a short implicit wait as a fallback
await driver.implicitWait(2000);

// Use explicit waits for anything that loads asynchronously
const dynamicContent = await driver.$('~productList');
await dynamicContent.waitForDisplayed({
  timeout: 15000,
  // Custom error message makes debugging much easier
  timeoutMsg: 'Product list did not appear within 15 seconds',
});
Implicit WaitExplicit Wait
What it doesRetries every element lookupWaits for a specific condition
Where it appliesAll element lookups globallyOnly where you write it
PerformanceSlow when element doesn't existFast — stops as soon as condition is met
RecommendedShort value as fallback onlyUse for async content and animations

8. Starting and ending a session in code

Here's a complete minimal example using WebdriverIO:

javascriptjavascript
import { remote } from "webdriverio";

// Define capabilities
const capabilities = {
  platformName: "Android",
  "appium:automationName": "UiAutomator2",
  "appium:deviceName": "Pixel_6_API_33",
  "appium:app": "/absolute/path/to/your/app.apk",
  "appium:noReset": false,
};

// WebdriverIO options — point to your running Appium server
const wdioOptions = {
  hostname: "localhost",
  port: 4723,
  logLevel: "info",
  capabilities,
};

// Always wrap in try/finally to guarantee session cleanup
async function runTest() {
  let driver;

  try {
    // This starts the session — sends POST /session to Appium
    driver = await remote(wdioOptions);

    // Your test actions go here
    const loginButton = await driver.$("~loginButton");
    await loginButton.waitForDisplayed({ timeout: 5000 });
    await loginButton.click();

    console.log("Test passed!");
  } catch (error) {
    console.error("Test failed:", error.message);
    throw error;
  } finally {
    // Always end the session — even if the test throws an error
    // This sends DELETE /session to Appium
    if (driver) {
      await driver.deleteSession();
    }
  }
}

runTest();

Why try/finally matters: If your test throws an error and you don't end the session, the device stays occupied. If you're running on a shared device farm, other tests will be blocked. If you're on your local machine, the emulator stays in a frozen state. Always clean up.


Official docs


Quick recap

TopicKey takeaway
SessionA connection between your test and the device — has a start, middle, and end
CapabilitiesJSON settings sent at session start — tell Appium what and how to test
appium: prefixRequired in Appium 2 for all non-standard capabilities
Android appUse .apk path, or appPackage + appActivity if already installed
iOS appUse .app/.ipa path, or bundleId if already installed
Implicit waitsUse sparingly — they slow down all element lookups globally
Explicit waitsPreferred — wait for specific elements with meaningful timeout messages
Session cleanupAlways use try/finally to call deleteSession()

Practice exercises

  1. Write an Android capability object for an app called MyShopApp on an emulator called Pixel_7_API_34 running Android 14. The APK is at /home/user/apps/myshop.apk. Include autoGrantPermissions and noReset.

  2. Write an iOS capability object for a simulator called iPhone 15 running iOS 17. The app is already installed and has bundle ID com.mycompany.myshopapp.

  3. Spot the bugs — find all errors in this capability object:

    javascriptjavascript
    {
      platform: "android",
      deviceName: "my_emulator",
      automationName: "xcuitest",
      app: "app.apk",
    }
    
  4. Research: Run adb devices in your terminal. What do you see? What does it tell you about connected devices?

  5. Explain in your own words: Why is using explicit waits better than setting a large implicit wait? Write 3-4 sentences as if explaining to a teammate.


Next: Lesson 03 — Locators and stability (lab03.md)