Automating Real iOS Devices with Appium
What a Tester Needs to Know to Automate Real iOS Devices
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.
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.
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.
② 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.
③ 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.
④ 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.
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.
③ 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"
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.
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.
Then, we select the device we intend to deploy to.
Now, we need to verify that we are signed in with a developer account. For that, navigate to the Xcode Settings menu.
If we have an existing account, it’d show under the Accounts tab of the Settings menu.
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.
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.
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.
Then, given that we have a developer account, we should go ahead and select that account from the Team drop-down.
Once we select the dev team, Xcode updates the provisioning profile.
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.”
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”.
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.
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.
⚠️ ⛔️ 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.
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.
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.
If we don’t have an AUT from our developers handy, we can run IntegrationApp.app which is readily available in Xcode for practice.
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.
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.
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.
③ 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.
It looks like we are able to run this Python script successfully, which is awesome!