iOS 多语言截图自动化:在云端 Mac mini 上批量产出 App Store 素材

CI/CD 实践 ·约 6 分钟阅读

iOS 多语言截图自动化:在云端 Mac mini 上批量产出 App Store 素材

出海团队第一次去 App Store Connect 传素材时都会撞上同一个数字:十四个语言 × 六张截图 × 三种设备尺寸,近三百张图。运营同学手工截一遍,一天搭进去还容易漏改状态栏时间。这类重复劳动最适合丢给一台跑 macOS 图形界面的机器批量处理,而不是占用团队里唯一那台 MacBook。

为什么放到云端 Mac mini 上跑

本地笔记本跑截图有两个硬伤:一是模拟器一开就是四五台,风扇狂转还要占着屏幕不能干别的;二是本地环境和发布分支不同步,截图用的代码跟实际提审的版本经常对不上。把这套流程搬到一台独享的 Mac mini 物理节点上,好处很直接:

截图自动化本质是"跑一遍 UI 测试顺便截图",所以前提是应用要能被 UI 测试正常驱动到目标页面,埋点式的强制登录、验证码拦截都要提前做测试环境开关。

用 fastlane snapshot 搭起流水线

先在项目里初始化 snapshot,生成 SnapshotHelper.swiftSnapfile:

cd MyApp
fastlane snapshot init

Snapfile 是整个流程的配置核心,把语言列表、设备列表、输出目录都写死在这里,避免每次手动传参数:

devices([
  "iPhone 16",
  "iPhone 16 Pro Max",
  "iPad Pro (12.9-inch)"
])

languages([
  "zh-Hans", "zh-Hant", "en-US", "ja",
  "ko", "de-DE", "fr-FR", "es-ES",
  "pt-BR", "ru", "it", "id",
  "th", "vi"
])

scheme("MyAppUITests")
output_directory("./screenshots")
clear_previous_screenshots(true)
override_status_bar(true)
concurrent_simulators(true)

UI 测试里只需要在目标页面调一句截图函数,不用关心多语言逻辑,fastlane 会自动按 languages 数组挨个跑:

func testHomeScreenshot() {
    let app = XCUIApplication()
    app.launch()
    snapshot("01Home")
}

多模拟器并发与资源边界

concurrent_simulators(true) 打开后,fastlane 会按设备数量自动创建对应的模拟器实例。但"能开"不代表"该同时开这么多",内存是真正的瓶颈。以云端 Mac mini 的两档配置为例:

机型 内存 建议并发模拟器数 完整跑一轮(14 语言 × 3 设备)耗时参考
Mini Basic(M4/16GB) 16GB 3~4 台 约 55~70 分钟
Mini Standard(M4/24GB) 24GB 5~6 台 约 35~45 分钟

超过建议并发数后,系统会因为内存压力把模拟器进程换出到磁盘,截图任务反而会排队变慢,还容易触发个别语言截图失败需要重跑。稳妥做法是用 xcrun simctl list devices 先确认当前已启动的模拟器数量,超过阈值就分批执行:

xcrun simctl list devices booted
fastlane snapshot --devices "iPhone 16" --languages "zh-Hans,ja,ko"
fastlane snapshot --devices "iPhone 16" --languages "en-US,de-DE,fr-FR"

命名规范与 frameit 加框

跑完一轮会得到几百张图,命名混乱是后续上传 App Store Connect 时最容易出错的环节。建议输出目录按 语言代码/设备名/序号-页面名.png 三层结构存放,和 Snapfile 里的默认行为保持一致,不要自己再改一层命名。

如果截图需要带设备边框展示(常见于宣传页而不是 App Store 商店页本身),用 frameit:

fastlane frameit

它会读取截图元数据自动匹配对应机型的边框,批量输出到 screenshots/framed 目录,不需要在 Photoshop 里一张张对齐。

常见坑与排查

CI 集成:定时增量出图

素材不需要每次发版都全量重截,只有 UI 改动的页面才需要更新对应截图。做法是在云端 Mac mini 上跑一个定时任务,对比 Git diff 涉及的页面,只重跑相关的 UI 测试用例:

git diff --name-only HEAD~1 HEAD | grep "Screens/" > changed_screens.txt

配合 self-hosted runner 把这段逻辑接进 GitHub Actions,PR 合并到发布分支时自动触发增量截图,再由人工确认无误后统一上传。整套流程搬到独享物理节点上跑,不会和团队其他人的构建任务抢资源,也不用为了截图专门占用一台开发机。

常见问题

一次跑多少个模拟器比较合适?

M4 芯片、16GB 内存的机型建议同时开 3~4 台模拟器截图,超过这个数字截图任务会因内存压力而排队变慢,不如分两批跑更快。

截图里的状态栏时间和电量怎么统一?

在 Snapfile 里设置 override_status_bar true,fastlane 会自动把时间改成 9:41、电量改成满格,不需要手动截屏后再修图。

需要一台独享 Mac mini 跑构建?

新加坡/东京/韩国/香港/美西五地物理节点,按天起租,10 分钟内收到 VNC/SSH 凭据。

立即租用 Mac mini