关于 patrol
patrol 是用于 Flutter 项目的端到端集成测试技能,面向在 macOS、iOS、Android 和 Chrome 上运行测试的开发者。它提供设备选择、测试前检查、断言模式和故障排查指导。
主要功能
- 01运行指定文件或全部集成测试
- 02支持 macOS、iOS、Android 和 Chrome
- 03提供测试前检查清单
- 04使用日志与条件等待进行断言
- 05提供常见故障排查指导
适用场景
- 01在指定设备上运行 Flutter 集成测试
- 02检查测试挂起或设备连接问题
- 03通过应用日志验证异步事件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ soliplex/ flutter --skill patrol安装后在 Claude Code 中输入/patrol确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
setup/android-setup.mdios-setup.mdmacos-setup.mdweb-setup.md详细介绍
Patrol 端到端测试技能
运行和管理此 Flutter 项目的 Patrol 集成测试(macOS、iOS、Android 和 Chrome)。
运行测试
CLI 位置: patrol
始终使用 --device,避免出现交互式设备选择提示。
macOS(默认)
# Run a specific test file
patrol test \
--device macos \
--target integration_test/$ARGUMENTS \
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
# Run all integration tests
patrol test \
--device macos \
--target integration_test/ \
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
首次设置: 有关权限和窗口限制,请参阅 macOS 设置指南。
iOS(模拟器)
使用已启动模拟器的设备 ID 或名称。--ios 标志用于指定操作系统
版本(默认为 latest,必须与模拟器的操作系统版本一致)。
# Run on a specific iOS simulator
patrol test \
--device <DEVICE_ID> \
--target integration_test/$ARGUMENTS \
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
# If simulator OS != latest SDK, specify explicitly
patrol test \
--device <DEVICE_ID> \
--ios=18.6 \
--target integration_test/$ARGUMENTS \
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
使用 xcrun simctl list devices booted 查找设备 ID 和操作系统版本。
首次设置: 有关 RunnerUITests 目标和模拟器操作系统版本匹配要求,请参阅 iOS 设置指南。
Android(模拟器或设备)
需要已启动的模拟器或已连接的设备。使用 adb devices 中的名称或序列号。
# Boot emulator (if not running)
export ANDROID_HOME=~/Library/Android/sdk
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"
emulator -avd Patrol_Test_API_36 &
# Run on Android emulator
patrol test \
--device emulator-5554 \
--target integration_test/$ARGUMENTS \
--dart-define SOLIPLEX_BACKEND_URL=http://10.0.2.2:8000
注意: Android 模拟器使用 10.0.2.2 访问主机的 localhost。
首次设置: 有关 AVD 创建、JDK 和 Google APIs 镜像要求,请参阅 Android 设置指南。
Chrome(Web)
需要 Node.js >= 18(Playwright 会在首次运行时自动安装)。
# Run a specific test in Chrome
patrol test \
--device chrome \
--target integration_test/$ARGUMENTS \
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
# Run headless (for CI or no-GUI environments)
patrol test \
--device chrome \
--web-headless true \
--target integration_test/$ARGUMENTS \
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
# Custom viewport (default is browser-dependent)
patrol test \
--device chrome \
--web-viewport "1280x720" \
--target integration_test/$ARGUMENTS \
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
首次设置: 有关 Node.js、CORS 和视口详情,请参阅 Web 设置指南。
如果 $ARGUMENTS 为空或为 "all",则针对 integration_test/ 运行测试(所有测试)。
如果 $ARGUMENTS 是类似 smoke_test.dart 的文件名,则运行该特定文件。
预检检查
运行测试前,请确认:
- 后端正在以
--no-auth-mode运行,且使用上文的 URL - 已安装 patrol CLI:
patrol --version - 代码可编译:先运行
dart analyze integration_test/ - test_bundle.dart 是最新的:Patrol 会自动生成此文件——如果测试未包含在测试包中,请删除该文件并让
patrol test重新生成
测试模式
切勿使用 pumpAndSettle
SSE 流会导致测试无法稳定。请改用以下方式:
waitForCondition—— 基于轮询,用于检查 UI 状态harness.waitForLog—— 基于流,用于异步事件(首选)tester.pump(duration)—— 固定延迟,仅用于短暂的渲染等待
标准测试结构
patrolTest('description', ($) async {
await verifyBackendOrFail(backendUrl);
ignoreKeyboardAssertions();
final harness = TestLogHarness();
await harness.initialize();
try {
await pumpTestApp($, harness);
// ... test body ...
} catch (e) {
harness.dumpLogs(last: 50);
rethrow;
} finally {
harness.dispose();
}
});
Widget 查找器参考
| 目标 | 查找器 |
|---|---|
| 房间列表项 | find.byType(RoomListTile) |
| 聊天输入框 | find.byType(TextField) |
| 发送按钮 | find.byTooltip('Send message') |
| 聊天消息 | find.byType(ChatMessageWidget) |
| 设置图标 | find.byIcon(Icons.settings) |
避免使用 find.bySemanticsLabel 输入文本——它可能会定位到 Semantics 包装器,而不是 TextField,导致 enterText 失败。
用于断言的日志模式
| 日志记录器 | 模式 | 含义 |
|---|---|---|
Router | redirect called | 应用已启动,路由器已激活 |
HTTP | /api/v1/rooms | 已调用房间 API |
Room | Rooms loaded: | 房间已成功解析 |
ActiveRun | RUN_STARTED | AG-UI SSE 流已打开 |
ActiveRun | TEXT_START: | 已收到第一个文本块 |
ActiveRun | RUN_FINISHED | SSE 流已完成 |
调试:两阶段工作流
第一阶段:探索(dart MCP — flutter run)
使用 mcp__dart-tools__launch_app 启动应用,然后检查实时
Widget 树和日志,找出测试所需的查找器和日志模式。
mcp__dart-tools__launch_app → starts app, returns DTD URI
mcp__dart-tools__connect_dart_tooling_daemon → connect to running app
mcp__dart-tools__get_widget_tree(summaryOnly: true) → see real widget hierarchy
mcp__dart-tools__get_app_logs(pid, maxLines: 50) → see stdout log output
mcp__dart-tools__get_runtime_errors → check for exceptions
标准输出日志会显示为 [DEBUG] Router: redirect called for / ——可据此
确定日志记录器名称和消息模式,用于 harness.expectLog()。
Widget 树会显示实际的 widgetRuntimeType 值——将其用于
find.byType() 查找器。
阶段 2:断言(TestLogHarness — 巡检运行)
Patrol 通过 xcodebuild 启动应用,但它不会公开 DTD URI。在巡检测试运行期间,dart MCP 工具无法连接。
因此,所有测试断言都使用 TestLogHarness,通过 MemorySink 在进程内捕获日志:
harness.expectLog('Router', 'redirect called')— 同步检查harness.waitForLog('ActiveRun', 'RUN_FINISHED')— 基于流的等待harness.dumpLogs(last: 50)— 失败时将日志转储到控制台
该测试框架可以看到与 get_app_logs 中相同的 [DEBUG] 日志,但日志是在进程内访问的,而不是通过 stdout 访问。
Git Worktree 注意事项
此仓库是一个 git worktree。调用 flutter/dart 的 pre-commit 钩子必须先 unset GIT_DIR,否则 Flutter 会报告版本 0.0.0-unknown。scripts/ 中的包装脚本会处理此问题。
故障排除
| 症状 | 原因 | 解决方法 |
|---|---|---|
| 出现“Multiple devices found”提示 | 缺少 --device 标志 | 添加 --device macos |
command not found: patrol | ~/.pub-cache/bin 不在 PATH 中 | 将其添加到 PATH,或使用完整路径 |
| 测试卡在“Waiting for app” | Entitlements 缺失或错误 | 参见 macOS 设置 |
版本为 0.0.0-unknown | worktree 中设置了 GIT_DIR | 使用 scripts/flutter-analyze.sh 包装脚本 |
| “did not appear within 10s” | Widget 位于抽屉中,而非主体中 | 参见 macOS 设置 |
enterText 时出现 Bad state: No element | Finder 匹配到的是 Semantics,而非 TextField | 改用 find.byType(TextField) |
| 辅助功能权限被拒绝 | Entitlements 已更改 | 在“系统设置”>“隐私与安全性”>“辅助功能”中重新批准 |
| iOS:xcodebuild 退出代码 70 | OS=latest 与模拟器不匹配 | 参见 iOS 设置 |
| iOS:“Device ... is not attached” | 设备 ID 格式错误 | 使用 xcrun simctl list devices booted 中的 UUID |
| Android:“No connected devices” | 模拟器未运行,或 adb 不在 PATH 中 | 参见 Android 设置 |
| Android:连接 localhost 被拒绝 | 模拟器无法访问主机的 localhost | 使用 10.0.2.2,而非 localhost |
| Android:启动时出现 Gmail 登录提示 | AVD 使用了 Google Play 镜像 | 参见 Android 设置 |
| Chrome:“Cannot find module playwright” | 未安装 Node.js | 参见 Web 设置 |
| Chrome:测试中出现 CORS 错误 | 后端缺少 CORS 标头 | 参见 Web 设置 |
Chrome:verifyBackendOrFail 出现“Failed to fetch” | 后端离线或被 CORS 阻止 | 启动后端;检查浏览器控制台 |