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:
- Who is allowed to sign code? (which developers)
- What apps are they allowed to sign? (typically a single app id, but can use a wildcard to match multiple apps)
- Where can those apps run? (platforms such as iOS)
- When can those apps run? (expiration date after which the profile becomes invalid)
- How can those apps be entitled? (what apps are entitled to do)
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.
Enter
brew install ruby
.Modify your shell configuration file.
Add
/opt/homebrew/opt/ruby/bin
to the beginning of thePATH
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)
Start a new shell session.
Enter
which ruby
and verify that it outputs/opt/homebrew/opt/ruby/bin/ruby
.Enter
gem install bundler
.
Fastlane
- Install fastlane by entering
brew install fastlane
. - If git is not already installed, enter
brew install git
. - If the Xcode Command Line Tools are not already installed:
- Enter
xcode-select --install
. - In the dialog that appears, click the "Install" button.
- Agree to the terms.
- Wait about 10 minutes for the download and install to complete.
- Enter
Configuring
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-8Open a Terminal window and cd to the project root directory.
Enter
fastlane init
.This will pause several times after outputting tips. After each tip, press the return key to continue.
Select one of the following options:
- Automate screenshots (recommended)
- Automate beta distribution to TestFlight
- Automate App Store distribution
- Manual setup (for implementing multiple lanes)
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".
This results in a new directory named
fastlane
. When the option "Manual" is selected, this directory will contain the filesAppfile
andFastfile
. When the option "Automate screenshots" is selected, this directory will also contain the filesSnapfile
, andSnapshotHelper.swift
.Add the
fastlane
directory and the filesGemfile
andGemfile.lock
to the Xcode project.- Select File ... Add Files to "{project-name}"...
- Select the
fastlane
directory. - Click the "Add" button.
Add the
fastlane
directory and the filesGemfile
andGemfile.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:
- Browse developer.apple.com.
- Click "Account" and sign in.
- Scroll down to the "Membership Details" section.
- Copy the "Team ID" value.
- Paste it as the value for
team_id
infastlane/Appfile
.
To get the AppStoreConnect Team ID:
- cd to the project root directory.
- Enter
fastlane produce
. - 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. - Paste the ITC team id as the value for
itc_team_id
infastlane/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.
If the file
fastlane/Snapfile
does not exist, cd to the project root directory and enterfastlane 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 thatXcode
such asXcode-beta
.Edit the
fastlane/Snapfile
file.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
])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
.Uncomment the line that calls the
scheme
function and change it toscheme("ScreenshotTests")
.Uncomment the line
output_directory("./screenshots")
and change the path to./fastlane/screenshots
.Uncomment the line
clear_previous_screenshots(true)
. This deletes all the.png
files in thefastlane/screenshots
directory. Perhaps this isn't always desirable.Uncomment the line
override_status_bar(true)
to set the status bar to Tuesday January 9th at 9:41AM with full battery and reception.Add the line
headless(false)
. Tests that need to wait for elements to appear seem to fail without this.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:
- creates a new private key
- creates a new signing request
- generates, downloads, and installs the certificate
- imports all the generated files into the Keychain app
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.
Verify project build settings.
- Open the project in Xcode.
- Select the top entry in the Project Navigator.
- Select the main target.
- Select the "Build Settings" tab.
- Scroll down to the "Versioning" section.
- Set "Versioning System" to "Apple Generic".
Verify that the scheme to be used is "shared".
- Open the project in Xcode.
- Select the main scheme from the dropdown to the left of the device dropdown at the top.
- Click the scheme dropdown again.
- Select "Edit Scheme...".
- In the dialog that appears, verify that "Shared" checkbox at the bottom is checked. It should be checked by default.
From the project root directory, enter
fastlane gym init
to create the filefastlane/Gymfile
.Edit the file
fastlane/Gymfile
and replace the contents with the following:scheme("{scheme-name}")
export_options({method: "app-store"})
output_directory("./fastlane/builds")Add the following lane in
fastlane/Fastfile
:lane :build do
build_app
endUpdate the project version with the following steps:
In Xcode, select the top entry in the Project Navigator.
Select the main target.
Select the General tab.
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.
From the project root directory, enter
fastlane build
.This creates the files
{scheme-name}.app.dSYM.zip
and{scheme-name}.ipa
in thefastlane/builds
directory.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:
- Browse App Store Connect.
- Click the "My Apps" button.
- Click the button for the app to be tested.
- Click the "TestFlight" tab.
- Create a group of testers by clicking the "+" button after either "Internal Testers" or "External Testers".
- Enter a group name.
- 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.
Create an app-specific password.
- Browse appleid.apple.com.
- Click the "Sign In" button and sign in.
- Click "App-Specific Passwords".
- If there are no existing app-specific passwords, click the "Generate an app-specific password" button.
- If there are existing app-specific passwords, click the "+" after "Passwords".
- Enter the app name.
- Click the "Create" button.
- Confirm your Apple ID password.
- Copy the generated password so it can be passed in the file described next.
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}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
)
endFrom the project root directory, enter
fastlane beta
. This waits for processing to complete which takes around four minutes.If the error message "The bundle version must be higher than the previously uploaded version" appears:
- See the steps in the "Building an App Archive" section to update the "Version" and "Build" numbers.
- Enter
fastlane build
to build a new a app archive. - 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.
Verify that
fastlane/Snapfile
was created as described in the "Configuring" section above.Create a new target.
Select the topmost entry in the Project Navigator.
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.
Enter "test" to filter the set of templates displayed.
Select "UI Testing Bundle".
Click the "Next" button.
Change the "Product Name" to "ScreenshotTests".
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.
Delete the file
ScreenshotTests/ScreenshotTestsLaunchTests.swift
. This isn't needed for capturing screenshots.Create a new scheme.
- Click the scheme dropdown at the top and select "New Scheme...".
- Enter "ScreenshotTests" for the name.
- Click the "OK" button.
- Click the scheme dropdown at the top again and select "Edit Scheme...".
- In the dialog that appears, verify that the "Shared" checkbox at the bottom is checked.
- Select "Test" in the left nav.
- If "ScreenshotTests" does not appear in the list of "Test Plans"
- Click "+" at the bottom.
- In the dialog that appears, select the "ScreenshotTests" target and click the "Add" button.
- Click the "Close" button.
Implement the UI test.
Move the
fastlane/SnapshotHelper.swift
into the newScreenshotTests
directory.Edit the file
ScreenshotTests/ScreenshotTests.swift
.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 propertyapp
so change the code above the following:continueAfterFailure = false
setupSnapshot(Self.app)
Self.app.launch()Delete the method definition for
testLaunchPerformance
.Rename the test method
testExample
totestScreenshots
.Replace the code in this method with code that visits each screen in the app.
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.
Add the following lane in
fastlane/Fastfile
:desc "Generates localized screenshots"
lane :screenshots do
capture_screenshots(scheme: "{screenshot-scheme-name}")
endVerify that all the Simulators to be used are in the expected light/dark mode. Many seem to default to dark mode.
- Open Xcode.
- Select Xcode ... Open Developer Tool ... Simulator.
- 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.
From the project root directory, enter
fastlane screenshots
. This generates.png
files in thefastlane/screenshots
directory. Warning messages that say "deviceType from ... was NULL when -platform called" can be ignored.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.
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
)
endFrom 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:
The get_version_number and get_build_number actions return values. They can be combined in a lane like the following:
desc "Prints the version and build number"
lane :version do
version = get_version_number
build = get_build_number
puts "version #{version}, build #{build}"
endThe notification displays a macOS notification. The first time this is used, the System Settings app will open. To enable "terminal-notifier" to display notifications, toggle "Allow Notifications" to on and select "Alerts".
I tried the following, but haven't gotten it to work yet.
notification(
title: "Morning Greeting",
subtitle: "Daily Advice",
message: "Good morning Mark! Learn something today."
)The puts prints given text to stdout.
echo
is an alias for this.The say action speaks given text. It is useful for announcing when a lane completes.
The sh to execute a shell command.
The slather action generates a code coverage report.
The swiftlint action performs code validation using SwiftLint.
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.
- Browse App Store Connect.
- Login
- Click the "Users and Access" button.
- Select the "Keys" tab.
- Click the "Request Access" button.
- Check the checkbox to agree to terms.
- Click the "Submit" button.
- Click the "Generate API Key" button.
- Enter a key name such as "fastlane key".
- Select an access role such as "App Manager".
- Click the "Generate" button.
- Click "Download API Key".
- Click the "Download" button.
- Move the downloaded
.p8
file to the projectfastlane
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()
:
- key_id: APP_STORE_CONNECT_API_KEY_KEY_ID
- issuer_id: APP_STORE_CONNECT_API_KEY_ISSUER_ID
- key_content: APP_STORE_CONNECT_API_KEY_KEY
- in_house: APP_STORE_CONNECT_API_KEY_IN_HOUSE
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