Creating screenshots is a tedious task that lends itself to automation. Ideally, it would be preferrable if it were possible to have simple, device-idependent testing scripts. The reality is that many exceptions creep into the code that simulates the UI interactions when generating the screenshots:
if UIDevice.current.userInterfaceIdiom == .phone {
// Do something different for iPhones.
}
But the real problem is maintaining these scripts. Whether it is iOS SDK changes, new device names, new screen sizes, or indeed new apps features, suddenly the scripts no longer work. Even if everything worked nicely last time, the tiniest of changes will trigger hours of UI scripts re-tuning.
I usually find myself having to re-run fastlane over and over in order to create new screen shots. Quite often, the scripts work well for some devices but fail for others. At that point it would be nice if fastlane could be instructed to only re-run the scripts for the devices that had failed. But fastlane has no option for that.
But it is possible to re-code the Fastfile
since it is essentially a Ruby script. All I need is a simple check whether the final screenshot exists for a device. When present, don't re-run the lengthy UI scripts for that device.
So here are the relevant parts of my new ./fastlane/Fastfile
; beware custom names like MyApp
:
delete_all_screenshots = false
swift_macro_defs =
"SWIFT_ACTIVE_COMPILATION_CONDITIONS='FASTLANE'"
output_directory = "./screenshots/fastlane"
# All languages and devices to run:
all_languages = [ "en-us" ]
all_devices = [
"iPad Pro (9.7-inch)",
"iPad Pro (10.5-inch)",
## "iPad Pro (11-inch)",
"iPad Pro (11-inch) (4th generation)",
"iPad Pro (12.9-inch) (2nd generation)",
"iPad Pro (12.9-inch) (6th generation)",
"iPhone 8",
"iPhone 8 Plus",
"iPhone 14",
"iPhone 14 Plus",
"iPhone 14 Pro",
"iPhone 14 Pro Max",
"Apple TV",
"Apple TV 4K (3rd generation)",
]
platform ::ios do
desc "Take screenshots (iOS)"
lane :ios_screenshot do
all_languages.each do |language|
# Work out the path to the screenshots.
# Beware: CWD is the directory of this file!
path_shot = File.join(
"..", output_directory, language
)
run_devices = Array.new
all_devices.each do |device|
# Neither 'Apple TV' nor 'Apple Watch' devices
next if device.start_with? "Apple"
unless delete_all_screenshots then
# If the last screenshot exists, skip this device:
last_shot = device + "-10_Exercises.lite.png"
last_shot = device + "-09_Exercises.lite.png" \
if device.start_with? "iPad"
## puts "Checking if file exists: #{last_shot}"
file_shot = File.join(path_shot, last_shot)
next if File.exists?(file_shot)
end
run_devices << device
end
next if run_devices.empty?
snapshot(
project: "./MyApp.xcodeproj",
scheme: "MyApp-iOS",
xcargs: swift_macro_defs,
devices: run_devices,
languages: [ language ],
localize_simulator: true,
reinstall_app: true,
headless: false,
concurrent_simulators: false,
clean: delete_all_screenshots,
clear_previous_screenshots:
delete_all_screenshots,
stop_after_first_error: true,
number_of_retries: 0,
override_status_bar: true,
test_without_building: false,
output_directory: output_directory,
disable_slide_to_type: true,
skip_open_summary: true,
only_testing: [
"UITests-iOD/UITests_Fastlane",
],
)
delete_all_screenshots = false
end
end
end
If you want to recreate all screenshots, you could set delete_all_screenshots
to true
or, well, delete the files manually.
Also note the SWIFT_ACTIVE_COMPILATION_CONDITIONS
compiler flag. This allows me to define a FASTLANE
macro that can be used in the app's Swift code:
#if FASTLANE
loadExampleData()
#endif