Creating an IOSDriver — Simulator and Real Device Sessions

7 min read

IOSDriver drives iOS apps through the XCUITest engine. The setup process is more involved than Android — you deal with code signing, simulator vs real device differences, and WebDriverAgent compilation — but the resulting driver is stable and fast.

XCUITestOptions

XCUITestOptions is the typed capability builder for iOS sessions, analogous to UiAutomator2Options for Android:

import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
 
import java.net.URL;
 
URL serverUrl = new URL("http://127.0.0.1:4723");
XCUITestOptions options = new XCUITestOptions()
    .setDeviceName("iPhone 15")
    .setPlatformVersion("17.0")
    .setApp("/path/to/MyApp.app")   // simulator build (.app)
    .setSimulatorStartupTimeout(Duration.ofSeconds(120))
    .setWdaStartupRetries(3);
 
IOSDriver driver = new IOSDriver(serverUrl, options);

Simulator vs real device

The setApp() path differs by target:

// Simulator — use .app bundle (unzipped)
options.setApp("/Users/me/builds/MyApp.app");
 
// Real device — use .ipa file (signed)
options.setApp("/Users/me/builds/MyApp.ipa");

For real devices, you also need:

options.setUdid("00008030-001A23456789002E") // device UDID from `xcrun xctrace list devices`
       .setXcodeCertificate("Apple Distribution: Company Name (TEAMID)")
       .setXcodeOrgId("TEAMID");

Simulator management

Appium boots a simulator if the named device isn't running. To control which simulator is used:

options.setDeviceName("iPhone 15 Pro")
       .setPlatformVersion("17.2");

List available simulators with xcrun simctl list devices available. The device name must match exactly, including any Pro/Max/Plus suffixes.

To reuse a running simulator instead of booting a fresh one:

options.setSimulatorStartupTimeout(Duration.ofSeconds(60))
       .setWdaLocalPort(8100) // avoid port collision in parallel runs

Preventing simulator shutdown between sessions

Booting a simulator takes 30–90 seconds. setKeepAlive(true) tells the XCUITest driver to leave the simulator running after the session ends:

XCUITestOptions options = new XCUITestOptions()
    .setDeviceName("iPhone 15")
    .setKeepAlive(true);

The next session on the same simulator name reuses the running instance, shaving a minute off suite time.

App reset on iOS

// No reset — fastest, use for stateless tests
options.setNoReset(true);
 
// Default — app data cleared, app stays installed
options.setNoReset(false);
 
// Full reset — app uninstalled completely
options.setFullReset(true);

iOS full reset uninstalls the app and its data container. Use this when tests depend on a completely fresh install (e.g., onboarding flows, permission grant flows).

IOSDriver-specific methods

IOSDriver driver = new IOSDriver(serverUrl, options);
 
// Simulator-only: shake gesture
driver.shake();
 
// Get current bundle ID
String bundleId = driver.getBundleId();
 
// Activate an app by bundle ID
driver.activateApp("com.example.myapp");
 
// Terminate
driver.terminateApp("com.example.myapp");
 
// Lock / unlock device (real device only)
driver.lockDevice();
driver.unlockDevice();
 
// Read clipboard
String text = driver.getClipboardText();

System alerts

iOS shows permission dialogs (camera, location, notifications) as native system alerts outside the app's hierarchy. Handle them with:

try {
    driver.switchTo().alert().accept(); // "Allow"
} catch (NoAlertPresentException e) {
    // no alert shown — that's fine
}

Or use the XCUITest capability to accept all permissions automatically:

options.setAutoAcceptAlerts(true);

Be careful with setAutoAcceptAlerts(true) if any test verifies the permission denial path — it accepts every dialog regardless.

WebDriverAgent troubleshooting

WebDriverAgent (WDA) is the XCTest runner Appium compiles and installs on the target device/simulator. Session creation fails if WDA doesn't start. Common causes:

WDA timeout: Increase the startup wait:

options.setWdaStartupRetries(3)
       .setWdaStartupRetryInterval(Duration.ofSeconds(20));

Port conflict in parallel runs: Each simulator session needs its own WDA port:

// Session 1
options.setWdaLocalPort(8100);
 
// Session 2
options.setWdaLocalPort(8101);

Code signing error on real device: Xcode must trust the development certificate. Set xcodeOrgId to the 10-character team ID from your Apple Developer account.

Parallel iOS sessions

Running iPhone and iPad tests in parallel:

// Thread 1 — iPhone
XCUITestOptions iphone = new XCUITestOptions()
    .setDeviceName("iPhone 15")
    .setWdaLocalPort(8100)
    .setWebDriverAgentUrl("http://127.0.0.1:8100");
 
// Thread 2 — iPad
XCUITestOptions ipad = new XCUITestOptions()
    .setDeviceName("iPad Pro (12.9-inch) (6th generation)")
    .setWdaLocalPort(8101)
    .setWebDriverAgentUrl("http://127.0.0.1:8101");

Without distinct wdaLocalPort values, both sessions compete for port 8100 and one fails.

// tip to track lessons you complete and pick up where you left off across devices.