When developing iOS apps a number of things are required for producing a quality product. These include the ability to run unit tests on the code and to test the user interface automatically. We can set up Continuous Integration (CI) to complete these tests for us. If all tests are passed then we automatically upload to Hockeyapp for our testers to download. In this blogpost I'll take you through the process of setting up a CI pipeline for iOS.

We built a very simple app called ‘Tasky’ which pretty much does as little as possible but enough to be able to build a complete workflow. We wanted an app with a bit of navigation and Core Data to give us enough to test.

Setting up language support

We use WebTranslateIt to handle our language strings. This web tool lets you create keys and then provide text in different languages. It provides a Ruby command line tool that allows you to download the complete Localizable.strings files.

Set up WebTranslateIt (http://webtranslateit.com) and the WebTranslateIt Synchronization Tool. Go to https://github.com/AtelierConvivialite/webtranslateit/ and follow the instructions.

Install OS X Mavericks, Server and Xcode 5

Get the latest version of OS X Mavericks, Server and Xcode 5 from the Mac App Store and install. Run up the Server app and switch on Xcode. You then add the team from your developer account and add any connected devices.

You can also add you repositories to Xcode. Here we are using Git.

BotRuns and Provisioning

Provisioning can be a problem so it may be necessary to copy the correct provisioning file into: /Library/Server/Xcode/Data/ProvisioningProfiles. You will need to change permissions to have access to this folder. It is a good idea to be able to see into /Library/Server/Xcode/Data as this is where the BotRuns are stored. Also make sure your distribution certificate is in both the login and system keychains.

Edit Scheme

We added two scripts to the project scheme. One for build pre-action and one for archive post action. These are found on the project scheme tool.

Once 'Edit Scheme...' is selected, the action scripts can be created.

Create bot

Once the source code repository is set up and a device has been attached (simulators will also work) then the project bot can be created. Click on the ‘View bots’ button on the Xcode view in Server and add a bot from the web view.

Build pre-action

We do two things prior to the actual build. First we pull down the latest language files from WebTranslateIt and copy them to the right location in the project file structure.

Note: private_read-write_API_key is the key given to you language files by WebTranslateIt.

Then we create a unique build number by taking the date and time and creating a hexadecimal version so as to not make it too obvious. This is written back to project plist file.

Unit testing language strings are correct

We have unit tests for the app but of particular note is our unit test for the language strings. We use OCMock (http://ocmock.org) and OCHamcrest (http://qualitycoding.org/resources/) in our sample project. In the class below we step through the default language string file to extract the keys and then step through all the languages supported in the app to ensure that the keys exist. If they do not then this unit test will fail.

Archive post-action

We need to find the latest bot run in order to extract the latest build. The ‘latest’ build alias doesn’t actually point to the latest build until AFTER the archive post action has completed. We also extract the commit messages for the specific build.

Despite this being the archive post-action, at this time the final build has not actually happened so we have to do an additional packaging of the app for the upload to Hockeyapp.

We now run instruments with a javascript file to test the UI. This doesn't work out of the box and requires a slight hack to make it work. This involves extracting any errors from the script result so if the UI fails then an error is output to the bot log and the whole build process is stopped. This will then show up as a failed integration. If the UI passes then we upload the app to Hockeyapp.

Note: 'device_UDID' is the UDID of your iOS device attached to the CI box. 'app_id' is the ID your app is given by Hockeyapp. 'company_id_token' is the ID your company is given by Hockeyapp.'

Run the bot

Click the 'View bots' button on the Xcode panel in Server which will open the bot in Safari. Click the 'Integrate' button and when the integration has completed, this is hopefully what you’ll get.

When all test have passed successfully the app will appear on Hockeyapp as shown below. Note the unique build numbers.


We’ve used a variety of tools to bring together a complete Continuous Integration stack. With this we are able to commit updates, automatically build, unit test, run a UIAutomation script and upload to Hockeyapp.

Link to 'Tasky' in Github: https://github.com/madebymany/Tasky


Great article, just what I'm looking for.


Great article, just what I'm looking for.


Did you have any luck using CocoaPods with BuildBots? That's the main factor we're not using it yet.


Yeah cocoapods is the big blocker for us at this point

Vladimir Grichina

A bit of shameless plug – if you need to do CI with CocoaPods you may wish to try http://hosted-ci.com

Ilias Karim

great post! thanks so much for the write up!

the only bit I am having trouble with is packaging the app--/usr/bin/xcrun -sdk iphoneos PackageApplication [...] is unable to copy the application to /var/folders/[...]

I've tried adjusting the permissions but to no avail--PackageApplication just creates a new folder and complains about not being able to copy the application into it

Any ideas?

Ilias Karim

What I thought had been a permissions error was in fact a path error. See the non-approved response here: http://stackoverflow.com/questions/8536728/xcrun-packageapplication-failed-unable-to-copy-application

I had appended a trailing slash at the end of my app path since I had not defined $WRAPPER_NAME, causing the "unable to copy the application" error

Perhaps it would be helpful if you could elucidate how you use $WRAPPER_NAME

Either way, you've written a very informative post!

Taehun Kim

Great! extremely helpful!


Thanks for the article, it is quite detailed and easy to read and understand.

There is one minor improvement to these scripts. One can easily locate the latest integration via:

COMMIT_FILE=$( ls /Library/Server/Xcode/Data/BotRuns/Latest/output/commit* )


I'm sorry, please disregard my previous comment. You're right "Latest" isn't latest at the moment of archiving. :)


Regarding the inclusion of the UIAutomation scripts in the CI process, I've found that the "instruments" CLI does not actually deploy the application to the phone, meaning that unless you do that manually to the attached device, you risk running an old version of the app. I don't see anywhere in this post that addresses that. Am I wrong?


Seriously, this is a great article. Thank you.


Thanks for posting this. I've been needing my commit notes in teamviewer for some time now.. This was a big help!

Philip P

This post is outdated and should be labeled as such, additionally there were some things that you forgot to mention in the post. Mainly that your example only works for testing on Device and not simulator. Additionally there was no mention of HockeyApp being a third party service (Like testflight it sounds like with 2 seconds of googling). Finally, there's no mention of how the application will get to the device. Immediately after you have the example for packaging the app, you jump straight into the UIAutomation stuff, without any mention that Instruments will handle installation based on specific situations.

Overall, this article was a good read for some base ideas but the information should be updated.

Alex Cyon

What is the correct path nowadays?

Leave a comment

Required fields are marked. Don’t worry - we won’t do anything fishy with your email address.