告别手动调试!用 Flutter MCP 让 AI 直接操控你的 App

9 阅读5分钟

直接对 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 地址:

connection-dtd.png

ws://127.0.0.1:62633/P6KDlN4NFYw=

把这个地址发给 Claude Code,它会自动连接:

连接成功!Dart MCP 已就绪

连接成功后,Claude 有了"眼睛"和"手"——能看到 Widget Tree,也能操控 UI。

---

## 三、让 AI 直接操控 App

连接成功后,直接用自然语言下指令:

> "执行登录操作,账号 15899999999,密码 a123456,勾选协议,点击登录"

Claude 会自动拆解步骤,依次执行:

  1. waitFor('欢迎登录二手合规通!') → 确认登录页已渲染
  2. tap(phone_input) → 点击手机号输入框
  3. enterText('15899999999') → 输入手机号
  4. tap(password_input) → 点击密码输入框
  5. enterText('a123456') → 输入密码
  6. tap(AppCheckbox) → 勾选协议
  7. 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.dartDriver 入口,固定值
--driver=test/driver/xxx.dart测试文件路径
--dart-define-from-file必须,否则 API_HOST 为空导致 App 崩溃
NO_PROXY=127.0.0.1,localhostmacOS 有代理时必须设置

测试结果

+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 headermacOS 代理拦截了本地连接NO_PROXY=127.0.0.1,localhost
Invalid argument (baseUrl): Must be a valid URL缺少 --dart-define-from-file补上环境变量参数
No root widget is attachedDriver 连接时 runApp() 未完成waitUntilFirstFrameRasterized()
Service connection disposedWebView 页面导致 VM service 断开跳过含 WebView 的测试用例
enter_text 无效果未先 tap 聚焦输入框tapenterText

七、总结

Flutter MCP 把 AI 变成了你的测试工程师——它能看懂 Widget Tree,能操控 UI,能帮你写测试,还能帮你跑测试。

开发阶段不用反复手动填表单,测试阶段 AI 分析页面结构、补全 Widget Key、生成用例,测试命令固化后可以直接接入 CI 流水线。

写完登录页,直接说一句"帮我测一下登录流程"就够了。


参考文档:Flutter MCP Server 官方文档