SuperAPI AI 工具集

flutter-mcp-e2e-harness

测试与质量

查看项目

更新于 10-19 17:36

技能介绍

用途

flutter-mcp-e2e-harness 用于为 Flutter 应用编写和运行可重复的端到端(E2E)场景,以 checked-in Dart 代码形式提交入库,适合在 CI 中执行并以退出码反馈结果。与之相对,一次性的交互式调试应使用 flutter-mcp 提供的 MCP fmt_* 工具。

主要能力

  • 完整生命周期管理:packages/harness(flutter_mcp_harness)负责构建应用、启动进程、连接其 Dart VM service,然后再进行驱动与断言。
  • 同一套扩展:以纯 Dart 方式调用与 MCP 服务器相同的 ext.mcp.toolkit.* 服务扩展。
  • 通用驾驶契约:ToolkitDriver 暴露 observe/act/verify 循环:snapshot 返回带引用与边界的语义 AxNode 树;perform 支持 click、type、key、命名路由跳转与 evaluate;screenshot 输出 PNG。
  • 意图路由:IntentDriverRouter 可将意图的自动化提示(transport + verb + locator)路由到绑定的驱动。
  • 应用专属动作:通过 invoke 层(ADR-0017),应用自身的 intent 注册表即为动作目录;driver.actions() 列出动作,InvokeAction(name, args) 以 schema 校验参数派发到注册条目。
  • Web 支持:无 VM service 的 Web 表面(Jaspr、纯 JS)通过 CdpDriver 上的 window.__mcpActions 页面注册表以相同方式组合。

使用方式

场景由 Scenario 对象定义,步骤按顺序执行,首个失败(抛出或 report.fail)中止后续步骤,清理仍在调用方 finally 中执行。

final scenario = Scenario('checkout E2E', steps: [
  ('launch app', (context) async {
    final app = await MacosAppTarget(projectDir: appDir, binaryPath: binary)
        .launch(build: build);
    launched = app;
    await app.stdout.waitFor('app ready');
    context.bag['app'] = app;
  }),
  ('drive + assert', (context) async {
    final driver = await attachDriver(context.take<LaunchedApp>('app'));
    await driver.expectLabel('checkout');
    await driver.perform(const ClickAction(name: 'checkout'));
  }),
]);

var passed = false;
try {
  passed = await scenario.run();
  scenario.report.printSummary();
} finally {
  await launched?.stop();
}
exit(passed ? 0 : 1);

HarnessContext.bag 在步骤间传递值,take<T>(key) 读取;retry(action) 会轮询直到非空,用于应用侧就绪竞态。完整双实例组合根示例见 packages/harness/example/desktop_pair.dart。

Web/AOT 层示例:

final app = await WebBuildTarget(projectDir: '.', build: !skipBuild).launch();
final flutter = app.flutter;
await flutter.waitText('Settings');
await flutter.clickText('Sync between devices', expect: 'I have a pairing code');
await flutter.typeFocused('mesh-pair/v1 ...');
await app.stop();

FlutterWebCdp 中已固化的实测规则包括:语义点击需逐次用 expect 验证效果后再重试(切换控件重复点击会关掉自身);加载后首次点击会被吞掉,用 warmUpClick() 消耗;输入需要 Page.bringToFront + Input.insertText;仅做应用内跳转(popRoute = history.back()),重载会丢失内存状态;Headless Chrome 不执行 rAF 会使 Flutter 冻结,因此 WebBuildTarget 默认使用有界面 Chrome。

启动路径(按所有权选择,每个应用至多一个拥有会话):

  • FlutterRunTarget:由 flutter 工具拥有构建 + 热重载(经 stdin 的 r/R/q)。
  • BinaryAppTarget / MacosAppTarget / WindowsAppTarget:执行全新完整 flutter build <os> --debug 后直接启动二进制;launch(build: false) 可复用已有二进制;不会打开拥有会话。
  • AppOwnership.attachOnly:应用已在运行且不应由 harness 停止时使用;按二进制名发现 pid,校验运行中的可执行文件与 binaryPath、磁盘上的构建与进程启动时间,通过显式命名的 vmServiceUri 连接,stop() 不向进程发送信号。
  • 外部运行器拥有应用时:围绕拥有进程直接构造 LaunchedApp,从运行器会话契约而非 stdout 读取 VM URI。

证据收集:EvidenceCollector 可在场景运行时附加证据,首个失败步骤将证据转储到 $TMPDIR/<scenario>/;attachCdp 缓冲页面控制台与未捕获异常,attachLines 跟踪应用 stdout,attachFile 延迟输出 AX/状态转储。证据生成按契约是尽力而为,不应掩盖失败。对启动中的应用调用 VM 扩展可用 vm.callExtensionUntil(...),避免手写重试循环。

适用场景

  • 以代码形式编写、可重复执行、CI 友好的 E2E 场景。
  • 通过语义树与统一动作词汇驱动 Flutter 应用、浏览器(CdpDriver)和 OS 原生目标。
  • 在 AOT Web 构建(无 VM service)上通过 CDP 层做端到端验证。

限制

  • 不新增 toolkit 动作;ToolkitExtensions 须与 mcp_toolkit 的 interaction_toolkit.dart 保持同步。
  • BinaryAppTarget 不提供拥有的编译会话,只做全新完整构建。
  • 不支持声明式场景文档(YAML 运行器)。
  • Android/iOS 设备启动交由拥有的开发会话(oka_harness,ADR-0014),不在本包范围。
  • 证据边界:MCP/驱动 UI 场景只能证明控件存在、可达且有响应,无法证明输入不经过 UI 的路径(如摄像头手势);各层级结论不可相互替代。

验证

cd packages/harness && dart test
dart run example/desktop_pair.dart --skip-build