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'`]"));
FeatureAndroidiOS
DriverUiAutomator2XCUITest
Automation engineUIAutomator2WebDriverAgent (WDA)
OS requiredAnymacOS only
Tool for inspectionAppium Inspector / UIAutomator ViewerAppium Inspector / Xcode
Primary locatorresource-idaccessibilityIdentifier
Permission handlingautoGrantPermissionsautoAcceptAlerts
Gesture APIW3C PointerInputW3C PointerInput
App file formatAPKAPP (simulator) / IPA (device)
Advertisement

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 machineWindows, macOS or LinuxmacOS with Xcode
App file.apk / .aab.app (simulator) or signed .ipa (device)
Best locator after accessibility IDresource-id, UiAutomator selectoriOS predicate string, class chain
Real devicesUSB debuggingSigning 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.