Playwright MCP 0.0.85を0.0.84と比べて検証|見えている要素だけを操作する新設定と、設定ファイル内の相対パスの基準
Playwright MCP 0.0.85が11月10日に公開、同じテストページで0.0.84と比べた
AIにブラウザを操作させるMCPサーバー「Playwright MCP」の0.0.85を、Microsoftが2026年11月10日(日本時間)に公開しました(v0.0.85のリリースノート)。前の0.0.84は11月1日(日本時間)の公開です。
Playwright MCPは、Codex、Cursor、VS Code、Gemini CLIなどのAIツールに登録して使います。AIはページの構造を一覧にした「スナップショット」を受け取り、各要素に付いた番号(ref)や、getByRole(...)のような要素の指定(ロケーター)で、クリックや入力を頼みます。READMEの標準の設定はnpx @playwright/mcp@latestなので、この設定のままなら、次にAIツールを起動したときから0.0.85が動きます(2026年11月10日時点)。
この記事では、0.0.84と0.0.85を手元のMacに入れ、同じテストページに同じ操作を送って、変わった挙動を4つ確かめました。0.0.85へ上げたあとに直す設定があるか、新しい設定を使うかを決めるときの材料にしてください。
0.0.84と比べて分かったこと
0.0.85で変わった4つの挙動
1. 新しい設定
見えている要素だけを操作
設定ファイルにlocateOnlyVisibleを書くと、隠れた同名のボタンで止まらず、見えているほうを押します。既定はオフです。
2. 0.0.84の変更を戻した
モーダル表示中のスナップショット
ダイアログの背景の要素にも、再びrefが付きます。背景を押すと、ダイアログが操作を遮っていると理由が出ます。
3. 戻り値
browser_evaluateがNaNも返す
BigInt、NaN、undefined、自分自身を参照するオブジェクトもそのまま返ります。0.0.84はNaNをnullにしていました。
4. 設定の見直しが必要
設定ファイル内の相対パス
設定ファイルの場所が基準になりました。保存先やブラウザのプロフィールの場所が変わることがあります。
隠れた同名ボタンは、locateOnlyVisibleで見えているほうを押せる
スマホ用のメニューをdisplay:noneで隠しているページでは、同じ「保存」ボタンがHTMLの中に2つあることがよくあります。テストページでは、隠れたメニューの中と、見えているツールバーに、「保存」ボタンを1つずつ置きました。
AIがgetByText('保存')やCSSのbutton.saveで押そうとすると、指定に合う要素が2つあるため、Playwrightはどちらを押すか決められず、「strict mode violation」のエラーを返します。何も設定しなければ、0.0.84でも0.0.85でも次のエラーになりました(末尾のログは省略)。
Error: browserBackend.callTool: Error: strict mode violation: getByText('保存') resolved to 2 elements:
1) <button class="save" data-which="hidden-menu">保存</button> aka getByLabel('スマホ用メニュー').getByText('保存')
2) <button class="save" data-which="visible-toolbar">保存</button> aka getByRole('button', { name: '保存' })
0.0.85では、設定ファイルのbrowser.contextOptionsにlocateOnlyVisible: trueを書くと、ロケーターの対象が見えている要素だけになります(Playwrightの変更)。
{
"browser": {
"contextOptions": {
"locateOnlyVisible": true
}
}
}
4つの書き方で「保存」を押し、どちらのボタンが押されたかをページ側で記録した結果が次の表です。
getByTextとCSSの指定が見えているボタンを押しました。.first()はどの条件でも押せず、getByRoleはどの条件でも押せました。| MCPに送ったロケーター | 0.0.84(設定の有無を問わず) | 0.0.85 設定なし | 0.0.85 locateOnlyVisible: true |
|---|---|---|---|
getByText('保存') | エラー(2つに一致) | エラー(2つに一致) | 見えているボタンを押した |
button.save | エラー(2つに一致) | エラー(2つに一致) | 見えているボタンを押した |
locator('button.save').first() | 5秒待って時間切れ | 5秒待って時間切れ | 5秒待って時間切れ |
getByRole('button', { name: '保存' }) | 見えているボタンを押した | 見えているボタンを押した | 見えているボタンを押した |
隠れたメニューの中と、見えているツールバーに「保存」ボタンを1つずつ置いたテストページで確認(Chrome 157、ヘッドレス、2026年11月10日)。
設定を有効にしても、.first()を付けた指定は押せませんでした。0.0.85は.first()で先頭の要素(隠れたメニューのボタン)を選んだあとに「見えている要素だけ」の条件を足すため、条件に合う要素が残らず、5秒待って時間切れになります。ログにも、その順で待ったことが出ます。
TimeoutError: browserBackend.callTool: Timeout 5000ms exceeded.
Call log:
- waiting for locator('button.save').first().visible()
隠れた要素を避けるつもりで.first()を付けている指定は、ページに合わせて書き方を変えます。見えている同名の要素が1つだけのページなら、.first()を外してlocateOnlyVisibleに任せると押せました。見えている「保存」が2つあるページでも試しました。設定を有効にしてもbutton.saveはstrict mode violationになり、filter({ visible: true })を.first()より前に置いたlocator('button.save').filter({ visible: true }).first()なら、見えているうちの1つ目を押せました(0.0.84と0.0.85の両方で確認)。
getByRoleの指定は、隠れた要素を既定で対象にしないため(getByRoleのincludeHidden)、版や設定にかかわらず見えているほうを押しました。
0.0.84のままなら、ロケーターの最後にfilter({ visible: true })を足す
0.0.84に同じ設定ファイルを渡すと、エラーは出ずに、設定だけが無視されました。版を0.0.84に固定している場合は、locator('button.save').filter({ visible: true })のようにロケーターの最後へ条件を足すと、見えているほうを押せました。この書き方は0.0.85でも同じ結果です。
AIがPlaywrightのコードを直接実行するbrowser_run_code_unsafeでも、0.0.85で設定を有効にすると、コードの中のpage.locator('button.save').click()が見えているボタンを押しました。設定がないと、こちらも同じstrict mode violationになります。
AIツールのMCPの設定に--configを足す
locateOnlyVisibleは設定ファイルに書きます(リリースノート)。0.0.85のREADMEのオプション一覧には、対応するコマンドラインの引数や環境変数がありません。AIツールのMCPの設定で、argsに--configと設定ファイルのパスを足します。
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"@playwright/mcp@latest",
"--config",
"/Users/you/playwright-mcp/config.json"
]
}
}
}
ここでは多くのAIツールが使うmcpServersの形で示しました。Codexのconfig.tomlでは、args = [...]の配列に同じ2項目を足します。書式がほかと違うツールでも、Playwright MCPを起動するコマンドの引数に--configと設定ファイルのパスを足す点は同じです。
--configに渡すパスは、AIツールがPlaywright MCPを起動したときの作業フォルダーが基準になります。作業フォルダーはAIツールの側で決まるので、絶対パスで書くと、どこから起動しても同じ設定ファイルを読みます。npx @playwright/mcp@latestに絶対パスの設定ファイルを渡す形でも、隠れたボタンを避けて押せることを確かめました。
モーダル表示中も、背景の要素に再びrefが付く
0.0.84では、<dialog>をshowModal()で開いている間(モーダル表示。ダイアログの外を操作できなくする開き方)、ダイアログの外にある要素を、スナップショットで隠れた要素として扱う変更が入っていました(v0.0.84のリリースノート)。0.0.85はこの変更を元に戻しています(Playwrightの変更)。
確認には、注文の一覧と「注文を取り消す」ボタンがあり、ボタンを押すと確認のダイアログが開くページを使いました。
この状態でスナップショットを取ると、0.0.84では背景の見出し、表、ボタンにrefが付かず、「heading [level=1]: 注文一覧」のように、refのない行として出るだけでした。0.0.85では背景にもrefが付きました。
下の比較では、0.0.85のほうにダイアログの「取り消しの確認」と表の「今月の注文」という名前も付いています。名前のほうは、中にある見出しやcaptionの文字を、ダイアログや表そのものの名前として残す別の変更によるもので(Playwrightの変更)、ダイアログを開く前のスナップショットでも、0.0.85だけ表に「今月の注文」と付きました。
0.0.84:背景にrefなし
- generic:
- main:
- heading [level=1]: 注文一覧
- table:
- caption: 今月の注文
(行は省略)
- button: 注文を取り消す
- dialog [ref=e14]:
- heading "取り消しの確認" [level=2] [ref=e15]
- paragraph [ref=e16]: 注文 A-1001 を取り消しますか?
- button "取り消す" [active] [ref=e17]
- button "やめる" [ref=e18]
0.0.85:背景にもref
- generic [ref=e1]:
- main [ref=e2]:
- heading "注文一覧" [level=1] [ref=e3]
- table "今月の注文" [ref=e4]:
- caption [ref=e5]: 今月の注文
(行は省略)
- button "注文を取り消す" [ref=e13]
- dialog "取り消しの確認" [ref=e14]:
- heading "取り消しの確認" [level=2] [ref=e15]
- paragraph [ref=e16]: 注文 A-1001 を取り消しますか?
- button "取り消す" [active] [ref=e17]
- button "やめる" [ref=e18]
ダイアログを開いたまま、AIが背景の「注文を取り消す」をgetByRoleで押そうとしたときの返事も変わりました。
背景のボタンを押したときのエラー(0.0.84)
does not match any elements
要素が見つからない、という返事です。ダイアログが原因だと読み取れる情報は付きません。
背景のボタンを押したときのエラー(0.0.85)
<dialog …> intercepts pointer events
ボタンは見つかり、ダイアログがクリックを遮っていると出ます。5秒待ったあとに時間切れのエラーになります。
0.0.85の返事なら、AIは先にダイアログを閉じればよいと判断できます。そのかわり、ダイアログを閉じずに背景を押そうとすると、エラーが返るまでに5秒かかります。
browser_evaluateは、BigIntやNaNもそのまま返す
browser_evaluateは、ページの中でJavaScriptの関数を実行して、結果をAIへ返すツールです。0.0.84は結果をJSONに変換して返していたため、JSONで表せない値は、nullに置き換わるか、消えるか、エラーになっていました(Playwrightの変更)。同じ関数を両方の版で実行した結果です。
| 実行した関数 | 0.0.84の結果 | 0.0.85の結果 |
|---|---|---|
() => 2n ** 64n | エラーDo not know how to serialize a BigInt | 18446744073709551616n |
() => ({ ratio: 0 / 0, max: 1 / 0, note: undefined, count: 3 }) | {"ratio": null, "max": null, "count": 3}エラーは出ない | { ratio: NaN, max: Infinity, note: undefined, count: 3 } |
| 自分自身を参照するオブジェクト | エラーConverting circular structure to JSON | <ref *1> { name: 'root', self: [Circular *1] } |
() => ({ count: 3, items: ['a', 'b'] }) | {"count": 3, "items": ["a", "b"]}(JSON。実際の表示は改行付き) | 0.0.84と同じJSON |
2行目のNaNは0 / 0、Infinityは1 / 0の計算結果。Chrome 157、2026年11月10日に確認。
気をつけたいのは2行目です。0.0.84はエラーを出さずに、NaNとInfinityをnullへ置き換え、undefinedの項目を消していました。ページ上の計算結果をAIに確かめさせる使い方では、計算に失敗してNaNになった値も、0.0.84ではnullとして受け取っていたことになります。
JSONで表せる値は、0.0.85でもこれまでと同じJSONの形で返りました。BigInt、NaN、Infinity、undefined、循環参照など、JSONで表せない値を含むときは、キーを引用符で囲まないJavaScriptの書き方で返るので、JSON.parseで読み込むと失敗します。結果をプログラムでJSONとして読み込んでいる場合は、JSONで表せない値を含む結果をどう扱うかを決めておきます。
設定ファイル内の相対パスは、設定ファイルの場所が基準になった
0.0.85では、設定ファイルに書いた相対パスを、設定ファイルがあるフォルダーを基準に解釈するようになりました(Playwrightの変更)。0.0.84では、Playwright MCPを起動したときの作業フォルダーが基準でした。--output-dirのようなコマンドラインの引数や、PLAYWRIGHT_MCP_OUTPUT_DIRのような環境変数で直接渡すパスは、0.0.85でも作業フォルダーが基準です。設定ファイルそのものは--configのほか環境変数PLAYWRIGHT_MCP_CONFIGでも渡せ、どちらで渡しても、中に書いた相対パスは設定ファイルの場所が基準になりました。
作業フォルダーをproject/にして、project/.playwright/mcp.jsonに相対パスを書き、保存先がどこにできるかを比べました。
同じ置き方の設定ファイルで、保存先のフォルダーが作られた場所
設定ファイル: project/.playwright/mcp.json/起動した作業フォルダー: project/
設定に書いた値
"outputDir": "./mcp-output"
"browser": { "userDataDir": "./chrome-profile" }
0.0.84:作業フォルダーが基準
project/mcp-output/
project/chrome-profile/
0.0.85:設定ファイルの場所が基準
project/.playwright/mcp-output/
project/.playwright/chrome-profile/
outputDirはスクリーンショットなどを自動で保存するフォルダー、userDataDirはブラウザのプロフィール(ログイン状態やCookie)を保存するフォルダー。outputDirとuserDataDirは別々の設定ファイルで確かめた。コマンドラインの--output-dir ./cli-outは、0.0.85でもproject/cli-out/にできた。
影響が大きいのはuserDataDirです。0.0.85では、設定ファイルのフォルダーの下に新しいプロフィールが作られました。以前の場所のプロフィールは読まれないので、そこに保存されていたログイン状態は使えず、AIに操作させていたサイトにはログインし直すことになります。
設定ファイルを作業フォルダーの直下に置いている場合は、基準の場所が同じなので変わりません。ただし設定ファイルがシンボリックリンクのときは、リンク先のファイルがあるフォルダーが基準になりました。設定ファイルの実体が作業フォルダーと別の場所にある場合は、相対パスを絶対パスへ書き換えるか、これまで使っていたフォルダーを新しい基準から見た場所へ移します。例の./chrome-profileなら、設定ファイルの実体があるフォルダー(例ではproject/.playwright/、シンボリックリンクならリンク先のファイルがあるフォルダー)の下へ移します。
そのほかの変更:コンソールの行番号と、拡張機能で接続するときのオプション
手元で確認
コンソールの行番号が1始まりに
テストページの5行目のconsole.errorが、browser_console_messagesで0.0.84は:4、0.0.85は:5と出ました。エディターの行番号と一致します(Playwrightの変更)。
リリースノートの記載
--backgroundと--no-tab-group
普段使っているChromeへ拡張機能経由でつなぐ--extensionと組み合わせて、操作するタブを前面に出さない、タブグループにまとめない、と指定できます。拡張機能0.5.0以降が必要です。
0.0.85で見直す4つのこと
ここまでの結果から、0.0.85で確かめることを順にまとめます。
-
1. 動いている版を確かめる
MCPの設定が
@playwright/mcp@latestなら、2026年11月10日時点では0.0.85が動きます。@0.0.84のように版を書いていれば、その版です。@latestで取得される版は次のコマンドで確かめられます。npx -y @playwright/mcp@latest --version Version 0.0.85 -
2. 設定ファイルの相対パスを確かめる
--configやPLAYWRIGHT_MCP_CONFIGで設定ファイルを渡していて、その中にoutputDirやbrowser.userDataDirを相対パスで書いているなら、基準が設定ファイルのあるフォルダー(シンボリックリンクならリンク先)に変わります。設定ファイルを作業フォルダーの直下以外に置いている場合や、シンボリックリンクにしている場合は、保存先とプロフィールの場所が移るので、絶対パスへ書き換えます。 -
3. 隠れた重複要素のエラーが出ていたら、locateOnlyVisibleを足す
strict mode violationで止まっていたページがあるなら、設定ファイルに
locateOnlyVisible: trueを書きます。隠れた要素を避けるために付けていた.first()は、見えている同名の要素が1つなら外し、2つ以上ならfilter({ visible: true }).first()の順に書き換えます。 -
4. browser_evaluateの結果をJSONとして読む仕組みを確かめる
結果をプログラムで
JSON.parseしている場合は、BigInt、NaN、循環参照などを含む結果がJavaScriptの書き方で返ったときの扱いを決めます。
参考資料
- Playwright MCP v0.0.85 リリースノート(Microsoft、2026年11月10日・日本時間)
- Playwright MCP v0.0.84 リリースノート(Microsoft、2026年11月1日・日本時間)
- Playwright MCP README:Configuration file
- feat(locator): add locateOnlyVisible context option(microsoft/playwright #43903)
- chore: revert "fix(a11y): treat inert elements and content outside of modal dialogs as hidden"(microsoft/playwright #43850)
- fix(aria): preserve identifying container names in AI snapshots(microsoft/playwright #43810)
- fix(mcp): preserve non-JSON evaluation results(microsoft/playwright #43973)
- fix(mcp): resolve config file paths relative to their source(microsoft/playwright #43970)
- fix(mcp): report one-based console source lines(microsoft/playwright #43975)
- Playwright:page.getByRole()
検証環境
- macOS 27.0.1(Apple Silicon)、Google Chrome 157.0.8103.39
- Node.js 24.12.0、@playwright/mcp 0.0.84と0.0.85
- 検証日: 2026年11月10日
この記事は、AIが調査と下書きを担当し、上記の環境で実際に動かして結果を確かめ、下書きとは別の担当が公開前に本文と出典を確認しています。制作と訂正の方針は編集・訂正方針で公開しています。











