直接对 AI 说"帮我登录一下",然后它真的打开你的 Flutter App,填好账号密码,点击登录,跳转到首页。这不是科幻,今天就能用上。
背景:MCP 是什么?
MCP(Model Context Protocol)是 Anthropic 推出的开放协议,让 AI 助手能连接外部工具和服务。简单说,它是 AI 和你的开发工具之间的"翻译官"。
Flutter 官方推出了 dart mcp-server,让 Claude Code 可以直接:
- 获取运行中 App 的 Widget Tree
- 执行热重载 / 热重启
- 查看运行时错误
- 通过 Flutter Driver 操控 UI
一、环境配置
1. 安装 dart mcp-server
在项目根目录执行:
claude mcp add -s project --transport stdio dart -- dart mcp-server
此时项目根目录会自动生成.mcp.json
{
"mcpServers": {
"dart-mcp-server": {
"command": "dart",
"args": ["mcp-server"],
"env": {}
}
}
}
2. 开启 VSCode 支持
在 .vscode/settings.json 中添加:
{
"dart.mcpServer": true
}
3. 集成 Flutter Driver
pubspec.yaml 新增 dev 依赖:
dev_dependencies:
flutter_driver:
sdk: flutter
test: any
新建 main_driver.dart 作为 Driver 入口:
import 'package:flutter_driver/driver_extension.dart';
import 'lib/main.dart' as app;
void main() {
enableFlutterDriverExtension(); // 担心生产构建污染,可以用env控制
app.main();
}
二、连接 MCP
启动应用后,终端会打印 DTD 地址:
ws://127.0.0.1:62633/P6KDlN4NFYw=
把这个地址发给 Claude Code,它会自动连接:
连接成功!Dart MCP 已就绪
连接成功后,Claude 有了"眼睛"和"手"——能看到 Widget Tree,也能操控 UI。
---
## 三、让 AI 直接操控 App
连接成功后,直接用自然语言下指令:
> "执行登录操作,账号 15899999999,密码 a123456,勾选协议,点击登录"
Claude 会自动拆解步骤,依次执行:
- waitFor('欢迎登录二手合规通!') → 确认登录页已渲染
- tap(phone_input) → 点击手机号输入框
- enterText('15899999999') → 输入手机号
- tap(password_input) → 点击密码输入框
- enterText('a123456') → 输入密码
- tap(AppCheckbox) → 勾选协议
- tap('登录') → 点击登录按钮
整个过程无需手动操作。
> 关键细节:Driver 要精准定位输入框,需要给 Widget 加上 `Key`:
>
> ```dart
> TextField(
> key: const Key('phone_input'),
> ...
> )
> ```
---
## 自动化测试用例
和mcp无关, 使用的是flutter_driver
有了 Flutter Driver 的基础,把操作固化成可重复执行的测试用例。
### 测试文件结构
test/ └── driver/ └── login_test.dart # 登录页测试(8 个用例)
### setUpAll 模板
```dart
setUpAll(() async {
driver = await FlutterDriver.connect();
// 等待 runApp() 完成,避免"No root widget"错误
await driver.waitUntilFirstFrameRasterized();
// 等待目标页面渲染完成
await driver.waitFor(find.text('欢迎登录二手合规通!'));
});
不加 waitUntilFirstFrameRasterized() 会报 No root widget is attached,因为 Driver 连接时 runApp() 还没执行完。这是第一个坑。
登录页 8 个测试用例
| 用例 | 场景 | 验证点 |
|---|---|---|
| TC-01 | 手机号为空 | Toast "请输入手机号" |
| TC-02 | 手机号不足 11 位 | Toast "登录手机号格式不正确" |
| TC-03 | 密码为空 | Toast "请输入密码" |
| TC-04 | 密码少于 6 位 | Toast "请输入6-20位密码" |
| TC-05 | 未勾选协议 | Toast "请先阅读并同意..." |
| TC-06 | 点击清除按钮 | 输入框内容清空 |
| TC-07 | 点击协议文字 | 切换勾选状态 |
| TC-09 | 正确账号密码+勾选协议 | 跳转首页(ShellScaffold) |
TC-08(点击《用户协议》跳转 WebView)跳过了——WebView 页面会导致 flutter_driver VM service 连接断开,这是已知限制。
核心测试代码片段
// TC-06: 清除按钮验证
test('TC-06 手机号清除按钮 - 输入后点击清除,输入框变空', () async {
await driver.tap(find.byValueKey('phone_input'));
await driver.enterText('15899999999');
final textBefore = await driver.getText(find.byValueKey('phone_input'));
expect(textBefore, '15899999999');
await driver.tap(find.byValueKey('phone_clear_btn'));
final textAfter = await driver.getText(find.byValueKey('phone_input'));
expect(textAfter, '');
});
// TC-09: 正常登录
test('TC-09 正常登录 - 跳转到首页', () async {
await driver.tap(find.byValueKey('phone_input'));
await driver.enterText('15899999999');
await driver.tap(find.byValueKey('password_input'));
await driver.enterText('a123456');
await driver.tap(find.byType('AppCheckbox'));
await driver.tap(find.text('登录'));
await driver.waitFor(
find.byType('ShellScaffold'),
timeout: const Duration(seconds: 10),
);
});
五、执行测试
运行命令(3 个参数缺一不可)
NO_PROXY=127.0.0.1,localhost flutter drive \
--target=main_driver.dart \
--driver=test/driver/login_test.dart \
--dart-define-from-file .env.development
| 参数 | 说明 |
|---|---|
--target=main_driver.dart | Driver 入口,固定值 |
--driver=test/driver/xxx.dart | 测试文件路径 |
--dart-define-from-file | 必须,否则 API_HOST 为空导致 App 崩溃 |
NO_PROXY=127.0.0.1,localhost | macOS 有代理时必须设置 |
测试结果
+1: TC-01 手机号为空 - 提示"请输入手机号"
+2: TC-02 手机号格式不正确(少于11位)
+3: TC-03 密码为空 - 提示"请输入密码"
+4: TC-04 密码长度不足(少于6位)
+5: TC-05 未勾选协议 - 提示"请先阅读并同意..."
+6: TC-06 手机号清除按钮 - 输入后点击清除,输入框变空
+7: TC-07 点击协议文字 - 可切换勾选状态
+8: TC-09 正常登录 - 跳转到首页
All tests passed!
TC-09 不只是 UI 测试,它真实调用了后端接口,完整走完了:
获取客户端列表 → 登录 → 获取店铺列表 → 跳转首页
六、踩坑总结
调试过程中遇到了不少问题,记录下来省得下次再踩:
| 问题 | 原因 | 解决方案 |
|---|---|---|
Connection closed before full header | macOS 代理拦截了本地连接 | 加 NO_PROXY=127.0.0.1,localhost |
Invalid argument (baseUrl): Must be a valid URL | 缺少 --dart-define-from-file | 补上环境变量参数 |
No root widget is attached | Driver 连接时 runApp() 未完成 | 加 waitUntilFirstFrameRasterized() |
Service connection disposed | WebView 页面导致 VM service 断开 | 跳过含 WebView 的测试用例 |
enter_text 无效果 | 未先 tap 聚焦输入框 | 先 tap 再 enterText |
七、总结
Flutter MCP 把 AI 变成了你的测试工程师——它能看懂 Widget Tree,能操控 UI,能帮你写测试,还能帮你跑测试。
开发阶段不用反复手动填表单,测试阶段 AI 分析页面结构、补全 Widget Key、生成用例,测试命令固化后可以直接接入 CI 流水线。
写完登录页,直接说一句"帮我测一下登录流程"就够了。