はじめに
Stagehand と Gemini computer-use の組み合わせで、AI にブラウザを操作させる仕組みを試しました。
そこで公式ドキュメントに書かれていないハマりどころに3つ引っかかったので、書き残しておきます。
Stagehandとは?
Stagehand は Playwright ベースの AI エージェント向けブラウザ自動化フレームワークです (公式)。
CUA (Computer Use Agent) モードで、AI にスクリーンショットを見せて操作させることができます。
CUA モードの AI モデルは、Google (Gemini computer-use)、Anthropic (Claude)、OpenAI (computer-use-preview) 等から選べます。
Stagehand がブラウザ操作の実行環境を、AI モデルが「何をどうするか」の判断を担当する分担です。
ハマりどころ 1: AI がフォームで詰まったら Google 検索に飛ぶ
動かしてみたら、AI がフォームで詰まった瞬間、突然 Google 検索を始めました。
ログを見ると open_web_browser や search や navigate が呼ばれています。
Gemini computer-use のモデルには、こういう予約関数が最初から組み込まれていて、プロンプトで「使うな」と言っても止まりません。
飛んだ先の Google では、AI からのアクセスがロボット判定に引っかかって「不審なアクティビティ」の画面が出ます。
そこから AI が抜けられなくなって、テストがそのまま止まる事もありました。
対処は関数リストから物理的に消すしかありません。
ただ Stagehand のオプションでは公開されていないので、pnpm patch でライブラリ本体に注入する形になります。
excludedPredefinedFunctions: [
"open_web_browser",
"search",
"navigate",
"go_back",
"go_forward",
"take_screenshot",
],
これで脱走系のアクションが 1 ランあたりゼロに落ちました。
ハマりどころ 2: BLOCK_NONE を設定しても Gemini が止まる
購入や確定系のワードがプロンプトに多いと、Gemini が SAFETY 判定で応答を拒否します。
Google のドキュメント通り safetySettings で BLOCK_NONE を 5 カテゴリ全指定してみましたが、実際に走らせても SAFETY block は消えませんでした。
調べてみると、Gemini の安全チェックは 2 種類ありました。
自分たちで OFF にできる部分と、Google 側が強制的に動かす部分があって、BLOCK_NONE で切れるのは前者だけです。
後者は API 経由では触れないので、全カテゴリ指定してもここで SAFETY で止められていました。
同じタイミングで、別の症状も発生していました。
AI が「ボタンを押した」と自己申告しているのに、実際は何もしていない現象です。
実データを見ると、SAFETY block が確率的にクリック応答だけを落として、wait のような無反応系だけ通していた事が分かりました。
別の症状に見えたものが、実は同じ SAFETY block の副作用だったわけです。
Google 側の層を落とせない以上、AI に投げるリクエストの中身から危険語を減らすしかありません。
ただ Stagehand は、AI 起動時に指定した長いプロンプト (「勝手にクリックするな」等、危険語 60+、4,000 字超) を、その後の全リクエストの先頭に毎回付け直す設計でした。
起動時のこの指示が確定操作のリクエストにも毎回同居する事になっていて、危険語密度を下げようがありません。
対処は、確定操作専用の短いプロンプトで別のエージェントを、同じセッション上に追加で作ることでした。
Stagehand の agent() は同じセッション状態を共有しつつ複数持てるので、確定ボタンを押すフェーズだけそちらに切り替える形で回避できました。
まとめると 3 段構えです。
-
BLOCK_NONEを全カテゴリ設定 (定石を貼る) - リクエスト内の危険語を中立語に置換
- 確定フェーズ専用の短い systemPrompt で別エージェントを作る
ハマりどころ 3: ユニットテスト 80 本全部緑、なのに本番で動いてなかった
Web フォームの状態 (エラーメッセージや disabled 状態) を DOM から読み取る関数を実装して、ユニットテスト 80 本を通して本番投入していました。
しばらく動かしていたある日、実データを見て気づきました。
この関数、本番で一度も動いていませんでした。
原因を追ってみると、ビルドツールと実行環境の相性でだけ発火するバグでした。
本番の実行環境 (tsx) は、ビルド時に名前付き関数を包むヘルパを自動挿入します。
このヘルパはブラウザ側には存在しないため、Stagehand が DOM を読みに行くたびに、ブラウザ内で「そんな関数はない」で即失敗する状態でした。
さらに Stagehand の例外処理がこの失敗を「結果は空」として吸収する設計だったため、呼び出し元は「動いてるけど何も見つからない」と受け取っていました。
ユニットテストで使っていた vitest は同じヘルパを挿入しないので、テストは 80 本全部緑。
ローカルでは絶対に踏めないのに、tsx で走る本番だけ壊れる、というハマりどころでした。
対処は、Stagehand に渡す関数の中で名前付き関数を使わないことです。
const isVisible = ... のように名前を付けず、その場に書きます。
名前が付いていなければビルド時にヘルパが挿入されないので、混入を防げます。
まとめ
3つ潰すのに、それなりの時間が消えました。
どれも Stagehand と Gemini computer-use のドキュメントを読むだけではわからないず、実装ソースを追ったり実データを漁ったりしないと見えないタイプでした。
AI にブラウザ操作を任せる方向自体はうまく回っています。
ページ構造が変わってもテストが壊れず、E2E テストコードを毎回書き直す手間が消えました。
ただしこういう「ドキュメントに載らないハマりどころ」を実行して潰す作業は、まだ当面続く感覚があります。
Stagehand + Gemini computer-use で似た構成を検討している方の参考になれば幸いです。