Skip to main content

Overview

scan is the easiest way to run tests for your iOS, macOS, tvOS, and watchOS apps. It automatically handles building and testing your app, generating beautiful HTML reports.
scan is also available as the run_tests action in fastlane.

Installation

scan is part of fastlane. Install it with:
fastlane scan

Quick Start

1

Run tests

fastlane scan
scan will automatically detect your project and run tests.
2

View results

Test results are saved in ./test_output directory.

CLI Commands

fastlane scan

Key Options

workspace
string
Path to the workspace file.
scan(workspace: "MyApp.xcworkspace")
project
string
Path to the project file.
scan(project: "MyApp.xcodeproj")
scheme
string
The project’s scheme. Make sure it’s marked as Shared.
scan(scheme: "MyApp")
device
string
The name of the simulator to run tests on.
scan(device: "iPhone 15")
devices
array
Array of devices to run tests on.
scan(devices: [
  "iPhone 15",
  "iPad Pro (12.9-inch)"
])
clean
boolean
default:"false"
Should the project be cleaned before building?
scan(clean: true)
code_coverage
boolean
Should code coverage be generated?
scan(code_coverage: true)
output_directory
string
The directory where reports will be stored.
scan(output_directory: "./test_output")
output_types
string
default:"html,junit"
Comma-separated list of output types (html, junit, json-compilation-database).
scan(output_types: "html,junit")
output_files
string
Comma-separated list of output file names.
scan(output_files: "report.html,report.junit")
buildlog_path
string
The directory where build logs will be stored.
scan(buildlog_path: "./logs")
open_report
boolean
default:"false"
Should the HTML report be opened when tests complete?
scan(open_report: true)
configuration
string
The configuration to use when building (defaults to ‘Release’).
scan(configuration: "Debug")
xcargs
string
Pass additional arguments to xcodebuild.
scan(xcargs: "-parallel-testing-enabled YES")
only_testing
array
Array of test identifiers to run.
scan(only_testing: [
  "MyAppTests/LoginTests",
  "MyAppTests/SignupTests"
])
skip_testing
array
Array of test identifiers to skip.
scan(skip_testing: [
  "MyAppTests/SlowTests"
])
testplan
string
The testplan associated with the scheme to use for testing.
scan(testplan: "MyTestPlan")
skip_build
boolean
default:"false"
Should the build be skipped before testing?
scan(skip_build: true)
test_without_building
boolean
Test without building (requires derived_data_path).
scan(
  test_without_building: true,
  derived_data_path: "./DerivedData"
)
build_for_testing
boolean
Build for testing only, does not run tests.
scan(build_for_testing: true)
derived_data_path
string
The directory where build products will go.
scan(derived_data_path: "./DerivedData")
result_bundle
boolean
default:"false"
Should an Xcode result bundle be generated?
scan(result_bundle: true)
parallel_testing
boolean
Optionally override parallel testing setting.
scan(parallel_testing: true)
concurrent_workers
integer
Specify the exact number of test runners.
scan(concurrent_workers: 4)
max_concurrent_simulators
integer
Constrain number of simulators to test concurrently.
scan(max_concurrent_simulators: 2)
disable_concurrent_testing
boolean
default:"false"
Do not run test bundles in parallel.
scan(disable_concurrent_testing: true)
reset_simulator
boolean
default:"false"
Erase the simulator before running.
scan(reset_simulator: true)
reinstall_app
boolean
default:"false"
Uninstall the app before running.
scan(reinstall_app: true)
fail_build
boolean
default:"true"
Should this step stop the build if tests fail?
scan(fail_build: false)
slack_url
string
Create an Incoming WebHook to post results to Slack.
scan(slack_url: "https://hooks.slack.com/...")
slack_channel
string
#channel or @username for Slack.
scan(
  slack_url: "https://hooks.slack.com/...",
  slack_channel: "#ios-tests"
)
slack_only_on_failure
boolean
default:"false"
Only post on Slack if tests fail.
scan(slack_only_on_failure: true)

Output Formats

scan(output_types: "html")

Using with fastlane

Basic Testing

lane :test do
  scan(scheme: "MyApp")
end

Testing with Code Coverage

lane :test do
  scan(
    scheme: "MyApp",
    code_coverage: true,
    open_report: true
  )
end

Test Multiple Devices

lane :test_all do
  scan(
    scheme: "MyApp",
    devices: [
      "iPhone 15",
      "iPhone 15 Pro Max",
      "iPad Pro (12.9-inch)"
    ]
  )
end

Run Specific Tests

lane :test_login do
  scan(
    scheme: "MyApp",
    only_testing: [
      "MyAppTests/LoginTests",
      "MyAppTests/AuthenticationTests"
    ]
  )
end

Skip Slow Tests

lane :test_fast do
  scan(
    scheme: "MyApp",
    skip_testing: [
      "MyAppTests/IntegrationTests",
      "MyAppUITests"
    ]
  )
end

CI Testing

lane :ci_test do
  scan(
    scheme: "MyApp",
    device: "iPhone 15",
    clean: true,
    code_coverage: true,
    output_types: "html,junit",
    output_directory: "./test_output",
    buildlog_path: "./logs",
    fail_build: true
  )
end

Build and Test Separately

lane :build_and_test do
  # Build for testing
  scan(
    scheme: "MyApp",
    build_for_testing: true
  )
  
  # Run tests later
  scan(
    scheme: "MyApp",
    test_without_building: true,
    derived_data_path: "./DerivedData"
  )
end

Scanfile

Create a Scanfile for persistent configuration:
# Scanfile

scheme "MyApp"

devices [
  "iPhone 15",
  "iPad Pro (12.9-inch)"
]

clean true
code_coverage true

output_directory "./test_output"
output_types "html,junit"

open_report false

slack_url "https://hooks.slack.com/..."
slack_channel "#ios-tests"
slack_only_on_failure true
Generate a template:
fastlane scan init

Parallel Testing

Enable Parallel Testing

scan(
  scheme: "MyApp",
  parallel_testing: true,
  concurrent_workers: 4
)

Limit Concurrent Simulators

scan(
  scheme: "MyApp",
  parallel_testing: true,
  max_concurrent_simulators: 2
)

Environment Variables

SCAN_WORKSPACE
string
Path to the workspace file
SCAN_PROJECT
string
Path to the project file
SCAN_SCHEME
string
The project’s scheme
SCAN_DEVICE
string
The device to run tests on
SCAN_CLEAN
boolean
Should the project be cleaned?
SCAN_CODE_COVERAGE
boolean
Should code coverage be generated?
SCAN_OUTPUT_DIRECTORY
string
Output directory for test reports
SCAN_OUTPUT_TYPES
string
Comma-separated output types
SLACK_URL
string
Slack webhook URL for notifications

Tips & Best Practices

Use Scanfile: Store your test configuration in a Scanfile for consistency.
Reset simulator on CI: Use reset_simulator: true on CI to ensure clean test environment.
Enable code coverage: Set code_coverage: true to track test coverage over time.
Shared scheme: Make sure your scheme is marked as “Shared” in Xcode.

Troubleshooting

Scheme not found

Make sure the scheme is marked as “Shared” in Xcode:
  1. Product → Scheme → Manage Schemes
  2. Check “Shared” for your scheme

Simulator not found

List available simulators:
xcrun simctl list devices
Then use the exact name:
scan(device: "iPhone 15")

Tests hang

Try resetting the simulator:
scan(
  reset_simulator: true,
  reinstall_app: true
)
  • gym - Build your app
  • snapshot - Automate screenshot generation
  • slather - Generate code coverage reports

Build docs developers (and LLMs) love