From fastlane-skill
Sets up Fastlane Snapshot to automate App Store screenshot capture across multiple devices and languages, reducing manual effort for app updates.
How this skill is triggered — by the user, by Claude, or both
Slash command
/fastlane-skill:snapshotThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Set up Fastlane Snapshot to automatically capture App Store screenshots across multiple devices and languages.
Set up Fastlane Snapshot to automatically capture App Store screenshots across multiple devices and languages.
fastlane --version 2>/dev/null | grep "fastlane " | head -1 || echo "✗ Not installed - run: brew install fastlane"ls fastlane/Fastfile 2>/dev/null && echo "✓ Found" || echo "✗ Not found - run /setup-fastlane first"ls fastlane/Snapfile 2>/dev/null && echo "✓ Already configured" || echo "○ Not configured yet"find . -maxdepth 3 -name "*UITests*" -type d 2>/dev/null | head -1 || echo "○ No UI test target found"xcrun simctl list devices available | grep -E "iPhone|iPad" | head -3App Store requires screenshots for multiple device sizes. Manual capture means:
Snapshot automates this: run once, get all screenshots.
fastlane snapshot init
This creates:
fastlane/Snapfile - Configuration filefastlane/SnapshotHelper.swift - Helper for UI testsEdit fastlane/Snapfile:
# Devices to capture (App Store requirements)
devices([
"iPhone 15 Pro Max", # 6.7" display (required)
"iPhone 15 Pro", # 6.1" display
"iPhone SE (3rd generation)", # 4.7" display (if supporting older phones)
"iPad Pro 13-inch (M4)", # iPad screenshots (if universal app)
])
# Languages to capture
languages([
"en-US",
# "ja", # Japanese
# "de-DE", # German
# "fr-FR", # French
# "es-ES", # Spanish
])
# UI Test scheme
scheme("YourAppUITests")
# Output directory
output_directory("./fastlane/screenshots")
# Clear old screenshots before capture
clear_previous_screenshots(true)
# Stop on first error (set false to continue despite failures)
stop_after_first_error(true)
# Dark mode variants (iOS 13+)
# dark_mode(true)
# Workspace or project (uncomment one)
# workspace("YourApp.xcworkspace")
# project("YourApp.xcodeproj")
Add SnapshotHelper.swift to your UI test target:
fastlane/SnapshotHelper.swift into XcodeImport and configure in your UI test file:
import XCTest
class ScreenshotTests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
let app = XCUIApplication()
setupSnapshot(app) // Initialize snapshot
app.launch()
}
func testTakeScreenshots() throws {
let app = XCUIApplication()
// Screenshot 1: Home screen
snapshot("01_HomeScreen")
// Navigate to feature and capture
app.buttons["Feature"].tap()
snapshot("02_FeatureScreen")
// Screenshot with content
app.textFields["Search"].tap()
app.textFields["Search"].typeText("Example")
snapshot("03_SearchResults")
// Settings screen
app.buttons["Settings"].tap()
snapshot("04_Settings")
// Any additional screens...
snapshot("05_DetailView")
}
}
# Capture all screenshots
fastlane snapshot
# Specific device only
fastlane snapshot --devices "iPhone 15 Pro Max"
# Specific language only
fastlane snapshot --languages "en-US"
# Skip launch (use existing simulator state)
fastlane snapshot --skip_open_summary
Screenshots are saved to fastlane/screenshots/{language}/{device}/.
After capturing, upload with deliver:
# Upload screenshots only (no binary, no metadata)
fastlane deliver --skip_binary_upload --skip_metadata --overwrite_screenshots
deliver reads fastlane/screenshots/<locale>/, maps each PNG to a display size by its pixel dimensions, and orders them by filename sort — so prefix names (01_, 02_, …).
If you frame with your own pipeline (a design tool, a web framer) instead of frameit, deliver can still upload them — stage them into the <locale>/ layout and let dimension-mapping place them:
lane :upload_framed_screenshots do
framed = File.expand_path("../path/to/your/framed", __dir__)
staging = File.expand_path("./screenshots/en-US", __dir__) # deliver wants <locale>/
FileUtils.rm_rf(File.dirname(staging)); FileUtils.mkdir_p(staging)
FileUtils.cp(Dir.glob("#{framed}/*.png"), staging)
deliver(skip_binary_upload: true, skip_metadata: true,
skip_screenshots: false, overwrite_screenshots: true, force: true)
end
RGB only — no alpha. App Store rejects screenshots with an alpha channel (
ERROR ITMS-90475/IMAGE_ALPHA_NOT_ALLOWED). Web/toPngframers usually add one — flatten before upload:magick in.png -alpha remove -alpha off out.png.
If your framer is a full editor (not a static page), don't hand-click its
browser "Export bundle" button — that isn't repeatable and dumps a zip in the
editor's own nested layout. Instead, expose a tiny dev-only export hook on
window that reuses the editor's existing capture function (it already knows the
exact per-size toPng render), then drive it with headless Chrome:
// in the editor (dev only): loop slides × required sizes through the UI's own
// captureSlide(), return base64 PNGs — no zip, no download dialog.
window.__exportFramed = async () => {/* … returns [{name, dataUrl}] */};
// driver (puppeteer-core → system Chrome): call the hook, flatten, write flat.
const items = await page.evaluate(() => window.__exportFramed());
for (const { name, dataUrl } of items)
execFileSync("magick", ["png:-","-alpha","remove","-alpha","off", out(name)],
{ input: Buffer.from(dataUrl.split(",")[1], "base64") });
One command regenerates the whole set into your canonical output dir, already
RGB — then point the upload_framed_screenshots lane above at that dir. This
beats the editor's bundle button (which still emits RGBA in a layout deliver
can't read) and keeps the pipeline scriptable.
deliver does NOT uploaddeliver handles app-level metadata + screenshots only. It does not touch:
AppIcon asset catalog), never a separate upload.deliver needs App Store Connect credentials. For non-interactive/CI runs, configure an App Store Connect API key (.p8) — see the match skill's CI/CD section. With only an Apple ID it falls back to interactive 2FA, which can't be scripted.
| Display Size | Example Devices |
|---|---|
| 6.9" iPhone | iPhone 17 Pro Max, 16 Pro Max, 15 Pro Max, 14 Pro Max, 16/15 Plus |
| 6.5" iPhone | iPhone 14 Plus, 13/12/11 Pro Max, XS Max, XR |
| 13" iPad | iPad Pro (M5/M4), iPad Air (M4/M3/M2) |
| 12.9" iPad | iPad Pro (2nd gen) |
Minimum: 6.9" iPhone screenshots are the primary requirement. 6.5" is only required if 6.9" aren't provided. Smaller iPhone sizes (6.3", 6.1", 5.5", 4.7") auto-scale from the larger ones — only supply them if you want pixel-perfect framing on those displays. iPad screenshots are required if the app runs on iPad.
For exact pixel dimensions, see Apple's screenshot specifications — they update with each new device generation, so always check the current source.
Add device frames around screenshots using frameit:
# Install frameit
brew install imagemagick
# Frame screenshots
fastlane frameit
# Silver device frames
fastlane frameit silver
Create fastlane/screenshots/Framefile.json for custom titles:
{
"default": {
"title": {
"font": "./fonts/MyFont.ttf",
"color": "#000000"
},
"background": "#FFFFFF",
"padding": 50,
"show_complete_frame": true
}
}
Re-run fastlane snapshot init and add the helper to your UI test target.
Reset the simulator:
xcrun simctl shutdown all
xcrun simctl erase all
setupSnapshot(app) is called before app.launch()sleep(1) // Wait for content
snapshot("01_HomeScreen")
Check available simulators:
xcrun simctl list devices available
Update Snapfile device names to match exactly.
Use accessibility identifiers:
// In your app code
button.accessibilityIdentifier = "settingsButton"
// In UI test
app.buttons["settingsButton"].tap()
Under any automated run (xcodebuild, fastlane snapshot, simctl), StoreKit renders Product.displayPrice from the US storefront — regardless of the device region or the .storekit _storefront/_locale. Only the Xcode IDE Run button honours a configured storefront, and that can't be scripted. To screenshot a price in another currency, render it from your own region/pricing source behind a #if DEBUG, launch-arg-gated hook instead of relying on live StoreKit.
Add a dedicated lane for screenshots:
lane :screenshots do
snapshot(
scheme: "YourAppUITests",
devices: ["iPhone 15 Pro Max", "iPad Pro 13-inch (M4)"],
languages: ["en-US"]
)
# Optional: frame screenshots
# frameit(white: true)
end
lane :upload_screenshots do
deliver(
skip_binary_upload: true,
skip_metadata: true,
overwrite_screenshots: true
)
end
fastlane/
├── Snapfile # Snapshot configuration
├── SnapshotHelper.swift # Helper for UI tests (copy to test target)
└── screenshots/
├── en-US/
│ ├── iPhone 15 Pro Max/
│ │ ├── 01_HomeScreen.png
│ │ ├── 02_FeatureScreen.png
│ │ └── ...
│ └── iPad Pro 13-inch (M4)/
│ └── ...
└── ja/
└── ...
# 1. Set up snapshot
fastlane snapshot init
# 2. Write UI tests with snapshot() calls
# 3. Capture screenshots
fastlane snapshot
# 4. Review screenshots in fastlane/screenshots/
# 5. Optional: add device frames
fastlane frameit
# 6. Upload to App Store Connect
fastlane deliver --skip_binary_upload --skip_metadata
npx claudepluginhub greenstevester/fastlane-skill --plugin snapshotCreates, revises, translates, crops, validates, and prepares App Store marketing screenshots via a workspace workflow. Useful for building `.appstore-screenshots` assets from app inspection and install-promise copy.
Provides iOS Simulator bash commands for screenshots with auto-resize to API limits, device management, app operations, video recording, and xcodebuild. Use for simulator testing workflows.
Generates HiDPI marketing screenshots of web apps using Playwright for Product Hunt, social media, landing pages, and documentation.