Automating Store Screenshots With fastlane snapshot and frameit
8 min read

Automating Store Screenshots With fastlane snapshot and frameit

Set up fastlane snapshot, screengrab and frameit to capture, frame and upload store screenshots from CI, and learn when that automation actually pays off.

A
Written by
Begin reading

fastlane automates store screenshots in three stages. snapshot (iOS) and screengrab (Android) run your UI tests on simulators or emulators in every language you list and save a screenshot at each point you mark. frameit puts device frames, a background and a headline around them. deliver and supply upload the results to App Store Connect and Google Play. Once it's set up, regenerating every screenshot is one command.

That last sentence is the whole appeal, and the setup is the whole cost. This guide walks through each piece with working config, then gives a fair view of when it's worth it.

Key takeaways
  • fastlane snapshot runs Xcode UI tests across the devices and languages in a Snapfile and captures a screenshot wherever a test calls snapshot("name").
  • screengrab is the Android equivalent: it runs instrumentation tests, and Screengrab.screenshot("name") captures each screen into fastlane/metadata/android.
  • frameit adds device frames, a background and titles from a Framefile.json plus per-language title.strings files; it needs ImageMagick.
  • frameit's frames lag new hardware: in October 2026 its frames repository stopped at the iPhone 17 family, the 12.9-inch iPad Pro (4th generation) and the Pixel 5.
  • The automation pays off for apps that release often in several languages; for one language and rare releases, manual capture is usually faster.

How fastlane snapshot works

fastlane snapshot uses Xcode's UI testing. You write a UI test that walks through your app; at each screen worth showing, it calls snapshot("01Home"). snapshot runs that test once per device and language combination and collects the images into fastlane/screenshots/<language>/.

Setup

  1. Add a UI Test target to your Xcode project if you don't have one.
  2. Run fastlane snapshot init in the project folder. It creates a Snapfile and a SnapshotHelper.swift.
  3. Add SnapshotHelper.swift to the UI Test target.

The UI test

import XCTest

final class ScreenshotTests: XCTestCase {
    override func setUp() {
        continueAfterFailure = false
        let app = XCUIApplication()
        setupSnapshot(app)
        app.launchArguments += ["-demoData"]
        app.launch()
    }

    func testStoreScreenshots() {
        let app = XCUIApplication()

        snapshot("01Home")

        app.tabBars.buttons["Budget"].tap()
        snapshot("02Budget")

        app.buttons["Add expense"].tap()
        snapshot("03AddExpense")
    }
}

setupSnapshot(app) passes the current language and locale into the app, which is how one test produces every localized set. The -demoData argument is our own convention: the app checks for it in debug builds and loads sample content, so no screenshot shows an empty state. Prefixing names with numbers keeps the files in upload order.

The Snapfile

# fastlane/Snapfile
devices([
  "iPhone 17 Pro Max",
  "iPad Pro 13-inch (M5)"
])

languages([
  "en-US",
  "de-DE",
  "ja"
])

scheme("MyAppUITests")
output_directory("./fastlane/screenshots")
clear_previous_screenshots(true)

override_status_bar(true)   # 9:41, full battery, full reception
dark_mode(false)
launch_arguments(["-demoData"])

Device names must match simulators installed in your Xcode; xcrun simctl list devicetypes shows them. An iPhone 17 Pro Max simulator captures at 1320 × 2868, one of the sizes App Store Connect accepts for its largest iPhone slot. Then run:

fastlane snapshot

Other snapshot options worth knowing: localize_simulator sets the simulator's system language too, erase_simulator starts each run from a clean device, reinstall_app uninstalls the app first, and override_status_bar_arguments takes your own simctl status_bar flags. If you want to know what those flags do, our guide to clean simulator and emulator screenshots covers them and the manual commands snapshot automates.

screengrab: the Android equivalent

screengrab does the same job with Android instrumentation tests. Add the library to your test dependencies:

// app/build.gradle
androidTestImplementation 'tools.fastlane:screengrab:x.x.x'

Make sure src/debug/AndroidManifest.xml includes the permissions the docs list (such as DISABLE_KEYGUARD), then write the test:

@RunWith(AndroidJUnit4.class)
public class ScreenshotTest {
    @ClassRule
    public static final LocaleTestRule localeTestRule = new LocaleTestRule();

    @BeforeClass
    public static void beforeAll() {
        CleanStatusBar.enableWithDefaults();
    }

    @AfterClass
    public static void afterAll() {
        CleanStatusBar.disable();
    }

    @Rule
    public ActivityScenarioRule<MainActivity> rule =
        new ActivityScenarioRule<>(MainActivity.class);

    @Test
    public void storeScreenshots() {
        Screengrab.screenshot("01_home");
        onView(withId(R.id.nav_budget)).perform(click());
        Screengrab.screenshot("02_budget");
    }
}

LocaleTestRule switches the locale for each run, and CleanStatusBar is a wrapper around Android's System UI demo mode; the docs note it needs API level 23 or higher. Build the APKs, generate a Screengrabfile, and run it:

./gradlew assembleDebug assembleAndroidTest
fastlane screengrab init     # creates the Screengrabfile
fastlane screengrab
# fastlane/Screengrabfile
app_package_name("com.example.app")
app_apk_path("app/build/outputs/apk/debug/app-debug.apk")
tests_apk_path("app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk")
locales(["en-US", "de-DE", "ja-JP"])
clear_previous_screenshots(true)

Screenshots go to fastlane/metadata/android/<locale>/images/phoneScreenshots by default, which is exactly where supply looks for them. One thing to check on Android: Google Play rejects screenshots whose long side is more than twice the short side, so pick an emulator profile that isn't taller than 2:1.

frameit: device frames and headlines

frameit takes the raw captures and puts them inside device frames. It needs ImageMagick (brew install imagemagick). The frames download automatically the first time you run it.

fastlane frameit            # iOS and Mac screenshots
fastlane frameit android    # Android screenshots
fastlane frameit silver     # silver frame variants

A device frame on its own isn't automatically App Store-ready: the framed image may not match any size App Store Connect accepts. fastlane's docs say that for App Store uploads you need a Framefile.json with titles and a background, which produces a full marketing image instead.

Framefile.json

Put it in the screenshots folder:

{
  "device_frame_version": "latest",
  "default": {
    "title": {
      "font": "./fonts/Inter-Bold.ttf",
      "font_size": 110,
      "color": "#0F172A"
    },
    "keyword": {
      "font": "./fonts/Inter-Bold.ttf",
      "color": "#F97316"
    },
    "background": "./background.jpg",
    "padding": 50,
    "show_complete_frame": false,
    "title_below_image": false
  },
  "data": [
    { "filter": "01Home" },
    { "filter": "02Budget", "keyword": { "color": "#3B82F6" } },
    { "filter": "03AddExpense" }
  ]
}

Each data entry matches screenshots whose filename contains its filter, and can override the defaults. The text itself lives in per-language files, title.strings and optionally keyword.strings, inside each language folder:


"01Home" = "See every euro, every month";
"02Budget" = "Budgets that adjust themselves";
"03AddExpense" = "Log a purchase in two taps";

That leading empty line is deliberate: the docs say the file must begin with one, and must be UTF-8 or UTF-16 BE with a BOM. They also state you must provide a background if you want titles. If titles don't show up, check those three things first.

frameit's device frames lag new hardware

frameit's frames come from the separate frameit-frames repository, and frames are added after devices ship. When we looked in October 2026:

FamilyNewest frame in the repositoryNot there yet
iPhoneiPhone 17, 17 Pro, 17 Pro MaxiPhone 18 models, iPhone Air
iPadiPad Pro (12.9-inch) (4th generation), iPad Pro (11-inch)13-inch iPad Pro and iPad Air (M-series)
Google PixelPixel 5Pixel 6 and later
SamsungGalaxy S21 familyNewer Galaxy S models
MacMacBook Air, MacBook Pro 13 and 16Mac frames use a background image plus an offset rather than a device frame

In practice, if you capture on the newest simulator, there may be no frame for it. Your options are capturing on a device that has a frame (and accepting that its screen size may not match the store slot you want), forcing a device type with force_device_type, or skipping frames and using a full-bleed layout.

Uploading with deliver and supply

# iOS: upload only screenshots, replacing what's there
fastlane deliver --skip_binary_upload --skip_metadata --overwrite_screenshots

# Android: upload only images and screenshots
fastlane supply --skip_upload_apk --skip_upload_aab \
  --skip_upload_metadata --skip_upload_changelogs

deliver reads fastlane/screenshots with one subfolder per language and works out the device type from each image's resolution, so every file needs to be an accepted size before you upload. supply reads fastlane/metadata/android/<locale>/images/, with phoneScreenshots, sevenInchScreenshots and tenInchScreenshots folders. Chained together in a lane, capture to upload is one command:

lane :screenshots do
  snapshot
  frameit(white: true)
  deliver(skip_binary_upload: true, skip_metadata: true,
          overwrite_screenshots: true)
end

If you don't use fastlane but still want to skip the manual upload, we wrote about uploading directly to the app stores from a design tool.

Practical snags to plan for

None of these are reasons not to use fastlane, but each one costs time the first week if you don't expect it.

Screenshot tests are slow, so keep them separate

A run is roughly every screen, times every device, times every language. Three devices and ten languages is thirty passes through your test. Keep screenshot tests in their own scheme or test plan so they don't run with every unit test, and trigger them on release branches or by hand rather than on every commit.

Wait for content, not for time

A frequent cause of a bad screenshot is capturing a moment too early: a spinner, a half-loaded image, a list still animating in. In UI tests, wait for a specific element to exist (waitForExistence(timeout:) on iOS, an Espresso idling resource or a UI Automator wait on Android) before calling snapshot. Fixed sleeps work on your machine and fail on a slower CI runner.

Make demo data deterministic

If the screenshot shows dates, "3 days ago" changes every day and totals drift as fixtures change. Pin the clock in your demo-data mode, use fixed fixtures, and the screenshots only change when the UI does. That also makes the output diffable in review, which is one of the real advantages of keeping screenshots in version control.

Translated UI is only half of a localized screenshot

snapshot gives you the app's UI in each language for free. The headlines in title.strings still need writing per language, and a direct translation of a punchy English headline often isn't punchy. Our guide on localizing App Store screenshots covers what to adapt beyond the words.

CI needs a Mac with the right simulators

snapshot runs on macOS with Xcode, so CI means a macOS runner with the simulator runtimes your Snapfile names installed. Device names in the Snapfile have to match simulators that exist on that runner, so pin the Xcode version your CI uses and update the Snapfile when you move to a new one.

When automation pays off, and when it doesn't

We build a design tool, so take our view with that in mind. But fastlane is genuinely the right answer for some teams.

fastlane (snapshot + frameit)A design tool or generator
CostFree, open sourceFree tiers to paid plans
SetupUI tests, Snapfile, Framefile, Ruby and ImageMagickUpload captures, pick a layout
Re-running after a UI changeOne command, every device and languageRe-capture and replace by hand
Visual controlConfiguration: font, colours, padding, backgroundA visual canvas
Device framesWhatever the frames repository hasDepends on the tool's library
Lives in version controlYesUsually not

Automation tends to pay off when:

  • You release every few weeks and the UI on your screenshots changes between releases.
  • You ship in many languages, where each capture multiplies by the number of locales.
  • You already have UI tests, or want them anyway.
  • Someone on the team is comfortable owning a Ruby and ImageMagick toolchain in CI.

It tends not to when:

  • You ship in one or two languages and update screenshots a few times a year.
  • The screenshots are a design problem: custom layouts, illustrations, a panoramic set.
  • Your screens need state that's hard to reach from a UI test (payments, live data, other users).

Many teams split the difference: snapshot for clean, localized captures, and a design tool for the marketing layer on top. Our Launch Shots vs fastlane frameit comparison lays that out, and if you're weighing a design tool, our guide to making App Store screenshots in Figma shows the manual alternative. Launch Shots sits in the second camp: you bring captures (including ones from snapshot), and it handles the layout, translation and upload.

Try it now

Design store-ready screenshots in minutes — free, no export watermarks.

Launch Shots is free to start. Every template (110+), 220+ device frames plus 3D mockups, AI localization, and direct upload to App Store Connect and Google Play Console. No credit card needed.

Start creating free →

The short version

  • iOS: UI test target, fastlane snapshot init, setupSnapshot(app), snapshot("01Home"), a Snapfile with devices, languages and override_status_bar(true).
  • Android: tools.fastlane:screengrab, LocaleTestRule, CleanStatusBar, Screengrab.screenshot(), a Screengrabfile.
  • frameit: ImageMagick, Framefile.json with a background, title.strings per language starting with an empty line.
  • Check the frames repository has your capture device before you depend on it.
  • deliver and supply upload; check every file is an accepted size first. Our screenshot size reference and the ASO launch checklist help with the rest of the listing.

Frequently asked questions

05
What is fastlane snapshot?
fastlane snapshot is an open-source tool that automates iOS screenshots. It runs your Xcode UI tests on the simulators and languages you list in a Snapfile, and saves a screenshot wherever a test calls snapshot("name"). The results land in per-language folders that fastlane deliver can upload to App Store Connect. It is free and part of the fastlane toolchain.
Does fastlane frameit support the latest iPhones?
frameit's device frames come from the separate frameit-frames repository and are added after new devices ship. When we checked in October 2026, the repository had iPhone 17 family frames but none for iPhone 18 models or iPhone Air, and its newest iPad Pro frame was the 12.9-inch 4th generation. Check the repository for the device you capture on before relying on it.
Is fastlane snapshot still worth using?
It's worth it when you ship often, in several languages, and your app UI changes between releases, because regenerating every screenshot becomes one command. For an app with one language and occasional releases, the time spent writing and maintaining UI tests for screenshots is often more than capturing a handful of screens by hand.
How do I add a clean status bar with fastlane snapshot?
Set override_status_bar(true) in your Snapfile. fastlane snapshot then sets every simulator's status bar to 9:41 with full battery and full reception before capturing. For finer control, override_status_bar_arguments passes your own simctl status_bar flags. On Android, screengrab's CleanStatusBar helper does the same job using System UI demo mode on API 23 and later.
Can frameit add marketing headlines to screenshots?
Yes. frameit reads a Framefile.json for layout, fonts and colours, and per-language title.strings and keyword.strings files for the text, keyed by part of the screenshot filename. A background image is required for titles. It produces framed screenshots with a title above or below the device, though layout options are configuration rather than a visual design canvas.