Automating Real iOS Devices with Appium

What a Tester Needs to Know to Automate Real iOS Devices

Lana Begunova
15 min readSep 19, 2024

Background and Setup

In this section, we’ll cover the necessary background for automating real iOS devices. Real iOS device testing can only take place on a Mac machine.

Meme sourced from https://youtu.be/xgCOIMEECuc?si=Dp91fDpAUaJE-CA8&t=3

iOS App Security

  • Only authorized apps are allowed to run on iOS devices. Due to the complexities of its security model, Apple only allows authorized apps to run on Apple devices. For most regular users, this means every app on their device must be downloaded from the Apple App Store.
  • App verification is performed by checking digital signatures. There is no third party app ecosystem for iOS. Apple enforces this rule by forcing all iOS devices to check every app before they’re launched to make sure the app has been cryptographically signed by a certificate held by the App Store itself.
  • Developers can create, sign, and test their own applications. Apps need to be developed and tested before they’re submitted to the App Store, approved and signed. So Apple allows developers to build, sign and test their own apps.
  • There are strict rules and guidelines for testing apps during development. To maintain overall security Apple tightly controls the process by which developers can install and run even their own apps.

Apple-Specific Prerequisites

There are several Apple-specific concepts that we need to understand before we can even start thinking about automation. So let’s have a look at some of these Apple-specific concepts.

An app in .ipa format, already signed with a certificate we have access to.

The first thing we need is the app itself. The app should be archived in an .ipa format and signed with a certificate that we have also downloaded into our own Xcode organizer.

  • Apple requires all teams that want to build and test apps to have an Apple Developer account. Each developer account is assigned a unique Team ID. Each Apple developer can create one or more signing certificates. These are used by developers to sign the apps they build. Which allow the apps to be run on their phones, but only in certain approved circumstances. Apple ensures that every app installed on a device has a unique App ID to ensure that app can be validated as being allowed to be installed on a given device.

Device registered in Apple developers portal.

Secondly, we need to make sure our device ID is registered in the Apple Developer Portal.

  • In order to run tests on a given device, Apple requires that the device itself be registered with the developer account via its unique device ID.

Provisioning profile downloaded.

Next, we need a provisioning profile downloaded from the Apple Developer Portal which mentions our app’s ID, our certificate and our device. This can be given to us or created using the Apple Developer Portal itself.

  • Apple developers can create documents called provisioning profiles, which link all of the above concepts together, a provisioning profile references a certificate, an App ID and one or more registered devices. The provisioning profile is what allows a given app to be run on a given device. Without a provisioning profile that mentions the correct certificate, App ID, and device, the device will refuse to run an app, even our own app. Ideally, if we’re just writing test code for an app that we’ve been given, we won’t need to worry about building and signing our app for testing on a real device, since Apple apps building and signing happens all within the Xcode IDE, which is Apple’s tool for developing iOS applications.

Now that we’ve discussed what it is we need before we start testing our iOS app on a real device, let’s proceed to setting up our workspace.

Workspace Ergonomics

There are a few things we need to address on our Mac, before setting up the device. Following the Appium setup guide, install the latest versions of Xcode, Homebrew, NodeJS, and Appium.

Next, we need some packages/dependencies installed on the machine.

  • libimobiledevice: an open-source package able to communicate with iOS devices. The official Apple apps made for communicating with iOS devices (eg., Xcode, Finder), aren’t built for easy programmatic use. This is why Appium uses libimobiledevice for some operations. Run the following commands in the terminal to get it installed:
brew install libimobiledevice
  • ios-deploy: Appium uses the ios-deploy package to transfer iOS apps onto the device. Install it with the below command:
brew install ios-deploy
  • carthage: Appium uses the carthage dependency manager as part of the XCUITest driver, which is written as a Cocoa app so that it can run on iOS. WebDriverAgent (WDA) itself requires this package. Since Appium automatically builds the WDA app, we need to make carthage available to the WDA bootstrap process. It can be installed as follows:
brew install carthage

It’s time to shift attention from the Mac to the actual mobile iOS device.

iOS Device Setup

Let’s move onto installing and running the app on the device. There are a couple things we need to do on the phone itself to make sure the automation proceeds smoothly.

Connected to Xcode & computer trusted.

When we plug our phone into computer, we need to make sure to trust the computer by accepting the dialogue on our phone.

Connect the device & trust the computer.

To verify it worked we can open up Xcode and view our phone in the devices window. This window is also where we can find our device ID (UDID) if we don’t know it yet.

Extract UDID: Xcode > Window > Devices and Simulators > Devices > Identifier

Developer Mode is on.

The next step is turning on the developer mode in the device Settings. After we toggle the mode on, we need to restart the phone.

Settings > Privacy & Security > Developer Mode

UI automation enabled.

We also want to navigate to the phone’s settings then to the developer menu and check that the switch for enable UI automation is on.

Settings > Developer > Enable UI Automation

Screen lock/sleep disabled.

Finally, let’s disable screen locking and sleeping, so that our phone doesn’t turn itself off in the middle of a test. If the device is locked during test execution, things can go wrong and would require unplugging the device and plugging it back.

Disable Lock: Settings > Display & Brightness > Auto-Lock > Never

Once we’ve done all that, our phone should be good to receive and run our app but there’s still a WDA problem that needs addressing.

The WebDriverAgent Ordeal

This problem comes in the form an app called WebDriverAgent, which we briefly mentioned in the Carthage installation part.

Appium uses WDA to facilitate automation

WDA is part of Appium’s iOS driver and it’s the bit of Appium that runs on the device itself. To allow Appium to control the device via automation commands.

WDA is an iOS app and subject to all app restrictions

WDA is itself an iOS app. This means that it must also be appropriately signed and have an App ID which is mentioned in a provisioning profile before it can be run on our real device.

WDA is open-source, not yet signed with our certificate, and has a foreign App ID.

Unfortunately, WDA comes bundled with Appium and doesn’t know anything by default about our developer team. It’s not signed with our certificate. It also by default has an App ID (com.facebook.webdriveragent) which is not in our team’s list of registered IDs. This presents a challenge, because even though Appium can install our app just fine, it can’t install WDA on our phone, so what do we do?

Configure Appium and WDA

Luckily, Appium can be configured to solve these problems automatically for us with a little bit of extra information.

Appium re-signs WDA

Firstly, Appium can get over the signing issue by re-signing WDA with our own signing certificate. To do that it, of course, needs to know our team ID and the name of the signing certificate which we’ve downloaded through Xcode.

Appium updates WDA’s App ID to match our provisioning profile

Secondly, Appium can get around the App ID problem by changing WDA’s App ID before building it to something which is supported by our organization. This is why it’s extremely useful for our developer team to have registered something called a wildcard App ID.

Provisioning Profile: developer.apple.com/account/profiles

Use wildcard App IDs (com.our.company.*)

This is an ID that starts with our company’s group identifier, and then ends with an asterisk. If this App ID is included in a provisioning profile, then any app which matches that ID will be allowed to run. So it could be com.companyname.app or even com.companyname.wda.

WDA — Build and Deploy to Device

Before we script our first Appium test, it’s advisable to verify whether WDA and AUT are actually installable on the real device. Let’s open the terminal and run the find command to locate the WDA Xcode project.

find . -name "appium-webdriveragent"
Extract the WDA path: ./.appium/node_modules/appium-xcuitest-driver/node_modules/appium-webdriveragent

Upon obtaining the project path, open it with the following command:

open ./.appium/node_modules/appium-xcuitest-driver/node_modules/appium-webdriveragent

Observe the directory window pop up and open the Xcode project named WebDriverAgent.xcodeproj.

In the appium-webdriver directory, open WebDriverAgent.xcodeproj.

Once the project opens, we need to deploy the WDA app on our real device. First, we select the app from the available Xcode options.

Select app WebDriverAgentRunner or WebDriverAgentLib. In this demo, we go for the WebDriverAgentRunner.

Then, we select the device we intend to deploy to.

Select the physical target device.

Now, we need to verify that we are signed in with a developer account. For that, navigate to the Xcode Settings menu.

Xcode > Settings

If we have an existing account, it’d show under the Accounts tab of the Settings menu.

Xcode > Settings > Accounts displays an existing Apple dev account.

It’s not mandatory to have a paid Apple developer account for this demo. We can create a free account by going to developer.apple.com, clicking on Account, and either signing in with an existing account (if there’s one) or creating a new one.

Free Apple Developer Account: https://developer.apple.com/

If there are any issues with creating a new account and adding it to Xcode, watch How to get a FREE Apple Developer Program account.

Once we are certain that we’re logged into Xcode with a developer account, we need to find the WDA provisioning profile.

Navigate to WebDriverAgent > WebDriverAgentRunner target > Signing & Capabilities > iOS Signing Certificate

At this stage, we see a status error, “❌ Signing for “WebDriverAgentRunner” requires a development team. Select a development team in the Signing & Capabilities editor.

To resolve this, we should make sure that the Automatically manage signing option is always checked on.

Automatically manage signing should be turned on.

Then, given that we have a developer account, we should go ahead and select that account from the Team drop-down.

Click on Team drop-down menu. Select the desired developer account.

Once we select the dev team, Xcode updates the provisioning profile.

Xcode will display the “↺ Updating provisioning…” message.

We may encounter another status error, “❌ Device “Lana Begunova’s iPhone” isn’t registered in your developer account. The device must be registered in order to be included in a provisioning profile.

With a paid dev account, we may need to address another status error.

To resolve the error, click on Register Device, and Xcode adds the respective Signing Certificate.

Then, we may have to deal with a couple of additional errors such as “❌ Failed Registering Bundle Identifier” or “❌ No profiles for ‘com.facebook.WebDriverAgentRunner’ were found”.

An issue with the Bundle ID may arise. To resolve, change the Bundle ID to a unique string.

com.facebook.WebDriverAgentRunner is a not-so-unique Bundle ID provided by Facebook, which actually developed the WDA. We must supply a bundle ID value that is allowed by an App ID in our provisioning profile. To make it unique to our profile, we should merely edit the default ID to any string of choice. As mentioned before, if our devs registered a wildcard App ID, then we’d use something like com.ourcompany.wda. However, for the demo purposes, any unique string would suffice. I am merely adding -lana-sdet to the default Bundle ID to remedy the provisioning issue.

Make the WDARunner Bundle ID unique to avoid issues with signing certificate.

Now, there are a few ways to deploy the WDA app to the iOS device manually. I recommend verifying that WDA is installable, prior to jumping into automation.

From the Xcode, we can build and then run the current scheme.

A couple of options to deploy the WDA app to an iOS device, without installing the app under test (AUT).

⚠️ ⛔️ When we choose Option 1 and click the ▶ button in Xcode, we may encounter a pop-up window asking for the keychain password. Upon entering the password, make sure to click the Always Allow — not the Allow — button. If we click Allow, another one of these pop-ups appears, and then another. These windows will keep multiplying, unless we hit Always Allow from the get-go.

When triggering a WDA build in Xcode, we might be asked for a keychain access. Unequivocally, always click Always Allow.

If we misstep and end up with a bunch of these messages, do not despair! The way out is to patiently type your password and click Always Allow over and over until we work your way through all windows.

Then, we can proceed to Option 2. Here, we select the WDARunner project, then navigate to Products > Test. This will deploy the WDA app to the device.

Install WebDriverAgent (WDA) app on a Real iPhone via Xcode WebDriverAgentRunner: https://www.youtube.com/shorts/0M2VOacLXeY

As a result, we end up with the Build Succeeded message in Xcode, and the WDA app showing on phone screen. We will also observe the Automation Running watermark on the screen.

We can achieve the same result, by running the following command from the terminal:

xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination 'id=<device_udid>' test

In the ‘<device_udid>’ portion, provide the UDID for the target device.

Moreover, once we are candid about the WDA installation, in Xcode there’s an option for us to verify if an AUT is installable.

Apple provides IntegrationApp.app that we can use for testing the AUT installation process.

If we don’t have an AUT from our developers handy, we can run IntegrationApp.app which is readily available in Xcode for practice.

Use a unique Bundle ID for IntegrationApp.app, instead of the default com.facebook.IntegrationApp, just as we did with the WDARunner. Then, launch the build by clicking the ▶ button in Xcode.

Make sure to adjust the app Bundle ID to a unique string of your choice. I’m merely adding -lana-sdet to the default non-unique value.

Then we are ready to hit the ▶ button in Xcode and get an AUT installed on the iPhone.

Once the build is successful, the app installs on the device and launches on the screen.

Yet another way to install WDA on the iPhone, without a handy AUT, is to launch one of the iOS apps that come pre-installed on the device. It can be Settings, Weather, or even the Safari browser app. For that, we can use Appium Inspector to launch a session with just 4 capabilities — platformName, udid, automationName, and bundleId.

CAPS = {
"platformName": "iOS",
"appium:options": {
"udid": "auto",
"automationName": "XCUITest",
"bundleId": "com.apple.calculator"
}
}

The bundle ID of com.apple.calculator will launch the native calculator app. ⚠️ 𝘕𝘰𝘵𝘦 𝘵𝘩𝘢𝘵 𝘵𝘩𝘦 ​𝘣𝘶𝘯𝘥𝘭𝘦𝘐𝘥​ 𝘤𝘢𝘱𝘢𝘣𝘪𝘭𝘪𝘵𝘺 𝘤𝘢𝘯 𝘰𝘯𝘭𝘺 𝘣𝘦 𝘶𝘴𝘦𝘥 𝘧𝘰𝘳 𝘢𝘱𝘱𝘴 𝘸𝘩𝘪𝘤𝘩 𝘢𝘳𝘦 𝘢𝘭𝘳𝘦𝘢𝘥𝘺 𝘪𝘯𝘴𝘵𝘢𝘭𝘭𝘦𝘥 𝘰𝘯 𝘰𝘶𝘳 𝘪𝘖𝘚 𝘥𝘦𝘷𝘪𝘤𝘦.

Make sure to have the Appium server running and the device connected to the Mac, before launching a session via Inspector. Once we start the session, first, we should see the WDA app getting installed on the device, and then the calculator app opens.

The device screen will display the Automation Running watermark. But the Appium Inspector screenshot won’t show it.

Now, that we’ve verified that both WDA and AUT are installable on the physical device, let’s proceed to the long-awaited automation scripting part.

Appium Automation Test

Test Setup for Real Devices

We’re now ready to look specifically at how to code up a test to run on a real iOS device, once all the previous steps have been taken.

In addition to the 5 required capabilities, we need 4 new capabilities to run tests on a real device with Appium: xcodeOrgId, xcodeSigningId, udid, and updatedWDABundleId.

udid

We want to use the udid capability to tell Appium the ID of the device to run the test on. If we are only testing on a single device, the “auto” value will suffice for detecting it.

CAPS["udid"] = "auto"

xcodeOrgId

Next, we want to tell Appium about our Apple Developer account and signing certificate. The XcodeOrgID capability is where we put our team ID. This is something that our dev team should have handy for us.


CAPS["xcodeOrgId"] = "organizational_unit"

In case we are missing this piece of information, we can extract it from the certificates in the Keychain Access app on our Mac.

Go to Keychain Access > My Certificates > Apple Development > Organizational Unit to retrieve the 10-digit team ID.

xcodeSigningId

Then, we include the name of our signing certificate as the value of the XcodeSigningID capability. Typically, we can always just use the string “iPhone Developer” here. Double-check with the dev team, in case they have some other value here.

CAPS["xcodeSigningId"] = "iPhone Developer"

updatedWDABundleId

Finally, we need to tell Appium what App ID we should use for rebuilding WDA, and to do that we use the updatedWDABundleId capability. Here we set the App ID to something which is appropriate under the wildcard App ID our devs have registered. It’d be something like com.companyname.wda, assuming our wildcard App ID included in the provisioning profile is com.companyname.*.

CAPS["updatedWDABundleId"] = "com.companyname.wda"

The dev team typically supplies this information to the testers. For demo purposes, we can use the unique WDA Bundle ID — in my case it’s “com.facebook.WebDriverAgentRunner-lana-sdet” — which we’ve set up earlier. So my capability will look like this:

CAPS["updatedWDABundleId"] = "com.facebook.WebDriverAgentRunner-lana-sdet"

⚠️ 𝘕𝘰𝘵𝘦 𝘵𝘩𝘢𝘵 𝘵𝘩𝘪𝘴 𝘤𝘢𝘱𝘢𝘣𝘪𝘭𝘪𝘵𝘺 𝘪𝘴 𝘯𝘰𝘵 𝘢𝘭𝘸𝘢𝘺𝘴 𝘯𝘦𝘦𝘥𝘦𝘥. 𝘊𝘩𝘦𝘤𝘬 𝘸𝘪𝘵𝘩 𝘵𝘩𝘦 𝘥𝘦𝘷𝘦𝘭𝘰𝘱𝘦𝘳𝘴 𝘵𝘰 𝘣𝘦 𝘴𝘶𝘳𝘦.

With all of that in order, we should be able to run a test on a real iPhone. Let’s give that a try now.

iOS Device Automation Practice

I’ve already gone through all the steps we discussed in the previous sections. I’m mirroring the screen of my iPhone to the desktop. This phone is registered with my Apple Developer account. I’ve trusted the computer, and ensured the phone shows up in Xcode. So let’s run a test on it.

Moving over to our IDE, let’s create a new file real_device_ios_.py. This is an iOS test, where we’ve got a bunch of iOS setup boilerplate.

APPIUM = "http://localhost:4723"
CAPS = {
"platformName": "iOS",
"appium:options": {
"platformVersion": "17.7",
"deviceName": "iPhone 15 Plus",
"automationName": "XCUITest",
"bundleId": "<com.companyname.appid>", # or "app": "path/to/testapp.ipa", eg.: "https://github.com/saucelabs/sample-app-mobile/releases/download/2.7.1/iOS.RealDevice.SauceLabs.Mobile.Sample.app.2.7.1.ipa"
"udid": "<device_udid>", # or "auto" for a single device
"xcodeOrgId": "<org_unit_number>",
"xcodeSigningId": "iPhone Developer",
"updatedWDABundleId": "<com.companyname.wda>",
"showXcodeLog": True, # optional - shows xcodebuild's log details
"noReset": True # optional
}
}

The main difference between iOS capabilities for a virtual and a real device are:

"udid": "<device_udid>",
"xcodeOrgId": "<org_unit_number>",
"xcodeSigningId": "iPhone Developer",
"updatedWDABundleId": "<com.companyname.wda>"

With all this setup done, we can add any desired test methods, for example a login test. For this demo, I only want to start a driver session, launch an AUT, and then quit the session.

OPTIONS = AppiumOptions().load_capabilities(CAPS)
driver = webdriver.Remote(
command_executor=APPIUM,
options=OPTIONS
)

time.sleep(3) # for demo only

# test code

driver.quit()

So let’s try and run this test and see if we can watch the automation happen on the real device. Make sure to have the device up and running, as well as the Appium server started.

https://youtu.be/WA7bIwYR_lg

It looks like we are able to run this Python script successfully, which is awesome!

𝓗𝒶𝓅𝓅𝓎 𝓉𝓮𝓈𝓉𝒾𝓃𝓰 𝒶𝓃𝒹 𝒹𝓮𝒷𝓊𝓰𝓰𝒾𝓃𝓰!

I welcome any comments and contributions to the subject. Connect with me on LinkedIn, X , GitHub, or Insta. Check out my website.

If you find this post useful, please consider buying me a coffee.

--

--

Lana Begunova

I am a QA Automation Engineer passionate about discovering new technologies and learning from it. The processes that connect people and tech spark my curiosity.