I have quite a few projects in ‘beta’ stage for both Android and iOS, so I’ve become way more acquainted than I want to be with the painful process of beta distribution and store submission for both ecosystems. The two processes are vastly different, and in general iOS is way more of a pain and far more involved than Android. I finally decided to automate the whole thing from start to finish and make use of the amazing build tools that are out there instead of doing it manually. In the next two blog posts, I’ll share my learnings about how to use the iOS toolset in Fastlane, to completely streamline this whole schemozzle down to a single command.

Fastlane, in a nutshell, is basically voodoo magic. It started as a set of tools created by Felix Kraus, as a side project, who then got employed by Twitter to build cool dev tools full time. Fastlane is now a part of the toolset that is Twitter’s Fabric and has been growing steadily to include Android tools and continuous improvements.

The first part of this series will be setting up Fastlane and creating a process to run all your tests, build and signing tasks and then distribute it to TestFlight, while issuing Slack notifications about progress. The second part, coming soon, will be creating an auto sign-up page for TestFlight, that automatically does all the profile and provisioning stuff for the apple ecosystem, and further improvements on the slick build process you will end up with after today. BUCKLE UP!

Assumed Knowledge

It’s assumed that you have a project you’re setting this up on that you’ve already created unit tests and UI tests for.

This is by far the best tutorial on UI testing.


Unit testing is a more mature concept in Swift world with tonnes of tutorials and frameworks out there, but Ray Wenderlich is always a good starting point.

Setting Up Fastlane

In general, the best set of instructions on how to do this is the quickstart guide: https://docs.fastlane.tools/getting-started/ios/setup/

However, here are a few things to note:

Confirm that you have the correct version of Ruby (v2) installed by opening terminal and entering the following command:

ruby -v

If not you will need to upgrade it before you continue on.

Check whether you have Xcode Command Line Tools (CLT) installed are installed by entering the following command in terminal:

xcode-select --install

If the Xcode CLT are already installed, you will get an error, otherwise terminal will install the Xcode CLT for you.

If you’ve got those two sorted, go ahead and install Fastlane using the quickstart guide.

Super good idea to also use the Gemfile as per the documentation but as of -v1.13.1 bundler is creating issues so don’t even bother until that’s fixed.

Setting Up Scan

Installing Fastlane above should have installed the complete scan package as well. However if you run into any issues that indicate you don’t have scan installed:

sudo gem install scan

You can use scan from here just by running the manual command from the directory all your stuff is in, but we want to create a Scanfile to create a re-usable configuration that we can then call from our single Fastlane ‘lane’. A scan config like this is called a ‘scheme’ and we store those in a Scanfile. To create a Scanfile, type:

scan init

Now open up the Scanfile (should be in the fastlane directory in your project) and commence creating a scheme.

Mine looks like this:

scheme "example"
devices ["iPhone 6", "iPhone 7", "iPad Air"]
clean true
output_types "html"

Setting Up Match

The absolute worst part of iOS distribution is creating provisioning profiles, making sure you have the right users, making sure that your build is signed with the right one, all that jazz. Match is a code signing utility within Fastlane that removes a lot of the complexity and potential issues. It’s designed for when you’re working with teams and so you need the provisioning profiles on multiple machines and so on, but I also find it handy because I work between work and personal computers and also because Xcode likes to be weird after upgrades and so on. Additionally I make silly mistakes with code signing that waste a lot of time, with a notable sort of frequency. In a nutshell, it means you can use git to centrally manage provisioning profiles, and then automatically pull and use the correct one for the type of build you’re doing, essentially bec-proofing the whole process.


To get started with it go to Github and create a private git repo (don’t initialise it). Then type:

match init
match appstore

This will magically see what you’ve got in your repo and if it can’t find anything that matches, all required keys, certificates and provisioning profiles will be created, committed and installed. The above code snippet assumes that you are using this tutorial for TestFlight distribution so you’ll need the appstore provisioning profile.

If your exisiting profiles on the Developer Portal are a complete mess and you want to start again, in the Brave New World of matched profiles, before you do any of the above, you can use this command to, well, nuke the lot:

match nuke development 
match nuke distribution

Setting Up Slack

When I run my test and produce my pretty scan test report, I want it to appear in the build channel of my slack team. Additionally I want them to get notified of my new distributed builds. With Fastlane notifications actions I can do this really easily and customise every aspect of these notifications. There are also a bunch of other ways you can notify your team, including Mailgun, Twitter (why?) and IFFT recipes. However, for my lane, we’ll be going with slack.


Do do this you want to set up an incoming webhook with slack. There’s incoming and outgoing webhooks for slack, and you can set up the former for your team here:


You want to make sure the correct team is selected from the drop down in the top right hand corner – you might have to sign in if you use the desktop app. Once you select the team it’ll then ask you to pick the channel. I have a specially created #builds channel that I can spam with all my Fastlane notifications. You can also set the icon and other look and feel elements of the incoming webhook.

Now you’ve got everything you need to start setting up your lane for TestFlight distribution…

Setting Up Your Fastfile

You can now start modifying the Fastfile that was created for you in your directory. If you open it up you’ll see it already has a whole bunch of useful sample code, so we’re going to use that and make some modifications.

Uncomment the environment variable for slack by deleted the hash (#) , and past in the URL that you got from the previous part.

This is in the before all do part of the Fastfile which helpfully tells Fastlane important stuff about your environment. Mine also has Cocoapods in it, for example, so it builds from the xcworkspace and all my dependencies work.

before_all do
    ENV["SLACK_URL"] = "https://hooks.slack.com/services/{YOUR WEBHOOKS URL}"


You’ll notice further down the file there’s another slack message which is in after_all_do and an error_do. Basically that’ll only run after the whole thing has finished. We want this to give us as much info as possible and all of the default available payloads (test reports and so on) so we’ll leave it with this and just uncomment everything out:

after_all do |lane|
    # This block is called, only if the executed lane was successful
      message: "fastlane was successful :champagne:",
      success: true,
error do |lane, exception|
      message: exception.message,
      success: false

The only thing I’ve edited above, is the emoji. Everything between “::” gets rendered as emoji in Slack which is a handy way for you to put your own in. You can even make it :fire: for an error, for example. You can have that idea for free.

You already have a lane for tests, which runs tests on everything for all devices, so we’ll leave that one. We’re going to create our own lane that does a whole bunch of stuff in one command.

We’ll modify the beta lane to uncomment the stuff we need, delete the stuff we won’t.

desc "Submit a new Beta Build to Apple TestFlight"
  desc "This will also make sure the profile is up to date"
  lane :beta do
    scan(scheme: "example")
    match(type: "appstore")
    pilot(distribute_external: true, changelog: "New build for testing")

So what’s going on here…

  1. First we want to increment the build number. This in itself is hugely useful because I don’t know how many times I’ve forgotten to do this and not realised until the .ipa is fully created and Xcode gives me the error message.
  2. Run a quick, to check that the last thing you did didn’t massively stuff up anything.
  3. Use the provisioning profile from your repo. In your Matchfile the default is set to development so it’s important to specify here.
  4. Use gym to build (we’re just using the stock standard build config here but you can specify more of this).
  5. Then we push to TestFlight with pilot. It’s important that we set distribute_external to true, because it’s false by default and then it’ll only distribute to your team or give you an error if you don’t have a team on your iTunes Connect dashboard. We can also submit it with a changelog and this can be further customised using ruby variables so that it’s actually informative.

If you need to import a stack of new testers, because this is a new project, use the csv file template that you get from iTunes Connect in the testers import section, save it to your project directory, and put all your testers in, then use this command:

pilot import

There are a bunch of other useful commands for pilot here:


Also check that out for troubleshooting tips if you’re behind a firewall etc.

In Part 2 of this tutorial I’ll be making all the fiddling with pilot testers redundant but setting up an automatic sign up page, so keep an eye out for that.

Do Some Other Useful Stuff

There’s so many more actions that we can add to our lane for this beta test. For example, I want to up the commit version and then push it to the remote branch at the same time, so I add this to the end of my beta lane:

message: ‘Build Version Bump by fastlane’,
force: true
build_number = Actions.lane_context[Actions::SharedValues::BUILD_NUMBER]
add_git_tag(tag: “testflight-#{build_number}”)

More info on all the actions is here:


All Systems Go…

Ok are you ready?

Now you type this into your terminal:

fastlane beta

*fingers crossed*

Awww yiss…

Successful test notification
Successful build and distribution notification

Also of note:

Very conservative estimate of how much time I just saved — it was definitely much more

Other Hot Tips

  • This is, as you might imagine, a fairly memory intensive sort of process. Don’t try to do too much other stuff and maybe close some of your five million Chrome tabs before issuing the fastlane command.
  • Make sure you’ve closed all your simulators before you run the command or you’ll get this interesting error:
Yeah that would make sense, I guess…

Sometimes if you mess around with a few unsuccessful builds you’ll get errors related to it trying to submit builds it thinks it exists and/or ignoring your manual updates to the current build number in inspector. It might help you to manually set the current highest build number one time only in the Fastfile like so:

      build_number: [SPECIFY THE BUILD NUMBER HERE]


This showed the process from end to end for creating a lane to distribute TestFlight builds. I’ll be issuing Part 2 to show how to automate sign up and will link to it from here when it’s completed. In the meantime, if there’s anything else you’d like to see demonstrated in a lane, let me know!

Streamlining iOS Beta with Fastlane From End-to-End was originally published in Xero Developer on Medium, where people are continuing the conversation by highlighting and responding to this story.