
If your app ships in one language and you refresh screenshots twice a year, manual capture is fine. The iOS Simulator export guide covers that. Automation starts paying for itself as soon as any of these is true:
fastlane is an open-source toolchain for mobile releases. These are the actions that handle screenshots:
| Action | What it does | Platform |
|---|---|---|
snapshot (capture_screenshots) | Runs your UI tests across simulators and languages and captures screenshots | iOS |
frameit (frame_screenshots) | Wraps screenshots in device frames, with optional background and captions | iOS / Android |
deliver (upload_to_app_store) | Uploads screenshots and metadata to App Store Connect | iOS |
screengrab / supply | Android equivalents for capture and upload | Android |
From the project root, run:
fastlane snapshot init
This creates two files:
Snapfile holds your devices, languages and output folder.SnapshotHelper.swift must be added to your UI test target.Then call it from a UI test:
let app = XCUIApplication()
setupSnapshot(app)
app.launch()
// Navigate to each screen you want to capture
snapshot("01Home")
app.buttons["Search"].tap()
snapshot("02Search")
Start each name with a number. Files then sort in the order you want them on the store.
devices([
"iPhone 17 Pro Max",
"iPhone 17",
"iPad Pro 13-inch (M4)"
])
languages(["en-US", "zh-Hans", "ja"])
scheme("YourAppUITests")
output_directory("./fastlane/screenshots")
clear_previous_screenshots(true)
override_status_bar(true)
A few notes:
xcrun simctl list devices to check. Pick devices that cover the display classes you need. The required screenshots FAQ lists the mandatory ones.override_status_bar(true) gives you a clean time, full battery and full signal. To do it by hand, see the status bar cleanup guide.Run it:
fastlane snapshot
Screenshots are saved to fastlane/screenshots/<language>/, and fastlane writes an HTML summary page you can review.
frameit needs ImageMagick (brew install imagemagick on macOS). Then run:
fastlane frameit
The first run downloads the device frame assets. For backgrounds and captions, put a Framefile.json in fastlane/screenshots/:
{
"default": {
"keyword": { "color": "#FFD60A" },
"title": { "color": "#FFFFFF" },
"background": "./background.png",
"padding": 50,
"show_complete_frame": true
},
"data": [
{ "filter": "01Home", "keyword": { "color": "#FFD60A" } }
]
}
Caption text for each language goes in title.strings and keyword.strings inside that language's folder, keyed by screenshot name.
Know frameit's limits before you depend on it:
| Limitation | Impact | Workaround |
|---|---|---|
| New device frames can arrive late | The latest models may have no frame yet | Use a close model or frame elsewhere |
| Limited layout options | You get "caption + framed screenshot + background" | Build complex layouts in a design tool |
| Framing changes output size | Final images may not match an accepted store size | Check dimensions before uploading |
That last row matters most. A framed image must still be an exact accepted pixel size, or App Store Connect rejects it with a dimensions error. Many teams use snapshot only for raw captures, then produce the final marketing images in a design tool or a browser framing tool such as AppShot Maker.
fastlane deliver --skip_binary_upload --skip_metadata --overwrite_screenshots
--skip_binary_upload uploads assets only, with no build.--skip_metadata leaves your name, description and other text alone.--overwrite_screenshots deletes the existing screenshots in App Store Connect before uploading.deliver gets the locale from the fastlane/screenshots/<language>/ folder name and the display class from each image's pixel size. Any image at the wrong size fails. Set up an App Store Connect API key or your login before you run it.
| iOS | Android equivalent | Notes |
|---|---|---|
| snapshot | screengrab | Captures via instrumentation tests; add the tools.fastlane:screengrab dependency |
snapshot("name") | Screengrab.screenshot("name") | Called from test code |
| deliver | supply | Uploads to Google Play from metadata/android/<locale>/images/ |
For Play size rules, see Google Play screenshot requirements.
| Symptom | Cause | Fix |
|---|---|---|
| "Device not found" | Snapfile name doesn't match the simulator | Copy the exact name from xcrun simctl list devices |
| Loading spinners in shots | Captured before the UI finished rendering | Wait for the target element before calling snapshot |
| Every locale shows English | App ignores the launch language | Check your localization files and confirm setupSnapshot is called |
| Dimensions error on upload | frameit changed the output size | Check pixels, or upload unframed originals |
| Fails on CI | Simulator missing or slow on first boot | Create the simulator and boot it once before the run |