From 8425b6915e8ebe2b12bf4bd42975bfccfa62fc5c Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Fri, 4 Sep 2026 15:28:36 +0100 Subject: [PATCH] [DevTools] Upgrade chrome-devtools-mcp to 1.8.0 in cdt-mcp e2e (#37497) ## Summary - Upgrade the cdt-mcp e2e dependency from chrome-devtools-mcp 1.3.0 to 1.8.0. - Pass `pageId` to page-scoped CLI tools, which 1.8.0 requires. - Use a hex `sessionId` (`crypto.randomUUID()`). 1.8.0 rejects ids that are not `/[a-fA-F0-9-]+/`. Stacked on https://github.com/react/react/pull/37496 ## Test plan - [ ] `yarn --cwd packages/react-devtools-cdt-mcp test:e2e` --- .../react-devtools-cdt-mcp/e2e/run.flow.js | 78 +++++++++++++++++-- packages/react-devtools-cdt-mcp/package.json | 2 +- yarn.lock | 8 +- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/packages/react-devtools-cdt-mcp/e2e/run.flow.js b/packages/react-devtools-cdt-mcp/e2e/run.flow.js index c60432f2118a..2319dcef4e4a 100644 --- a/packages/react-devtools-cdt-mcp/e2e/run.flow.js +++ b/packages/react-devtools-cdt-mcp/e2e/run.flow.js @@ -11,6 +11,7 @@ const assert = require('assert'); const childProcess = require('child_process'); +const crypto = require('crypto'); const fs = require('fs'); const http = require('http'); const net = require('net'); @@ -42,6 +43,7 @@ type CommandOptions = { timeout?: number, }; type Chrome = { + pageId: number, run: (args: Array) => Promise, json: (args: Array) => Promise, }; @@ -164,7 +166,8 @@ const LOG_DIR = process.env.E2E_LOG_DIR || path.join(REPO_ROOT, 'tmp', 'react-devtools-cdt-mcp-e2e'); -const SESSION_ID = `react-devtools-cdt-mcp-${process.pid}-${Date.now()}`; +// chrome-devtools-mcp 1.8+ only accepts /[a-fA-F0-9-]+/ session ids. +const SESSION_ID = crypto.randomUUID(); function log(message: string): void { process.stdout.write(`${message}\n`); @@ -628,10 +631,61 @@ async function evaluatePageReadiness( chrome: Chrome, fn: string ): Promise { - const output = await chrome.json(['evaluate_script', fn]); + const output = await chrome.json([ + 'evaluate_script', + fn, + '--pageId', + String(chrome.pageId), + ]); return parsePageReadiness(parseJsonFromText(unwrapTextResponse(output))); } +function parsePageId(value: mixed): number | null { + if (typeof value === 'number' && Number.isInteger(value)) { + return value; + } + if (value == null || typeof value !== 'object') { + return null; + } + if (Array.isArray(value)) { + for (let index = 0; index < value.length; index++) { + const pageId = parsePageId(value[index]); + if (pageId != null) { + return pageId; + } + } + return null; + } + const object = value; + if (object.pages != null) { + const pageId = parsePageId(object.pages); + if (pageId != null) { + return pageId; + } + } + if (object.pageId != null) { + const pageId = parsePageId(object.pageId); + if (pageId != null) { + return pageId; + } + } + if (object.id != null) { + return parsePageId(object.id); + } + return null; +} + +async function resolvePageId(chrome: Chrome): Promise { + const pages = await chrome.json(['list_pages']); + const pageId = parsePageId(pages); + if (pageId == null) { + throw createError( + `Expected list_pages to include a page id. Saw: ${formatValue(pages)}` + ); + } + return pageId; +} + function parseToolResponse(output: mixed): mixed { return parseJsonFromText(unwrapTextResponse(output)); } @@ -942,12 +996,19 @@ function assertSourceReference(sourceResult: SourceResult): void { } async function runE2E(chrome: Chrome, appUrl: string): Promise { - await chrome.json(['navigate_page', '--type', 'url', '--url', appUrl]); + await chrome.json([ + 'navigate_page', + String(chrome.pageId), + '--type', + 'url', + '--url', + appUrl, + ]); await waitForPageReady(chrome, 30000); log('Checking third-party tool discovery...'); const discovery = parseToolDiscovery( - await chrome.json(['list_3p_developer_tools']) + await chrome.json(['list_3p_developer_tools', String(chrome.pageId)]) ); const toolGroup = getReactToolGroup(discovery); if (toolGroup == null) { @@ -991,6 +1052,7 @@ async function runE2E(chrome: Chrome, appUrl: string): Promise { chrome .json([ 'execute_3p_developer_tool', + String(chrome.pageId), toolName, '--params', JSON.stringify(params || {}), @@ -1095,7 +1157,9 @@ async function runE2E(chrome: Chrome, appUrl: string): Promise { ['TodoList', 'Todo'] ); - const snapshot = parseSnapshotResponse(await chrome.json(['take_snapshot'])); + const snapshot = parseSnapshotResponse( + await chrome.json(['take_snapshot', String(chrome.pageId)]) + ); const buttonNode = findSnapshotNode( snapshot.snapshot, node => node.role === 'button' && node.name === '+1', @@ -1197,7 +1261,7 @@ async function runE2E(chrome: Chrome, appUrl: string): Promise { traceName, } ); - await chrome.json(['click', buttonUid]); + await chrome.json(['click', String(chrome.pageId), buttonUid]); const stopResult = parseStopProfilingResult( await callTool('react_stop_profiling') ); @@ -1264,6 +1328,7 @@ async function main(): Promise { } ); const chrome: Chrome = { + pageId: 1, run: runChrome, async json(args: Array): Promise { const result = await runChrome([...args, '--output-format', 'json']); @@ -1313,6 +1378,7 @@ async function main(): Promise { } log('Starting chrome-devtools daemon...'); await chrome.run(startArgs); + chrome.pageId = await resolvePageId(chrome); await runE2E(chrome, appUrl); log('react-devtools-cdt-mcp E2E passed.'); diff --git a/packages/react-devtools-cdt-mcp/package.json b/packages/react-devtools-cdt-mcp/package.json index 3dce576eef86..6d4b710ff871 100644 --- a/packages/react-devtools-cdt-mcp/package.json +++ b/packages/react-devtools-cdt-mcp/package.json @@ -32,7 +32,7 @@ "@babel/core": "^7.11.1", "@babel/plugin-transform-flow-strip-types": "^7.10.4", "@babel/register": "^7.14.5", - "chrome-devtools-mcp": "1.3.0", + "chrome-devtools-mcp": "1.8.0", "cross-env": "^7.0.3" } } diff --git a/yarn.lock b/yarn.lock index 18a421b05611..23bcd90d2be7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6670,10 +6670,10 @@ chownr@^1.0.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -chrome-devtools-mcp@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/chrome-devtools-mcp/-/chrome-devtools-mcp-1.3.0.tgz#7aeb4c8dab5d8dc536ef683b75e7a81b3989ad0e" - integrity sha512-52NVUwWSL4eW7W9nsDrzYJF96IKVuxEwAn4O7ZfdNRtopS954P9nryJbdYwg7vdqxhLrvioGFlm5e4P41WXsiw== +chrome-devtools-mcp@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/chrome-devtools-mcp/-/chrome-devtools-mcp-1.8.0.tgz#f509e26068b9b96302eba68d9965dd3022fffdd6" + integrity sha512-Wrm9z0/5WbVs778apjWgYRkpe9bvYQWjK2zVRwqoPAtz1IHQ5+GvotM07UGXJcfrA0rj6Gt1Pnn5+w/Tf1nU4w== chrome-launch@^1.1.4: version "1.1.4"