生成 Flutter App Store 截图:integration_test、Golden Tests 与更快的路径(2026)
integration_test + flutter drive 捕获真实帧,用 golden tests 渲染组件快照(适合回归测试,用于商店素材则勉强),或将上述任一方案接入 Fastlane/Codemagic CI。以上方案都不负责组合营销轮播图——文案、设备框架、背景、50 种语言版本——那是独立的步骤,正是 Mokbi 的用武之地:它组合并本地化你捕获的截图,不负责截图本身。如果你用 Flutter 开发并已上架 App Store 或 Play Store,你一定遇到过这个缺口:没有 flutter screenshots 命令。Apple 要求精确像素尺寸的 PNG,例如 1320 × 2868(6.9 英寸 iPhone)和 2064 × 2752(13 英寸 iPad);Flutter 给你一个渲染引擎和测试框架,商店流程留给你自己处理。本文是 2026 年真正可行方案的如实梳理,附可直接使用的代码,并明确每个工具各自承担哪部分工作。如果你想先了解框架专属概览,参见 面向 Flutter 开发者的 Mokbi。
先说清楚一个框架,能省去很多困惑:捕获帧(将应用驱动到某个状态并保存屏幕 PNG)和组合商店截图(将该帧放入设备样机,加上标题、背景和翻译好的文案,按每种所需尺寸导出)是两个不同的问题。下面的 Flutter 工具解决的是前者,没有一个能解决后者。
选项 1:integration_test + flutter drive(捕获真实帧)
这是 Flutter 最接近官方截图路径的方案。integration_test 包在真实设备或模拟器上启动完整应用,用与 widget 测试相同的 finder/tester API 驱动它,并提供 binding.takeScreenshot()。需要注意的是,takeScreenshot 将字节返回给测试进程——不会自动写文件——因此需要扩展驱动程序在主机端接收这些字节并保存。
首先是测试代码。注意 Android 特有的步骤:在捕获前必须调用 convertFlutterSurfaceToImage()(并泵送一帧),因为 Android 将 Flutter 渲染到 SurfaceView 中,帧缓冲无法直接读取。
// 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 drive 运行,而非 flutter test——只有驱动程序路径才会触发 onScreenshot:
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/screenshot_test.dart \
-d emulator-5554你能得到什么,以及得不到什么:
- 真实应用像素。这些是实际运行中的 widget 截图,不是重新渲染——字体、主题、动态数据,全部原生呈现。
- 确定性状态。你将应用驱动到指定界面,按需捕获。
- 设备类型 = 运行所用的设备。输出分辨率取决于模拟器/设备。要达到 Apple 要求的精确 1320 × 2868,需要在 6.9 英寸 iOS 模拟器上运行;13 英寸 iPad 同理。没有「自动调整为 App Store 尺寸」的开关。
- 语言处理是你的工作。你可以在启动时向应用传入语言设置并重跑,但捕获的是应用界面的语言版本——并不会写入你的营销文案。
- 维护成本是真实存在的。finder(
find.byKey、点击操作)会随 UI 变更而失效,和任何 UI 测试一样。
选项 2:golden tests——以及为何它们不是商店素材
Flutter 的 golden tests(matchesGoldenFile,或 golden_toolkit 等包提供的高级封装 testGoldens)将 widget 树渲染为图像,并与已提交的参考图像进行逐字节对比。它们很出色,但设计目的与商店截图不同:像素回归检测。它的价值在于发现某次 padding 变更或字体替换悄悄改变了 UI 的那一天。golden 是契约;差异就是失败的测试。
不建议将原始 golden tests 作为商店截图流程的两个原因:
- Golden tests 在无头测试环境中渲染 widget,而非真实设备界面。Platform views、部分着色器,以及任何依赖真实 GPU 合成器的内容,渲染结果可能与用户看到的不同(甚至无法渲染)。用于回归检测没问题——你是在和自己对比。用于商店素材则需要真实帧,那是选项 1 的用途。
- 像素级精确匹配会成为障碍。原始 golden tests 要求完全匹配,因此不同机器上的抗锯齿和字体微调差异会导致测试随机失败。这种严格程度适合回归检测,却不适合「生成一张好看的营销图」。
话虽如此,有一条合理的中间路径。golden_screenshot 等包刻意将 golden 机制用于商店输出:它们使用 testGoldens,用真实设备框架包裹你的界面(手机/平板/桌面),启用容差比较器(允许约 0.1% 的差异,可配置,而非要求像素完美),并将文件写入 Fastlane 风格的目录结构(metadata/<locale>/images/...)。如果你的界面是纯 Flutter(无 platform views),且希望在不启动模拟器的情况下在渲染结果外包一层设备框架,这是一个合理且快速的方案。只需清醒地认识到:你渲染的是 widget,而非捕获运行中的应用;你得到的「营销素材」只是设备框架——没有标题、背景,也没有多面板轮播图。
选项 3:Fastlane / Codemagic 接入 Flutter
Fastlane 是 iOS/Android 自动化工具,并非 Flutter 专用,但可以通过两种方式嵌入。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 runner 上跨所需设备类型运行 integration_test 截图任务,将 PNG 作为构建产物收集,再调用 deliver/supply 推送。对于每次发版截图都会过时、且发版频繁的团队,这确实有用。关于「自动化 vs 无代码」的整体权衡——包括何时这套方案大材小用——参见 fastlane snapshot 与无代码 App Store 截图工具对比。
选项 4:手动截图(依然完全可行)
对于很多独立 Flutter 应用来说,诚实的答案是:每次发版手动截图一次。在正确设备类型的 iOS 模拟器中运行应用(6.9 英寸 iPhone、13 英寸 iPad),导航到每个界面,用模拟器的截图命令捕获——它会以设备精确分辨率输出,这正是 App Store Connect 所需格式。Android 模拟器的相机按钮同理。一个五个界面的应用,大概十分钟搞定。
手动截图没有维护成本,没有随机失效的 finder,也没有 CI 需要看护。唯一的短板是不会自动重新生成——如果你改了 UI,就需要重新截。对于一年发几次版的团队,这个代价完全值得。选项 1–3 的自动化真正划算的前提是:「每次手动重新截图」不再是十分钟的活——因为你每周发版,而且面向多个语言市场。
以上方案都无法完成的部分:组合 + 50 种语言本地化
说清楚这个边界。上面每种方案产出的都是裸帧——应用界面在某个设备分辨率下的截图。而能带来转化的 App Store 和 Play Store 轮播图不是裸帧:它是放在设备样机内、搭配背景、配上简短标题、常以多面板形式呈现的画面,且需要覆盖所有目标语言。这个组合步骤正是 Mokbi 的用途。你带来捕获好的帧(来自 integration_test、golden_screenshot、模拟器,或任何其他来源),拖入浏览器编辑器,添加设备框架、文案和背景,只需点击一下即可将文案翻译为最多 50 种 App Store 语言,并按每种所需尺寸批量导出。设计阶段免费(带水印预览);无限次导出与商店发布需订阅——Solo €29.99/mo(1 个应用)或 Studio €49.99/mo(最多 5 个应用),无一次性买断。需要明确边界:Mokbi 不捕获源截图——它不运行你的应用,也不驱动你的 widget。捕获由上述 Flutter 工具完成;Mokbi 负责组合并本地化捕获结果。
现实可行的 Flutter 组合工作流
- 捕获源帧。为需要在轮播图中展示的界面编写
integration_test截图任务(选项 1),或者——如果发版不频繁——直接从模拟器手动截图。在 6.9 英寸 iPhone 类设备和 13 英寸 iPad 类设备上运行,确保分辨率正确。 - (可选)接入 CI。如果截图过时是真实痛点,在 Codemagic 上运行截图任务并将 PNG 作为产物收集(选项 3)。如果不是,完全可以跳过。
- 组合营销轮播图。将帧拖入 Mokbi,添加设备框架、文案和背景,搭建多面板轮播图。这是 Flutter 工具无法完成的步骤。
- 本地化并导出。一键将文案翻译为目标 App Store 语言,按每种所需尺寸批量导出,如有需要可将输出放入 Fastlane 目录,由
deliver/supply自动上传。 - 下次 UI 变更时,重新捕获并重新打开。重跑截图任务(或重新截图),重新打开保存的 Mokbi 项目,替换帧,重新导出。组合结构和翻译已经保留。
何时可以完全跳过自动化
如果你每年发布一到三个版本,搭建 integration_test 截图任务和 CI 流水线花费的时间远多于它能节省的时间。从模拟器手动截图,组合并本地化一次,然后继续前进。选项 1–3 的自动化适合截图过时成为反复出现的问题的团队——发版频繁、语言市场众多,或应用组合较大。让工具匹配发版节奏,而非追求看起来严格。
在导出任何内容之前,值得核对一下目标规格:从 App Store 截图尺寸指南确认精确的像素尺寸,从 App Store 截图要求指南确认格式规则(PNG/JPEG、RGB、无 alpha 通道、每设备类型 1–10 张)。Apple 对尺寸的容差为零,多花三十秒确认值得。