· Workflow · 9 分钟阅读

生成 Flutter App Store 截图:integration_test、Golden Tests 与更快的路径(2026)

生成 Flutter App Store 截图:integration_test、Golden Tests 与更快的路径(2026)
TL;DR. Flutter 没有官方的「生成商店截图」命令。现实可行的选项有:用 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_testgolden_screenshot、模拟器,或任何其他来源),拖入浏览器编辑器,添加设备框架、文案和背景,只需点击一下即可将文案翻译为最多 50 种 App Store 语言,并按每种所需尺寸批量导出。设计阶段免费(带水印预览);无限次导出与商店发布需订阅——Solo €29.99/mo(1 个应用)或 Studio €49.99/mo(最多 5 个应用),无一次性买断。需要明确边界:Mokbi 不捕获源截图——它不运行你的应用,也不驱动你的 widget。捕获由上述 Flutter 工具完成;Mokbi 负责组合并本地化捕获结果。

现实可行的 Flutter 组合工作流

  1. 捕获源帧。为需要在轮播图中展示的界面编写 integration_test 截图任务(选项 1),或者——如果发版不频繁——直接从模拟器手动截图。在 6.9 英寸 iPhone 类设备和 13 英寸 iPad 类设备上运行,确保分辨率正确。
  2. (可选)接入 CI。如果截图过时是真实痛点,在 Codemagic 上运行截图任务并将 PNG 作为产物收集(选项 3)。如果不是,完全可以跳过。
  3. 组合营销轮播图。将帧拖入 Mokbi,添加设备框架、文案和背景,搭建多面板轮播图。这是 Flutter 工具无法完成的步骤。
  4. 本地化并导出。一键将文案翻译为目标 App Store 语言,按每种所需尺寸批量导出,如有需要可将输出放入 Fastlane 目录,由 deliver/supply 自动上传。
  5. 下次 UI 变更时,重新捕获并重新打开。重跑截图任务(或重新截图),重新打开保存的 Mokbi 项目,替换帧,重新导出。组合结构和翻译已经保留。

何时可以完全跳过自动化

如果你每年发布一到三个版本,搭建 integration_test 截图任务和 CI 流水线花费的时间远多于它能节省的时间。从模拟器手动截图,组合并本地化一次,然后继续前进。选项 1–3 的自动化适合截图过时成为反复出现的问题的团队——发版频繁、语言市场众多,或应用组合较大。让工具匹配发版节奏,而非追求看起来严格。

在导出任何内容之前,值得核对一下目标规格:从 App Store 截图尺寸指南确认精确的像素尺寸,从 App Store 截图要求指南确认格式规则(PNG/JPEG、RGB、无 alpha 通道、每设备类型 1–10 张)。Apple 对尺寸的容差为零,多花三十秒确认值得。

延伸阅读

打开编辑器 →