Running Appium tests on a real iPhone requires more setup than running tests on a simulator. You need the right Appium driver, a trusted iOS device, valid Xcode signing, WebDriverAgent configuration, and correct capabilities before the test can launch the app.
In this guide, I will explain how to run Appium iOS tests on a real device using the XCUITest driver. For iOS, this is the required Appium driver because Appium cannot automate real iPhones directly. It sends commands through the XCUITest driver, which uses WebDriverAgent and Apple’s XCTest framework to interact with the device.
I will cover the setup steps, device preparation, signing requirements, test script, element inspection, and common errors you may face during execution.
How Appium Runs Tests on Real iOS Devices
When you run an Appium test on a real iOS device, your test does not talk to the app directly. The test sends commands to the Appium server, and Appium passes those commands to the XCUITest driver. Appium works through WebDriver-based automation, and iOS support is handled through platform-specific drivers.
For iOS, the XCUITest driver uses WebDriverAgent to run commands on the device. WebDriverAgent is installed on the iPhone during the session and acts as the bridge between Appium and Apple’s XCTest framework.
The flow looks like this:
This is why iOS real device testing fails differently from Android testing. A test may fail before the app opens if the device is not trusted, the UDID is wrong, Xcode signing is not configured, WebDriverAgent cannot build, or the required capabilities do not match the connected device.
Prerequisites for Appium iOS Real Device Testing
Before you start the setup, make sure the basic device, Apple, and Appium requirements are ready. iOS real device testing depends on Xcode and Apple signing, so missing one item here can stop the test before the app even launches.
| Requirement | Why it is needed |
|---|---|
| Mac machine | Appium iOS real device testing needs Xcode and Apple developer tools. The XCUITest driver supports iOS automation from macOS because it depends on Xcode. |
| Real iPhone or iPad | The device is needed to validate real iOS behavior such as touch, permissions, sensors, push notifications, and performance. |
| Xcode | Xcode is required for iOS device pairing, signing, building WebDriverAgent, and using Apple’s XCTest framework. |
| Apple Developer account or valid development team | Required to sign WebDriverAgent and install it on a real iOS device. Appium’s real-device setup depends on signing WebDriverAgentRunner correctly. |
| Node.js and npm | Required to install and run Appium from the command line. |
| Appium server | Receives commands from your test script and starts the automation session. |
| XCUITest driver | Required for iOS automation. In Appium 2 and later, platform drivers are installed separately using the Appium driver command. |
| Appium Java client | Required if you are writing the test in Java. Use the matching client library for your chosen language. |
| App under test | You need either the app file or the installed app’s bundle ID. |
| Device UDID | Appium uses the UDID to identify the real iOS device. |
| Developer Mode enabled on the iPhone | iOS 16 and later require Developer Mode before running development-signed apps or WebDriverAgent on the device. |
You should also keep these values ready before writing the test:
- Device name
- iOS version
- Device UDID
- App bundle ID
- Xcode team ID
- Signing certificate name
- Updated WebDriverAgent bundle ID
For example, the test setup usually needs values such as platformName, automationName, udid, bundleId, xcodeOrgId, and xcodeSigningId. If any of these values are wrong, Appium may fail while creating the session or while building WebDriverAgent.
Setting Up Appium for iOS Real Device Testing
Once the prerequisites are ready, install Appium in this order: check Node.js, install the Appium server, install the XCUITest driver, verify the driver, and then start the server.
For a current 2026 setup, use Appium 3 with the latest XCUITest driver. Appium drivers are managed separately from the Appium server, but the driver version still needs to match the Appium server version. The current XCUITest driver documentation states that XCUITest driver 10.0.0 and later requires Appium 3. So if you are using Appium 2, pin a compatible XCUITest driver version instead of installing or updating to the latest driver.
Step 1: Verify Node.js and npm
Appium runs on Node.js, so first check whether Node.js and npm are installed on your machine.
node -v npm -v
If both commands return version numbers, you can install Appium.
Step 2: Install Appium
Install Appium globally using npm:
npm install -g appium
After installation, verify that Appium is available from the command line:
appium -v
If this returns an Appium version, the server installation is complete.
Output:
Step 3: Install the XCUITest Driver
Appium needs the XCUITest driver to automate iOS apps. Install it with:
appium driver install xcuitest
The XCUITest driver is the Appium driver used for iOS, iPadOS, and tvOS automation. It must be installed before you create an iOS test session.
Output:
Step 4: Verify the Installed Driver
Check whether the XCUITest driver is installed correctly:
appium driver list --installed
You should see xcuitest in the installed driver list.
Output:
Step 5: Check for XCUITest Driver Updates
For a current Appium 3 setup, check whether the installed XCUITest driver has an update:
appium driver list --updates
If an update is available, update the driver:
appium driver update xcuitest
If you are using Appium 2, do not update blindly. The latest XCUITest driver may require Appium 3. In that case, install a compatible driver version instead:
appium driver install xcuitest@<compatible-version>
Step 6: Start the Appium Server
Start the Appium server:
appium
For Appium 2 and later, use this server URL in your test code:
http://127.0.0.1:4723
Do not use the old Appium 1 URL by default:
http://127.0.0.1:4723/wd/hub
The /wd/hub path was the old Appium 1 default. In Appium 2 and later, the default base path is /. Use /wd/hub only if you start the server with that base path explicitly.
appium --base-path=/wd/hub
At this point, Appium and the XCUITest driver are ready. The next step is to prepare the real iOS device and configure Xcode signing.
Preparing the Real iOS Device for Appium Testing
After Appium and the XCUITest driver are installed, prepare the iPhone or iPad that will run the test. Appium cannot automate a real iOS device unless the Mac can detect the device, the device trusts the Mac, the device is unlocked during session startup, and Appium can target it using the correct UDID.
Step 1: Connect the iOS Device to the Mac
Connect the iPhone or iPad to the Mac using a USB cable. You can use wireless debugging later, but for the first setup, use a cable because it is easier to debug connection and trust issues.
When the device shows a Trust This Computer? prompt, tap Trust and enter the device passcode.
Step 2: Check the Device in Xcode
Open Xcode and go to:
Window > Devices and Simulators
Select the connected iPhone or iPad from the device list.
Keep the device unlocked while Xcode detects it and while Appium starts the first session. If Xcode does not show the device, Appium will not be able to use it either. In that case, reconnect the device, unlock it, trust the Mac again, and check whether your Xcode version supports the iOS version on the device.
Step 3: Enable Developer Mode for iOS 16 and Later
For iOS 16 and later, enable Developer Mode before running development-signed apps or WebDriverAgent on the device.
On the iPhone, go to:
Settings > Privacy & Security > Developer Mode
Turn on Developer Mode, restart the device when prompted, and confirm the setting after restart.
Step 4: Get the Device UDID
Appium needs the device UDID to target the correct real device.
You can get it from Xcode:
Xcode > Window > Devices and Simulators > Select device > Identifier
Copy the identifier and keep it ready. You will use it later in the Appium capability:
appium:udid
Step 5: Check the iOS Version
In the same Xcode device window, note the iOS version of the connected device. You can also check it on the device:
Settings > General > About > iOS Version
Keep this value ready for the capability:
appium:platformVersion
Step 6: Find the App Bundle ID
If the app is already installed on the device, you need its bundle ID. For example:
com.example.MyApp
Use the app’s actual bundle ID, not the app name shown on the home screen.
For an installed app, you will usually use:
appium:bundleId
For an app file, you can use:
appium:app
Appium needs one of these values to know which app to launch. For iOS sessions, that is usually appium:bundleId for an installed app or appium:app for an app file.
Step 7: Confirm the App Can Launch Manually
Before running the Appium test, open the app manually on the device.
This catches simple problems early, such as:
- the app is not installed
- the app build is not trusted
- Developer Mode is disabled
- the app crashes on launch
- the wrong build is installed
Do not skip this step. If the app cannot launch manually, Appium will not fix that problem.
Step 8: Confirm You Have a Valid Signing Setup
A valid Apple development team and signing setup are needed to sign WebDriverAgent for the real device. In many teams, this comes from an Apple Developer Program account, but the key requirement is that WebDriverAgent can be signed and installed on the connected device.
Keep these signing values ready for the next section:
xcodeOrgId xcodeSigningId updatedWDABundleId
At this point, the real iOS device is ready for Appium. The next step is to configure Xcode signing and WebDriverAgent, which is usually where most real-device iOS setup issues happen.
Configuring Xcode Signing and WebDriverAgent
After the device is ready, configure Xcode signing for WebDriverAgent. This is the part that blocks many iOS real device tests. Appium uses WebDriverAgent as a helper app on the device, and that helper app must be signed before iOS will allow it to run. The XCUITest driver documentation confirms that real-device automation works through WebDriverAgent-Runner, and code signing is usually the main setup challenge.
Step 1: Add Your Apple Account in Xcode
Open Xcode and go to:
Xcode > Settings > Accounts
Add the Apple ID or developer account used by your team. Then select the correct team. You need this team later as the value for appium:xcodeOrgId.
For a paid Apple Developer account, the Team ID is a 10-character value assigned by Apple. The Appium XCUITest docs map this Team ID to appium:xcodeOrgId or DEVELOPMENT_TEAM.
Step 2: Check Available Signing Identities
Run this command and use the signing identity available on your Mac:
security find-identity -v -p codesigning
In many current Xcode setups, the certificate appears as:
Apple Development: Name (TEAMID)
Appium examples may use Apple Development, Apple Developer, or iPhone Developer. Use the signing identity name that matches your installed certificate. Do not paste the full security find-identity output line with the hash.
Output:
Step 3: Choose a Unique WebDriverAgent Bundle ID
WebDriverAgent needs its own bundle ID when it is built and installed on the device. Do not keep the default bundle ID if it causes signing issues.
Use a unique value such as:
com.yourcompany.WebDriverAgentRunner
You will pass this value later as:
appium:updatedWDABundleId
Appium replaces the default WebDriverAgent bundle ID with the value passed through appium:updatedWDABundleId. The docs also note that xcodebuild adds the .xctrunner suffix for XCTest packages, so the provisioning profile must support that resulting bundle ID or a wildcard pattern.
Output:
Step 4: Use Automatic WebDriverAgent Signing
For most teams, the simplest setup is to let Appium pass signing details to Xcode through capabilities.
Use these values later in your Appium capabilities:
{
"appium:xcodeOrgId": "YOUR_TEAM_ID",
"appium:xcodeSigningId": "Apple Development",
"appium:updatedWDABundleId": "com.yourcompany.WebDriverAgentRunner"
}Use your actual Team ID and WebDriverAgent bundle ID. Replace Apple Development with the signing identity available on your Mac if your local certificate uses a different name.
Do not use both appium:xcodeConfigFile and the direct signing capabilities together. Appium’s XCUITest docs say these are mutually exclusive strategies. Use either the direct capability approach or the .xcconfig file approach.
Step 5: Use an .xcconfig File When Signing Values Should Stay Outside Test Code
If you do not want signing values inside the test code, create an .xcconfig file.
Example:
DEVELOPMENT_TEAM = YOUR_TEAM_ID CODE_SIGN_IDENTITY = Apple Development
Replace Apple Development with the signing identity name used by your local certificate. Do not paste the full command output with the hash.
Then pass the file path in your capabilities:
{
"appium:xcodeConfigFile": "/path/to/wda-signing.xcconfig"
}Use this approach when different developers or CI machines use different signing values. It keeps signing configuration separate from the test script.
Step 6: Allow Provisioning Updates When Needed
If Xcode cannot create or update the provisioning profile for the connected device, use:
{
"appium:allowProvisioningDeviceRegistration": true
}This lets the XCUITest driver pass provisioning update flags to xcodebuild, which can help register the target device to the matching provisioning profile.
Use this only when your team allows Xcode to manage provisioning. In locked-down enterprise setups, the provisioning profile may need to be created and distributed manually.
Step 7: Confirm WebDriverAgent Can Build and Launch
Start the Appium session after the signing values are configured. During session startup, Appium will try to build and install WebDriverAgent on the device.
If WebDriverAgent signing is wrong, the Appium logs often show an xcodebuild exited with code 65 error. Appium’s real-device setup docs call out this error as a common sign that code signing is not configured correctly.
Output:
A successful setup means:
- WebDriverAgent builds without signing errors
- WebDriverAgent installs on the connected iPhone
- iOS allows WebDriverAgent to launch
- Appium creates the session
- the app under test opens on the device
If this step fails, do not change the test script first. Check signing, team ID, bundle ID, provisioning profile, device trust, and Developer Mode. Most failures at this stage happen before your test code reaches the app.
Setting Appium Capabilities for a Real iOS Device
After WebDriverAgent signing is ready, configure the Appium capabilities for the real iOS session. Capabilities tell Appium four important things: which driver to use, which device to target, which app to open, and how WebDriverAgent should be signed.
For a current 2026 setup, use Appium 3 with the latest XCUITest driver. If your project still uses Appium 2, make sure the XCUITest driver version is compatible with Appium 2 before updating it. The XCUITest driver documentation states that driver version 10.0.0 and later requires Appium 3.
Appium-specific capabilities should use the appium: prefix. platformName is a standard WebDriver capability, but values such as automationName, udid, bundleId, app, xcodeOrgId, and xcodeSigningId are Appium-specific, so they should be written with the appium: prefix. Appium’s capability documentation also explains that capabilities are fixed when the session starts and cannot be changed during the session.
Start with the device and driver details
These capabilities identify the platform, the iOS driver, and the real device Appium should use.
{
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:deviceName": "iPhone 15",
"appium:platformVersion": "17.5",
"appium:udid": "YOUR_DEVICE_UDID"
}For real iOS devices, appium:udid is important because it tells Appium exactly which iPhone or iPad to target. This is especially useful when more than one device or simulator is connected.
Choose how Appium should launch the app
Next, tell Appium which app to open. Use one of these approaches for the first test.
For an app that is already installed on the iPhone, use the bundle ID:
{
"appium:bundleId": "com.example.MyApp"
}For an .ipa file that Appium should install before starting the test, use the app path:
{
"appium:app": "/Users/qa/apps/MyApp.ipa"
}Use appium:bundleId when the app is already installed. Use appium:app when Appium should install the app file. The XCUITest driver capability docs list appium:app and appium:bundleId as supported app launch options.
Do not use appium:app and browserName together. If you are testing Safari, use browserName. If you are testing a native app, use appium:app or appium:bundleId.
Add WebDriverAgent signing capabilities
For real iOS devices, Appium also needs signing details for WebDriverAgent. Without these values, the session may fail before the app opens.
{
"appium:xcodeOrgId": "YOUR_TEAM_ID",
"appium:xcodeSigningId": "Apple Development",
"appium:updatedWDABundleId": "com.yourcompany.WebDriverAgentRunner"
}appium:xcodeOrgId is your Apple development team ID. appium:xcodeSigningId is the signing identity available on your Mac. appium:updatedWDABundleId gives WebDriverAgent a bundle ID that can be signed for your device.
Use the signing identity type that matches your installed certificate. Common values include Apple Development, Apple Developer, or iPhone Developer.
You can confirm the available signing identities with:
security find-identity -v -p codesigning
Do not paste the full command output with the hash. Use only the signing identity name in appium:xcodeSigningId.
Set reset behavior carefully
Reset capabilities control what happens to app data between sessions. Do not set them casually because they affect login state, test data, app permissions, and execution time.
For a first test, start simple:
{
"appium:noReset": false,
"appium:fullReset": false
}Use appium:noReset: true when you want to keep app data between sessions, such as a logged-in user state.
Use appium:fullReset: true only when the test needs a complete uninstall before the session. It can slow down execution and add extra install-related failures on real devices.
Add debug capabilities only when needed
Debug capabilities are useful when a session fails, but they can make logs noisy. Add them only while investigating failures.
{
"appium:showXcodeLog": true,
"appium:printPageSourceOnFindFailure": true,
"appium:wdaLaunchTimeout": 120000
}Use appium:showXcodeLog when WebDriverAgent build or launch fails. Use appium:printPageSourceOnFindFailure when element lookup fails and you need to see the page source. Use appium:wdaLaunchTimeout when WebDriverAgent needs more time to start on a real device. The XCUITest capability docs list WebDriverAgent timeout and logging capabilities for these cases.
Complete capability example for an installed app
If the app is already installed on the real iPhone, use this capability set:
{
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:deviceName": "iPhone 15",
"appium:platformVersion": "17.5",
"appium:udid": "YOUR_DEVICE_UDID",
"appium:bundleId": "com.example.MyApp",
"appium:xcodeOrgId": "YOUR_TEAM_ID",
"appium:xcodeSigningId": "Apple Development",
"appium:updatedWDABundleId": "com.yourcompany.WebDriverAgentRunner",
"appium:noReset": false
}Use the same signing identity confirmed earlier in the WebDriverAgent signing section.
Complete capability example for an .ipa file
If Appium should install an .ipa file before running the test, use appium:app instead of appium:bundleId:
{
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:deviceName": "iPhone 15",
"appium:platformVersion": "17.5",
"appium:udid": "YOUR_DEVICE_UDID",
"appium:app": "/Users/qa/apps/MyApp.ipa",
"appium:xcodeOrgId": "YOUR_TEAM_ID",
"appium:xcodeSigningId": "Apple Development",
"appium:updatedWDABundleId": "com.yourcompany.WebDriverAgentRunner",
"appium:noReset": false
}Keep these values outside the test class when possible. Store the device name, iOS version, UDID, app path, bundle ID, Team ID, signing identity, and WebDriverAgent bundle ID in environment variables, config files, or CI secrets. This makes the same test easier to run across different devices, app builds, and machines.
Creating the Appium Test Project
After the capabilities are ready, create a Maven test project. This gives the test a proper structure and avoids putting Appium code inside a main() method. For this guide, the project uses Java, Appium Java Client, and JUnit Jupiter.
As of 2026, Appium Java Client 10.1.1 is available on Maven Central, and JUnit Jupiter 6.1.0 is also available. This example uses Java 17 because it is a practical baseline for current Java test projects.
Step 1: Create a Maven Project
Create a Maven project with this structure:
appium-ios-real-device-test ├── pom.xml └── src └── test └── java └── tests └── IOSRealDeviceTest.java
Keep the Appium test class under src/test/java because this is test code, not application code.
Step 2: Add the Appium and JUnit Dependencies
Add Appium Java Client and JUnit Jupiter to the pom.xml file.
<dependencies> <dependency> <groupId>io.appium</groupId> <artifactId>java-client</artifactId> <version>10.1.1</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>6.1.0</version> <scope>test</scope> </dependency> </dependencies>
The Appium Java Client gives you classes such as IOSDriver, XCUITestOptions, and Appium locator support. JUnit Jupiter gives you the test annotations and test runner support needed for a clean test class.
Step 3: Add Maven Plugins
Add the Maven Compiler Plugin and Maven Surefire Plugin. Use the stable compiler plugin line, not the Maven 4 beta line.
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.15.0</version> <configuration> <release>17</release> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.5</version> </plugin> </plugins> </build>
The Compiler Plugin compiles the Java code, and Surefire runs tests during the Maven test phase. Surefire 3.5.5 is listed as the current version in the Maven Surefire docs.
Step 4: Use the Complete pom.xml
Here is the full pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.appium</groupId>
<artifactId>appium-ios-real-device-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>10.1.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.15.0</version>
<configuration>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.5</version>
</plugin>
</plugins>
</build>
</project>Step 5: Verify the Maven Project
Run this command from the project root:
mvn test
At this point, Maven may say that no tests were found. That is fine because the test class has not been added yet. The important part is that Maven downloads the dependencies and finishes without dependency or compilation errors.
Output:
Next, you can add the actual IOSRealDeviceTest.java file using IOSDriver, XCUITestOptions, and AppiumBy.accessibilityId. Use AppiumBy instead of older shortcuts such as findElementByAccessibilityId, because the Appium Java Client migration guide points users toward the newer locator API.
Writing the Appium iOS Test Script
Now add the first test class. This example uses IOSDriver, XCUITestOptions, and AppiumBy.accessibilityId(). The Appium Java Client supports IOSDriver for XCUITest, and its own usage example shows XCUITestOptions, IOSDriver, and AppiumBy.accessibilityId() together.
This test assumes the app is already installed on the real iPhone and launches it using the bundle ID. It then waits for a login button, taps it, checks that the login screen appears, and quits the driver after the test.
Create the test file
Create this file in the Maven project:
src/test/java/tests/IOSRealDeviceTest.java
Add the test code
package tests;
import io.appium.java_client.AppiumBy;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.net.MalformedURLException;
import java.net.URI;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class IOSRealDeviceTest {
private IOSDriver driver;
private WebDriverWait wait;
@BeforeEach
void setUp() throws MalformedURLException {
XCUITestOptions options = new XCUITestOptions()
.setDeviceName(requiredEnv("IOS_DEVICE_NAME"))
.setPlatformVersion(requiredEnv("IOS_PLATFORM_VERSION"))
.setUdid(requiredEnv("IOS_UDID"))
.setBundleId(requiredEnv("IOS_BUNDLE_ID"))
.setNoReset(false);
options.setCapability("appium:xcodeOrgId", requiredEnv("XCODE_ORG_ID"));
options.setCapability("appium:xcodeSigningId", envOrDefault("XCODE_SIGNING_ID", "Apple Development"));
options.setCapability("appium:updatedWDABundleId", requiredEnv("WDA_BUNDLE_ID"));
options.setCapability("appium:wdaLaunchTimeout", 120000);
driver = new IOSDriver(
URI.create("http://127.0.0.1:4723").toURL(),
options
);
wait = new WebDriverWait(driver, Duration.ofSeconds(15));
}
@Test
void shouldOpenLoginScreen() {
WebElement loginButton = wait.until(
ExpectedConditions.elementToBeClickable(AppiumBy.accessibilityId("Login"))
);
loginButton.click();
WebElement loginScreenTitle = wait.until(
ExpectedConditions.visibilityOfElementLocated(AppiumBy.accessibilityId("Login Screen"))
);
assertTrue(loginScreenTitle.isDisplayed(), "Login screen should be displayed");
}
@AfterEach
void tearDown() {
if (driver != null) {
driver.quit();
}
}
private static String requiredEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalStateException("Missing required environment variable: " + name);
}
return value;
}
private static String envOrDefault(String name, String defaultValue) {
String value = System.getenv(name);
return value == null || value.isBlank() ? defaultValue : value;
}
}What does this code do?
The setUp() method creates the iOS session before each test. It reads the device name, iOS version, UDID, bundle ID, Xcode Team ID, signing identity, and WebDriverAgent bundle ID from environment variables. This keeps machine-specific values out of the test class.
The test method waits for the login button before clicking it. This is better than using a fixed sleep because the test waits for the actual UI condition.
The tearDown() method quits the driver after the test so the Appium session does not remain open.
Replace these values before running
The locator values in the example are placeholders:
AppiumBy.accessibilityId("Login")
AppiumBy.accessibilityId("Login Screen")Replace them with accessibility IDs from your app.
The signing identity also needs to match your Mac. The code uses this default:
envOrDefault("XCODE_SIGNING_ID", "Apple Development")If your local signing identity uses another name, set XCODE_SIGNING_ID to the exact value returned by:
security find-identity -v -p codesigning
Use an .ipa file instead of an installed app
The example uses setBundleId() because it assumes the app is already installed on the iPhone.
.setBundleId(requiredEnv("IOS_BUNDLE_ID"))If Appium should install an .ipa file before running the test, replace that line with:
.setApp(requiredEnv("IOS_APP_PATH"))Use setBundleId() for an installed app and setApp() for an app file. The capability section above explains when to use each option.
Set the environment variables
The code reads device, app, signing, and WebDriverAgent values from environment variables. These values are set in the next section before running the test.
Keeping these values outside the code makes the same test easier to run across different devices, app versions, local machines, and CI agents.
Running the Appium iOS Test on a Real Device
Once the test project is ready, run the test against the connected iPhone or iPad. Keep the Appium server running in one terminal, the real device connected and unlocked, and the Maven test command in a separate terminal. This makes it easier to check whether a failure comes from Appium, Xcode, WebDriverAgent, or the test code.
Step 1: Connect and Unlock the iOS Device
Connect the iPhone or iPad to the Mac and keep it unlocked during the first session startup.
Then confirm that Xcode can detect the device:
Xcode > Window > Devices and Simulators
If the device does not appear in Xcode, fix that before running the Appium test. Appium will not be able to use a real iOS device that Xcode cannot detect.
Step 2: Start the Appium Server
Open a terminal and start Appium:
appium
Use the same Appium server URL configured earlier:
http://127.0.0.1:4723
Do not use /wd/hub unless you started Appium with that base path. Appium 2 changed the default base path from /wd/hub to /.
Step 3: Set the Environment Variables
Set the values used in the test class:
export IOS_DEVICE_NAME="iPhone 15" export IOS_PLATFORM_VERSION="17.5" export IOS_UDID="your-device-udid" export IOS_BUNDLE_ID="com.example.MyApp" export XCODE_ORG_ID="your-apple-team-id" export XCODE_SIGNING_ID="Apple Development" export WDA_BUNDLE_ID="com.yourcompany.WebDriverAgentRunner"
If you are using an .ipa file instead of an installed app, set the app path:
export IOS_APP_PATH="/Users/qa/apps/MyApp.ipa"
Use IOS_BUNDLE_ID for an installed app. Use IOS_APP_PATH only when Appium should install an .ipa file before running the test.
Step 4: Run the Maven Test
From the project root, run:
mvn test -Dtest=IOSRealDeviceTest
During the first run, Appium may take longer because it needs to build, sign, install, and launch WebDriverAgent on the real device. WebDriverAgent is included with the XCUITest driver and is used by Appium to communicate with the iOS device.
Output:
Step 5: Check What Happens on the Device
If the setup is correct, you should see this flow:
- Appium receives the session request.
- The XCUITest driver starts the iOS session.
- WebDriverAgent builds and launches on the iPhone.
- The app under test opens.
- The test finds the target element.
- The test performs the action and assertion.
- The driver quits the session.
The first successful run is important because it proves that device trust, signing, WebDriverAgent, capabilities, and the test script are working together.
Step 6: Read the Appium Logs if the Test Fails
If the test fails before the app opens, check the Appium server logs first. The most useful lines are usually near:
Creating new XCUITestDriver session
Building WebDriverAgent
xcodebuild
WebDriverAgentRunner
Original error
If the logs show xcodebuild exited with code 65, the issue is usually related to Xcode signing, provisioning, WebDriverAgent bundle ID, or the connected device. Do not start changing test locators in that case. The test has not reached the app yet.
If the app opens but the test fails on element lookup, then check the locator, wait condition, app screen state, and accessibility ID.
Step 7: Confirm the Test Result
A successful Maven run should end with a test summary similar to this:
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS
If you see BUILD SUCCESS, the Appium iOS real device setup is working. From here, you can add more test cases, move device values into a config file, and improve the test structure with reusable setup and page objects.
Output:
Inspecting iOS Elements for Appium Tests
Once the test can launch the app, inspect the UI and choose stable locators before adding more actions to the script. Appium Inspector helps you see how Appium reads the current screen, including the element hierarchy, attributes, accessibility values, and supported locator strategies.
Using Appium Inspector
Start the Appium server and open Appium Inspector with the same capabilities used in your test. If the session starts successfully, Inspector will show the current app screen and the UI hierarchy.
Use this view to select an element and check values such as:
type name label value enabled visible accessible
For iOS tests, the most useful values are usually name, label, and accessibility-related attributes.
Choosing the Right iOS Locator
Use locators in this order:
| Priority | Locator strategy | Best use case |
|---|---|---|
| 1 | Accessibility ID | Best default when the app has stable accessibility identifiers |
| 2 | iOS Predicate String | Good for matching attributes such as name, label, value, or visible |
| 3 | iOS Class Chain | Good for native iOS hierarchy queries |
| 4 | Class Name | Useful for broad element groups |
| 5 | XPath | Use only when there is no better option |
Accessibility ID should be the first choice because it is readable, stable, and less tied to screen layout. Predicate strings and class chains are useful when accessibility IDs are missing. XPath should be the fallback because it is often slower and more fragile in mobile UI tests.
Locator Examples in Java
Accessibility ID:
driver.findElement(AppiumBy.accessibilityId("login_button"));iOS predicate string:
driver.findElement(
AppiumBy.iOSNsPredicateString("name == 'Login' AND visible == 1")
);iOS class chain:
driver.findElement(
AppiumBy.iOSClassChain("**/XCUIElementTypeButton[`name == 'Login'`]")
);Avoid locators that depend only on position:
driver.findElement(AppiumBy.xpath("//XCUIElementTypeButton[3]"));This can break when another button is added above it. A better locator describes what the element is, not where it appears.
Asking for Stable Accessibility IDs
If the app does not expose stable identifiers, work with developers to add them. Good accessibility IDs should be unique, stable across builds, and tied to the element’s purpose.
Examples:
login_button email_input password_input submit_login_button profile_settings_button
This makes Appium tests easier to read and easier to maintain when the UI changes.
Common Appium iOS Real Device Errors and Fixes
Appium iOS failures usually fall into two groups. The first group happens before the app opens, which usually points to device, signing, WebDriverAgent, or capability issues. The second group happens after the app opens, which usually points to locators, waits, app state, or test logic.
Use this table to identify where the failure is coming from before changing the test script.
| Error or symptom | Likely cause | Fix |
|---|---|---|
| Session fails with /wd/hub or 404 error | The test is using the old Appium 1 server path. In Appium 2 and later, the default server path is /, not /wd/hub. | Use http://127.0.0.1:4723 in the test code. Use /wd/hub only if the server is started with appium –base-path=/wd/hub. Appium 2 changed the default base path from /wd/hub to /. |
| automationName cannot be blank | The XCUITest driver is not being selected, or the capability is missing. | Add appium:automationName with the value XCUITest. The XCUITest capability docs state that appium:automationName must be set to xcuitest. |
| Capabilities are not accepted | Appium-specific capabilities are missing the appium: prefix. | Use appium:deviceName, appium:platformVersion, appium:udid, appium:bundleId, and other Appium-specific names. Appium 2 follows W3C capability rules, where non-standard capabilities need a vendor prefix. |
| Device is not detected | The device is locked, not trusted, disconnected, or not visible in Xcode. | Unlock the device, reconnect it, trust the Mac, and confirm it appears in Xcode > Window > Devices and Simulators. Use the exact UDID shown by Xcode. |
| xcodebuild exited with code 65 | WebDriverAgent could not build, usually because code signing is not configured correctly. | Check appium:xcodeOrgId, appium:xcodeSigningId, appium:updatedWDABundleId, provisioning profile, and Xcode team selection. Appium’s real-device setup docs call this error a common sign of code signing problems. |
| WebDriverAgent installs but does not launch | The developer profile is not trusted on the device, or the app has invalid signing or entitlements. | On the iPhone, trust the developer profile from device settings. Appium’s real-device setup docs mention this case when WebDriverAgentRunner installs but fails to launch due to invalid signature, entitlements, or an untrusted profile. |
| Appium keeps rebuilding WebDriverAgent | WebDriverAgent cannot be reused, or the previous WDA session is not available. | Avoid forcing new WDA builds unless needed. Reusing WDA can reduce startup time and avoid repeated signing work. Use WDA rebuild-related capabilities only when debugging WDA issues. |
| App does not launch | Wrong bundle ID, wrong app path, app not installed, app not trusted, or app crashes on launch. | For an installed app, verify appium:bundleId. For an app file, verify appium:app. Also open the app manually once on the device before running the test. |
| Session starts from the Home screen | Appium was not given an app or browser target. | Provide appium:bundleId for an installed app, appium:app for an app file, or browserName for Safari. The XCUITest driver expects these values to know what to launch. |
| Element not found | Locator is wrong, screen is not ready, or the app is on a different state than expected. | Check the element in Appium Inspector, prefer accessibility ID, add an explicit wait, and confirm the app is on the expected screen before locating the element. |
| XPath locator is slow or flaky | XPath depends heavily on UI hierarchy and can break when the screen structure changes. | Prefer accessibility ID first, then iOS predicate string or iOS class chain. Use XPath only when there is no stable alternative. |
| Old app data affects the test | The app state is being preserved between runs. | Review appium:noReset and appium:fullReset. noReset keeps app data, while fullReset forces uninstall before a new session. The XCUITest capability docs define both reset behaviors. |
How to Debug Appium iOS Failures Faster
When a test fails, first check whether the app opened.
If the app did not open, look at:
UDID device trust Developer Mode Xcode signing WebDriverAgent build bundle ID Appium server URL capability prefix
If the app opened but the test failed, look at:
locator wait condition screen state test data app permissions language or region setting
This split saves time. A WebDriverAgent signing failure will not be fixed by changing a locator. An element lookup failure will not be fixed by changing the Xcode team ID. First identify whether the failure happened before or after the app launch, then debug the right layer.
Real Device vs Simulator for Appium iOS Testing
Simulators are useful, but they should not be treated as a full replacement for real iPhones or iPads. Apple’s own Xcode guidance separates Simulator testing from hardware device testing, and notes that real devices are needed to confirm interaction and performance on actual hardware.
Use simulators for early feedback. Use real devices when the test needs production-like behavior.
| Area | iOS Simulator | Real iOS Device |
|---|---|---|
| Setup speed | Faster to create, reset, and run | Slower because it needs device trust, signing, and WebDriverAgent setup |
| Best use | Early UI checks, basic navigation, simple regression checks | Final validation, hardware behavior, device-specific issues, release confidence |
| Hardware behavior | Simulated, so it cannot fully match real device hardware | Uses actual camera, GPS, sensors, CPU, memory, battery, and screen behavior |
| Performance testing | Not reliable for final performance calls because it runs on the Mac | Better for checking real CPU, memory, launch time, animation smoothness, and battery impact |
| Touch and gestures | Useful for basic taps and swipes | Better for real gestures, multi-touch behavior, scrolling, and device handling |
| Push notifications | Useful for some notification testing, but not enough for complete production-like validation | Better for validating real notification delivery, permissions, device tokens, and user behavior |
| Camera, GPS, biometrics, sensors | Limited or simulated | Required when the app depends on real hardware behavior |
| App install and update flow | Useful for basic checks | Better for checking real install, upgrade, trust, permissions, and launch behavior |
| Debugging speed | Faster for quick development checks | Better for finding issues that only happen on real devices |
Read More: iOS Simulator or Real Device for Appium?
When to Use a Simulator
Use a simulator when you want quick feedback and the test does not depend on real hardware. It is a good choice for:
- checking basic screen navigation
- validating simple UI flows
- running quick smoke tests during development
- testing layout across different iPhone screen sizes
- debugging locator logic before moving to a real device
Apple also positions Simulator as useful when a physical device is not available and for testing across Apple device and OS combinations.
When to Use a Real iOS Device
Use a real device when the test result must reflect how the app behaves for users. This is important for:
- push notification flows
- camera or media upload flows
- GPS and location-based flows
- biometric authentication
- deep links and app switching
- permission prompts
- background and foreground behavior
- performance-sensitive screens
- install, update, and launch behavior
- release candidate validation
For Appium specifically, real device testing also validates the parts that simulators do not fully cover: device trust, WebDriverAgent signing, provisioning profiles, UDID targeting, and real iOS device state.
Conclusion
Running Appium iOS tests on a real device takes more setup than simulator testing, but the extra setup is necessary when the test must reflect real user conditions. The main pieces are Appium, the XCUITest driver, a trusted iPhone or iPad, correct Xcode signing, WebDriverAgent, and the right capabilities.
Once the first test runs successfully, focus on keeping the suite stable. Use accessibility IDs where possible, avoid hardcoded device values, manage app state clearly, reuse WebDriverAgent when practical, and check logs before changing test code. Many iOS failures happen before the app opens, so signing, provisioning, UDID, and WebDriverAgent setup should always be checked first.
For reliable coverage, use simulators for quick feedback and real devices for critical flows, hardware-dependent features, permissions, performance-sensitive screens, and release validation.

























