Playwright MCPはトークンを1.5M消費、CLIは27k。段階を分割するスキルを作った

Dev.to / 2026/5/3

💬 オピニオンDeveloper Stack & InfrastructureSignals & Early TrendsTools & Practical UsageIndustry & Market MovesModels & Research

要点

  • 著者はNext.jsのポートフォリオとSaaSのチャットに対する、アクセシビリティやコンソールエラー、モバイルでの不具合確認といった通常のPlaywrightテストを行おうとしたが、Claude CodeがPlaywright MCPに接続するとトークン消費が極端に大きくなった。
  • 記事内で引用された分析とベンチマークによると、Playwright MCPは単一テストあたり約114kトークン、検証ワークフローでは最大約150万トークンを消費しうる一方、Playwright CLIは約27kにとどまり、50〜60倍のコスト差が生じる。
  • 原因はアーキテクチャにあり、MCPは各ステップ(移動/クリック/待機/スクリーンショット/推論)でLLMをブラウザのループ内に置くため、未知のUI探索には有効でも同じ手順を再実行する場面では非常に高コストになる。
  • さらに著者は、Claude Codeに「見えない画像バジェット」(セッションあたり概ね50〜100の画像ブロック)があることも発見したが、カウンタや警告がなく、オプションフラグも用意されていない。
  • これらの問題を解決するために、著者はオープンソースのwebtest-orchとして、探索とリプレイを分離し、E2Eテスト中にトークンや画像コンテキスト制限に引っかからないようにするClaude Codeスキルを作った。

Webアプリをテストしたかった。それだけです。Next.jsのポートフォリオとSaaSのチャット——いくつかのアクセシビリティチェックを走らせ、コンソールエラーを拾い、モバイルで何も壊れていないことを確認する。プロダクションに出す前にやるような、そういう類のことです。

Claude Codeを開いて、Playwright MCPに接続し、「test the app」と打って、まるで明日がないかのようにトークンが燃えていくのを見ました。次に/compactがテキストコンテキスト18%で発火。さらに不可視の画像バジェットを見つけました。そして、存在してほしかったツールを作るのに3日を費やしました。

これは、日常的なテストセッションがwebtest-orchになるまでの物語です——トークン予算を破産させたり、不可視のコンテキスト制限にぶつかったりせずに、e2eテストを行うオープンソースのClaude Codeスキル。

問題:MCPは探索には優れているが、再生(リプレイ)にはひどい

2025年11月、Pramod Duttaが分析を公開しました。AIテスト界隈のあちこちで話題になった内容です:Playwright MCPは、単体のテストごとに約114kトークンを消費します。Microsoft自身の課題トラッカー上のÖzalベンチマークでは、eコマースの検証ワークフローで、MCPにより約150万トークンに到達することが示されています。Playwright CLIは? 依然として約27kです。

これは50〜60倍の非対称性です。原因はアーキテクチャにあります:MCPは、すべてのアクションごとにLLMをブラウザのループ内に保ち続ける——ナビゲーション、クリック、待機、スクリーンショット、推論、繰り返し。初めて見るUIを探索するのには最高です。同じフローを2回目に再生するのに対しては、破滅的に高コストです。

それ以来、Microsoft自身のREADMEは、コーディングエージェントに対してMCPよりもCLI+スキルを推奨するよう更新されました。公式のTest Agentsドキュメントも、サポートされているアーキテクチャとしてPlanner / Generator / Healerの3点セットを同梱しています——「セッション全体をMCPの中にエージェントがとどまる」ではありません。

解決策は「Playwright MCPをもっと少なく使う」ではありません。探索と再生を分割することです。

次の問題:不可視の画像バジェット

トークン使用量をデバッグしている最中に、さらに悪いものを見つけました。Claude Codeには、誰もドキュメントしていない2つ目のコンテキスト制限があります——インライン画像のバジェットで、1セッションあたりおよそ50〜100ブロック。カウンターなし。警告なし。--show-image-budgetフラグなし。

すべてのPlaywright:browser_take_screenshotは1つの画像ブロックを返します。スクリーンショットを50回撮ると、テキスト予算の0.4%を使い、画像予算の100%を使い切っていることになります。次に/compactが、テキストコンテキストが80%空いているタイミングで発火します。すると、ディスクにバックアップされていないものはすべて失われます。

私は3つの修正を試しました:

「スクリーンショットをもっと少なく撮るだけ」——しかし、実際の探索セッションでは30ターンほどで規律が崩れます。

CLAUDE.mdのルール「決してスクリーンショットを撮らない」——ソフトルールは、プレッシャー下でおよそ30ターンしか持ちません。エージェントはモーダルで詰まるとスクリーンショットに手を伸ばし、その同じターンでルール違反を正当化します。

スキルのフロントマターでcontext: fork——ドキュメントされた公式フィールドですが、私のClaude Code 2.1.xのWindowsビルドでは、何も言わずにそのスキル登録が失敗していました。諦めるまでに90分デバッグし、その後はスキル本体の中に保護を入れました。

実際に機能するのはタスクのサブエージェントが画像バジェットを分離して持つことです。サブエージェントが読み込むものは、親のカウンターにカウントされません。経験的に確認しました——サブエージェントに6枚のPNGを読み込ませて6つのテキスト説明を返させたところ、親のカウンターは0のままでした。

3日後:webtest-orch

3日目には、ある1つのアーキテクチャ不変条件に基づいて動作するスキルを作り上げていました:親のチャットは、そもそも画像を受け取らない。 これを守るための4つのパターンがあります:

パターンA(作業の90%): Playwright:browser_snapshotによるARIAツリーの探索——ページを画像ではなくテキストとして返します。スクリーンショットと同等のロケータ情報を持つ一方で、エージェントはgrepや差分比較ができます。画像バジェットのコストはゼロです。

パターンB(1回の実行あたり3〜5): ビジョンが本当に必要なとき(ピクセル差分が発火した、ベースラインが0のページでレイアウトチェックが必要になった等)には、タスクのサブエージェントが1枚の画像を読み込み1行のテキストを返します。サブエージェントが自分自身のバジェットを消費し、親は汚れません。

パターンC: Playwrightの組み込みtoHaveScreenshot()は、npx playwright testで実行するとdiff%をJSONとして返します。テキストのまま最後まで。ビジョントークンは、差分が本当に発火したときだけ消費されます——しかも、その場合でもパターンBです。

パターンD: ストレージ上のスクリーンショット(失敗アーティファクト、MCPキャッシュ)は、明示的にReadしない限りコストゼロ。「ファイルが存在する」ことと「ファイルがコンテキストを消費する」ことを混同しないでください。

スキルのフロー:最初の実行→ Playwright MCPがサブエージェントの中でARIAスナップショットを通じてアプリを歩き回り、*.spec.tsを生成します。以降のすべての実行→ npx playwright testを直接実行——決定論的で、進行中のLLMコストはほぼゼロ。SHA-256の複合キーでバグの指紋を作り、実行差分によってバグをnew / regression / persisting / fixedとして分類します。

issues[]コレクターパターン

生成されるすべてのspecは、すべてのソフトチェックを1つの配列に収集します:

test('home page baseline', async ({ page }) => {
  const consoleErrors: string[] = [];
  const failedRequests: string[] = [];
  const issues: string[] = [];

返却形式: {"translated": "翻訳されたHTML"}page.on('pageerror', (e) => consoleErrors.push(`pageerror: ${e.message}`));
  page.on('console', (m) => m.type() === 'error' && consoleErrors.push(`console: ${m.text()}`));
  page.on('response', (r) => r.status() >= 400 && failedRequests.push(`${r.status()} ${r.url()}`));

  await page.goto('/');

  const a11y = await new AxeBuilder({ page })
    .withTags(['wcag2a','wcag2aa','wcag21aa','wcag22aa']).analyze();
  a11y.violations.forEach((v) =>
    issues.push(`a11y[${v.impact}] ${v.id}: ${v.help} (${v.nodes.length}x nodes)`));

  // オーバーフロー、見出し階層、タッチターゲット、html-lang — すべて issues[] に投入されます

  expect(issues, `${issues.length} issues found:
  - ${issues.join('
  - ')}`).toEqual([]);
  expect(consoleErrors).toEqual([]);
  expect(failedRequests).toEqual([]);
});

テストが失敗した場合、すべての問題が1つのメッセージにまとめて表示されます。実行後スクリプトは出力を解析し、クロス実行の差分比較用に安定したフィンガープリントを付けて、issueごとに1つのバグレコードを出力します。

ボックスから出した時点で何がテストされるか

生成されるすべての仕様は次を強制します:console のエラーリスナー(page.goto() の前にアタッチし、GTM/Stripe/Sentry/Next.js/Supabase/ResizeObserver のノイズをフィルタ)、axe-core による WCAG の監査(wcag2a から wcag22aa)、見出し階層(h1 → h3 の飛びなし)、タッチターゲットのサイズ(WCAG 2.5.8 AA = 24×24 CSS px)、水平方向のオーバーフロー検出、そして html lang の存在。視覚回帰は Playwright の組み込み toHaveScreenshot() を使用します — 外部依存はゼロです。

重大度は axe の impact フィールドとエラークラスから自動で割り当てられ、3つの上書きメカニズムがあります:収集器内の [severity:S0]、テスト名での指定、または // @severity: S0test() の前にコメントとして記述。Linear、GitHub、Jira へのトラッカー対応マッピングは、最初から同梱されています。

正直な競争の全体像

Octomind は別れの手紙を掲載しました(2026年4月30日)。依然として料金を払って利用されている名称 — QA WolfBug0 の分析によると年間 $60〜250k)、Mabl、BrowserStack AI — は、クラウドの並列処理、人によるトリアージ、SOC 2、SLA といった「本当の」価値を売っています。

webtest-orch は、それらのどれとも規模の面で競合しません。マネージドクラウドも、人手によるレビュー層も、コンプライアンス認証もありません。Claude Code を使っているソロ開発者や小規模チームで、QA予算がない場合には、現実的な $0/月 の選択肢です。 50人チームがクロスブラウザの夜間回帰を回すなら、これはそうではありませんし、それをそうであるかのように振る舞うのは不誠実です。

正直なピアグループは、無料/OSSティアです。Microsoftのネイティブ playwright init-agents --loop=claude(Planner/Generator/Healerのトリプレット)と、Magnitude(Apache/MIT、vision-AIのフレームワーク)。webtest-orchの差分は、既製のaxe-core+コンソール+ネットワーク監査、run-diffによるバグのフィンガープリンティング、そしてトラッカーのマッピングです。これらは、無料の代替案にはありません。

ビルドしなかったこと

自己修復(self-healing)なし。 QAコミュニティは自己修復に対して押し返してきています — 障害モードはよく文書化されています。Healerが見た目は似ているが誤りの要素を選び、テストがグリーンになって、そのままバグが出荷されます。webtest-orchは、誤ってグリーンになることよりも赤を好みます。

ベンダーのクラウドなし。 テストはリポジトリ内に残ります。レポートはあなたのファイルシステム上に出力されます。もし明日npmパッケージが消えても、テストスイートは引き続き動きます。

「AIがあなたのテストを全部書く」系の売り込みなし。 これはエンジニアリング判断の代替ではなく補完です。特に、退屈な80%に強いです。a11y、コンソール、ネットワーク、レスポンシブ、回帰差分。

実際の検証実行(2回)

どちらのアプリもGitHub上で公開されています。合成ベンチマークではありません。

CreatmanCEO/portfoliocreatman.site)— 静的なNext.jsポートフォリオ、モバイルビューポート。実際のバグを4件見つけ、誤検知は0件:axe-coreの色コントラスト失敗(8要素、S1)、24×24px未満のタッチターゲットが2つ(S2)、/projectsでの見出しジャンプ h1→h3(S2)。親側で消費した画像予算:ゼロ

CreatmanCEO/lingua-companion — プライベートベータの、音声ファーストAI言語学習SaaS(Next.js 16+FastAPI+Supabase+WebSocket)。ログイン、チャット、翻訳、TTS、設定、フレーズライブラリ、シナリオモード、統計、ログアウトにまたがる11の仕様。4回の反復後に10/10でグリーン、所要は壁時計で約12分。ドッグフードのラウンドではv0.2.0向けの修正が6件出ました。

クイックスタート(3分)

npx webtest-orch@beta install

# MCPが見つからない場合:
claude mcp add --scope user playwright npx @playwright/mcp@latest
claude mcp add --scope user chrome-devtools npx chrome-devtools-mcp@latest

TEST_BASE_URL を使ってプロジェクトに .env.test を置き、Claude Codeを再起動して「test the app」と言います。スキルは認証済みか公開かを自動検知し、Playwright+axe-coreを足場(scaffold)し、探索パスを実行して、reports/<run-id>/index.html を書き込みます。

ステータス

0.3.1-beta — 113テスト、Linux/macOS/Windowsで完全なCI。MITライセンス。Linux/macOSの早期ユーザーを募集中です。issuesには 5分で書けるOS互換性レポートのテンプレート があります。

リポジトリ:github.com/CreatmanCEO/webtest-orch。ライセンスMIT。

エージェントループにおける、もう一つの目に見えないトークン消費(階層化されたプロジェクトコンテキスト)についてのコンパニオン記事はこちら:The context problem nobody talks about

Nick(Creatman)。フルスタック開発者。15以上のWebアプリで毎日Claude Codeを使用。リモートの機会歓迎 — creatmanick@gmail.com