ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

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

Flutter android_engine_test 详解:用 Native Flutter Driver 在真机上做截图金标准端到端测试 Flutter android_engine_test 详解用 Native Flutter Driver 在真机上做截图金标准端到端测试【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter本文以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 开宗明义该目录包含一组示例应用和测试演示如何使用实验性的nativeFlutter Driver API 来驱动运行在 Android 设备或模拟器上的 Flutter 应用与应用交互、截取应用截图并将截图与金标准图像进行比对。README 中有一条值得特别关注的 CAUTION 提示这套测试是一条veryend-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.xml、MainActivity.kt提供平台视图工厂、纹理插件与native_driver方法通道的原生支持本地工具tool/deflake.dart一键构建 建立 golden 基线 重复运行 N 次验证稳定性pubspec.yaml 声明了套件的核心依赖flutter、flutter_driver均来自 SDK以及本地路径依赖android_driver_extensions指向 dev/tools/android_driver_extensions——后者正是“Native Flutter Driver”能力所在后文第三节详述。二、CILUCI上的运行方式README 给出 CI 入口的 TL;DR# TIP: If golden-files do not exist locally, this command will fail locally. SHARDandroid_engine_vulkan_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart SHARDandroid_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。该文件头部的文档注释描述了完整的本地复现流程连接一台 Android 设备或模拟器在dev/bots目录执行dart pub get在 Flutter 仓库根目录执行# 先生成本地 golden 基线 SHARDandroid_engine_vulkan_tests UPDATE_GOLDENS1 bin/cache/dart-sdk/bin/dart dev/bots/test.dart # 然后对着基线跑测试 SHARDandroid_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:nameio.flutter.embedding.android.EnableImpeller android:valuetrue / meta-data android:nameio.flutter.embedding.android.ImpellerBackend android:valuevulkan /CI 运行时_impellerBackendMetadata会把ImpellerBackend的 meta-data 值替换为当前分片要求的vulkan或opengles在finally块中恢复 manifest 原始内容保证仓库文件不被污染。逐测试注入 golden 变体每次flutter drive都附带环境变量ANDROID_ENGINE_TEST_GOLDEN_VARIANTbackend。在测试侧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 中EnableHcppmeta-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.dartvoid 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.dartsetUpAll(() 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 TrueisLuci时启用 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.1flutter_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.dart4.2external_texture/surface_producer_smiley_face黄色背景上的全屏矩形变形笑脸。端到端测试SurfaceProducerAPI并覆盖“应用退后台 → 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.dart4.3external_texture/surface_texture_image_smiley_face同样是黄色背景全屏变形笑脸但测试的是 dart:ui 的getImageFromTextureAPI。注意 README 正文小标题写作surface_texture_image_smiley_face而当前仓库lib/external_texture/目录下实际存在的入口是surface_texture_smiley_face_main.dart对应SurfaceTextureAPI 的端到端测试从源码结构看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.dart4.4external_texture/surface_texture_smiley_face黄色背景全屏变形笑脸端到端测试SurfaceTextureAPI。# 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.dart4.5platform_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.dart4.6platform_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.dart4.7platform_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 中通过configureFlutterEngine向platformViewsController.registry注册blue_orange_gradient_platform_view、blue_orange_gradient_surface_view_platform_view等工厂并刻意不使用GeneratedPluginRegistrant直接add各测试插件SmileyFaceTexturePlugin、OtherFaceTexturePlugin、NativeDriverSupportPlugin保证测试环境完全显式可控。4.8platform_view_tap_color_changeREADME 说明显示一个蓝色矩形由平台视图实现被原生点按而非 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.9system_ui_mode_transitionsREADME 对该用例的描述较详细应用暴露一个 Flutter DriverrequestDatahandler按序应用SystemUiMode值并通过native_driver方法通道读取 decor view 的systemUiVisibility标志配套测试驱动断言“从任一隐藏模式leanBack/immersive/immersiveSticky切换到edgeToEdge会清除FLAG_FULLSCREEN/FLAG_HIDE_NAVIGATION从edgeToEdge切到隐藏模式则应用预期的沉浸式标志”。其中一条以回归命名的测试复现了 issue #186723 的immersiveSticky → edgeToEdge场景要求 Android 10API 29及以上更低 API 级别会自动跳过。源码 印证了这一协议应用侧通过MethodChannel(native_driver)实现get_system_ui_visibility并注册enableFlutterDriverExtension(handler: _handleCommand, ...)其中getSystemUiVisibility命令返回systemUiVisibility标志位 JSONapplyMode: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 测试的标准流程为连接 Android 真机或模拟器应用入口中的ensureAndroidDevice()会做设备检查在dev/integration_tests/android_engine_test下解析依赖该包使用 workspace 级解析见 pubspec.yaml 中resolution: workspace在改动前的基线代码上生成本地 goldenUPDATE_GOLDENS1 flutter drive lib/flutter_rendered_blue_rectangle_main.dart应用改动后不带UPDATE_GOLDENS重跑测试即对基线图比对flutter drive lib/flutter_rendered_blue_rectangle_main.dart若要按 CI 完整分片跑使用第二节中的SHARDandroid_engine_vulkan_tests/SHARDandroid_engine_opengles_tests命令同理UPDATE_GOLDENS1也可以前置在分片命令上来整体建立本地基线。需要说明的适用前提golden 比对是像素级强约束本地 golden 由本机渲染产出更换设备、分辨率或渲染后端后应重新生成基线。system_ui_mode_transitions则额外要求 API 29 设备旧版本 API 会跳过而非失败。六、Deflaking用 tool/deflake.dart 验证测试稳定性README 最后一节介绍了去抖动工具dart tool/deflake.dart lib/flutter_rendered_blue_rectangle_main.darttool/deflake.dart path/to/lib/main.dart一条命令完成三件事构建 APK、在本地建立一组 golden 基线、随后在相同状态下连续运行 N 次默认 10 次并断言输出一致。更细的选项可用dart tool/deflake.dart --help查看。结合 tool/deflake.dart 源码可确认其完整参数与行为参数默认值说明--runs / -n10基线之后重复运行测试的次数必须为正整数--generate-initial-goldentrue首次运行是否生成基线 golden写入本地文件设为 false 时假定 golden 已存在--build-app-oncetrue先flutter build apk --debug构建一次之后每次 drive 复用该 APK--use-application-binary设为 false 则每轮重建--verbose / -v—打印完整子进程输出--help / -h—打印用法从源码执行流程看工具先以UPDATE_GOLDENS1环境跑一次建立基线然后循环 N 次执行flutter drive复用同一 APK逐轮打印RUN i of N与PASS/FAIL最终输出PASSED: x / N只要存在失败轮次进程以非零退出码结束。这个“同状态重复 N 次”的思路与 CI 分片调试“不同状态”的思路正好互补——前者用于确认抖动本身后者用于定位引入抖动的那次改动。七、关键环境变量与配置小结配置项出现位置作用SHARDandroid_engine_vulkan_tests/android_engine_opengles_testsCI 分片命令选择 Impeller 渲染后端决定 manifest 被改写成的后端值UPDATE_GOLDENS1本地/CI 命令生成覆盖本地 golden 基线而非比对ANDROID_ENGINE_TEST_GOLDEN_VARIANTCI 驱动器注入生成 golden 文件名后缀.vulkan/.opengles区分后端的金标准图LUCI_CITrue_luci_skia_gold_prelude.dart判定是否在 LUCI 上是则启用 Skia Gold 比对器io.flutter.embedding.android.EnableImpeller/ImpellerBackendAndroidManifest.xml声明启用 Impeller 及默认 Vulkan 后端CI 会按分片动态改写io.flutter.embedding.android.EnableHcpp同上控制 HCPPHybrid Composition Platform Views 共存能力HCPP 分片流程会显式改写并验证其与--enable-hcpp/--no-enable-hcppflag 的交互八、小结android_engine_test的价值不在于覆盖面上的广度而在于它把“图形后端 → Android embedder → Framework → 工具链”这条最脆弱、最难单测的链路压成了一条条可复现、可本地运行、可去抖动的端到端测试每个*_main.dart都是独立应用flutter run直接看效果、flutter drive跑金标准比对deflake.dart负责稳定性验证CI 则按 Vulkan/OpenGL ES 两个分片全量回归。如果你需要在自有项目或 Flutter 开发中排查平台视图、纹理或渲染后端的像素级回归这个目录既是可直接运行的操作手册也是“Native Driver golden 比对”这一模式在 Flutter 官方 CI 中的完整参考实现。【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表