Fastlane

Overview

Fastlane is an open source platform managed by Google that automates many tasks related to iOS and Android mobile app deployment. These include running tests, generating screenshots, deploying iOS apps to TestFlight, deploying iOS apps to the App Store, and more.

Management of the Fastlane project may be moving from Google to the Mobile Native Foundation soon. This is seen as a positive move since Google has not devoted time to Fastlane development lately.

This page focuses on usage for iOS apps.

Some tasks require interacting with the Apple Developer Portal and App Store Connect. Fastlane provides a way to do this from the command line.

Fastlane is primarily implemented in Ruby.

Deploying apps to TestFlight and the App Store requires enrolling in the Apple Developer Program which is currently $99/year USD.

Many of the sections below describe how to accomplish a specific task using a "lane". The final section pulls it all together into a complete Fastfile.

Resources

Provisioning Profiles and Code Signing

For details on "code signing" and "provisioning profiles", see the Apple Tech Note TN3125: Inside Code Signing: Provisioning Profiles. This document says the following:

Apple platforms, except macOS, won't run arbitrary third-party code. All execution of third-party code must be authorized by Apple. This authorization comes in the form of a provisioning profile, which ties together five criteria:

The exact format of provisioning profiles isn't documented and could change at any time.

Provisioning profiles include signing certificates, device identifiers, and a bundle ID. They are cryptographically signed.

Installing

Ruby

Ruby must be installed in order to use Fastlane. There are often issues with using the version of Ruby that comes with macOS. To avoid these issues, install a new version of Ruby using Homebrew.

  1. Enter brew install ruby.

  2. Modify your shell configuration file.

    Add /opt/homebrew/opt/ruby/bin to the beginning of the PATH environment variable value. When using zsh, the file to edit is .zshrc and the line to add is:

    path=("/opt/homebrew/opt/ruby/bin" $path)
  3. Start a new shell session.

  4. Enter which ruby and verify that it outputs /opt/homebrew/opt/ruby/bin/ruby.

  5. Enter gem install bundler.

Fastlane

  1. Install fastlane by entering brew install fastlane.
  2. If git is not already installed, enter brew install git.
  3. If the Xcode Command Line Tools are not already installed:
    1. Enter xcode-select --install.
    2. In the dialog that appears, click the "Install" button.
    3. Agree to the terms.
    4. Wait about 10 minutes for the download and install to complete.

Configuring

  1. Add the following environment variables in your shell configuration file such as .zshrc:

    export LANG=en_US.UTF-8
    export LC_ALL=en_US.UTF-8
  2. Open a Terminal window and cd to the project root directory.

  3. Enter fastlane init.

  4. This will pause several times after outputting tips. After each tip, press the return key to continue.

  5. Select one of the following options:

    • Automate screenshots (recommended)
    • Automate beta distribution to TestFlight
    • Automate App Store distribution
    • Manual setup (for implementing multiple lanes)
  6. Answer many more questions including your Apple ID and password.

    • For the UI testing scheme, choose the default scheme.
    • For "Enable automatic upload", choose "n".
  7. This results in a new directory named fastlane. When the option "Manual" is selected, this directory will contain the files Appfile and Fastfile. When the option "Automate screenshots" is selected, this directory will also contain the files Snapfile, and SnapshotHelper.swift.

  8. Add the fastlane directory and the files Gemfile and Gemfile.lock to the Xcode project.

    1. Select File ... Add Files to "{project-name}"...
    2. Select the fastlane directory.
    3. Click the "Add" button.
  9. Add the fastlane directory and the files Gemfile and Gemfile.lock to the git repository.

Appfile

This is a Ruby source file found in the fastlane directory that defines values used in the Fastfile file. It should contain the following:

app_identifier "{app-bundle-identifier}"
apple_id "{my-apple-id}"

# The steps below describe how to obtain this value.
team_id "{developer-portal-team-id}" # 10 characters

# The steps below describe how to obtain this value.
# Omit this line until the value is known.
itc_team_id "{app-store-connect-team-id}" # 9-digit number

To get the Developer Portal Team ID:

  1. Browse developer.apple.com.
  2. Click "Account" and sign in.
  3. Scroll down to the "Membership Details" section.
  4. Copy the "Team ID" value.
  5. Paste it as the value for team_id in fastlane/Appfile.

To get the AppStoreConnect Team ID:

  1. cd to the project root directory.
  2. Enter fastlane produce.
  3. You may be prompted to select a team. If so, each team name will be followed by the ITC team id in parentheses. Copy the desired ITC team id value. If not prompted to select a team, a summary table should be output. Copy the itc_team_id value.
  4. Paste the ITC team id as the value for itc_team_id in fastlane/Appfile.

For more information about the file Appfile, see the fastlane docs on Appfile.

Snapfile

This file is used to determine the variations of screenshots (device types and languages) that should be created. It is only needed if screenshots will be generated. It is a Ruby source file found in the fastlane directory.

  1. If the file fastlane/Snapfile does not exist, cd to the project root directory and enter fastlane snapshot init to create it.

    If you get the error "Could not determine installed iOS SDK version.", enter sudo xcode-select -s /Applications/Xcode.app/Contents/Developer and try again. Note that the app may have a different name that Xcode such as Xcode-beta.

  2. Edit the fastlane/Snapfile file.

  3. Uncomment lines so it indicates the devices and languages to use for creating screenshots. For example:

    devices([
    "iPhone 8 Plus",
    "iPhone 13 Pro Max",
    "iPad Pro (12.9-inch) (2nd generation)",
    "iPad Pro (12.9-inch) (6th generation)"
    ])

    languages([
    # These are five most used languages in the world
    # in order from most to least used
    # including the region in which they are most used.
    "en-US", # English - USA
    "zh-CN", # Chinese Simplified - China
    "hi-IN", # Hindi - India
    "es-ES", # Spanish - Spain
    "fr-FR" # French - France
    ])
  4. Add simulators for each of the devices listed above. It may be necessary to add new versions of these devices that use a newer version of iOS if the app requires that. For example, you may have a simulator for "iPhone 8 Plus" running iOS 15.5, but not one running iOS 16.4. If when running Fastlane you get an error saying it cannot find a certain simulator and you have multiple version of Xcode installed, verify that it is using the intended version by entering xcode-select -p.

  5. Uncomment the line that calls the scheme function and change it to scheme("ScreenshotTests").

  6. Uncomment the line output_directory("./screenshots") and change the path to ./fastlane/screenshots.

  7. Uncomment the line clear_previous_screenshots(true). This deletes all the .png files in the fastlane/screenshots directory. Perhaps this isn't always desirable.

  8. Uncomment the line override_status_bar(true) to set the status bar to Tuesday January 9th at 9:41AM with full battery and reception.

  9. Add the line headless(false). Tests that need to wait for elements to appear seem to fail without this.

  10. Add the file fastlane/Snapfile to the Git repository.

Registering an App

To register the app to be managed on the Apple Developer Portal and App Store Connect, see TestFlight.

The fastlane action produce which is an alias for create_app_online can be used to automate this process. Since new apps are not created frequently, it may be better to do this manually.

Lanes

The file Fastfile defines "lanes" which are sequences of actions that automate a specific task. Lanes can be run from the command line or by CI/CD servers. A lane can be specific to a given platform (ex. ios or mac) or it can be platform independent.

The fastlane command can be passed the name of an action or a lane to run. The syntax is fastlane {platform} {action-or-lane}. For example, fastlane ios screenshots.

If a default platform is specified in Fastfile using default_platform :ios then lanes for that platform can be executed without providing the platform. For example if ios is the default platform then the previous command can be executed with fastlane screenshots.

To list the lanes implemented for a given project, enter fastlane lanes.

To list the lanes in a table and optionally select one to execute, enter fastlane. To execute one of the lanes, enter its number. To exit without executing a lane, enter 0 or press ctrl-c.

.gitignore

The following Fastlane-related files should be listed in the .gitignore file, either because they contain sensitive information or because they contain data that changes frequently and just doesn't need to be persisted.

fastlane/AuthKey_*.p8
fastlane/builds
fastlane/Preview.html
fastlane/report.xml
fastlane/screenshots/**/*.png
fastlane/test_output
fastlane/.env.default
*.cer
*.mobileprovision
*.p12

Running Tests

For information on creating unit and UI tests for a project, see XCTest.

To run all the automated unit and UI tests in the project, use the fastlane tool scan which is an alias for run_tests. The tests run in a Simulator or on a connected device. They run much faster in Xcode than they do from fastlane.

Add the following lane in fastlane/Fastfile:

platform :ios do
desc "Run tests"
lane :tests do
run_tests(
scheme: "{scheme-name}"
devices: ['iPhone 8 Plus', 'iPhone 13 Pro Max']
)
end
end

From the project root directory enter fastlane tests.

Creating a Signing Certificate

To create a signing certificate, use the fastlane action cert which is an alias for get_certificates. This determines if a new signing certificate is needed. If so it:

The types of certificates can be created include development, adhoc, and appstore.

A .cer file file is created in the project root directory. This file should be excluded from Git by adding it in .gitignore.

This can be combined with the next step.

To see all your certificates, browse developer.apple.com, click "Account", sign in, and click "Certificates" under the "Certificates, Identifiers & Profiles" section.

Creating a Provisioning Profile

To create a provisioning profile, use the fastlane action sigh which is an alias for get_provisioning_profile. This can create, renew, download, and repair provisioning profiles.

Add the following lane in fastlane/Fastfile:

desc "Creates a signing certificate and provisioning profile"
lane :certs do
get_certificates(development: true)
get_provisioning_profile(development: true)
end

From the project root directory enter fastlane certs.

A .mobileprovision file is created in the project root directory. This file should be excluded from Git by adding it in .gitignore.

Team Development

To configure Fastlane for use by a development team, use the fastlane action match which is an alias for sync_code_signing. This combines the functionality of cert and sigh. In addition, it stores the certificates and provisioning profiles in a separate private git repository (or another supported location) so a team of developers can share them.

It is recommended to use match in place of cert and sigh when an app is being developed by more than one person.

This is the most complex fastlane action and I have not used it yet.

Building an App Archive

To build an app archive file (.ipa), use the fastlane tool gym which is an alias for build_app. This builds and packages an app, creating a signed .ipa or .app file.

  1. Verify project build settings.

    1. Open the project in Xcode.
    2. Select the top entry in the Project Navigator.
    3. Select the main target.
    4. Select the "Build Settings" tab.
    5. Scroll down to the "Versioning" section.
    6. Set "Versioning System" to "Apple Generic".
  2. Verify that the scheme to be used is "shared".

    1. Open the project in Xcode.
    2. Select the main scheme from the dropdown to the left of the device dropdown at the top.
    3. Click the scheme dropdown again.
    4. Select "Edit Scheme...".
    5. In the dialog that appears, verify that "Shared" checkbox at the bottom is checked. It should be checked by default.
  3. From the project root directory, enter fastlane gym init to create the file fastlane/Gymfile.

  4. Edit the file fastlane/Gymfile and replace the contents with the following:

    scheme("{scheme-name}")
    export_options({method: "app-store"})
    output_directory("./fastlane/builds")
  5. Add the following lane in fastlane/Fastfile:

    lane :build do
    build_app
    end
  6. Update the project version with the following steps:

    1. In Xcode, select the top entry in the Project Navigator.

    2. Select the main target.

    3. Select the General tab.

    4. In the "Identity" section, update the "Version" and "Build" numbers.

      The version number should be a semantic version like 1.2.3. The build number should be a sequential integer number like 7.

  7. From the project root directory, enter fastlane build.

    This creates the files {scheme-name}.app.dSYM.zip and {scheme-name}.ipa in the fastlane/builds directory.

  8. If you get an error message that says "Provisioning profile ... doesn't include signing certificate", launch the "Keychain Access" app, delete all certificates with the name that appears in the error message, and enter fastlane build again.

Registering Beta Testers

To register beta testers in TestFlight:

  1. Browse App Store Connect.
  2. Click the "My Apps" button.
  3. Click the button for the app to be tested.
  4. Click the "TestFlight" tab.
  5. Create a group of testers by clicking the "+" button after either "Internal Testers" or "External Testers".
  6. Enter a group name.
  7. For each tester to be added, click the "+" after "Testers" and enter their email address and name.

Deploying to TestFlight

To deploy the app to TestFlight, use the fastlane tool pilot which is an alias for upload_to_testflight. This can upload a build to TestFlight, add or remove testers, get information about testers and devices, and import or export data describing all the testers.

  1. Create an app-specific password.

    1. Browse appleid.apple.com.
    2. Click the "Sign In" button and sign in.
    3. Click "App-Specific Passwords".
    4. If there are no existing app-specific passwords, click the "Generate an app-specific password" button.
    5. If there are existing app-specific passwords, click the "+" after "Passwords".
    6. Enter the app name.
    7. Click the "Create" button.
    8. Confirm your Apple ID password.
    9. Copy the generated password so it can be passed in the file described next.
  2. Create the file fastlane/.env.default with the following contents:

    FASTLANE_USER={apple-id}
    FASTLANE_PASSWORD={apple-password}
    FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD={app-specific-password}
  3. Add the following lane in fastlane/Fastfile:

    lane :beta do
    # I prefer to update the Version and Build numbers manually in Xcode.
    # increment_build_number
    # increment_version_number(bump_type: "patch")
    upload_to_testflight(
    ipa: './fastlane/builds/{ipa-name}.ipa', # in fastlane/builds
    # I prefer to submit manually on the App Store Connect web page
    # so I can enter a description of what changed in this version.
    skip_submission: true
    )
    end
  4. From the project root directory, enter fastlane beta. This waits for processing to complete which takes around four minutes.

  5. If the error message "The bundle version must be higher than the previously uploaded version" appears:

    1. See the steps in the "Building an App Archive" section to update the "Version" and "Build" numbers.
    2. Enter fastlane build to build a new a app archive.
    3. Enter fastlane beta again.

Creating Screenshots

To create localized screenshots for each screen in the app, use the fastlane tool snapshot which is an alias for capture_screenshots which is an alias for capture_ios_screenshots. This automates generating screenshots for each screen navigated to in a UI Test. It repeats this for each supported device size and language.

The following steps assume that "Automate screenshots" was selected when the fastlane init command was run.

  1. Verify that fastlane/Snapfile was created as described in the "Configuring" section above.

  2. Create a new target.

    1. Select the topmost entry in the Project Navigator.

    2. Click the "+" button at the bottom of the left nav to create another target that is separate from the main unit and UI test targets.

    3. Enter "test" to filter the set of templates displayed.

    4. Select "UI Testing Bundle".

    5. Click the "Next" button.

    6. Change the "Product Name" to "ScreenshotTests".

    7. Click the "Finish" button.

      This creates a new folder that appears in the Project Navigator whose name is "ScreenshotTests". The new folder contains a .swift file with the same name containing starter test code.

  3. Delete the file ScreenshotTests/ScreenshotTestsLaunchTests.swift. This isn't needed for capturing screenshots.

  4. Create a new scheme.

    1. Click the scheme dropdown at the top and select "New Scheme...".
    2. Enter "ScreenshotTests" for the name.
    3. Click the "OK" button.
    4. Click the scheme dropdown at the top again and select "Edit Scheme...".
    5. In the dialog that appears, verify that the "Shared" checkbox at the bottom is checked.
    6. Select "Test" in the left nav.
    7. If "ScreenshotTests" does not appear in the list of "Test Plans"
      1. Click "+" at the bottom.
      2. In the dialog that appears, select the "ScreenshotTests" target and click the "Add" button.
    8. Click the "Close" button.
  5. Implement the UI test.

    1. Move the fastlane/SnapshotHelper.swift into the new ScreenshotTests directory.

    2. Edit the file ScreenshotTests/ScreenshotTests.swift.

    3. In the setupWithError method, add the following:

      continueAfterFailure = false
      let app = XCUIApplication()
      setupSnapshot(app)
      app.launch()

      If using your XCTestCaseExtension.swift file, it defines the static property app so change the code above the following:

      continueAfterFailure = false
      setupSnapshot(Self.app)
      Self.app.launch()
    4. Delete the method definition for testLaunchPerformance.

    5. Rename the test method testExample to testScreenshots.

    6. Replace the code in this method with code that visits each screen in the app.

    7. After the code that visits each screen, call snapshot("{sequence-number}-{screen-name}").

      The sequence numbers keep the screenshots in the intended order. The actual file name will begin with the device name (ex. "iPhone 14-") and end with ".png".

      Screenshots will only be captured when running in a simulator. The snapshot function does nothing when running on a real device.

  6. Add the following lane in fastlane/Fastfile:

    desc "Generates localized screenshots"
    lane :screenshots do
    capture_screenshots(scheme: "{screenshot-scheme-name}")
    end
  7. Verify that all the Simulators to be used are in the expected light/dark mode. Many seem to default to dark mode.

    1. Open Xcode.
    2. Select Xcode ... Open Developer Tool ... Simulator.
    3. For each device
      • In the Simulator app, select File ... Open Simulator ... iOS {version} ... {device-type}.
      • In the device simulator
        • Open the Settings app.
        • Select "Developer".
        • Toggle "Dark Appearance" to the desired setting.
  8. From the project root directory, enter fastlane screenshots. This generates .png files in the fastlane/screenshots directory. Warning messages that say "deviceType from ... was NULL when -platform called" can be ignored.

  9. An HTML file that displays all the screenshots will open in your default web browser. To skip this, add the following to the screenshots lane definition:

    skip_open_summary(true)

For more information, see fastlane screenshots.

Adding Device Frames to Screenshots

To add device frames around screenshots, use the fastlane tool "frameit" frameit which is an alias for frame_screenshots.

Before running this, enter brew install imagemagick.

This action creates new .png files below the fastlane/screenshots directory that have _framed appended to their names. The framed screenshots are beautiful, but they are all larger than the originals and are incompatible with the sizes the App Store accepts. See this issue.

Add the following lane in fastlane/Fastfile:

desc "Creates new screenshots from existing ones that have device frames"
lane :frames do
frame_screenshots
end

From the project root directory enter fastlane frames.

Uploading Screenshots

To upload the app to the App Store, use the fastlane tool deliver which is an alias for upload_to_app_store. This can upload screenshots, metadata, and binaries to App Store Connect. It can also update the app version number and submit the app for review.

The app must already be registered in order to upload screenshots.

Add the following lane in fastlane/Fastfile:

  desc "Uploads localized screenshots to App Store"
# I needed to rename the "hi-IN" directory to "hi"
# and the "zh-CN" directory to "zh-Hans".
lane :upload_screenshots do
# Only uploading screenshots.
upload_to_app_store(
skip_app_version_update: true,
skip_binary_upload: true,
skip_metadata: true
)
end

From the project root directory enter fastlane upload_screenshots.

Deploying to the App Store

The same fastlane action used to upload screenshots, deliver is used to upload an app to the App Store.

I prefer to submit manually on the App Store Connect web page so I can enter a description of what changed in the new version.

The lane definition below has not worked for me yet, but it provides a starting point. See deliver - Submit Build.

  1. Add the following lane in fastlane/Fastfile:

    lane :prod do
    upload_to_app_store(
    ipa: './fastlane/builds/{ipa-name}.ipa', # in fastlane/builds
    skip_app_version_update: true,
    skip_binary_upload: true, # gets from TestFlight
    skip_metadata: true,
    skip_screenshots: true
    # I prefer to submit manually on the App Store Connect web page
    # so I can enter a description of what changed in this version.
    # submit_for_review: true # defaults to false
    )
    end
  2. From the project root directory, enter fastlane prod.

Other Actions

All the actions supported by fastlane are listed at fastlane actions. There are many more than were described above!

In addition to the actions already described, consider using these:

Ruby vs. Swift

By default Fastfile contains code written in the Ruby programming language. There is a option to use code written in the Swift programming language, but that executes more slowly because it still interacts with Ruby. See Getting Started with Fastlane.swift.

Authentication

The information in this section may only be needed when using the match action.

  1. Browse App Store Connect.
  2. Login
  3. Click the "Users and Access" button.
  4. Select the "Keys" tab.
  5. Click the "Request Access" button.
  6. Check the checkbox to agree to terms.
  7. Click the "Submit" button.
  8. Click the "Generate API Key" button.
  9. Enter a key name such as "fastlane key".
  10. Select an access role such as "App Manager".
  11. Click the "Generate" button.
  12. Click "Download API Key".
  13. Click the "Download" button.
  14. Move the downloaded .p8 file to the project fastlane directory.

To get base64 encoded key content enter cat {key-name}.p8 | base64.

In the file Fastfile add the following before any action that requires authentication:

app_store_connect_api_key(
key_id: "{key-id}", # from downloaded key file name
issuer_id: "{issuer-id}", # How can this be determined?
key_content: "{base64-encoded-key}", # from base64 command above
is_key_content_base64: true,
in_house: false # indicates whether the team is Enterprise
)

Alternatively, set the following environment variables and just use app_store_connect_api_key():

Calling app_store_connect_api_key() with or without arguments sets a shared variable that can be used to get the API key in any lane. For example:

api_key = lane_context[SharedValues::APP_STORE_CONNECT_API_KEY]
# Pass api_key to any action.

Complete Fastfile

This is the Fastfile for my WeatherKitDemo a project.

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform :ios

platform :ios do

desc "Runs all unit and UI tests"
# This works despite warnings that say
# "deviceType from ... was NULL when -platform called".
lane :tests do
run_tests(
devices: ["iPhone 8 Plus", "iPhone 13 Pro Max"],
scheme: "WeatherKitDemo"
)
end

desc "Creates a signing certificate and provisioning profile"
lane :certs do
get_certificates(development: true)
get_provisioning_profile(development: true)
end

desc "Builds the app and produces symbol and ipa files."
lane :build do
build_app
end

desc "Uploads the app to TestFlight"
# Update version and build number of target before running this.
lane :beta do
build
# I prefer to update the Version and Build numbers manually in Xcode.
# increment_build_number
# increment_version_number(bump_type: "patch")
upload_to_testflight(
ipa: './fastlane/builds/WeatherKitDemo.ipa',
# I prefer to submit manually on the App Store Connect web page
# so I can enter a description of what changed in this version.
skip_submission: true
)
end

desc "Generates localized screenshots"
lane :screenshots do
capture_screenshots(scheme: "ScreenshotTests")
end

desc "Creates new screenshots from existing ones that have device frames"
lane :frames do
frame_screenshots
end

desc "Uploads localized screenshots to App Store"
# I needed to rename the "hi-IN" directory to "hi"
# and the "zh-CN" directory to "zh-Hans".
lane :upload_screenshots do
# Only uploading screenshots.
upload_to_app_store(
skip_app_version_update: true,
skip_binary_upload: true,
skip_metadata: true
)
end

desc "Uploads the app to the App Store"
# Update version and build number of target before running this.
# See https://docs.fastlane.tools/actions/deliver/#submit-build.
lane :prod do
build
upload_to_app_store(
ipa: './fastlane/builds/WeatherKitDemo.ipa', # in fastlane/builds
# run_precheck_before_submit: false,
skip_app_version_update: true,
skip_binary_upload: true, # gets from TestFlight
skip_metadata: true,
skip_screenshots: true
# I prefer to submit manually on the App Store Connect web page
# so I can enter a description of what changed in this version.
# submit_for_review: true # defaults to false
)
end

end