patrol について
patrol は、macOS、iOS、Android、Chrome でテストを実行する開発者向けの Flutter 用 E2E テストスキルです。デバイス指定、事前確認、アサーション手法、トラブルシューティングを案内します。
主な機能
- 01特定ファイルまたは全統合テストの実行
- 02macOS、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 E2E テストスキル
この Flutter プロジェクト(macOS、iOS、Android、Chrome)で Patrol 統合テストを実行・管理します。
テストの実行
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 フラグで OS
バージョンを指定します(デフォルトは latest で、シミュレーターの OS と一致している必要があります)。
# 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 と OS バージョンを確認してください。
初回セットアップ: RunnerUITests ターゲットとシミュレーターの OS の一致については、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 エミュレーターからホストの localhost にアクセスするには、10.0.2.2 を使用します。
初回セットアップ: AVD の作成、JDK、Google APIs イメージの要件については、Android セットアップガイドを参照してください。
Chrome(ウェブ)
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、ビューポートの詳細については、ウェブセットアップガイドを参照してください。
$ARGUMENTS が空、または "all" の場合は、integration_test/(すべてのテスト)を対象に実行します。
$ARGUMENTS が smoke_test.dart のようなファイル名の場合は、そのファイルを指定して実行します。
実行前の確認
テストを実行する前に、次の項目を確認してください。
- バックエンドが 上記の URL で
--no-auth-modeにて実行中である - 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();
}
});
ウィジェットファインダーのリファレンス
| 対象 | ファインダー |
|---|---|
| ルームリスト項目 | find.byType(RoomListTile) |
| チャット入力欄 | find.byType(TextField) |
| 送信ボタン | find.byTooltip('Send message') |
| チャットメッセージ | find.byType(ChatMessageWidget) |
| 設定アイコン | find.byIcon(Icons.settings) |
テキスト入力には find.bySemanticsLabel を使用しないでください。TextField ではなく Semantics ラッパーが検出され、enterText が失敗することがあります。
アサーション用のログパターン
| ロガー | パターン | 意味 |
|---|---|---|
Router | redirect called | アプリが起動し、ルーターが有効になった |
HTTP | /api/v1/rooms | Rooms API が呼び出された |
Room | Rooms loaded: | ルームの解析に成功した |
ActiveRun | RUN_STARTED | AG-UI SSE ストリームが開始された |
ActiveRun | TEXT_START: | 最初のテキストチャンクを受信した |
ActiveRun | RUN_FINISHED | SSE ストリームが完了した |
デバッグ:2 段階のワークフロー
フェーズ 1:確認(dart MCP — flutter run)
mcp__dart-tools__launch_app を使用してアプリを起動し、実行中の
ウィジェットツリーとログを調べて、テストに適したファインダーとログパターンを見つけます。
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() 用のロガー名とメッセージパターンを特定してください。
ウィジェットツリーには実際の widgetRuntimeType の値が表示されます。これらを find.byType() ファインダーに使用してください。
フェーズ 2:検証(TestLogHarness — patrol の実行)
Patrol は xcodebuild を通じてアプリを起動しますが、DTD URI は公開しません。そのため、patrol テストの実行中は 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/ 内のラッパースクリプトがこの処理を行います。
トラブルシューティング
| 症状 | 原因 | 対処方法 |
|---|---|---|
| 「複数のデバイスが見つかりました」というプロンプト | --device フラグがない | --device macos を追加します |
command not found: patrol | PATH に ~/.pub-cache/bin がない | PATH に追加するか、フルパスを使用します |
| 「Waiting for app」でテストが停止する | Entitlements がない、または誤っている | macOS のセットアップを参照します |
バージョンが 0.0.0-unknown と表示される | worktree で GIT_DIR が設定されている | scripts/flutter-analyze.sh ラッパーを使用します |
| 「10 秒以内に表示されませんでした」 | ウィジェットが body ではなく drawer にある | macOS のセットアップを参照します |
enterText で Bad state: No element が発生する | Finder が TextField ではなく Semantics に一致した | 代わりに find.byType(TextField) を使用します |
| アクセシビリティ権限が拒否される | Entitlements が変更された | 「システム設定」>「プライバシーとセキュリティ」>「アクセシビリティ」で再承認します |
| iOS:xcodebuild の終了コードが 70 | OS=latest がシミュレーターに一致しない | iOS のセットアップを参照します |
| iOS:「デバイス ... が接続されていません」 | デバイス ID の形式が間違っている | xcrun simctl list devices booted の UUID を使用します |
| Android:「接続されたデバイスがありません」 | エミュレーターが起動していない、または PATH に adb がない | Android のセットアップを参照します |
| Android:localhost への接続が拒否される | エミュレーターからホストの localhost に接続できない | localhost の代わりに 10.0.2.2 を使用します |
| Android:起動時に Gmail のログインプロンプトが表示される | AVD が Google Play イメージを使用している | Android のセットアップを参照します |
| Chrome:「playwright モジュールが見つかりません」 | Node.js がインストールされていない | Web のセットアップを参照します |
| Chrome:テストで CORS エラーが発生する | バックエンドに CORS ヘッダーがない | Web のセットアップを参照します |
Chrome:verifyBackendOrFail で「Failed to fetch」が発生する | バックエンドがオフライン、または CORS によってブロックされている | バックエンドを起動し、ブラウザーのコンソールを確認します |