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つの書き方で「保存」を押し、どちらのボタンが押されたかをページ側で記録した結果が次の表です。

表の見方: 0.0.85で設定を有効にした列だけ、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の変更)。

確認には、注文の一覧と「注文を取り消す」ボタンがあり、ボタンを押すと確認のダイアログが開くページを使いました。

注文一覧のページの上に「取り消しの確認」ダイアログが開き、「取り消す」と「やめる」のボタンが並んでいる画面
検証に使ったテストページ。「注文を取り消す」を押して、確認のダイアログを開いた状態(Playwright MCP 0.0.85のbrowser_take_screenshotで撮影、2026年11月10日)

この状態でスナップショットを取ると、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. 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. 2. 設定ファイルの相対パスを確かめる

    --configやPLAYWRIGHT_MCP_CONFIGで設定ファイルを渡していて、その中にoutputDirやbrowser.userDataDirを相対パスで書いているなら、基準が設定ファイルのあるフォルダー(シンボリックリンクならリンク先)に変わります。設定ファイルを作業フォルダーの直下以外に置いている場合や、シンボリックリンクにしている場合は、保存先とプロフィールの場所が移るので、絶対パスへ書き換えます。

  3. 3. 隠れた重複要素のエラーが出ていたら、locateOnlyVisibleを足す

    strict mode violationで止まっていたページがあるなら、設定ファイルにlocateOnlyVisible: trueを書きます。隠れた要素を避けるために付けていた.first()は、見えている同名の要素が1つなら外し、2つ以上ならfilter({ visible: true }).first()の順に書き換えます。

  4. 4. browser_evaluateの結果をJSONとして読む仕組みを確かめる

    結果をプログラムでJSON.parseしている場合は、BigInt、NaN、循環参照などを含む結果がJavaScriptの書き方で返ったときの扱いを決めます。

参考資料

検証環境

  • 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が調査と下書きを担当し、上記の環境で実際に動かして結果を確かめ、下書きとは別の担当が公開前に本文と出典を確認しています。制作と訂正の方針は編集・訂正方針で公開しています。