Implementing Continuous Integration for iOS Projects

In this blog post I’m going to share my experience of implementing Continuous Integration for iOS projects.
To be more specific, I’m going to tell you how our team successfully completed the task on automating iOS applications testing and integrating it into the server testing process.

Continuous Integration for iOS Projects


Continuous Integration (CI) is a software engineering practice that involves frequent builds and their automated testing for early error detection and correction.

In what way is it useful? Firstly, CI allows to test the whole system, enabling to catch integration errors in advance. Secondly, it speeds up the process of providing the latest QA versions and simplifies the process of distribution to the AppStore.

So, the goal is to create a system for building iOS projects and their distribution.


We use TeamCity as a CI platform. Our team have already used TeamCity for testing the server, so deployment of something else for iOS doesn’t make any sense.

The choice of automated testing platform was up to our QA team, and they chose Sauce Labs in conjunction with Appium. The reason for this was our previous solid experience of using SauceLabs for web testing, while Appium is free, easy to configure, has large community support, as well as official SauceLabs support.


TeamCityCI platform

More about how it works
Build configuration for our mobile app is created in TeamCity, and is executed when changes in the source control occur (or by pressing the build button, or as a part of the server testing process). Unit tests are running during the build, and if they run successfully, .app (xcarchive) is compiled, archived and uploaded to our SauceLabs storage. After that, build configuration for automated tests is triggered, during which execution virtual machine is launched. Appium, which is launched on virtual machine, executes automated test code, and finally we find out, whether everything is fine with the new build.

A few words about configuration
I see no point in describing the process of build configuring, as there are plenty of articles on the subject. However, I would like to mention that it’s a good practice to specify the path to the results of the build, which will enable downloading them directly from TeamCity.

Preparing the project for building
Firstly, it’s necessary to configure project sсhemes. Once you open a project in Xcode, sсhemes are created automatically. But in case you build on the server, that won’t happen, and build won’t start. To solve the problem, open Xcode project settings, choose Product → Schemes → Manage Schemes, and select Shared option for the schemes you want to use.


(Uploading your build on Fabric or Hockeyapp is similar. I recommend to use shenzhen (or just curl). You can check the examples on the shenzhen git page.)

Secondly, you need to take care of used dependencies. You probably use dependency manager cocoapods, and certainly do not keep your project dependencies at the source control, but without them build won’t start as well. To solve this problem, run the following command before building:

if [ -e "Pods" ]														
    sudo -u YOUR_USER pod update										
    sudo -u YOUR_USER pod install										

(If a folder with dependencies exists, you need to check them for updates. If it doesn’t, it’s necessary to create one, download all dependencies and integrate them into the project.)

Finally, we are ready to build!
There are a lot of plug-ins for TeamCity that can automatically build different types of projects, including a plug-in for xcode. But for greater flexibility, I recommend to perform build using the command line, and to use xctool (a tool developed by guys from Facebook) – instead of xcodebuild from Apple. Xctool provides human-friendly, ANSI-colored output. In addition, it simplifies the procedure of running unit tests by using a single command – test, and allows you to run them in parallel.

To start building you need to pass the name of your .xcworkspace (or .xcodeproj), as well as the name of the scheme, according to which you want to build. For convenience, I would also recommend you to redefine the directory for the build result. In my commands I pass option -sdk iphonesimulator, because Appium in my case is testing on the simulator. But if you want to run tests on an actual device, pass -sdk iphoneos.

Initially, I run unit tests, and, if everything is fine, I proceed to running the build:

xctool \															
-workspace WORKSPACE_NAME \										
-scheme TESTS_ SCHEME_NAME \										
-sdk iphonesimulator \												
xctool \															
-workspace WORKSPACE_NAME \										
-scheme SCHEME_NAME \											
-sdk iphonesimulator													
-configuration Release clean build DEPLOYMENT_LOCATION=YES DSTROOT='build'

(To run the unit tests or for any other interaction with simulator, mac build agent user must have access to the GUI)

As a result, we get .app at build/applications which can be sent to SauceLabs.

For this purpose we archive it...

zip -r

...and upload to the Sauce Labs server...

-H "Content-Type: application/octet-stream"
--data-binary @

That’s it! At this point build configuration for build is over, and build configuration for automated tests is triggered. Our work here is done.

But wait a sec! Since we can build the project for testing, let's configure build for distribution to the iTunes Connect. As you know, a build can be uploaded to iTunes Connect only if it is of higher version than the previous uploaded one.

This can be achieved as follows:

version=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion"
newversion=$(($version + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $newversion" 

(Do not forget to take care of dependencies used before build.)

let's proceed to DISTRIBUTION
For distribution, we need to get .ipa signed with distribution certificate and upload it through API to iTunes Connect. For this purpose I recommend to use fastlane/gym and fastlane/pilot utilities. Of course, you can use standard utilities like xcrun and curl, but fastlane utilities are much easier and, in addition, allow to automatically generate screenshots for all screen sizes with fastlane/frameit, or to generate build descriptions for multiple languages with fastlane/deliver.

Gym encapsulates the build of the project (using xcodebuild) and generation of a signed .ipa using xcrun.

Gym configuration is pretty similar to xctool or xcodebuild one, but you need to pass the complete name of your distribution certificate to the codesigning_identity parameter (e.g. "iPhone Distribution: SolbegSoft (XX0X0X0X0)"). In theory, this should be enough, but I recommend to optionally specify your distribution provisioning profile id to the -x PROVISIONING_PROFILE parameter to eliminate the possibility of using incorrect .mobileprovision.

The command for signing .ipa will look as follows:

gym \
--workspace WORKSPACE_NAME \
--scheme SCHEME_NAME \
--clean \
--output_directory BUILD_HISTORY_DIR \
--output_name APPLICATION_NAME \
--codesigning_identity DEVELOPPER_NAME \

We got .ipa, now we can send it to iTunes Connect. And fastlane/pilot will help us. Pilot is a wrapper over the iTunes Connect API, that allows you to manage your iTunes Connect account using the command line, e.g. send a new build.

By default, pilot looks for signed .ipa in the current directory and automatically sends it to the iTunes Connect. You just need to specify the login:
(login and password for itunesconnect must be stored in your keychain):

pilot upload \

That's it! Now you can have a cup of coffee! If you did everything right, your build will get on iTunes Connect server, take the stage of processing (usually it takes half an hour), and become available for installation for internal testers and for sending to AppStore moderators.

That's all! Thanks for reading, and simple releases to you!

Daniil Lobanov
Daniil Lobanov Mobile Application Developer at SolbegSoft