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:
Quick Start
Run tests
scan will automatically detect your project and run tests.
View results
Test results are saved in ./test_output directory.
CLI Commands
Run tests
Run specific scheme
Run on specific device
Initialize Scanfile
Key Options
Path to the workspace file. scan ( workspace: "MyApp.xcworkspace" )
Path to the project file. scan ( project: "MyApp.xcodeproj" )
The project’s scheme. Make sure it’s marked as Shared.
The name of the simulator to run tests on. scan ( device: "iPhone 15" )
Array of devices to run tests on. scan ( devices: [
"iPhone 15" ,
"iPad Pro (12.9-inch)"
])
Should the project be cleaned before building?
Should code coverage be generated? scan ( code_coverage: true )
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" )
Comma-separated list of output file names. scan ( output_files: "report.html,report.junit" )
The directory where build logs will be stored. scan ( buildlog_path: "./logs" )
Should the HTML report be opened when tests complete?
The configuration to use when building (defaults to ‘Release’). scan ( configuration: "Debug" )
Pass additional arguments to xcodebuild. scan ( xcargs: "-parallel-testing-enabled YES" )
Array of test identifiers to run. scan ( only_testing: [
"MyAppTests/LoginTests" ,
"MyAppTests/SignupTests"
])
Array of test identifiers to skip. scan ( skip_testing: [
"MyAppTests/SlowTests"
])
The testplan associated with the scheme to use for testing. scan ( testplan: "MyTestPlan" )
Should the build be skipped before testing?
Test without building (requires derived_data_path). scan (
test_without_building: true ,
derived_data_path: "./DerivedData"
)
Build for testing only, does not run tests. scan ( build_for_testing: true )
The directory where build products will go. scan ( derived_data_path: "./DerivedData" )
Should an Xcode result bundle be generated? scan ( result_bundle: true )
Optionally override parallel testing setting. scan ( parallel_testing: true )
Specify the exact number of test runners. scan ( concurrent_workers: 4 )
max_concurrent_simulators
Constrain number of simulators to test concurrently. scan ( max_concurrent_simulators: 2 )
disable_concurrent_testing
Do not run test bundles in parallel. scan ( disable_concurrent_testing: true )
Erase the simulator before running. scan ( reset_simulator: true )
Uninstall the app before running. scan ( reinstall_app: true )
Should this step stop the build if tests fail?
Create an Incoming WebHook to post results to Slack. scan ( slack_url: "https://hooks.slack.com/..." )
#channel or @username for Slack. scan (
slack_url: "https://hooks.slack.com/..." ,
slack_channel: "#ios-tests"
)
Only post on Slack if tests fail. scan ( slack_only_on_failure: true )
HTML Report
JUnit XML
JSON Compilation Database
Multiple 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:
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
Path to the workspace file
The device to run tests on
Should the project be cleaned?
Should code coverage be generated?
Output directory for test reports
Comma-separated output types
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:
Product → Scheme → Manage Schemes
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