maestro/docs/tools/browseweb.md
2026-06-03 05:08:00 +00:00

288 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# BrowseWeb 詳細ガイド
ヘッドレスブラウザで Web ページを操作するツール。同一ジョブ内ではブラウザコンテキストCookie・ログイン状態が永続化される。
## 2 つのモード
### 1. 基本モード — URL を開いてテキスト取得
```js
BrowseWeb({ url: "https://example.com" })
```
ローカルで生成した HTML をブラウザで確認したい場合は、workspace ルートからの **相対パス** をそのまま渡す(推奨)。
```js
BrowseWeb({ url: "output/viewer.html" })
```
例:
- `output/viewer.html` を開く → `BrowseWeb({ url: "output/viewer.html" })`
- `input/sample.html` を開く → `BrowseWeb({ url: "input/sample.html" })`
内部的には実行中ジョブの workspace 絶対パスと結合され `file://` URL に変換される。`../` で workspace 外に出るパスは拒否される。`file:///` で始まる絶対 URL を直接渡すことも可能だが、workspace 外を指すものは拒否される。
オプション:
- `waitFor`: 待機する CSS セレクタ(省略時は load イベント完了まで待機)
- `extractSelector`: 特定要素のテキストだけ抽出する CSS セレクタ
- `screenshot`: スクリーンショットを保存するファイル名(例: `"page.png"``output/page.png`
- `timeout`: タイムアウトms、デフォルト 60000
### 2. アクションモード — 連続操作
```js
BrowseWeb({
actions: [
{ type: "goto", url: "https://example.com/login" },
{ type: "fill", ref: "e3", value: "user@example.com" },
{ type: "click", ref: "e5" },
{ type: "getText" }
]
})
```
利用可能な `type`:
- `goto``url` で指定したページに遷移
- `click``selector` または `ref` で要素をクリック
- `fill``selector` または `ref` の input/textarea に `value` を入力
- `screenshot``value` で指定したファイル名で保存(省略時 `screenshot.png`
- `getText` — 全ページのスナップショットref 注釈付き)または `selector` 内のテキストを取得
- `wait``ms` ミリ秒待機(最大 30000
- `dumpHtml``ref` または `selector`(省略時 bodyの outerHTML を取得(脱出口、後述)
## 長文ページの取得preview + ファイル保存)
`getText` (selector 有無問わず) およびスナップショットの戻り値が **5000 文字を超える** 場合、フルテキストはワークスペースの `logs/browse/{ISO-timestamp}-{hash}.txt` に保存され、戻り値は **先頭 5000 文字 + 続きの取得方法案内** になる:
```
(先頭 5000 文字)
... (truncated; full 38214 chars saved to logs/browse/2026-05-07T09-30-12-a1b2c3d4.txt — Read({file_path:"logs/browse/2026-05-07T09-30-12-a1b2c3d4.txt", offset, limit}) で続きを取得可能)
```
続きを読みたい場合は `Read` ツールで `offset` / `limit` を指定:
```js
Read({ file_path: "logs/browse/2026-05-07T09-30-12-a1b2c3d4.txt", offset: 200, limit: 200 })
```
5000 文字以下のページなら従来通り全文が直接返り、ファイルは作成されない。
## ref 注釈の仕組み(重要)
`BrowseWeb({ url })``getText` の出力には、操作可能な要素が以下のような注釈付きで埋め込まれる:
```
ようこそ
{e1 link "ホーム" href="/"} {e2 link "製品" href="/products"}
ログインしてください
{e3 textbox name="email" placeholder="メールアドレス"}
{e4 textbox name="password"}
{e5 button "ログイン"}
```
- `e1`, `e2`, ... の IDrefは出現順に自動採番される
- 各 ref は内部的に Playwright で解釈可能なセレクタ(`data-testid` / `id` / `[name]` / `aria-label` / nth-of-type CSS chain の優先順)にマッピングされている
- click/fill アクションで `ref: "e5"` のように指定するだけで操作できる
- **CSS セレクタを自分で組み立てる必要がない**
### 検出される要素の範囲
ref が振られるのは以下の要素:
- 標準 HTML タグ: `<a>` / `<button>` / `<input>` / `<select>` / `<textarea>` / `<label>` / `<summary>` / `<details>` / `<option>`
- ARIA role: `button` / `link` / `menuitem` / `menuitemcheckbox` / `menuitemradio` / `tab` / `option` / `checkbox` / `radio` / `switch` / `combobox` / `listbox` / `slider` / `spinbutton` / `textbox` / `searchbox` / `treeitem`
- `[onclick]` / `[tabindex>=0]` / `[contenteditable=true]` 属性
- JavaScript で `addEventListener('click'|'mousedown'|'pointerdown', ...)` 経由で listener が後付けされた要素jQuery / vanilla JS / Vue / Svelte の compile 後コードで多用される)
- open shadow DOM 内部の上記要素
- iframe 内の上記要素(同一オリジン / cross-origin 共に対応。Stripe Elements / OAuth / reCAPTCHA など)
検出されないもの: closed shadow DOM、`<canvas>` / WebGL の描画内容、React の `onClick={...}`(ただし React コンポーネントは大抵 `<button>``role="button"` を使うので別経路で拾える)。
### iframe 内の要素
iframe を含むページの `getText` の出力は、メインフレームのテキストの後ろに **フレームごとのセクション** が並ぶ形式になる。メインフレーム本文中には iframe の位置に `[[IFRAME ...]]` プレースホルダーが残るので、フレームの出現順や種別が把握できる:
```
これは決済画面です
[[IFRAME name=card title=Card details src=https://js.stripe.com/v3/elements]]
[ボタン] {e3 button "支払う"}
--- iframe f1 url="https://js.stripe.com/v3/elements/..." name="card" ---
{f1.e1 textbox "Card number"}
{f1.e2 textbox "MM / YY"}
{f1.e3 textbox "CVC"}
--- end iframe f1 ---
```
iframe 内の要素を click / fill / dumpHtml したいときは、frame ID prefix 付きの ref を指定するだけ:
```js
BrowseWeb({
actions: [
{ type: "fill", ref: "f1.e1", value: "4242 4242 4242 4242" },
{ type: "fill", ref: "f1.e2", value: "12 / 30" },
{ type: "fill", ref: "f1.e3", value: "123" },
{ type: "click", ref: "e3" } // メインフレームの「支払う」ボタン
]
})
```
frame ID (`f1`, `f2`, …) は `getText` 取得時の出現順に採番される。同じページに同じ iframe が複数ある場合は src/name で見分けてセクションヘッダーで識別する。
cross-origin iframe (Stripe / OAuth / reCAPTCHA など) でも Playwright が内部で透過的に DOM を取得するので、同じ感覚で操作できる。ただし iframe の中身が完全に読み込まれる前に snapshot を取ると `[empty]``[cannot inspect: ...]` が出ることがあるので、その場合は `wait` を挟んで再取得する。
### 状態属性
ref 注釈の末尾には ARIA 状態が列挙される。エージェントは「いまトグルが開いてるか」「チェック済みか」「無効化されてるか」を判断できる:
```
{e3 tab "設定" selected}
{e7 button "保存" disabled}
{e2 combobox "国" expanded haspopup}
{e9 checkbox "規約に同意" checked}
{e5 button "メニュー" pressed}
```
利用される状態: `expanded` / `collapsed` / `pressed` / `selected` / `checked` / `mixed` / `disabled` / `required` / `haspopup`
### ref はいつリセットされる?
- ページ遷移(`goto` または click でナビゲーションが発生)したとき
- 同一ジョブ内でも、ナビゲーション後は **getText を呼んで新しいスナップショットを取得する**
- 同一ジョブが終わるとブラウザコンテキストごと破棄される
## ワークフロー例
### 例1: ログインしてダッシュボードのデータを取得
```js
// Step 1: ログインページを開いて要素を確認
BrowseWeb({ url: "https://app.example.com/login" })
// → 出力に {e3: input[email]}, {e4: input[password]}, {e5: button "ログイン"} が含まれる
// Step 2: フォーム入力 → 送信 → 遷移後の状態を取得
BrowseWeb({
actions: [
{ type: "fill", ref: "e3", value: "user@example.com" },
{ type: "fill", ref: "e4", value: "p@ssword" },
{ type: "click", ref: "e5" },
{ type: "getText" } // ← ダッシュボードの新 ref を取得
]
})
// Step 3: ダッシュボードでさらにナビゲートCookie が維持されているため再ログイン不要)
BrowseWeb({ url: "https://app.example.com/dashboard/orders" })
```
### 例2: 複数ページを順に巡回
```js
// 検索結果ページを開く
BrowseWeb({ url: "https://example.com/search?q=foo" })
// → {e1: link "結果1" href="/item/1"}, {e2: link "結果2" href="/item/2"} ...
// 各リンクの href を確認したら、url 直接指定で各ページへ
BrowseWeb({ url: "https://example.com/item/1" })
BrowseWeb({ url: "https://example.com/item/2" })
```
### 例3: 動的ページの読み込み待ち
```js
BrowseWeb({
url: "https://app.example.com/spa",
waitFor: ".content-loaded" // この CSS セレクタが現れるまで待つ
})
```
## ユーザーに手動操作を委譲するnoVNC 経由のハンドオフ)
BrowseWeb で詰まったとき、エージェントは `InteractiveBrowse` を呼んでブラウザの操作権をユーザーに渡せる。
### 使うべき場面
1. **ログイン / 2FA / SSO 同意画面** — パスワードや TOTP / プッシュ通知を agent に持たせず、ユーザーに直接入力してもらう
2. **CAPTCHA / bot 検証** — reCAPTCHA、画像選択、Cloudflare チャレンジ等
3. **BrowseWeb の click が空振りし続ける**`dumpHtml` でも構造が複雑すぎて selector が組めない、closed shadow DOM、ドラッグ&ドロップが必須等
4. **canvas / WebGL ベースの UI** — 地図ペインや図形エディタなど DOM では addressable でない領域
5. **画面状態を目視確認したい** — agent が想定通りの画面にいるか不安なとき
### フロー
```js
// Step 1: ユーザーに引き継ぐ宣言
InteractiveBrowse({
url: "https://example.com/login",
reason: "ログインが必要です。ID / パスワードを入力して、画面右下の release ボタンを押してください。"
})
// → ジョブが waiting_human に遷移し、UI に noVNC リンクが表示される
// → ユーザーがブラウザ画面で操作 → release を押すとジョブが再開
// → 戻り値に sessionId が含まれる
```
ジョブ再開後、agent は **同じ sessionId で `BrowseWithSession`** を呼んで続きを引き継ぐ:
```js
// Step 2: ユーザーが完了させた状態 (ログイン済み等) で続行
BrowseWithSession({
sessionId: "abc-123", // InteractiveBrowse の戻り値の sessionId
url: "https://example.com/dashboard",
action: "getText" // または click / fill / screenshot
})
```
### `reason` の書き方
ユーザーに何をしてほしいかは `reason` フィールドで明確に伝えること。UI に表示される。良い例:
- 「ログインしてください。完了したら release を押してください」
- 「reCAPTCHA を解いてください。完了したら release を押してください」
- 「カートに入れたい商品を選んでください。完了したら release を押してください」
### 制約
- `InteractiveBrowse`**ローカルタスク経由のジョブ** でのみ使える(`taskId` が必要。Gitea Issue 直接実行や taskId が立たない subtask root では使えない
- noVNC が orchestrator にインストール / 設定されていない環境ではエラーXvfb / x11vnc / websockify が必要、`config.yaml``browser.captcha_solve: novnc` 設定)
- ユーザーが release を押さない限りジョブは進まない。長時間放置すると `browser.auth_timeout`(デフォルト 10 分)で timeout
### 既存の Browser Sessions 機能との違い
| 機能 | 用途 |
|---|---|
| **Browser Sessions** (Settings UI から保存) | スケジュール実行や定期タスクなど **agent しか動いていない時間帯** に、過去にログイン済みの cookie / storageState を再利用 |
| **InteractiveBrowse** | **ジョブ実行中、その場で** ユーザーがブラウザを操作してログインや人間判断を行う |
定期タスクで毎回 InteractiveBrowse を呼ぶのは非効率なので、定常運用のサイトは Browser Sessions として登録するのが正解。「初回ログイン or セッション切れ時だけ InteractiveBrowse」のような使い分けが望ましい。
## トラブルシューティング
- **「ref "e5" not found in current snapshot」と出る**: ページ遷移後で ref がリセットされている。`getText` で新しいスナップショットを取得する
- **テキストが取れない / 空に近い**: ページが SPA で JavaScript で描画されている。`waitFor` で描画完了を待つ
- **ボタンを押せない / click しても何も起きない**:
1. 要素が visible でない可能性。先に getText で本当に存在するか確認
2. ref 注釈に `disabled` が出ていないか確認
3. `dumpHtml({ ref: "..." })` で要素の生 HTML を見て、独自 selector を組む
4. それでもダメなら `InteractiveBrowse` でユーザーに引き継ぐ
- **`<div>` に click 反応する独自 UI が ref に出ない**: addEventListener フックで多くは検出されるが、React の `onClick={...}` (root delegation) や `el.onclick = fn` 直接代入は捕捉できない。`dumpHtml` で構造を見て selector を直接組むか、`InteractiveBrowse` で渡す
- **ログインが維持されない**: 別ジョブから呼んでいる可能性。同一ジョブ内なら維持される。定常運用は Browser Sessions に保存する
## ファイルダウンロード
リンククリック等でブラウザがファイルダウンロードを開始すると、自動的に workspace の `output/` 配下に保存される。戻り値の末尾に以下の形式で通知される:
```
[download] saved output/report.csv (12345 bytes)
```
- ファイル名は server-suggested 名から path traversal 対策と禁則文字置換を経て決定される
- 衝突時は `foo-1.csv`, `foo-2.csv` 形式で番号付与される
- 失敗時は `[download] FAILED <name>: <reason>` と出る
- ダウンロードされたファイルは続く `Read`, `ReadPdf`, `ReadExcel`, `Bash` 等の tool で参照できる
- 履歴は `logs/downloads.jsonl` に追記される (DownloadFile と同じファイル、`source: 'BrowseWeb'` フィールドで区別)
ダウンロードを認証付きで行いたい場合は、Browser Sessions 機能で対象サイトのログインセッションを保存し、タスクで bind した状態で BrowseWeb を呼ぶこと。
## SSRF 保護
ローカル/プライベート IP127.x.x.x, 10.x.x.x, 172.16-31.x.x, 192.168.x.x, ::1, fc00::/7 等へのアクセスはデフォルトでブロックされる。社内ホストへアクセスする必要がある場合は、Settings UI の「SSRF Allowed Hosts」に追加する。