The iOS driver uses XCUITestOptions and targets either a simulator or a real device. The setup is more involved than Android — code signing for real devices, WebDriverAgent compilation, and simulator boot times add complexity, but the resulting driver is fast and accurate.
Simulator session
from appium import webdriver
from appium.options import XCUITestOptions
import os
def create_ios_driver() -> webdriver.Remote:
options = XCUITestOptions()
options.device_name = os.getenv("IOS_DEVICE", "iPhone 15")
options.platform_version = os.getenv("IOS_VERSION", "17.0")
options.app = os.path.abspath("apps/MyApp.app") # unzipped .app bundle
options.no_reset = True
options.simulator_startup_timeout = 120_000 # 120 seconds in ms
options.wda_startup_retries = 3
return webdriver.Remote("http://127.0.0.1:4723", options=options)Real device session
Real device testing requires code signing and a connected device with Developer Mode enabled:
def create_ios_real_device_driver() -> webdriver.Remote:
options = XCUITestOptions()
options.device_name = "My iPhone" # name from Xcode
options.udid = os.getenv("IOS_DEVICE_UDID") # from `xcrun xctrace list devices`
options.platform_version = "17.2"
options.app = os.path.abspath("apps/MyApp.ipa")
options.xcode_org_id = os.getenv("XCODE_ORG_ID") # 10-char team ID
options.xcode_signing_id = "Apple Development"
return webdriver.Remote("http://127.0.0.1:4723", options=options)Keeping the simulator warm between sessions
Booting a simulator from scratch takes 30–90 seconds. Set keep_alive = True to leave it running after the session ends:
options.keep_alive = TrueThe next session targeting the same simulator reuses the running instance. This saves around a minute per test file when using module-scoped fixtures.
Parallel iOS sessions
Each parallel iOS session needs its own WebDriverAgent port to avoid conflicts:
# Session 1 (iPhone 15)
options_iphone = XCUITestOptions()
options_iphone.device_name = "iPhone 15"
options_iphone.wda_local_port = 8100
# Session 2 (iPhone 14)
options_ipad = XCUITestOptions()
options_ipad.device_name = "iPhone 14"
options_ipad.wda_local_port = 8101App lifecycle
# Activate by bundle ID
driver.activate_app("com.example.myapp")
# Get active bundle ID
bundle_id = driver.bundle_id
# Terminate
driver.terminate_app("com.example.myapp")
# Query installed
installed = driver.is_app_installed("com.example.myapp")Clipboard (simulator only)
Clipboard access on real devices requires additional permissions. On simulators:
driver.set_clipboard_text("test@example.com")
email = driver.get_clipboard_text()Handling system alerts
from selenium.common.exceptions import NoAlertPresentException, TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
def accept_alert_if_present(driver, timeout=3):
try:
WebDriverWait(driver, timeout).until(
lambda d: _try_accept_alert(d)
)
except TimeoutException:
pass # No alert shown
def _try_accept_alert(driver):
try:
driver.switch_to.alert.accept()
return True
except NoAlertPresentException:
return FalseAuto-accept all alerts via capability (use only when alert handling isn't under test):
options.auto_accept_alerts = TrueDebugging session failures
WDA startup timeout: The most common iOS failure. Increase retries and wait:
options.wda_startup_retries = 5
options.wda_startup_retry_interval = 20_000 # ms between retriesSimulator not found: List available simulators:
xcrun simctl list devices availableThe device_name must match exactly, including any "Pro" or "Max" suffixes.
Session hangs indefinitely: Set new_command_timeout to force-kill a stuck session:
options.new_command_timeout = 60 # secondsIf no command is sent within 60 seconds, Appium kills the session and frees the simulator.
Typical conftest.py fixture
@pytest.fixture(scope="function")
def ios_driver():
options = XCUITestOptions()
options.device_name = os.getenv("IOS_DEVICE", "iPhone 15")
options.app = os.path.abspath("apps/MyApp.app")
options.no_reset = True
options.wda_startup_retries = 3
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
yield driver
try:
driver.quit()
except Exception:
pass