iOS automation with Appium uses the XCUITest driver, which drives Apple's XCTest framework through WebDriverAgent on a simulator or real device. This guide covers iOS setup and locators, then how to design a single cross-platform framework so the same tests run on Android and iOS, plus optimisation practices for large mobile suites.
iOS Automation with XCUITest
iOS automation requires a Mac machine with Xcode installed. The XCUITest driver communicates with iOS Simulator or real devices via Instruments.
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
public class IOSSetup {
public IOSDriver getIOSDriver() throws Exception {
XCUITestOptions options = new XCUITestOptions();
options.setPlatformName("iOS");
options.setPlatformVersion("17.0");
options.setDeviceName("iPhone 15 Pro");",
// Simulator — use .app bundle path
options.setApp("/path/to/MyApp.app");
// Real device — use .ipa file + UDID
// options.setApp("/path/to/MyApp.ipa");
// options.setUdid("00008110-XXXXXXXXXX");
options.setAutoAcceptAlerts(true); // auto-accept iOS alerts
options.setWdaLaunchTimeout(60000L); // WDA startup timeout
return new IOSDriver(new URL("http://127.0.0.1:4723"), options);
}
}
// iOS-specific locators
// Accessibility ID (preferred)
driver.findElement(AppiumBy.accessibilityId("loginButton"));
// iOS Predicate String
driver.findElement(AppiumBy.iOSNsPredicateString("type == 'XCUIElementTypeButton' AND label == 'Login'"));
// iOS Class Chain
driver.findElement(AppiumBy.iOSClassChain("**/XCUIElementTypeTextField[`value BEGINSWITH 'Enter email'`]"));
| Feature | Android | iOS |
|---|---|---|
| Driver | UiAutomator2 | XCUITest |
| Automation engine | UIAutomator2 | WebDriverAgent (WDA) |
| OS required | Any | macOS only |
| Tool for inspection | Appium Inspector / UIAutomator Viewer | Appium Inspector / Xcode |
| Primary locator | resource-id | accessibilityIdentifier |
| Permission handling | autoGrantPermissions | autoAcceptAlerts |
| Gesture API | W3C PointerInput | W3C PointerInput |
| App file format | APK | APP (simulator) / IPA (device) |
Cross-Platform Framework Design
// DriverFactory.java — create driver based on platform
public class DriverFactory {
public static AppiumDriver createDriver(String platform) throws Exception {
switch (platform.toLowerCase()) {
case "android":
return createAndroidDriver();
case "ios":
return createIOSDriver();
default:
throw new IllegalArgumentException("Unsupported: " + platform);
}
}
private static AndroidDriver createAndroidDriver() throws Exception {
UiAutomator2Options opts = new UiAutomator2Options();
opts.setDeviceName(ConfigReader.get("android.device"));
opts.setAppPackage(ConfigReader.get("android.package"));
opts.setAppActivity(ConfigReader.get("android.activity"));
return new AndroidDriver(new URL("http://127.0.0.1:4723"), opts);
}
private static IOSDriver createIOSDriver() throws Exception {
XCUITestOptions opts = new XCUITestOptions();
opts.setDeviceName(ConfigReader.get("ios.device"));
opts.setApp(ConfigReader.get("ios.app.path"));
return new IOSDriver(new URL("http://127.0.0.1:4723"), opts);
}
}
// Cross-platform page class using @AndroidFindBy + @iOSFindBy
public class LoginPage extends BasePage {
@AndroidFindBy(id = "com.example:id/username")
@iOSFindBy(accessibility = "usernameTextField")
private WebElement usernameField;
@AndroidFindBy(id = "com.example:id/login_btn")
@iOSFindBy(accessibility = "loginButton")
private WebElement loginButton;
}
Test Optimization & Best Practices
Common causes of slow/flaky Appium tests
- Using Thread.sleep instead of explicit waits
- Using xpath when resource-id or accessibilityId is available
- Not using ThreadLocal — tests interfere in parallel runs
- Re-initialising driver in every test method instead of class
- Not handling app state — tests depend on previous test outcome
- Not waiting for activity transition animations to complete
Optimization techniques
// 1. Use noReset=true to avoid reinstalling between tests
options.setNoReset(true);
// 2. Start driver once per class, not per test
@BeforeClass // NOT @BeforeMethod
public void setUp() { driver = DriverManager.createAndroidDriver(); }
// 3. Use resource-id over xpath
By.id("com.example:id/btn") // usually faster and more stable than XPath
// 4. Skip animations in test builds
// Developer option: Animator duration scale = 0
// Or via ADB:
// adb shell settings put global window_animation_scale 0
// adb shell settings put global transition_animation_scale 0
// adb shell settings put global animator_duration_scale 0
// 5. Use UIAutomator scroll instead of repeated swipes
AppiumBy.androidUIAutomator("new UiScrollable(...).scrollIntoView(...)");
// 6. Batch similar tests on same device session
// Group tests that don't need clean state into one TestNG class
Advanced Locator Strategies
// 1. UIAutomator2 — most powerful Android locator
// Multi-condition
AppiumBy.androidUIAutomator(
"new UiSelector().text(\"Add to Cart\").className(\"android.widget.Button\")");
// By index
AppiumBy.androidUIAutomator("new UiSelector().className(\"android.widget.EditText\").instance(1)");
// Child element
AppiumBy.androidUIAutomator(
"new UiSelector().resourceId(\"com.example:id/cart_list\")
.childSelector(new UiSelector().index(2))");
// 2. iOS Class Chain — hierarchy navigation
AppiumBy.iOSClassChain("**/XCUIElementTypeCell[`label == 'Product 1'`]/XCUIElementTypeButton[1]");
// 3. iOS Predicate String — multiple conditions
AppiumBy.iOSNsPredicateString(
"type == 'XCUIElementTypeButton' AND enabled == true AND label CONTAINS 'Buy'");
// 4. Image matching — for elements without IDs
// Encode reference image as base64
String base64 = Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("btn.png")));
driver.findElement(AppiumBy.image(base64)).click();
iOS vs Android: What Changes in Your Tests
| Android (UiAutomator2) | iOS (XCUITest) | |
|---|---|---|
| Host machine | Windows, macOS or Linux | macOS with Xcode |
| App file | .apk / .aab | .app (simulator) or signed .ipa (device) |
| Best locator after accessibility ID | resource-id, UiAutomator selector | iOS predicate string, class chain |
| Real devices | USB debugging | Signing and provisioning for WebDriverAgent |
FAQs
Do you need a Mac for Appium iOS testing?
Yes. The XCUITest driver depends on Xcode and WebDriverAgent, which only run on macOS; cloud device farms are the alternative if your team has no Macs.
What is WebDriverAgent?
A WebDriver server that runs on the iOS device or simulator and executes XCUITest commands for Appium; it must be built and signed to run on real devices.
Which iOS locator should I use?
Accessibility ID first, then iOS predicate strings or class chains; avoid XPath on iOS because it is especially slow.