camera_web 浏览器集成测试实战:解读 example 测试应用与 Web 端验证体系
2026/9/18 2:27:57 网站建设 项目流程

camera_web 浏览器集成测试实战:解读 example 测试应用与 Web 端验证体系

【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages

camera_web 是 Flutter 官方团队维护的camera插件在 Web 平台的实现,其example目录是一个专门用于在真实浏览器中验证该实现的“平台实现测试应用”(Platform Implementation Test App)。本文以 example/README.md 为主线,完整梳理该测试应用的定位、目录结构、运行方式、Mock 基础设施与底层 Web API 支撑,帮助你理解 camera_web 的集成测试是如何组织的,以及如何在本仓库中亲手跑起一套浏览器端自动化测试。

一、example 的定位:它不是“使用示例”,而是“实现验证台”

example/README.md 开篇就明确指出:这是一个用于对该平台实现进行手动测试和自动化集成测试的测试应用,并非为了演示该 package 的实际用法。原因是 Flutter 插件体系遵循“应用面向包”(app-facing package)的设计——真实业务代码应使用面向应用层的camera包,而camera_web只是底层实现。

由此可以得出三个关键结论:

  1. 如果你只是在业务项目中使用相机能力,请直接在pubspec.yaml里依赖cameracamera_web会作为 endorsed(被背书) 的联邦插件被自动带入,无需手动添加;
  2. 只有当你正在修改 camera_web 这个实现包本身(例如修复 Web 端 bug、新增 Web 能力)时,这个 example 才与你的工作高度相关;
  3. 因此这个测试应用的全部价值,集中在“验证实现行为是否符合预期”这件事上,而不是展示 API 的友好用法。

仓库中 camera_web/test/README.md 也印证了这一分工:该包“使用集成测试进行测试”,并明确指引读者“参见 example/README.md 获取更多信息”——也就是说,整个 camera_web 的功能验证都收敛到这个 example 测试应用里

二、测试应用目录结构一览

先整体看一眼 example 的布局(下文会逐一展开):

packages/camera/camera_web/example/ ├── lib/ │ └── main.dart # 极简测试宿主 App ├── integration_test/ │ ├── helpers/ │ │ ├── helpers.dart # 统一导出 mocks │ │ ├── mocks.dart # mockito 注解 + @JSExport Mock 类 │ │ └── mocks.mocks.dart # build_runner 生成的 mock 代码 │ ├── camera_bitrate_test.dart │ ├── camera_error_code_test.dart │ ├── camera_metadata_test.dart │ ├── camera_options_test.dart │ ├── camera_service_test.dart │ ├── camera_test.dart │ ├── camera_web_capture_test.dart │ ├── camera_web_controls_test.dart │ ├── camera_web_devices_test.dart │ ├── camera_web_exception_test.dart │ ├── camera_web_lifecycle_and_events_test.dart │ ├── camera_web_orientation_test.dart │ └── zoom_level_capability_test.dart ├── test_driver/ │ └── integration_test.dart # flutter drive 用的驱动入口 ├── web/ │ └── index.html # 浏览器宿主页面 ├── pubspec.yaml └── README.md

三、运行环境与依赖约束

example/pubspec.yaml 给出了这套测试对 Flutter/Dart 环境的硬性要求:

environment: sdk: ^3.10.0 flutter: ">=3.38.0" dependencies: camera_platform_interface: ^2.6.0 camera_web: path: ../ flutter: sdk: flutter web: ^1.0.0 dev_dependencies: async: ^2.5.0 build_runner: ^2.4.12 cross_file: ^0.3.1 flutter_test: sdk: flutter integration_test: sdk: flutter mockito: ^5.4.4

其中三个要点值得注意:

  • Dart SDK^3.10.0、Flutter>=3.38.0:这是运行本测试的最低版本前提。版本不满足时,pub get会直接解析失败;
  • camera_web使用path: ../本地路径依赖:正如文件内注释所述,example 与插件源码同仓打包,因此必须指向父目录以使用当前正在开发中的插件版本;而真实业务应用则应写camera_web: ^x.y.z这样的版本约束;
  • integration_test来自sdk: flutter:这是 Flutter SDK 自带的集成测试框架,也是原 README 强调的“使用package:integration_test在浏览器中运行测试”的载体;build_runnermockito则用于生成 mocks.mocks.dart 这类 mock 代码。

四、如何运行浏览器集成测试

原 README 明确说明:本包使用package:integration_test在浏览器中运行测试,并建议查阅 Flutter 官方文档中“Plugin Tests > Web Tests”一节获取搭建与运行指引。结合仓库结构,可以还原出两种标准的运行方式(均需先安装 Chrome 等浏览器):

方式一:flutter test直接运行(推荐用于 CI)

cd packages/camera/camera_web/example flutter test integration_test -d chrome

方式二:flutter drive配合 test_driver(经典集成测试链路)

test_driver/integration_test.dart 是驱动端入口,代码极为简短:

import 'package:integration_test/integration_test_driver.dart'; Future<void> main() => integrationDriver();

它负责在宿主机侧与浏览器内运行的测试通信、收集结果并返回退出码。对应的驱动命令为:

cd packages/camera/camera_web/example flutter drive \ --driver=test_driver/integration_test.dart \ --target=integration_test/camera_test.dart \ -d chrome

宿主页面web/index.html 是一个最小的 Flutter Web 引导页,通过flutter_bootstrap.js加载应用:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Browser Tests</title> </head> <body> <script src="flutter_bootstrap.js" async></script> </body> </html>

而 lib/main.dart 中的宿主 App 同样“极简到极致”——只渲染一行提示文字Testing... Look at the console output for results!,并刻意用Directionality包裹而不引入 Material 主题。这说明测试结论全部通过控制台输出与测试报告呈现,而非依赖界面,页面本身只是承载测试运行的壳。

五、测试入口与基础设施:从 camera_test.dart 看测试骨架

以体量最大的 integration_test/camera_test.dart 为例,可以看到标准的 integration_test 写法:

void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('Camera', () { const textureId = 1; late MockWindow mockWindow; late MockNavigator mockNavigator; late MockMediaDevices mockMediaDevices; ... setUp(() { mockWindow = MockWindow(); mockNavigator = MockNavigator(); mockMediaDevices = MockMediaDevices(); window = createJSInteropWrapper(mockWindow) as Window; navigator = createJSInteropWrapper(mockNavigator) as Navigator; mediaDevices = createJSInteropWrapper(mockMediaDevices) as MediaDevices; ... }); }); }

这里透露出两条关键实现事实:

  1. IntegrationTestWidgetsFlutterBinding.ensureInitialized()是每个测试文件的标配,它把flutter_test的 Widget 测试能力与真实浏览器环境桥接起来;
  2. 测试通过 JS 互操作层“注入”浏览器对象:利用dart:js_interopcreateJSInteropWrapper,把 Dart 侧编写的 Mock(MockWindowMockNavigatorMockMediaDevices等)包装成真实的WindowNavigatorMediaDevices实例,从而在不真正唤起摄像头的情况下完整模拟浏览器的媒体设备环境。

camera_test.dart内部按能力分组的测试覆盖了相机核心生命周期:initializeplaypausestoptakePicturegetVideoSizesetFlashModezoomLevelgetLensDirectiongetViewTypevideo recording等。例如初始化测试断言了videoElementautoplay为 false、muted为 true、playsinline属性存在,以及样式objectFit: cover、后置摄像头(environment)会施加scaleX(-1)水平镜像——这些细节直接对应 camera.dart 中创建<video>元素的实现行为。

六、Mock 机制深度解析:JSExport + mockito 双管齐下

测试能够脱离真实摄像头运行,核心功臣是 integration_test/helpers/mocks.dart。它采用两层策略

第一层:mockito 注解生成 Dart 侧 Mock

@GenerateNiceMocks(<MockSpec<dynamic>>[ MockSpec<CameraService>( fallbackGenerators: <Symbol, Function>{ #window: windowShim, #getMediaStreamForOptions: getMediaStreamForOptionsShim, }, ), MockSpec<JsUtil>(), MockSpec<Camera>( fallbackGenerators: <Symbol, Function>{ #videoElement: videoElementShim, #divElement: divElementShim, #window: windowShim, #blobBuilder: blobBuilderShim, }, ), MockSpec<CameraOptions>( fallbackGenerators: <Symbol, Function>{#toMediaStreamConstraints: toMediaStreamConstraintsShim}, ), ]) export 'mocks.mocks.dart';

通过build_runner执行dart run build_runner build即可重新生成mocks.mocks.dart。注意那些fallbackGenerators:像#videoElement: videoElementShim这样的兜底函数,是为了在 mock 未被显式 stub 时返回合法的假HTMLVideoElementvideoElementShim内部用MockVideoElement包装),避免空指针中断测试。

第二层:@JSExport 编写浏览器对象假实现

由于插件内部操作的是真实浏览器对象(MediaStreamMediaStreamTrackMediaRecorder等),Mock 文件还定义了一批用@JSExport()标记的假类型,例如MockMediaDevices(含getUserMediagetSupportedConstraintsenumerateDevices)、MockMediaStreamTrack(含getCapabilitiesgetSettingsapplyConstraintsstop)、MockMediaRecorder(含startpauseresumestopaddEventListener)、FakeMediaStreamFakeMediaDeviceInfoFakeMediaErrorFakeBlobEvent等。

此外,helpers.dart 只做了一件事:export 'mocks.dart';,把所有 mock 与假类型统一对外暴露,供各测试文件import 'helpers/helpers.dart'使用。而getVideoElementWithBlankStream(Size)这个工具函数则通过HTMLCanvasElement.captureStream()生成一个“空白视频流”,让takePicturegetVideoSize等用例无需真实摄像头即可拿到可用的MediaStream

七、13 个测试文件:Web 端能力验证全景

从目录结构看,example 的集成测试按能力拆分为 13 个文件,构成了对 camera_web 的完整覆盖矩阵:

测试文件验证重点
camera_test.dart相机核心生命周期与音视频行为(初始化、播放、暂停、停止、拍照、闪光灯、变焦、镜头方向、录像)
camera_service_test.dartCameraService层:媒体流获取、设备枚举、约束转换
camera_web_devices_test.dart设备列表(enumerateDevices)、可用摄像头枚举
camera_web_capture_test.dart拍照捕获与 Blob 处理链路
camera_web_controls_test.dart相机控制项(闪光灯、缩放等 UI 控制)
camera_web_orientation_test.dart设备方向(Screen Orientation API)与捕获方向锁定
camera_web_lifecycle_and_events_test.dart生命周期事件与浏览器事件流
camera_web_exception_test.dart异常映射为CameraWebException的路径
camera_error_code_test.dartCameraErrorCode错误码语义
camera_metadata_test.dart相机元数据(CameraMetadata
camera_options_test.dartCameraOptionsMediaStreamConstraints转换
camera_bitrate_test.dart视频码率相关行为
zoom_level_capability_test.dart变焦能力(ZoomLevelCapability)边界

例如 camera_test.dart 中setFlashMode(FlashMode.torch)的用例,会断言applyConstraints被调用一次且约束中的torchtrue;而变焦用例则会断言setZoomLevel(45.0)(低于最小值 50)抛出zoomLevelInvalidCameraWebException。这些用例直接验证了 camera_service.dart 与 camera_error_code.dart 中的映射逻辑。

八、测试背后:camera_web 依赖的底层 Web API

原 README 提到的“Web 测试”之所以需要真实浏览器,是因为 camera_web/README.md 列出的每项能力都映射到具体的 Web 平台 API。这套集成测试本质上就是在真实浏览器中验证这些 API 的接线是否正确:

  • 摄像头设备访问:基于 Media Streams API(getUserMedia/enumerateDevices)。注意访问摄像头要求安全上下文(HTTPS 或本机localhost),在非安全上下文中availableCameras可能抛出permissionDeniedCameraException——这也是为什么运行测试必须用chrome等真实浏览器而不是 Dart VM;
  • 设备方向:基于 Screen Orientation API,不支持的浏览器中onDeviceOrientationChanged返回空流,lockCaptureOrientation/unlockCaptureOrientation抛出orientationNotSupported
  • 闪光灯与变焦:基于 Image Capture API(MediaTrackConstraints.torch/zoom),不支持时分别抛出torchModeNotSupported/zoomLevelNotSupported
  • 拍照:基于URL.createObjectURL生成 Blob URL。Web 端不支持dart:io,所以不能用Image.file展示照片,而应使用Image.network(capturedImage.path)或先加载字节再用Image.memory
  • 录像:基于 MediaRecorder API,产物 MIME 类型为video/webm(Chrome/Firefox)或video/mp4(Safari);未开始录制就暂停/恢复/停止会抛videoRecordingNotStarted,浏览器不支持时startVideoRecordingnotSupported

此外,camera_web/pubspec.yaml 显示该插件声明plugin: implements: camerapluginClass: CameraPlugin,印证了其“被背书实现”的身份;同时其依赖flutter_web_pluginsstream_transformwebcamera_platform_interface: ^2.6.0camera_web.dart的入口则仅仅是export 'src/camera_web.dart';

九、尚未实现的 Web 能力(测试边界提示)

camera_web/README.md 明确标注了 Web 端目前缺失的功能,这些也正是现有测试不覆盖、将来新增测试时的方向:

  • Exposure mode、point 和 offset(曝光模式、点与偏移)
  • Focus mode 和 point(对焦模式与对焦点)
  • Sensor orientation(传感器方向)
  • Image format group(图像格式组)
  • Streaming of frames(逐帧流式输出)

十、何时该关注这个 example:一个判断清单

回到 example/README.md 的忠告——“除非你在修改这个实现包,否则该 example 与你的工作极可能无关”。可以据此整理出一个实用判断清单:

  • 作为业务开发者:直接使用camera包,参考 camera 的文档与示例,无需理会本 example;
  • 作为插件维护者/贡献者:修改camera_web的 Dart 实现(如lib/src/camera.dartcamera_service.dart)后,在example目录下用flutter test integration_test -d chrome回归验证,并记得同步更新 CHANGELOG.md;
  • 作为测试基建学习者:本 example 的“mockito 生成 Dart mock + @JSExport 伪造浏览器对象 + integration_test 真实浏览器驱动”三件套,是 Flutter 插件 Web 端测试的成熟范式,可直接借鉴到其他联邦插件的测试工程中。

小结

camera_web的 example 是一台精心搭建的“实现验证台”:它以integration_test为骨架、以 13 个按能力拆分的测试文件为用例集、以 mockito +@JSExport的假浏览器对象为隔离手段,在真实浏览器中完整验证了摄像头枚举、预览、拍照、闪光灯、变焦、方向与录像等 Web 端能力。理解它的定位与运行方式,既能在贡献camera_web时快速上手回归测试,也能为你自己的 Flutter Web 插件测试工程提供一套可复用的参考模板。

【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询