Automate iOS devices — The (almost) Mac free way

Daniel Paulus
6 min readApr 9, 2021

Intro TL;DR: I have recently released an update of go-iOS on github. It’s a set of tools that allow you to do everything with iOS devices a Mac can do, only without a Mac :-) If you want to set up your Raspberry Pi or any other Linux box to run automated tasks on a series of iOS devices, here is how. You can also watch the demo on youtube:

https://www.youtube.com/watch?v=aqM-g01qP2c

Let’s start with Go-iOS: https://github.com/danielpaulus/go-ios/ which is a set of tools written in golang that allow you to control iOS devices on Linux and potentially Windows (not added yet). Most notably it includes the capability of starting and killing Apps and even cooler, running UI-tests on iOS devices. It is using a reverse engineered version of the DTX Message Framework, or how I call it, the most overcomplicated RPC framework known to humankind 😇.

To make the process of reverse engineering Apple tools less cumbersome, I have also added a debug proxy. Once started, it will dump every message between Mac OS and Linux in plain text. That way you can easily extend go-iOS or fix it for new iOS releases. So if you need anything XCode or Configurator can do, but it is not available anywhere? Run the proxy and build it :-D

But now let’s look at an example of how we can run a simple automation script on a Linux box. Next you can see an outline of what we will need:

  1. Getting WebDriverAgent built and installed
  2. Setting up libimobiledevice’s USBMUXD
  3. Setting up Appium
  4. Creating a WebDriver.io based script to automate our device
  5. Finally, can we start WebDriverAgent and run our script?
  6. Build Rasperry Pi iOS automation tools

1. Getting WebDriverAgent built and installed

To run any automation on an iOS device, you need WebDriverAgent installed on it. Why? Well, WebDriverAgent, or WDA, is the foundation for every tool or SaaS service that automates iOS devices in some way. Unlike for Android, iOS devices are tightly locked down and do not permit generating input events like taps and keystrokes or taking screenshots through any public API or shell. So the really clever way that people and companies work around this is with WebDriverAgent. It basically starts a HTTP Server on the device itself, as a never ending test case, so that it can expose XCUITest framework functions as simple REST calls. It started as a Facebook project and now is maintained by Appium. It’s best to follow the instructions given here to install it using Xcode: https://github.com/appium/WebDriverAgent/

You could also download a prebuilt IPA file, but then the problem remains that it would need to be signed with your Apple developer account’s certificate. If you really want to automate the full process, you could build and codesign WDA on a CI service like github actions. However, codesigning is a whole topic on its own. The easiest way is to just build and sign WDA with XCode for now. Should automating this be interesting to you, just ask me.

2. Setting up libimobiledevice’s USBMUXD

The next piece of the puzzle is getting devices in a state where Linux tools can access them. On a Mac you have the USB multiplexing daemon running, which manages a list of all devices, stores pairing information and multiplexes multiple iOS devices over one USB connection. You can think of it as a service that allows software on the host machine to transparently create regular TCP host:port connections to services on the iOS device.

Luckily some very clever folks have already reverse engineered this and released a Linux compatible version of it with the amazing libimobiledevice USBMUXD component. On Ubuntu you can easily install it with apt if it is not already present:

apt install usbmuxd libimobiledevice6 libimobiledevice-utils

Now run it with sudo usbmuxd -f if you want to see logs or sudo usbmuxd& otherwise. The last step to get the device ready is to connect it with the Linux box and run idevicepair pair then the device should be good to go.

If you want to have a real deep dive into this, I can only recommend reading Jon’s excellent explanation here: https://jon-gabilondo-angulo-7635.medium.com/understanding-usbmux-and-the-ios-lockdown-service-7f2a1dfd07a

3. Setting up Appium

Ok, we are almost there. We have WDA installed and usbmuxd running on Linux with a paired device connected. What is left is getting Appium up and running so let’s clone the repo I have set up to make this whole process a little smoother.

https://github.com/danielpaulus/ios-appium-on-linux comes with a preconfigured Dockerfile that will start Appium with NodeJS 14 and download all the necessary dependencies. Also there are scripts to run appium using the USBMUXD we installed in the last step. Let’s check out the steps needed.

First let’s build our docker image and tag it with “ios-appium-on-linux”:

docker build -t ios-appium-on-linux .

I have provided a convenient run script that starts the container with the correct mounts and port forwards if you pass the docker image name as a parameter like so:

./run.sh ios-appium-on-linux

Now you should see the log output of your container, telling you that Appium is running.

4. Creating a WebDriver.io based script to automate our device

While it is not strictly necessary to use webdriver.io for this, it seemed like the best available option to quickly implement something with JavaScript. Appium generally has clients in many programming languages available. First, run npm install to get the script ready before we can run it in the next step. While the install is ongoing, let's check out the script itself. It mainly has two parts:

const wdio = require("webdriverio");const opts = {
path: '/wd/hub',
port: 4723,
capabilities: {
deviceName: "blabla",
automationName: "XCUITest",
platformName: "iOS",
browserName: "Safari",
udid: "auto",
usePrebuiltWDA: true,
startIWDP: true,
webDriverAgentUrl: "http://localhost:7777"
}
};

This is what Appium uses for configuration the “Desired Capabilities”. I set up some working defaults to get going quickly. Most notably, we are using a prebuilt and already running WebDriverAgent and Appium needs to know that. Appium does not know how to launch an XCUITest on Linux, but we do. If you have more than one device attached, you might want to change the udid to the device's. I am not installing an App to test or automate so I am happy with just Safari. A fun thing about Appium is that the deviceName parameter is mandatory, but also completely unused. Imho, those are the best parameters, they break your stuff if omitted, but you can put in random values and it will work 😂 . 4723 is the Appium port, luckily our runscript automatically exposes that from inside the Docker container.

The actual webdriver.io script is pretty self explanatory as you can see:

async function main () {
const client = await wdio.remote(opts);
await client.url('https://www.youtube.com/watch?v=8v5f_ybSjHk');
client.pause(5000);
await client.activateApp( "com.apple.weather")
await client.deleteSession();
}
main();

It really only opens youtube, waits 5 seconds and then opens the weather app. That’s it. You can extend this in any way you want to f.ex. automate the preferences app to configure a fleet of devices without the need for a MDM solution.

5. Finally, can we start WebDriverAgent and run our script?

Ok, the time has come to tie it all together, we are almost there :-) With docker ps find out your container ID (in my case aa6314e10f32). Then run docker exec aa6314e10f32 /ios runwda and see how WebDriverAgent is launched on a Linux box without us having to buy a Mac at all 🥳🥳🥳🥳 Next we finally can execute our webdriver.io automation script provided in the ios-appium-on-linux repo by running node client/test.js Now you should be able to see your device opening Safari and showing a cool youtube video.

6. Build Rasperry Pi iOS automation tools

Now, please go ahead and do cool stuff with this :-D

--

--