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:
- Your test sends a "start session" request to Appium with your settings (called capabilities)
- Appium sets up the device, installs the app, and opens it
- Appium gives your test a unique session ID (like
"a1b2c3d4-5678-...") - Your test uses that session ID for every subsequent command
- 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.
// 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)
| Capability | What it does | Example value |
|---|---|---|
platformName | Which mobile OS | "Android" or "iOS" |
appium:automationName | Which driver to use | "UiAutomator2" or "XCUITest" |
appium:deviceName | Name of the device/emulator | "Pixel_6_API_33" |
appium:app | Path to your app file | "/path/to/app.apk" |
Note: Instead of
appium:app(a path to a file), you can useappium:appPackage+appium:appActivity(Android) orappium:bundleId(iOS) if the app is already installed on the device.
Optional but commonly used capabilities
| Capability | What it does | Example value |
|---|---|---|
appium:platformVersion | OS version on the device | "13" or "16.4" |
appium:noReset | Don't reinstall the app between sessions | true |
appium:fullReset | Uninstall app and clear all data | true |
appium:autoGrantPermissions | Auto-accept Android permission dialogs | true |
appium:newCommandTimeout | Seconds before idle session is killed | 60 |
appium:language | App language | "en" |
appium:locale | Device locale | "US" |
4. Android capabilities — full example
Here is a complete, working capability object for Android testing using WebdriverIO:
// 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:
// 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:
// 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:
// 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
| Setting | Android | iOS |
|---|---|---|
| automationName | UiAutomator2 | XCUITest |
| App file format | .apk (or .aab built to APK) | .app (simulator) or .ipa (device) |
| Already-installed app | appPackage + appActivity | bundleId |
| Specific device targeting | deviceName (or udid from adb devices) | udid from Xcode |
| Auto permissions | autoGrantPermissions: true | Handle in test (no equivalent) |
| macOS required? | No | Yes |
6. Common capability mistakes
These are the most frequent errors beginners encounter:
Mistake 1: Missing the appium: prefix
// 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.
# Android: see connected devices and emulators
adb devices
# iOS: see simulators (macOS only)
xcrun simctl list devices | grep BootedMistake 3: App path doesn't exist
// 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
// 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:
# Check what drivers are installed
appium driver list --installed
# Install the missing driver
appium driver install uiautomator2
appium driver install xcuitest7. 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."
// 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."
// 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
// 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 Wait | Explicit Wait | |
|---|---|---|
| What it does | Retries every element lookup | Waits for a specific condition |
| Where it applies | All element lookups globally | Only where you write it |
| Performance | Slow when element doesn't exist | Fast — stops as soon as condition is met |
| Recommended | Short value as fallback only | Use for async content and animations |
8. Starting and ending a session in code
Here's a complete minimal example using WebdriverIO:
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
| Topic | Key takeaway |
|---|---|
| Session | A connection between your test and the device — has a start, middle, and end |
| Capabilities | JSON settings sent at session start — tell Appium what and how to test |
appium: prefix | Required in Appium 2 for all non-standard capabilities |
| Android app | Use .apk path, or appPackage + appActivity if already installed |
| iOS app | Use .app/.ipa path, or bundleId if already installed |
| Implicit waits | Use sparingly — they slow down all element lookups globally |
| Explicit waits | Preferred — wait for specific elements with meaningful timeout messages |
| Session cleanup | Always use try/finally to call deleteSession() |
Practice exercises
-
Write an Android capability object for an app called
MyShopAppon an emulator calledPixel_7_API_34running Android 14. The APK is at/home/user/apps/myshop.apk. IncludeautoGrantPermissionsandnoReset. -
Write an iOS capability object for a simulator called
iPhone 15running iOS 17. The app is already installed and has bundle IDcom.mycompany.myshopapp. -
Spot the bugs — find all errors in this capability object:
javascriptjavascript { platform: "android", deviceName: "my_emulator", automationName: "xcuitest", app: "app.apk", } -
Research: Run
adb devicesin your terminal. What do you see? What does it tell you about connected devices? -
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)