首页
/ Flutter android_engine_test 详解:用 Native Flutter Driver 在真机上做截图金标准端到端测试

Flutter android_engine_test 详解:用 Native Flutter Driver 在真机上做截图金标准端到端测试

2026-09-06 18:03:54作者:田桥桑Industrious

本文以 dev/integration_tests/android_engine_test/README.md 为主体,讲解 Flutter 仓库中 android_engine_test 集成测试套件的定位、组成与运行方式:它如何使用实验性的 Native Flutter Driver API 驱动运行在 Android 真机或模拟器上的应用,完成原生控件截图、与金标准(golden)图像比对,并在 LUCI CI 上按 Impeller 渲染后端分片执行。读完本文,你可以掌握在该目录下本地运行任意示例应用与测试、生成/更新本地 golden 基线、使用 tool/deflake.dart 做去抖动(deflake)验证的完整操作路径,以及 CI 侧套件入口 run_android_engine_tests.dart 的底层工作原理。

一、套件定位:一条“非常端到端”的测试链

README 开宗明义:该目录包含一组示例应用和测试,演示如何使用(实验性的)native Flutter Driver API 来驱动运行在 Android 设备或模拟器上的 Flutter 应用,与应用交互、截取应用截图,并将截图与金标准图像进行比对。

README 中有一条值得特别关注的 CAUTION 提示:这套测试是一条 very end-to-end 的测试链,它同时覆盖 图形后端(graphics backend)+ Android embedder + Flutter Framework + Flutter 工具链 的组合行为,因此只有当文档与命名保持最新、且指引清晰可操作时才有价值;更新测试套件时必须同步更新 README。这也解释了它的组织方式:每个可测试单元都是一个“独立应用 + 配套测试驱动”的最小闭环,失败时可以快速定位是链路中哪一层出了问题。

从源码结构看,套件分为四层:

路径 职责
示例应用 lib/ 下各 *_main.dart 每个文件是一个可独立 flutter run 的完整 Flutter 应用
测试驱动 test_driver/ 下各 *_test.dart 在宿主机上运行,通过 Native Driver 截图并与 golden 比对
Android 宿主 android/app/src/main/AndroidManifest.xmlMainActivity.kt 提供平台视图工厂、纹理插件与 native_driver 方法通道的原生支持
本地工具 tool/deflake.dart 一键构建 + 建立 golden 基线 + 重复运行 N 次验证稳定性

pubspec.yaml 声明了套件的核心依赖:flutterflutter_driver(均来自 SDK),以及本地路径依赖 android_driver_extensions(指向 dev/tools/android_driver_extensions)——后者正是“Native Flutter Driver”能力所在,后文第三节详述。

二、CI(LUCI)上的运行方式

README 给出 CI 入口的 TL;DR:

# TIP: If golden-files do not exist locally, this command will fail locally.
SHARD=android_engine_vulkan_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart
SHARD=android_engine_opengles_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart

两个 SHARD 分别对应 Impeller 的 Vulkan 与 OpenGL ES 两个渲染后端,因此同一组测试会按后端各跑一遍,golden 文件名也随之带上有后缀区分。

CI 的真正实现在 dev/bots/suite_runners/run_android_engine_tests.dart。该文件头部的文档注释描述了完整的本地复现流程:

  1. 连接一台 Android 设备或模拟器;
  2. dev/bots 目录执行 dart pub get
  3. 在 Flutter 仓库根目录执行:
# 先生成本地 golden 基线
SHARD=android_engine_vulkan_tests UPDATE_GOLDENS=1 bin/cache/dart-sdk/bin/dart dev/bots/test.dart
# 然后对着基线跑测试
SHARD=android_engine_vulkan_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart

注释中还给出一个实用技巧:如果在调试某个提交,应先执行第 3 步(在改动前的 HEAD 上建基线),再应用该提交(或 flag),最后跑第 4 步;而如果只是想确认“同一状态下”的抖动,参考 tool/deflake.dart(见第七节)。

2.1 CI 驱动器的关键实现细节

阅读 run_android_engine_tests.dart 源码,可以确认以下 CI 行为:

  • 枚举所有入口:用 Glob('dev/integration_tests/android_engine_test/lib/**_main.dart') 扫描全部应用入口,逐一对 flutter drive 发起测试。这就是 README 中“每个 lib/{prefix}_main.dart 都是独立应用”这一约定的执行基础。

  • 动态改写渲染后端:套件 AndroidManifest.xml 中默认声明了:

    <meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="true" />
    <meta-data android:name="io.flutter.embedding.android.ImpellerBackend" android:value="vulkan" />
    

    CI 运行时,_impellerBackendMetadata 会把 ImpellerBackend 的 meta-data 值替换为当前分片要求的 vulkanopengles,在 finally 块中恢复 manifest 原始内容,保证仓库文件不被污染。

  • 逐测试注入 golden 变体:每次 flutter drive 都附带环境变量 ANDROID_ENGINE_TEST_GOLDEN_VARIANT=<backend>。在测试侧,test_driver/_luci_skia_gold_prelude.dart 读取该变量生成 goldenVariant.vulkan / .opengles 后缀),从而让同一测试在不同后端下比对不同的 golden 图。

  • 关闭非必要开发设施:每次 drive 都带上 --no-dds--no-enable-dart-profiling,源码注释说明这是为了避免不必要的启动开销和随之而来的 flakiness。

  • HCPP 专项流程(仅 Vulkan 分片):Vulkan 分片下,lib/hcpp/ 目录中的测试(如 upgrade_legacy_pv_types_main)会先以 --enable-hcpp / --no-enable-hcpp 命令行 flag 单独运行以验证 flag 本身的行为(含“manifest 启用 HCPP 时 --no-enable-hcpp 仍能禁用”的用例),随后 CI 再把 manifest 中 EnableHcpp meta-data 从 false 改为 true 运行其余 HCPP 测试。该目录自身也有一句话说明(“此路径下所有文件都会启用 hcpp”),见 lib/hcpp/README.md

三、Native Flutter Driver 扩展:截图、交互与黄金比对的基石

README 通篇围绕 “native Flutter Driver API” 展开,其实现位于 dev/tools/android_driver_extensions。该库自述为“在 flutter_driver 之上的最小扩展库”,用于执行那些纯 Flutter Driver(跑在设备侧)无法完成、需要在宿主机上执行的外部操作:

  • 截取屏幕截图,包括原生控件(平台视图、纹理);
  • 点按原生控件;
  • 旋转设备;
  • 将应用切到后台并向设备发送 trim memory 信号。

其 README 同时声明:该库运行在 Flutter 自己的 CI 中、用于测试 Flutter 的 Platform Views,但不是官方对外支持的 API,随时可能变化或被移除;对外部项目建议使用 Integration Test 等既有设施。

一个最小应用侧接入示例见 lib/flutter_rendered_blue_rectangle_main.dart

void main() async {
  ensureAndroidDevice();
  enableFlutterDriverExtension(commands: <CommandExtension>[nativeDriverCommands]);

  // Run on full screen.
  await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);

  runApp(const MainApp());
}

要点:enableFlutterDriverExtension 传入 commands: nativeDriverCommands,即把 Native Driver 的命令扩展注册进设备侧驱动端;SystemUiMode.immersive 让应用全屏运行,避免系统栏干扰像素比对。

宿主侧测试则按固定模板连接(见 test_driver/flutter_rendered_blue_rectangle_main_test.dart):

setUpAll(() async {
  if (isLuci) {
    await enableSkiaGoldComparator(namePrefix: 'android_engine_test$goldenVariant');
  }
  flutterDriver = await FlutterDriver.connect();
  nativeDriver = await AndroidNativeDriver.connect(flutterDriver);
  await nativeDriver.configureForScreenshotTesting();
  await flutterDriver.waitUntilFirstFrameRasterized();
});

test('should screenshot and match a full-screen blue rectangle', () async {
  await expectLater(
    nativeDriver.screenshot(),
    matchesGoldenFile('fluttered_rendered_blue_rectangle.png'),
  );
}, timeout: Timeout.none);

从源码结构看,这里体现了“双通道 golden 策略”:当 LUCI_CI == 'True'isLuci)时启用 Skia Gold 比对器,前缀带上 goldenVariant 后端后缀;本地则要求预先存在一份本地 golden 文件作为基线,否则比对会失败——这正是 CI 命令上方那条 “TIP: If golden-files do not exist locally, this command will fail locally” 提示的来由。

四、示例应用与配套测试逐一解析

README 的“Running the apps and tests”部分枚举了每个 lib/{prefix}_main.dart 应用。以下按 README 原顺序完整继承,并补充源码级佐证。

4.1 flutter_rendered_blue_rectangle

全屏蓝色矩形。README 说明它“主要验证 Flutter 能在目标设备上跑起来、Native Driver 能截图并与 golden 比对;如果这个应用或测试失败,其他应用和测试大概率也会失败”——即整个套件的冒烟测试(smoke test)。其 UI 实现也确实只有 DecoratedBox 一层蓝色 BoxDecoration

# Run the app
$ flutter run lib/flutter_rendered_blue_rectangle_main.dart

# Run the test
$ flutter drive lib/flutter_rendered_blue_rectangle_main.dart

4.2 external_texture/surface_producer_smiley_face

黄色背景上的全屏矩形变形笑脸。端到端测试 SurfaceProducer API,并覆盖“应用退后台 → trim memory → 恢复前台”这一历史回归场景。应用入口 通过 MethodChannel('smiley_face_texture')initTexture 方法向原生侧 SmileyFaceTexturePlugin 申请一个 512×512 的纹理,再用 Texture 控件渲染。

# Run the app
$ flutter run lib/external_texture/surface_producer_smiley_face_main.dart

# Run the test
$ flutter drive lib/external_texture/surface_producer_smiley_face_main.dart

4.3 external_texture/surface_texture_image_smiley_face

同样是黄色背景全屏变形笑脸,但测试的是 dart:ui 的 getImageFromTexture API。注意 README 正文小标题写作 surface_texture_image_smiley_face,而当前仓库 lib/external_texture/ 目录下实际存在的入口是 surface_texture_smiley_face_main.dart(对应 SurfaceTexture API 的端到端测试);从源码结构看,surface_texture_image_smiley_face 可能是演进过程中的命名遗留,本地运行前建议先 ls lib/external_texture/ 确认实际文件名。

# Run the app
$ flutter run lib/external_texture/surface_texture_image_smiley_face_main.dart

# Run the test
$ flutter drive lib/external_texture/surface_texture_image_smiley_face_main.dart

4.4 external_texture/surface_texture_smiley_face

黄色背景全屏变形笑脸,端到端测试 SurfaceTexture API。

# Run the app
$ flutter run lib/external_texture/surface_texture_smiley_face_main.dart

# Run the test
$ flutter drive lib/external_texture/surface_texture_smiley_face_main.dart

4.5 platform_view/hybrid_composition_platform_view

显示蓝橙渐变平台视图,随后应用退后台再恢复,端到端验证 Hybrid Composition 实现

# Run the app
$ flutter run lib/platform_view/hybrid_composition_platform_view_main.dart

# Run the test
$ flutter drive lib/platform_view/hybrid_composition_platform_view_main.dart

4.6 platform_view/texture_layer_hybrid_composition_platform_view

同样为蓝橙渐变 + 退后台再恢复,验证 Texture Layer Hybrid Composition 实现

# Run the app
$ flutter run lib/platform_view/texture_layer_hybrid_composition_platform_view_main.dart

# Run the test
$ flutter drive lib/platform_view/texture_layer_hybrid_composition_platform_view_main.dart

4.7 platform_view/virtual_display_platform_view

同样模式,验证 Virtual Display 实现

# Run the app
$ flutter run lib/platform_view/virtual_display_platform_view_main.dart

# Run the test
$ flutter drive lib/platform_view/virtual_display_platform_view_main.dart

以上三个平台视图应用的工厂注册统一发生在原生宿主侧:MainActivity.kt 中通过 configureFlutterEngineplatformViewsController.registry 注册 blue_orange_gradient_platform_viewblue_orange_gradient_surface_view_platform_view 等工厂,并刻意不使用 GeneratedPluginRegistrant,直接 add 各测试插件(SmileyFaceTexturePluginOtherFaceTexturePluginNativeDriverSupportPlugin),保证测试环境完全显式可控。

4.8 platform_view_tap_color_change

README 说明:显示一个蓝色矩形(由平台视图实现),被原生点按(而非 Flutter 点按)后从蓝色变为红色。注意其 run/test 命令与其他应用不同——测试驱动文件名为 platform_view_tap_color_change_main_test.dart

# Run the app
$ flutter run lib/platform_view_tap_color_change_main.dart

# Run the test
$ flutter drive lib/platform_view_tap_color_change_main_test.dart

该用例正是第三节中“点按原生控件”能力的典型应用场景:纯设备侧 Flutter Driver 无法命中平台视图内部的原生视图,必须借助 Native Driver。

4.9 system_ui_mode_transitions

README 对该用例的描述较详细:应用暴露一个 Flutter Driver requestData handler,按序应用 SystemUiMode 值,并通过 native_driver 方法通道读取 decor view 的 systemUiVisibility 标志;配套测试驱动断言“从任一隐藏模式(leanBack/immersive/immersiveSticky)切换到 edgeToEdge 会清除 FLAG_FULLSCREEN/FLAG_HIDE_NAVIGATION,从 edgeToEdge 切到隐藏模式则应用预期的沉浸式标志”。其中一条以回归命名的测试复现了 issue #186723 的 immersiveSticky → edgeToEdge 场景;要求 Android 10(API 29)及以上,更低 API 级别会自动跳过。

源码 印证了这一协议:应用侧通过 MethodChannel('native_driver') 实现 get_system_ui_visibility,并注册 enableFlutterDriverExtension(handler: _handleCommand, ...),其中 getSystemUiVisibility 命令返回 systemUiVisibility 标志位 JSON,applyMode:<name> 命令调用 SystemChrome.setEnabledSystemUIMode 应用 leanBack/immersive/immersiveSticky/edgeToEdge 四种模式。README 提到的原生侧支撑即 MainActivity.kt 中通过 WindowCompat.getInsetsController 配置 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 并隐藏系统栏的逻辑。

# Run the app
$ flutter run lib/system_ui_mode_transitions_main.dart

# Run the test
$ flutter drive lib/system_ui_mode_transitions_main.dart

五、本地运行的完整步骤

综合 README 与各测试文件的文档注释,在本地(非 LUCI)跑通一个 golden 测试的标准流程为:

  1. 连接 Android 真机或模拟器(应用入口中的 ensureAndroidDevice() 会做设备检查);

  2. dev/integration_tests/android_engine_test 下解析依赖(该包使用 workspace 级解析,见 pubspec.yamlresolution: workspace);

  3. 改动前的基线代码上生成本地 golden:

    UPDATE_GOLDENS=1 flutter drive lib/flutter_rendered_blue_rectangle_main.dart
    
  4. 应用改动后,不带 UPDATE_GOLDENS 重跑,测试即对基线图比对:

    flutter drive lib/flutter_rendered_blue_rectangle_main.dart
    
  5. 若要按 CI 完整分片跑,使用第二节中的 SHARD=android_engine_vulkan_tests / SHARD=android_engine_opengles_tests 命令;同理,UPDATE_GOLDENS=1 也可以前置在分片命令上来整体建立本地基线。

需要说明的适用前提:golden 比对是像素级强约束,本地 golden 由本机渲染产出;更换设备、分辨率或渲染后端后应重新生成基线。system_ui_mode_transitions 则额外要求 API 29+ 设备,旧版本 API 会跳过而非失败。

六、Deflaking:用 tool/deflake.dart 验证测试稳定性

README 最后一节介绍了去抖动工具:

dart tool/deflake.dart lib/flutter_rendered_blue_rectangle_main.dart

tool/deflake.dart <path/to/lib/main.dart> 一条命令完成三件事:构建 APK、在本地建立一组 golden 基线、随后在相同状态下连续运行 N 次(默认 10 次)并断言输出一致。更细的选项可用 dart tool/deflake.dart --help 查看。

结合 tool/deflake.dart 源码,可确认其完整参数与行为:

参数 默认值 说明
--runs / -n 10 基线之后重复运行测试的次数,必须为正整数
--generate-initial-golden true 首次运行是否生成基线 golden(写入本地文件);设为 false 时假定 golden 已存在
--build-app-once true flutter build apk --debug 构建一次,之后每次 drive 复用该 APK(--use-application-binary);设为 false 则每轮重建
--verbose / -v 打印完整子进程输出
--help / -h 打印用法

从源码执行流程看:工具先以 UPDATE_GOLDENS=1 环境跑一次建立基线,然后循环 N 次执行 flutter drive(复用同一 APK),逐轮打印 RUN i of NPASS/FAIL,最终输出 PASSED: x / N;只要存在失败轮次,进程以非零退出码结束。这个“同状态重复 N 次”的思路,与 CI 分片调试“不同状态”的思路正好互补——前者用于确认抖动本身,后者用于定位引入抖动的那次改动。

七、关键环境变量与配置小结

配置项 出现位置 作用
SHARD=android_engine_vulkan_tests / android_engine_opengles_tests CI 分片命令 选择 Impeller 渲染后端,决定 manifest 被改写成的后端值
UPDATE_GOLDENS=1 本地/CI 命令 生成(覆盖)本地 golden 基线而非比对
ANDROID_ENGINE_TEST_GOLDEN_VARIANT CI 驱动器注入 生成 golden 文件名后缀(.vulkan/.opengles),区分后端的金标准图
LUCI_CI=True _luci_skia_gold_prelude.dart 判定是否在 LUCI 上,是则启用 Skia Gold 比对器
io.flutter.embedding.android.EnableImpeller / ImpellerBackend AndroidManifest.xml 声明启用 Impeller 及默认 Vulkan 后端,CI 会按分片动态改写
io.flutter.embedding.android.EnableHcpp 同上 控制 HCPP(Hybrid Composition + Platform Views 共存)能力,HCPP 分片流程会显式改写并验证其与 --enable-hcpp/--no-enable-hcpp flag 的交互

八、小结

android_engine_test 的价值不在于覆盖面上的广度,而在于它把“图形后端 → Android embedder → Framework → 工具链”这条最脆弱、最难单测的链路压成了一条条可复现、可本地运行、可去抖动的端到端测试:每个 *_main.dart 都是独立应用,flutter run 直接看效果、flutter drive 跑金标准比对,deflake.dart 负责稳定性验证,CI 则按 Vulkan/OpenGL ES 两个分片全量回归。如果你需要在自有项目或 Flutter 开发中排查平台视图、纹理或渲染后端的像素级回归,这个目录既是可直接运行的操作手册,也是“Native Driver + golden 比对”这一模式在 Flutter 官方 CI 中的完整参考实现。

登录后查看全文
热门项目推荐
相关项目推荐