海外展開を進めるチームが App Store Connect に素材をアップロードする際、最初に必ずぶつかるのが同じ数字だ。14言語 × 6枚のスクリーンショット × 3種類のデバイスサイズ、合計でおよそ300枚近くにもなる。運営担当が手作業で撮り直すと丸1日を消費し、しかもステータスバーの時刻を直し忘れて撮り漏れることも多い。この種の繰り返し作業こそ、チームで唯一のMacBookを占有するのではなく、macOSのGUIを回せる専用マシンにまとめて任せるべきものだ。
なぜクラウドのMac miniで実行するのか
ローカルのノートPCでスクリーンショット作業を実行するには、構造的な弱点が2つある。1つ目は、シミュレータを4〜5台同時に立ち上げるとファンが全開になり、画面も占有されて他の作業ができなくなること。2つ目は、ローカル環境とリリースブランチがずれやすく、スクリーンショットに使ったコードと実際に審査に出すバージョンが食い違うことが頻発する点だ。この一連の作業を専用の物理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テスト側では対象画面でスクリーンショット関数を1行呼ぶだけでよく、多言語切り替えのロジックを意識する必要はない。fastlaneがlanguages配列を自動的に1つずつ回してくれる:
func testHomeScreenshot() {
let app = XCUIApplication()
app.launch()
snapshot("01Home")
}
シミュレータの並列実行とリソースの境界
concurrent_simulators(true)を有効にすると、fastlaneはデバイス数に応じてシミュレータインスタンスを自動的に作成する。しかし「起動できる」ことと「同時にそれだけ起動すべき」ことは別問題で、本当のボトルネックはメモリだ。クラウドMac miniの代表的な2構成で見てみると:
| 機種 | メモリ | 推奨並列シミュレータ数 | フル実行(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による枠付け
1回実行すると数百枚の画像ができるが、命名の乱れは後でApp Store Connectにアップロードする際に最もミスが起きやすいポイントだ。出力ディレクトリは言語コード/デバイス名/番号-画面名.pngの3階層構成にしておき、Snapfileのデフォルトの挙動に合わせること。自分で別の階層構造を上乗せしないのが望ましい。
デバイス枠付きの見せ方が必要な場合(App Storeの商品ページ自体ではなく、宣伝用ページで使うことが多い)はframeitを使う:
fastlane frameit
スクリーンショットのメタデータを読み取り、対応する機種の枠を自動的にマッチさせてscreenshots/framedディレクトリへ一括出力してくれる。Photoshopで1枚ずつ位置を合わせる必要はない。
よくある落とし穴と対処法
- ステータスバーが統一されていない:
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台が目安です。それ以上だとメモリ圧迫でキューが詰まり、逆に遅くなるため2回に分けたほうが速く終わります。
スクリーンショットのステータスバーはどう統一しますか?
Snapfile に override_status_bar true を設定すると、fastlane が時刻を9:41、バッテリーを満タンに自動で揃えてくれるため、手作業での修正は不要です。
ビルド専用に独享Mac miniが必要ですか?
東京・ソウル・シリコンバレーの5拠点に物理ノードを配置。1日単位でレンタル可能、申込から10分でVNC/SSH接続情報をお届けします。
今すぐMac miniをレンタル