出海團隊第一次上傳素材到 App Store Connect 時,幾乎都會撞上同一個數字:十四種語言 × 六張截圖 × 三種裝置尺寸,將近三百張圖。營運同事手動截一輪,一天時間就搭進去,還很容易漏改狀態列時間。這類重複性工作最適合丟給一台跑 macOS 圖形介面的機器批量處理,而不是佔用團隊裡唯一那台 MacBook。
为什么放到云端 Mac mini 上跑
本機筆電跑截圖有兩個硬傷:一是模擬器一開就是四五台,風扇狂轉還佔著螢幕沒法做別的事;二是本機環境和發布分支不同步,截圖用的程式碼常常跟實際送審的版本對不上。把這套流程搬到一台獨享的雲端 Mac mini 上,好處很直接:
- 開多少台模擬器都不影響手邊的開發機;
- 可以固定一個專門出素材用的分支和 Xcode 版本,截圖環境與建構環境徹底分開;
- 任務跑完直接透過 SSH 把結果目錄同步下來,不用守在機器前面等。
截圖自動化本質上是「跑一遍 UI 測試順便截圖」,所以前提是應用程式要能被 UI 測試正常導引到目標頁面,埋點式的強制登入、驗證碼攔截都要提前做好測試環境開關。
用 fastlane snapshot 搭起流水线
先在專案裡初始化 snapshot,產生 SnapshotHelper.swift 和 Snapfile:
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 裡一張一張手動對齊。
常见坑与排查
- 狀態列沒統一:漏掉
override_status_bar(true)會導致截圖裡的時間、電量、訊號格各不一樣,審核團隊肉眼就能看出是拿真機隨手截的。 - 系統字型缺失導致排版跑掉:部分語言(泰文、阿拉伯文)在舊版 iOS 模擬器上渲染行高異常,升級到最新 Xcode 附帶的模擬器執行環境通常就能解決。
- 鍵盤語言殘留:UI 測試若涉及輸入框,記得在測試前清掉模擬器的鍵盤設定,否則某些語言的截圖裡會帶出上一次殘留的輸入面板。
- 模擬器狀態未重置:多輪測試之間建議執行
xcrun simctl erase all清空所有模擬器資料,避免登入狀態、快取內容串到下一個語言的截圖裡。
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、電量改成滿格,不需要手動修圖。