FlutterアプリのApp Storeスクリーンショットを生成する方法:integration_test、ゴールデンテスト、より速い方法(2026年版)
integration_test と flutter drive で実際のフレームをキャプチャする方法、ゴールデンテストでウィジェットのスナップショットをレンダリングする方法(リグレッション検出には優れているが、ストアアセットとしては使いにくい)、またはどちらかを Fastlane/Codemagic CI に組み込む方法です。ただし、これらはいずれもマーケティングカルーセル――キャプション、デバイスフレーム、背景、50言語分のコピー――を作成しません。それは別の工程であり、Mokbi が担う部分です。Mokbi は、キャプチャしたスクリーンショットをコンポーズしてローカライズします。キャプチャ自体は行いません。Flutter で開発して App Store または Play Store にリリースした経験があれば、すでにこのギャップに直面しているはずです。flutter screenshots というコマンドは存在しません。Apple は 1320 × 2868(6.9インチ iPhone)や 2064 × 2752(13インチ iPad)など正確なピクセル寸法の PNG を要求しますが、Flutter が提供するのはレンダリングエンジンとテストハーネスだけで、ストアパイプラインはあなた自身が構築する必要があります。この記事は、2026年時点で実際に機能する方法を貼り付けられるコードとともに正直に解説し、どのツールがどの作業を担うかを明確にします。フレームワーク別の概要を先に確認したい場合は、Flutter開発者向けMokbiをご覧ください。
混乱を避けるために最初に整理しておきます。フレームのキャプチャ(アプリを特定の状態に誘導して画面の PNG を保存すること)とストアスクリーンショットのコンポジション(そのフレームをデバイスモックアップに収め、見出し・背景・翻訳済みコピーを添えて必要な全サイズでエクスポートすること)は、まったく別の問題です。以下で紹介する Flutter のツールのほとんどは前者を解決するものです。後者を解決するものは一つもありません。
選択肢1:integration_test + flutter drive(実際のフレームをキャプチャ)
これが Flutter に最も近い公式スクリーンショットパスです。integration_test パッケージは実際のデバイスまたはエミュレータ上でアプリ全体を起動し、ウィジェットテストと同じ finder/tester API で操作して、binding.takeScreenshot() を公開しています。ただし takeScreenshot はバイトをテストプロセスに返すだけでファイルを書き出しません。そのため、ホスト側でそのバイトを受け取ってファイルに保存する拡張ドライバが必要です。
まずテストファイルです。Android 特有の手順に注意してください。Android は Flutter を SurfaceView にレンダリングするためフレームバッファから直接読み出せないので、キャプチャ前に convertFlutterSurfaceToImage() を呼び出し(さらにフレームをポンプ)する必要があります。
// integration_test/screenshot_test.dart
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('home + detail screenshots', (tester) async {
app.main();
await tester.pumpAndSettle();
// Android needs the surface converted to an image before capture.
if (Platform.isAndroid) {
await binding.convertFlutterSurfaceToImage();
await tester.pumpAndSettle();
}
await binding.takeScreenshot('01_home');
await tester.tap(find.byKey(const Key('open-detail')));
await tester.pumpAndSettle();
await binding.takeScreenshot('02_detail');
});
}次にドライバファイルです。ホストマシン上で動作し、テストが送り返す PNG バイトをファイルに書き出します。このファイルは test_driver/ に置き、integration_test_driver_extended.dart を使います(プレーンな integration_test_driver.dart には onScreenshot フックがありません):
// test_driver/integration_test.dart
import 'dart:io';
import 'package:integration_test/integration_test_driver_extended.dart';
Future<void> main() async {
await integrationDriver(
onScreenshot: (String name, List<int> bytes,
[Map<String, Object?>? args]) async {
final file = await File('screenshots/$name.png').create(recursive: true);
file.writeAsBytesSync(bytes);
return true; // false fails the test (e.g. on a diff mismatch)
},
);
}重要な点として、flutter test ではなく flutter drive で実行する必要があります。ドライバパスを経由しないと onScreenshot が呼び出されません:
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/screenshot_test.dart \
-d emulator-5554得られるものと得られないもの:
- 実際のアプリのピクセル。再レンダリングではなく、実際に動作するウィジェットのスクリーンショットです。フォント、テーマ、動的データ、すべてがネイティブです。
- 決定論的な状態。アプリを任意の画面に誘導して、任意のタイミングでキャプチャできます。
- デバイスクラス = 実行するデバイスに依存。出力解像度はエミュレータ/デバイスのものになります。Apple が要求する正確な 1320 × 2868 を取得するには 6.9インチクラスの iOS シミュレータで実行し、13インチ iPad セットにはそのシミュレータを使います。「App Store 向けに自動リサイズ」するフラグはありません。
- ロケール対応は自前で行う必要がある。起動時にロケールをアプリに渡して言語ごとに再実行することは可能ですが、それはキャプチャされるのはその言語でのアプリ UIであり、マーケティング見出しのコピーを書き出すわけではありません。
- メンテナンスコストは無視できない。finder(
find.byKey、タップ操作)は UI が変わると壊れます。通常の UI テストと同じです。
選択肢2:ゴールデンテスト――ストアアセットには向かない理由
Flutter のゴールデンテスト(matchesGoldenFile、または golden_toolkit などのパッケージが提供する高レベルな testGoldens)はウィジェットツリーを画像にレンダリングして、コミットされた参照画像とバイト単位で比較します。優れた仕組みですが、目的がストアスクリーンショットとは異なります。その目的はピクセルリグレッションの検出です。パディングの変更やフォントの差し替えが UI をサイレントに変更してしまう日を検知するためのものです。ゴールデンはコントラクトであり、差分はテスト失敗を意味します。
ゴールデンテストをそのままストアパイプラインとして使わない理由が2つあります:
- ゴールデンはウィジェットをヘッドレステスト環境でレンダリングし、実際のデバイス表面ではない。プラットフォームビュー、一部のシェーダー、実際の GPU コンポジターに依存するものは、ユーザーが見るものと異なる(あるいはまったくレンダリングされない)場合があります。リグレッションコントラクトとしては問題ありません――自分自身と比較するだけだからです。ストアアセットとしては実際のフレームが必要であり、それは選択肢1が提供するものです。
- ピクセルパーフェクトマッチングが邪魔をする。バニラのゴールデンテストは完全一致を期待するため、マシン間のアンチエイリアスやフォントヒンティングの差異がフレーキーな失敗を引き起こします。これはリグレッションに対しては正しい厳格さであり、「きれいなマーケティング画像を生成する」には間違った厳格さです。
ただし、合理的な中間路もあります。golden_screenshot などのパッケージは、ゴールデンの仕組みをストア出力向けに意図的に転用しています。testGoldens を使い、画面を実際のデバイスフレーム(スマートフォン/タブレット/デスクトップ)で囲み、ピクセルパーフェクトを求めるのではなく少量(約0.1%、設定可能)の不一致を許容するファジーコンパレータを採用し、Fastlane スタイルのディレクトリレイアウト(metadata/<locale>/images/...)にファイルを書き出します。画面が純粋な Flutter(プラットフォームビューなし)で、エミュレータを起動せずにフレーム付きレンダリングを得たい場合は、合理的で高速な手段です。ただし、ライブアプリをキャプチャしているのではなくウィジェットをレンダリングしていること、そして得られる「マーケティング」はデバイスフレームだけであり、見出し・背景・マルチパネルカルーセルは含まれないことをはっきり認識しておく必要があります。
選択肢3:Fastlane / Codemagic(Flutter向け)
Fastlane は iOS/Android の自動化ツールであり、Flutter 専用ではありませんが、2つの形で活用できます。Fastlane 独自のキャプチャツール(iOS 向け XCUITest ベースの snapshot、Android 向け Espresso/UI Automator ハーネスの screengrab)はネイティブ UI テストを前提としており、Flutter アプリには通常それがないため、ほとんどの Flutter チームはキャプチャ目的では snapshot を使いません。Fastlane が真価を発揮するのは配信です。deliver(iOS)と supply(Android)は、すでにキャプチャ済みの PNG とメタデータを App Store Connect / Play Console にアップロードし、Fastlane 形式のフォルダ構成を読み込みます。これはまさに golden_screenshot が書き出すレイアウトであり、選択肢1のドライバに書き出させることもできます。
Codemagic は、多くのチームがここで選ぶ Flutter ネイティブ CI です。現実的な CI レシピは次のとおりです。macOS/Android ランナー上で必要なデバイスクラスにわたって integration_test のスクリーンショットドライブを実行し、PNG をビルドアーティファクトとして収集し、deliver/supply を呼んでアップロードします。これはリリースごとにスクリーンショットがズレて頻繁にリリースするチームには本当に便利です。「自動化 vs ノーコード」のトレードオフ(このセットアップが過剰になる場合を含む)の詳細は、Fastlane snapshot vs ノーコードのApp Storeスクリーンショットをご覧ください。
選択肢4:手動キャプチャ(今でも十分有効)
多くのインディー Flutter アプリにとって、正直な答えはこうです。リリースのたびに手動でスクリーンショットを撮るだけで十分です。適切なデバイスクラス(6.9インチ iPhone、13インチ iPad)の iOS シミュレータでアプリを実行し、各画面に移動してシミュレータのスクリーンショットコマンドを使います。シミュレータは正確なデバイス解像度でキャプチャするため、App Store Connect が求めるサイズになります。Android でも、エミュレータのカメラボタンで同じことができます。5画面のアプリであれば、合計作業時間は10分程度です。
手動キャプチャにはメンテナンスコストがなく、フレーキーな finder もなく、CI の世話も不要です。唯一の弱点は、自動再生成がない点です。UI を変更してリリースしたら、再度撮り直す必要があります。年数回しかリリースしないチームにとって、このトレードオフは圧倒的に割に合います。選択肢1〜3の自動化が効果を発揮するのは、毎週リリースして多くのロケールに対応しているため「手動で再撮影」が10分の作業ではなくなった場合です。
上記のどれもカバーしない部分:コンポジションと50言語へのローカライズ
正直に、かつ簡潔に述べます。上記のすべての選択肢が出力するのは生フレーム――デバイス解像度でのアプリ UI――です。コンバージョンを生む App Store・Play Store のカルーセルは生フレームではありません。フレームをデバイスモックアップの中に収め、背景を敷き、短い見出しを添えた、多くの場合マルチパネルのシーケンスとして、対象の全ロケール分繰り返したものです。このコンポジション工程こそが Mokbi の担当です。キャプチャしたフレーム(integration_test、golden_screenshot、シミュレータ、どこからでも)を持ち込み、ブラウザのエディタにドロップして、デバイスフレームとキャプションと背景を追加し、最大50のApp Store言語にキャプションをほぼワンクリックで翻訳して、必要な全サイズを一括エクスポートします。透かし入りプレビューでのデザインは無料、透かしなしのエクスポートとストアへの公開はサブスクリプション中は無制限に利用できます――Solo €29.99/mo(1アプリ)または Studio €49.99/mo(最大5アプリ)で、一度きりの購入はありません。境界を明確にしておきます。Mokbi はソーススクリーンショットをキャプチャしません――アプリを実行したりウィジェットを操作したりはしません。キャプチャは上記の Flutter ツールが担い、Mokbi はそのツールが生成したものをコンポーズしてローカライズします。
現実的な Flutter の組み合わせワークフロー
- ソースフレームをキャプチャする。カルーセルに使いたい画面の
integration_testスクリーンショットドライブ(選択肢1)を書く。リリース頻度が低い場合はシミュレータから手動キャプチャでも構いません。6.9インチ iPhone クラスのデバイスと 13インチ iPad クラスのデバイスで実行して正しい解像度を取得します。 - (任意)CI に組み込む。ズレが実際の問題であれば、Codemagic でドライブを実行して PNG をアーティファクトとして収集します(選択肢3)。そうでなければ、この手順は完全にスキップして構いません。
- マーケティングカルーセルをコンポーズする。フレームを Mokbi にドロップし、デバイスフレーム・キャプション・背景を追加してマルチパネルのシーケンスを構築します。Flutter ツールができない工程はここです。
- ローカライズして一括エクスポートする。ワンクリックで対象 App Store ロケールにキャプションを翻訳し、必要な全サイズを一括エクスポートします。必要であれば出力を Fastlane フォルダに入れて
deliver/supplyでアップロードします。 - 次の UI 変更時は再キャプチャして再度開く。ドライブを再実行(または手動で再撮影)し、保存済みの Mokbi プロジェクトを開き直してフレームを差し替えて再エクスポートします。コンポジションと翻訳はそのまま保持されます。
自動化を完全にスキップできる場合
年1〜3回しかリリースしないなら、integration_test スクリーンショットハーネスと CI パイプラインを構築するコストは、削減できる手間より大きくなります。シミュレータから手動キャプチャし、一度コンポーズしてローカライズすれば、それで完了です。選択肢1〜3の自動化は、スクリーンショットのズレが繰り返し発生するコストになっているチーム――頻繁なリリース、多くのロケール、または複数のアプリを持つポートフォリオ――向けです。厳密に見えるかどうかではなく、リリース頻度に合ったツールを選びましょう。
エクスポートの前に確認しておく価値があるのは、App Storeスクリーンショットのサイズガイドで正確なピクセル寸法を確認し、App Storeスクリーンショットの要件ガイドでフォーマットルール(PNG/JPEG、RGB、アルファチャンネルなし、デバイスクラスごとに1〜10枚)を把握することです。Apple は1ピクセルの誤差も許容せずに却下するため、30秒かける価値があります。