mcpe2e——人工智能驱动的Flutter E2E测试
](https://github.com/JhonaCodes/mcpe2e/releases/tag/v2.2.0) 
mcpe2e允许AI代理(Claude、Codex、Gemini)控制在设备或模拟器上运行的真正Flutter应用程序。代理调用MCP工具——点击、键入、滚动、断言——这些命令作为真实的指针事件到达实时小部件树。
不需要修改UI。没有测试双打。应用程序按原样运行。
______________________________________________________________________
建筑
User → AI Agent (Claude / Codex / Gemini)
|
| MCP (JSON-RPC 2.0 / stdio)
v
mcpe2e_server (Dart binary) installed in ~/.local/bin/
Translates MCP tool calls → HTTP
Exposes workflow skill via MCP Prompts
|
| HTTP localhost:7778
| (ADB forward / iproxy / direct)
v
mcpe2e (Flutter library) dev_dependency in the app
HTTP server embedded inside the app
Executes real pointer events via GestureBinding
|
v
Flutter app on device / simulator两个独立的组件:
| 组件 | 它是什么 | 它在哪里运行 |
|---|---|---|
mcpe2e | Flutter库——应用程序中嵌入的HTTP服务器 | 设备内部 |
mcpe2e_server | MCP服务器二进制文件——将AI工具连接到HTTP | 开发人员的机器 |
mcpe2e 是 不 MCP服务器。它是一个轻量级的HTTP服务器,位于正在运行的应用程序内部,并在实时小部件树上执行手势。
______________________________________________________________________
它是如何工作的——三种查找小部件的方法
代理按照以下优先级顺序解析小部件:
1.McpMetadataKey(推荐)
在小部件上注册密钥,以获得最可靠、最稳定的访问。这就是 默认和推荐方法 --它允许名为的代理访问小部件,启用断言,并在布局更改后幸存下来。
ElevatedButton(
key: const McpMetadataKey(id: 'auth.login_button', widgetType: McpWidgetType.button),
onPressed: _login,
child: const Text('Log in'),
)AI calls: tap_widget key: auth.login_button
AI calls: assert_enabled key: auth.login_button2.现有Flutter键(自动)
如果你的应用程序已经使用 ValueKey 或者其他Flutter密钥,代理会自动从 inspect_ui --无需更改。这些元素以其键值出现在树中:
{ "type": "ElevatedButton", "key": "login_btn", "label": "Login", "x": 20, "y": 400 }AI calls: tap_widget key: login_btn3.坐标(回退)
当小部件根本没有键时,代理会从以下位置回退到屏幕坐标 inspect_ui。这不需要更改应用程序代码,但在布局重建过程中稳定性较差。
{ "type": "ElevatedButton", "label": "Login", "x": 20, "y": 400, "w": 350, "h": 52 }Center: cx = 20 + 350/2 = 195, cy = 400 + 52/2 = 426
AI calls: tap_at x: 195 y: 426
AI calls: input_text x: 16 y: 220 text: "user@example.com"这适用于 任何小部件 在树中——按钮、卡片、列表项、标签、下拉菜单——无需触摸应用程序源代码。
______________________________________________________________________
快速开始
步骤1--将mcpe2e添加到Flutter应用程序中
# pubspec.yaml
dev_dependencies:
mcpe2e: ^2.2.0flutter pub get步骤2——安装MCP服务器并注册您的AI代理
dart run mcpe2e:setup这个命令:
- 下载
mcpe2e_server二进制为您的平台~/.local/bin/ - 打开一个交互式菜单,向您的AI代理注册(Claude Code、Claude Desktop、Codex CLI、Gemini CLI)
随时更改代理人注册:
mcpe2e_server setup步骤3--在main.dart中启动服务器
import 'package:flutter/foundation.dart';
import 'package:mcpe2e/mcpe2e.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (kDebugMode) await McpEventServer.start();
runApp(const MyApp());
}服务器在端口上启动 7777 在发布版本中是不允许的。
步骤4--运行应用程序
flutter runmcpe2e_server 自动运行 adb forward 对于启动时连接的每台Android设备,无需手动端口转发。对于iOS,运行 iproxy 7778 7777 一次。
验证连接:
curl http://localhost:7778/ping
# {"status":"ok","port":7777}步骤5——加载工作流技能(可选但推荐)
在开始任何任务之前,AI代理可以请求内置的工作流指南:
prompts/get { "name": "mcpe2e_workflow" }这提供了一个完整的交互指南——核心循环、工具决策树、表单/对话框/导航模式、错误恢复和代理协议。任何兼容MCP的客户端(Claude、Gemini、Codex)都可以使用它。
第6步——进行第一次测试
在应用程序运行且代理已连接的情况下,典型的测试如下:
使用McpMetadataKey(推荐):
1. inspect_ui → see all widgets
2. input_text key: auth.email_field text: "user@example.com"
3. input_text key: auth.password_field text: "secret123"
4. tap_widget key: auth.login_button
5. wait duration_ms: 1500
6. assert_text key: dashboard.greeting text: "Hello, user"无按键(坐标回退):
1. inspect_ui
→ Agent sees: TextField "Email" at (16, 220), ElevatedButton "Login" at (20, 400, 350x52)
2. input_text x: 16 y: 220 text: "user@example.com"
3. input_text x: 16 y: 296 text: "secret123"
4. tap_at x: 195 y: 426 → center of Login button
5. wait duration_ms: 1500
6. inspect_ui → verify new screen content这两种方法都有效。键提供稳定性并启用断言;坐标不需要更改应用程序。
______________________________________________________________________
可用工具(34)
大多数工具使用key一McpMetadataKeyid或现有Flutter密钥。看 小部件键. 当没有键可用时,使用基于坐标的工具(tap_at,tap_by_label,input_text(x, y))作为退路。
多设备
| 工具 | 参数 | 说明 |
|---|---|---|
list_devices | -- | 发现所有连接的Android设备/模拟器、自动转发端口,ping每个端口 |
select_device | serial | 通过串行方式切换活动设备。所有后续工具都针对所选设备 |
run_command | command, working_dir?, background? | 运行任何shell命令(flutter run, adb等等) |
背景和检查
| 工具 | 参数 | 说明 |
|---|---|---|
get_app_context | -- | 已注册的具有元数据和功能的小部件 |
list_test_cases | -- | 别名 get_app_context |
inspect_ui | -- | 包含值、状态和屏幕坐标的完整小部件树-- 无需注册 |
inspect_ui_compact | -- | 分组摘要(交互式/文本/其他/覆盖/加载)--在简单屏幕上保存令牌 |
capture_screenshot | -- | 当前屏幕为PNG图像--代理直接看到它 |
手势
| 工具 | 参数 | 键 | 说明 |
|---|---|---|---|
tap_widget | key | 必填 | 按键点击小部件-- 推荐 |
double_tap_widget | key | 必填 | 双击 |
long_press_widget | key, duration_ms? | 必填 | 长按 |
swipe_widget | key, direction, distance? | 必填 | 向某个方向滑动 |
scroll_widget | key, direction | 必需 | 滚动可滚动的小部件 |
scroll_until_visible | key, target_key, max_attempts? | 必需 | 滚动直到目标小部件可见 |
drag_widget | key, dx, dy, duration_ms? | 必需 | 从中心按像素偏移拖动 |
pinch_widget | key, scale | 必需 | 缩放 |
tap_at | x, y | -- | 点击屏幕坐标(没有可用键时回退) |
tap_by_label | label | -- | 按可见文本内容点击(例如。 "Login", "Submit") |
输入
| 工具 | 参数 | 键 | 说明 |
|---|---|---|---|
input_text | key, text, clear_first? | 必填 | 按键在文本字段中键入-- 推荐 |
input_text | x, y, text, clear_first?, skip_focus_tap? | -- | 按坐标键入文本字段(回退) |
clear_text | key | 必填 | 清除文本字段 |
select_dropdown | key, value 或 index | 必填 | 选择下拉选项 |
toggle_widget | key | 必需 | 切换复选框、开关或收音机 |
set_slider_value | key, value (0.0–1.0) | 必填 | 设置滑块位置 |
键盘和导航
| 工具 | 参数 | 说明 |
|---|---|---|
show_keyboard | key | 请求焦点并显示虚拟键盘 |
hide_keyboard | -- | 关闭虚拟键盘 |
press_back | -- | 返回导航--自动点击AppBar返回按钮(如果可见),返回系统返回事件 |
wait | duration_ms | 暂停执行(在动画或网络调用后有用) |
断言(需要注册密钥)
| 工具 | 参数 | 说明 |
|---|---|---|
assert_exists | key | 小部件在树中 |
assert_text | key, text | 可见文本与预期值匹配 |
assert_visible | key | 小部件在视口中可见 |
assert_enabled | key | 小部件已启用 |
assert_selected | key | 复选框、开关或收音机处于活动状态 |
assert_value | key, value | TextField控制器值匹配 |
assert_count | key, count | 列表或列恰好有N个子项 |
没有钥匙,代理仍然可以通过读取inspect_ui直接响应——每个文本小部件的内容、每个按钮的启用状态和每个复选框的选中状态都包含在树中。但是,对于正式的测试断言,请注册McpMetadataKey在widget上。
______________________________________________________________________
小部件键(McpMetadataKey)
McpMetadataKey 是 推荐 识别小部件的方法。注册密钥可以获得:
- 稳定的命名访问 --在布局更改和屏幕重建中幸存下来
- 断言 —
assert_text,assert_enabled,assert_selected等需要钥匙 - 可靠的叠加交互 --对话框、底部纸张、抽屉在动画过程中会移动;钥匙绕过了那个
- 更快的查找 --直接进入,而不是步行
如果你的应用程序已经使用 ValueKey,代理会自动从 inspect_ui --无需更改。
如果小部件根本没有键,则代理将退回到基于坐标的工具(tap_at, input_text(x, y)).这是有效的,但不太稳定。
如何添加密钥
分配一个 McpMetadataKey 作为小部件的 key:两者都有 id 和 widgetType 需要:
import 'package:mcpe2e/mcpe2e.dart';
// Button
ElevatedButton(
key: const McpMetadataKey(
id: 'auth.login_button',
widgetType: McpWidgetType.button,
),
onPressed: _login,
child: const Text('Log in'),
)
// Text field
TextField(
key: const McpMetadataKey(
id: 'auth.email_field',
widgetType: McpWidgetType.textField,
),
controller: _emailController,
)
// Checkbox
Checkbox(
key: const McpMetadataKey(
id: 'settings.dark_mode',
widgetType: McpWidgetType.checkbox,
),
value: _darkMode,
onChanged: _toggle,
)
// List (for assert_count)
ListView(
key: const McpMetadataKey(
id: 'order.list',
widgetType: McpWidgetType.list,
),
children: _items.map(_buildItem).toList(),
)一旦分配了密钥,小部件就会出现在 inspect_ui 带着一个 "key" 现场。然后,代理可以使用基于密钥的工具:
tap_widget key: auth.login_button
input_text key: auth.email_field text: "user@example.com"
assert_text key: auth.email_field text: "user@example.com"
assert_enabled key: auth.login_button
assert_count key: order.list count: 5McpWidgetType值
widgetType 描述小部件类型并确定其可用功能:
button · textField · text · list · card · image · container · dropdown · checkbox · radio · switchWidget · slider · tab · custom
密钥命名约定
跟随 module.element[.variant] 图案:
auth.login_button Login button on the auth screen
auth.email_field Email input on the auth screen
profile.avatar User avatar image
order.list The orders list
order.card.{id} A dynamic card identified at runtime
settings.dark_mode Dark mode toggle
modal.confirm.delete Confirmation dialog
sheet.address.submit Submit button inside a bottom sheet
nav.drawer Navigation drawer
snackbar.undo Undo action in a snackbar决议优先级
代理按以下顺序解析小部件:
| 优先级 | 方法 | 何时使用 |
|---|---|---|
| 第一名 | McpMetadataKey | 所有可测试小部件的默认值。支持断言,在重建过程中保持稳定 |
| 第二名 | 现有Flutter密钥 (ValueKey,等等) | 应用程序已经有密钥,不需要更改 |
| 第三名 | 坐标 (tap_at, input_text(x, y)) | 不带任何键的小部件的回退——动态列表、第三方小部件、快速探索 |
当每种方法都适合时
| 场景 | 方法 |
|---|---|
| 您控制的任何小部件 | McpMetadataKey --注册一次,随处使用 |
应用程序已具有 ValueKey on小部件 | 按原样使用它们——代理会看到它们 inspect_ui |
断言(assert_text, assert_enabled等) | 密钥 必需的 --断言仅适用于键 |
| 对话框/底板/抽屉/零食架 | 按键 强烈推荐 --动画期间叠加坐标偏移 |
| API的动态列表项 | order.card.{id} 使用运行时ID或坐标作为回退 |
| 您无法修改的第三方小部件 | 通过 inspect_ui → tap_at |
| 快速一次性探索 | 坐标-快速,无需更改代码 |
推荐:为覆盖的小部件添加按键
对话框、底图、抽屉和任务栏在单独的覆盖层中渲染。在打开动画的过程中,它们的坐标可能会发生偏移 tap_at 可靠性较低。添加a McpMetadataKey 让代理使用这些表面 tap_widget 而不是追逐坐标。
// Confirmation dialog actions
AlertDialog(
title: const Text('Delete item?'),
actions: [
TextButton(
key: const McpMetadataKey(
id: 'modal.confirm.cancel',
widgetType: McpWidgetType.button,
),
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
key: const McpMetadataKey(
id: 'modal.confirm.ok',
widgetType: McpWidgetType.button,
),
onPressed: _delete,
child: const Text('Delete'),
),
],
)
// Bottom sheet action button
showModalBottomSheet(
context: context,
builder: (_) => ElevatedButton(
key: const McpMetadataKey(
id: 'sheet.checkout.submit',
widgetType: McpWidgetType.button,
),
onPressed: _submit,
child: const Text('Confirm'),
),
);没有密钥,代理仍然可以使用以下命令与覆盖小部件交互 tap_at 呼叫之后 inspect_ui --但可能需要 wait 为了让动画完成,坐标可能会在运行之间移动。
______________________________________________________________________
路线跟踪(McpNavigatorObserver)
get_app_context 报告当前路线。为了获得准确的路线名称,请注册观察者:
// With MaterialApp
MaterialApp(
navigatorObservers: [McpNavigatorObserver.instance],
...
)
// With GoRouter
GoRouter(
observers: [McpNavigatorObserver.instance],
...
)如果没有它,路由将回退到从屏幕名称导出的值。
______________________________________________________________________
对话框中的文本输入
当对话框或覆盖层阻止焦点点击时,使用ADB回退:
// 1. Focus the field
tap_at x: y:
// 2. Type via ADB (run_command)
run_command: adb -s shell input text "your_text"
// Get the serial from:
run_command: adb devices这适用于身份验证码、PIN码和任何 TextField 内部A AlertDialog 或 BottomSheet.
______________________________________________________________________
管理代理
mcpe2e_server setup交互式菜单,无需重新安装即可启用或禁用单个代理(Claude Code、Claude Desktop、Codex CLI、Gemini CLI)。
______________________________________________________________________
平台连接性
| 平台 | 机制 | 设置 |
|---|---|---|
| Android | ADB转发(自动) | 无-- mcpe2e_server 启动时处理它 |
| iOS | iproxy | iproxy 7778 7777 |
| macOS/Linux/Windows桌面 | 直接本地主机 | 设置 TESTBRIDGE_URL=http://localhost:7777 |
| Web | 不支持 | Flutter Web无法打开TCP套接字 |
TESTBRIDGE_URL 告诉 mcpe2e_server 在哪里访问应用程序。违约: http://localhost:7778.
______________________________________________________________________
存储库结构
mcpe2e/ Flutter library — add to your app as dev_dependency
mcpe2e_server/ MCP server binary — install once on your machine
docs/ Integration guides and examples
CLAUDE.md Architecture reference for Claude Code context______________________________________________________________________
文档
| 文件 | 描述 |
|---|---|
mcpe2e/README.md | Flutter库参考(端点、API、生产安全) |
mcpe2e_server/README.md | MCP服务器设置、配置和工具参考 |
docs/integration-guide.md | 任何Flutter应用程序的逐步集成 |
docs/test-flow-example.md | 完成测试演练 |
docs/writing-tests.md | 脚本模式和目标模式测试格式、模板 |
CLAUDE.md | Claude代码上下文的架构参考 |
______________________________________________________________________
从源头构建
git clone https://github.com/JhonaCodes/mcpe2e
cd mcpe2e/mcpe2e_server
dart pub get
dart compile exe bin/mcp_server.dart -o mcpe2e_server需要Dart SDK>=3.5.0。macOS(arm64、x86_64)、Linux(x86_24)和Windows(x86_14)的预编译二进制文件可在每个平台上使用 .
