技術 約8分で読めます

Playwright MCPにbrowser_dropが入ってドラッグ操作が普通のツールになった

いけさん目次

DEV Communityに Native Drag-and-Drop Automation Arrives in Playwright MCP という記事が出ていた。
Playwright MCP v0.0.71で browser_drop が追加され、MCPクライアントからドラッグ&ドロップを直接実行できるようになった、という話だ。

GitHubの v0.0.71リリースでも、New Toolsとして browser_drop が載っている。
リリースノート上はPlaywright本体の Locator.drop をMCPツールとして公開する変更、と説明されている。
同時に browser_network_requests のresponse body取得、browser_evaluate のplain expression対応も入っている。

browser_dropはドラッグ操作をMCP側へ出したもの

これまでAIエージェントにドラッグ操作をやらせると、だいたい面倒だった。
クリック、入力、ナビゲーションはMCPツールで素直に呼べる。
でもドラッグ&ドロップは、JavaScriptの evaluate でイベントを捏造したり、mouse.move を細かく刻んだりする逃げ方になりがちだった。

browser_drop は、その逃げ道をPlaywrightのネイティブ操作に置き換える。

{
  "source": "text=report.pdf",
  "target": "[data-testid='upload-zone']"
}

こういう形で、ドラッグ元とドロップ先を渡す。
実際の操作はPlaywrightのlocatorベースのドラッグ処理に乗るので、Playwrightが持っているauto-waitや可視性チェックの流れに乗る。
手製のDOMイベント発火より、ブラウザ差分やUIフレームワーク側の実装差に強い。

効いてくるのは、ファイルアップロードのdrop zone、並べ替えグリッド、Kanban、リッチテキストエディタ、ノードエディタみたいなUIだ。
人間にとっては普通の操作でも、テスト自動化では最後まで残りやすい場所だった。

AIテスト生成の失敗パターンがひとつ減る

過去に Shortestの記事 で、Playwright MCPと自然言語E2Eテストの違いを書いた。
Shortestは「何をテストするか」を自然言語で固定し、実行方法をAIに任せる。
Playwright MCPは、エージェントがその場でブラウザ状態を見ながらツールを呼ぶ。

この違いは、ドラッグ操作でかなり出る。
エージェントが evaluatedragstart / drop を組み立て始めると、DataTransferの扱い、pointer event、フレーム境界、スクロール位置で壊れやすい。
しかも失敗したとき、テスト対象のUIが悪いのか、エージェントが作ったイベントが悪いのか切り分けづらい。

browser_drop があれば、少なくとも「ドラッグ操作そのもの」は名前付きツールになる。
エージェントはセレクタ選びと前後の検証に集中できる。
これは派手な新機能というより、AIが余計な手製コードを書き始める余地を減らす修正だ。

同じv0.0.71で browser_network_requests がレスポンス本文を返せるようになったのも地味に効く。
ドロップでアップロードし、APIレスポンスを見て、UI表示を browser_evaluate で確認する、という一連の検査がMCP内で閉じやすくなる。

CLI派ならPlaywright本体のドラッグAPIで足りる場面も多い

CLI経由でエージェントを回している場合、「MCPに入ったならCLI側でも何か変わるのか?」が気になる。

Playwrightの通常テストやCLIベースのワークフローでは、今回の変更を待つ必要はあまりない。
Playwright本体にはすでに locator.dragTo() がある。
テストコードを書けるなら、素直にこう書けばいい。

const source = page.getByText("report.pdf");
const target = page.getByTestId("upload-zone");

await source.dragTo(target);

CIで回すE2Eテスト、リポジトリに残す再現可能なテスト、レビュー対象にしたい操作列なら、MCPよりPlaywright Testのコードに落とすほうが扱いやすい。

公式READMEも、コーディングエージェントではMCPより CLI+SKILLSのほうが向く場合がある と書いている。
理由はトークン効率だ。
MCPはツール定義とアクセシビリティツリーをモデルのコンテキストに載せる。
CLIなら、エージェントは必要なコマンドだけを叩き、結果をファイルやstdoutで読む。

この話は CLIからAIへ、人間がソフトウェアと話す入口が変わる で書いた使い分けと同じだ。
CLIは軽く、MCPは発見性と状態保持が強い。
ドラッグ&ドロップ対応もその境界を変えるものではなく、「MCPを選ぶ場面で欠けていた操作が埋まった」と見るほうが近い。

画面内にない要素は普通に落ちる

元記事で一番実用的だったのは、browser_drop は両方の要素が画面内にある前提で動く、という注意だ。
これはPlaywrightの挙動として自然だが、CIでだけ落ちる原因になりやすい。

ローカルの広い画面ではdrop zoneが見えている。
CIの狭いviewportでは下に隠れている。
この差で browser_drop が失敗する。

対処は単純で、先にスクロールする。

{
  "expression": "document.querySelector('[data-testid=\"upload-zone\"]')?.scrollIntoView()"
}

そのあとに browser_drop を呼ぶ。
ここをツール定義やプロンプト側の定型手順に入れておかないと、せっかくネイティブ操作になってもテストは不安定なままだ。

ブラウザMCPを常時つなぐなら権限境界も見る

Playwright MCPは便利だが、ブラウザ操作系MCPを雑に常時接続するのは別の話だ。
以前 オープンソースMCPサーバー50件のセキュリティスキャン で、Playwright MCP v0.0.39以下のDNSリバインディング問題を取り上げた。
v0.0.40で直っているが、ブラウザ操作系MCPは外部Webページ、ローカルサーバー、認証済みセッションに触れるので、失敗時の影響が大きい。

v0.0.71のREADMEでは、デフォルトの永続プロファイル、isolated mode、拡張機能経由で既存ブラウザへ接続するモードが説明されている。
ログイン済みセッションを使うなら便利だが、エージェントがアクセスするURLと認証情報の境界は先に決めておく。

自分なら、探索や一回限りのUI確認ではMCPを使う。
テストとして残すならPlaywright Testに書き下す。
大量に回すならCLIか通常のテストランナーに寄せる。
browser_drop はこの判断を変えるというより、MCP側の弱かった操作をひとつ普通にしたアップデートだ。

ドラッグ中の中間イベントは発火するのか

ドラッグ&ドロップには途中経過がある。
HTML Drag and Drop APIでは、ドラッグ中に dragstartdragdragenterdragoverdragleavedropdragend という一連のイベントが発火する。
drop zone内でカーソルが動けば dragover が繰り返し飛ぶし、領域外に出れば dragleave が飛ぶ。

Playwrightの dragTo()browser_drop は、実際のポインタ操作(mousedown → mousemove → mouseup)を実行する。
ブラウザはこのポインタ操作を受けてネイティブのDrag and Dropイベントを発火するので、中間イベントは普通に起きる。
evaluate でDOMイベントを手製する場合と違い、ブラウザの標準的なイベント列がそのまま流れる。

つまり、drop zoneが dragenter でCSSクラスを付けてハイライトするUIなら、browser_drop の実行中にそのクラスは付く。
dragleave で外す処理も、カーソルが領域外を経由すれば走る。
アプリ側のイベントリスナーから見れば、人間がマウスでドラッグしたのと同じイベント列が来る。

CSSの動的変化をスクリーンショットで検証できるか

問題はここだ。
browser_drop はMCPの1ツール呼び出しで、ドラッグ開始からドロップ完了まで一気に走る。
途中で止めてスクリーンショットを撮る手段がない。

drop zone上にカーソルがある瞬間のハイライト表示を確認したい、というケースは実際にある。
ファイルアップロード系のUIでは、drop zone内に入ったときの「ここにドロップ」表示がUXの一部だ。
でも browser_drop を呼んだ後にスクリーンショットを撮っても、ドロップ完了後の状態しか写らない。

ドラッグ中のCSS変化を検証したいなら、操作をバラす必要がある。
browser_evaluate でpointerdown → pointermoveを手動で発行し、途中で browser_screenshot を挟み、最後にpointerupで完了する、という組み立てになる。

手順としては、まず browser_evaluate でドラッグ元の pointerdown を発行し、次に pointermove でdrop zone上まで座標を動かす。
ここで browser_screenshot を呼べば、ハイライト中の画面が撮れる。
最後に pointerup でドロップを完了する。

ただ、この手順は browser_drop が解決しようとしていた「手製イベントの面倒さ」にそのまま戻る。
DataTransferオブジェクトの組み立て、座標計算、フレーム境界の考慮が全部復活する。

ドラッグ中のCSS検証と、ドロップ後の結果検証は分けて考えるしかない。
drop後にUIが正しい状態になっているかは browser_dropbrowser_screenshot で普通に確認できる。
ドラッグ中のハイライト表示を確認したいなら、Playwright Testのコードで page.mouse.move() を使い、途中でスクリーンショットを撮るテストを書くほうが安定する。

const source = page.getByText("report.pdf");
const target = page.getByTestId("upload-zone");

const sourceBox = await source.boundingBox();
const targetBox = await target.boundingBox();

await page.mouse.move(sourceBox!.x + sourceBox!.width / 2, sourceBox!.y + sourceBox!.height / 2);
await page.mouse.down();
await page.mouse.move(targetBox!.x + targetBox!.width / 2, targetBox!.y + targetBox!.height / 2);

// ドラッグ中の状態をキャプチャ
await page.screenshot({ path: 'drag-over-state.png' });

await page.mouse.up();

MCPだけで完結させようとすると、途中停止の手段がないので、中間状態のスクリーンショット検証は向いていない。
browser_drop は「ドラッグの結果だけ確認できればいいケース」で使うもので、途中の見た目を含めて検証したいならPlaywright Testに降りるのが筋だ。

参照