flutter_wright 0.11.0
flutter_wright: ^0.11.0 copied to clipboard
Playwright-style control of a running Flutter app — snapshot, tap, type, waitFor, network capture — for AI-driven and automated end-to-end testing. Debug-only in-app HTTP server.
更新日志 #
0.11.0 #
- feat(network):
GET /network网络请求捕获(对标 Playwright network 观测)。start(enabled: true)时链式包装HttpOverrides.global,记录 dart:ioHttpClient层全部请求(http包与 Dio 默认 adapter 都在内):method/url/status/耗时/字节数 + 文本类响应体截断捕获(4KB,入缓冲前按 key 名脱敏 token/password/secret/api_key/session/cookie →[REDACTED],启发式非穷举),环形缓冲 200 条。since=(seq 增量)/clear=(取完清空)。FlutterWrightConfig.recordNetwork(默认 true)可关,关闭时端点回 501。不可见:原生通道流量(cronet/OkHttp)、WebView 内请求。 - feat(snapshot):
GET /snapshot?bounds=true—— 每个输出节点附rect=[l,t,w,h](设备物理像素,语义根 transform 自带 DPR;与adb input tap/screencap同坐标系,可直接喂坐标点按)。默认关闭,不影响既有输出。 - feat(actionability):
/tap/long_press动作前预检(对齐 Playwright actionability 最小子集):节点 hidden / 带 enabled 态且禁用 → 422 并在响应体给出原因(此前只有「无对应 action」一种 422)。 - perf(wait_for): 轮询期改走节点级探针(
containsTextAsync/containsRefAsync),不再每 100ms 全量序列化整棵语义树后做 substring —— 轮询开销大幅下降且不再抢 UI 线程;完整快照只在命中后构建一次。行为变更:text=/gone=匹配语义文本(label/value 与 H5 切片),不再命中 role 名(button/textfield)、[ref=…]标记等 YAML 语法(旧行为的误命中属 bug)。 - docs: pubspec description 去掉不存在的 "assert",改为如实的 "waitFor, network capture"。
0.10.1 #
- fix:
/navigate/reset对宿主适配器闭包改有界等待(3s)——闭包把路由 pop-future 透传回来时(GetX=> Get.toNamed(...)的经典误接,该 future 要等页面被 pop 才完成),控制面不再永久挂起:超时按「已发起」返回 200 + settled snapshot,响应附warning字段点名疑似 pop-future;3s 内抛错仍走 500;窗口外迟到的错误只记日志、不产生 unhandled async error。 - docs: dartdoc/README/集成指南(zh×2)的 GetX 示例全部改为 void 块 fire-and-forget 并注明 pop-future 陷阱 —— 此前示例教的正是误接写法(集成测试
getx_integration_test.dart早已注释此坑,文档未同步)。
0.10.0 #
- feat: 动作端点(
/tap/type/scroll/long_press/navigate/reset)回吐的snapshot改为动作后下一帧序列化(endOfFramesettle,500ms 真实计时器兜底防挂):非导航动作的回吐即动作后最新状态,不再是「动作前同帧」旧快照;导航动作回吐已含新路由首帧(转场期间新旧页节点可能并存,确定性同步仍用/wait_for)。app 退后台等无帧场景由兜底路径返回,可能仍是旧快照。HTTP 响应结构不变,仅采样时机后移。 - fix:
example/补package:flutter/foundation.dartimport(kDebugMode在 Flutter 3.44 下 analyze 报错)。
0.9.2 #
- docs: 新增
example/(最小可运行 app:FlutterWright.start(enabled: kDebugMode)+ 计数器页),补齐 pub.flutter-io.cn「Package has an example」评分项。 - docs:
wait_for_handlerdartdoc 的ref=<sN>等查询参数加反引号 —— 消除 dartdoc「尖括号被当 HTML」告警,pub.flutter-io.cn 静态分析满分。 - fix: 对齐代码内
FlutterWright.version常量到包版本(此前滞留0.9.0,致/health在 0.9.1 包仍上报 0.9.0)。
0.9.1 #
- docs: README 重写为英文、精简为快速上手。修正
FlutterWrightRoot被误列入基本集成 —— 它仅为/screenshot的 Flutter 原生截图所需、属可选;最小集成只需FlutterWright.start()+runApp(MyApp())。Navigation 一节改用 GetX/GoRouter adapter 示例。
0.9.0 #
- feat(webview): 选填
FlutterWright.registerWebView(evalJs, {label, context})—— 一行闭包让内嵌 WebView 的 H5 透明进snapshot,并可被同一套tap/type/scroll/longPress/waitFor(wXeYref)驱动。核心零 webview 插件依赖;不调则行为与 0.8.0 完全一致。
0.8.0 - 2026-05-28 #
破坏性变更 #
- 启用改为宿主显式控制:
FlutterWright.start({bool enabled = false})。默认 false → 不绑定控制面(fail-safe,正式包零运行时攻击面)。移除FlutterWrightConfig.enableInDebugOnly。- 迁移:旧
enableInDebugOnly: true(仅 debug 启用)≈FlutterWright.start(enabled: kDebugMode);提测的 release 包按自有「测试包?」判断接线,如FlutterWright.start(enabled: AppEnv.isTestBuild)。
- 迁移:旧
新增 #
- 可选 token 鉴权:
start({String? token})。非空时除GET /health外所有请求须带匹配X-FW-Token,否则 401(常量时间比对)。为空 = 不鉴权(仅 loopback)。token 由集成方自定,只从 env/本地配置读,绝不进仓库。 /health应用身份:start({String? appName, String? package}),/health(免 token)回{ok, service, version, name, package},供发现/探活确认目标 app。name/package未设时为 JSONnull(键始终存在,便于强类型反序列化)。
0.7.0 - 2026-05-27 #
新增 #
- snapshot-first 交互层(对齐 Playwright MCP):
GET /snapshot(带[ref=sN]的 YAML 语义树,SemanticsSnapshot.serialize)。POST /tap/long_press/scroll/type(经SemanticsOwner.performAction/EditableTextState.userUpdateTextEditingValue,请求体element+ref, 成功响应自动回吐snapshot)。GET /wait_for?text=|ref=|gone= [&timeout=ms](轮询条件,408 超时)。start()持有SemanticsBinding.instance.ensureSemantics()句柄, 强制常开语义树(无障碍服务未开时也能读到节点);stop()释放。
- skill 侧脚本:
snapshot.sh/tap.sh/long_press.sh/type.sh(含submit=发 ENTER) /scroll.sh/wait_for.sh(SDK);press_key.sh/back.sh(adb keyevent,免 SDK);logs.sh(读 daemonapp.log,免 SDK)。
变更 #
- navigatorKey 降级为可选:
FlutterWright.start()仅当传了navigatorKey或navigationAdapter才注册/navigate/reset/routes;否则这三个端点回 501 「navigation not configured」。交互闭环只需start()即可。 /navigate/reset的成功响应也附snapshot字段(与 /tap 等动作一致)。- 示例
dev/main_dev.dart显式传navigatorKey: FlutterWright.navigatorKey。
测试 / 兼容 #
- 单元测试:
semantics_snapshot_test、semantics_action_test、start_navigation_test(SDK)。 - E2E:
e2e_interaction_test(snapshot/type→tap/wait_for/navigate 回吐)。既有e2e_control_plane_test与各 example 测试改造withApp在测试体内 stop() 以释放 SDK 持有的SemanticsHandle(flutter_test 的 invariant 检查早于 tearDown)。 - API 全部兼容 Flutter 3.24(已下限)~3.44。
0.6.0 - 2026-05-25 #
破坏性变更。
移除 #
FlutterWright.start(testRoutes:)参数;FlutterWright.routes(RouteRegistry) 静态字段;src/route_registry.dart整个文件及其 barrel export。路由发现改由NavigationAdapter.discoverableRoutes提供。POST /reload端点与ReloadHandler;vm_service依赖。热重载改由 flutter-wright skill 经run持有的flutter run --machinedaemon(app.restart)驱动,不再经 SDK。SDK 自此 只负责goto/reset(导航)与/screenshot(FlutterWrightRoot)、/health。
新增 #
NavigationAdapter.discoverableRoutes(可选,默认null):GET /routes的唯一来源。NavigatorKeyAdapter(.., {routes})与CallbackNavigationAdapter(.., {routesProvider})用于喂路由发现。FlutterWright.start({navigatorKey, routes}):可传宿主自有 navigatorKey;routes路由名列表喂GET /routes(取代testRoutes)。- (skill 侧)
run/stop方法:AI 持有一个后台flutter run --machinedaemon;reload重写为驱动该 daemon。env-check 从全局/health闸门拆为逐方法前提。
修复 #
- 统一版本号:
pubspec.yaml与代码version常量对齐为 0.6.0。
迁移 #
start(testRoutes: [...])→ Navigator 1.0(map)用start(routes: map.keys); Navigator 1.0(onGenerateRoute)暴露一次路由名常量start(routes: AppRouter.names); 其他栈把路由放进 adapter 的routesProvider。- 自定义
implements NavigationAdapter的 adapter 需补一行Iterable<String>? get discoverableRoutes => null;(不可枚举时)。 - 用过
POST /reload或 skillreload(走 SDK)的:改用 flutter-wright skill 的run起 app,reload即驱动该 daemon;或在自己的 flutter run 控制台按r。
0.4.0 - 2026-05-25 #
破坏性变更。 不保证低版本兼容。
移除 #
- 整个 mock 能力:
MockDataProvider、InMemoryMockDataProvider、POST /mock端点、FlutterWright.start(mockProvider:)参数、FlutterWright.mockProvidergetter 全部删除。理由:让/mock真正改 UI 需要宿主把 provider 接进 repository/数据层, 对大部分真实项目改动量太大、几乎没人会接入 —— 属伪需求。SDK 聚焦真正低成本、 架构无关的能力(navigate / screenshot / routes / reset / reload / health)。- 配套移除 skill 的
mock方法(scripts/mock.sh)与reset的clearMock参数;POST /reset现在只接受空 body、只把 navigator pop 回根。
- 配套移除 skill 的
变更 #
ResetHandler不再持有 mock,只驱动NavigationAdapter.reset()。- 示例 app 去掉 mock 桥接(
DataStore/DevDataStore),dev/main_dev.dart复用lib/app.dart的createApp()工厂,不再重复 app 启动逻辑(改善 dev 入口可维护性)。
0.3.0 - 2026-05-25 #
新增 #
- 可插拔导航(
NavigationAdapter):/navigate与/reset不再硬编码 Navigator 1.0 的pushNamed/popUntil,改为经NavigationAdapter适配, 从而路由架构无关(Navigator 1.0 / GoRouter / GetX / auto_route 等)。NavigatorKeyAdapter—— 默认实现(命名路由,行为同旧版)。CallbackNavigationAdapter—— 逃生舱:宿主提供onNavigate/onReset两个闭包,适配 GoRouterrouter.go、GetXGet.toNamed等任意栈。FlutterWright.start新增可选参数navigationAdapter;不传时默认NavigatorKeyAdapter(navigatorKey),完全向后兼容。
变更 #
- 推荐集成方式改为
dev_dependencies+ 独立 debug 入口(dev/main_dev.dart), 使生产lib/对 SDK 零引用、release 构建零残留。packages/example已按此范式重构 (mock 经 app 本地DataStore接口解耦,DevDataStore在dev/桥接 SDK 的MockDataProvider)。详见docs/integration-guide.md。
修复 #
GET /screenshot(flutter 渲染模式)恒返回 500 的真实 bug:captureFlutterScreen原先读取binding.rootElement.renderObject(恒为RenderView,永远不是RenderRepaintBoundary),导致FlutterWrightRoot包装器形同虚设。现改为给FlutterWrightRoot的RepaintBoundary挂GlobalKey(fwRepaintBoundaryKey)并经其定位 捕获,回退到旧的根检查。Android 模拟器实测返回 200 PNG。
0.2.0 - 2026-05-22 #
0.1.0 (首次发布) #
- 仅 debug 启用的 HTTP server,默认绑
127.0.0.1:9123(可配置)。 - 接口:
/health、/routes、/navigate、/reset、/mock、/screenshot。 MockDataProvider接口 +InMemoryMockDataProvider默认实现。FlutterWrightRootWidget 包装,让 app 内截图更可靠。- Release 构建是 no-op(由
kDebugMode守门)。