From 016d469c89323d0e6d9752f3a26f67d0a32ab574 Mon Sep 17 00:00:00 2001 From: Akshat Date: Fri, 17 Jul 2026 15:15:52 +0530 Subject: [PATCH 1/2] fix(extension): pass client name on token-bypass connect Token-bypass called handleConnectToTab in the same effect as setClientInfo, so the callback still closed over the initial "unknown" name. Pass the parsed client name explicitly and assert it on the status page. --- packages/extension/src/ui/connect.tsx | 18 +++++++++++------- tests/extension/extension.spec.ts | 5 +++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/extension/src/ui/connect.tsx b/packages/extension/src/ui/connect.tsx index 22c3ec632b69c..2f0da5803de9e 100644 --- a/packages/extension/src/ui/connect.tsx +++ b/packages/extension/src/ui/connect.tsx @@ -59,9 +59,10 @@ const ConnectApp: React.FC = () => { return; } + let info: string; try { const client = JSON.parse(params.get('client') || '{}'); - const info = `${client.name || 'unknown'}`; + info = `${client.name || 'unknown'}`; setClientInfo(info); setStatus({ type: 'connecting', @@ -97,7 +98,9 @@ const ConnectApp: React.FC = () => { const expectedToken = getOrCreateAuthToken(); const token = params.get('token'); if (token === expectedToken) { - await handleConnectToTab(); + // Pass `info` explicitly: setClientInfo only schedules a re-render, so + // handleConnectToTab would still close over the initial 'unknown' state. + await handleConnectToTab(undefined, info); return; } if (token) { @@ -128,28 +131,29 @@ const ConnectApp: React.FC = () => { setStatus({ type: 'error', message: 'Failed to load tabs: ' + response.error }); }, []); - const handleConnectToTab = useCallback(async (tab?: chrome.tabs.Tab) => { + const handleConnectToTab = useCallback(async (tab?: chrome.tabs.Tab, clientNameOverride?: string) => { setShowTabList(false); + const name = clientNameOverride ?? clientInfo; try { const response = await chrome.runtime.sendMessage({ type: 'connectToTab', tab, - clientName: clientInfo, + clientName: name, }); if (response?.success) { - setStatus({ type: 'connected', message: `"${clientInfo}" connected.` }); + setStatus({ type: 'connected', message: `"${name}" connected.` }); } else { setStatus({ type: 'error', - message: response?.error || `"${clientInfo}" failed to connect.` + message: response?.error || `"${name}" failed to connect.` }); } } catch (e) { setStatus({ type: 'error', - message: `"${clientInfo}" failed to connect: ${e}` + message: `"${name}" failed to connect: ${e}` }); } }, [clientInfo]); diff --git a/tests/extension/extension.spec.ts b/tests/extension/extension.spec.ts index 98ba4643b71dd..60afcd14292fd 100644 --- a/tests/extension/extension.spec.ts +++ b/tests/extension/extension.spec.ts @@ -328,7 +328,9 @@ test(`bypass connection dialog with token`, async ({ browserWithExtension, start const token = await page.locator('.auth-token-code').textContent(); const [, value] = token?.split('=') || []; + const clientName = 'token-bypass-client'; const { client } = await startClient({ + clientName, args: [`--extension`], env: { PLAYWRIGHT_MCP_EXTENSION_TOKEN: value, @@ -344,6 +346,9 @@ test(`bypass connection dialog with token`, async ({ browserWithExtension, start expect(await navigateResponse).toHaveResponse({ snapshot: expect.stringContaining(`- generic [active] [ref=f1e1]: Hello, world!`), }); + + await page.goto(`chrome-extension://${extensionId}/status.html`); + await expect(page.locator('.client-info')).toContainText(`Connected to "${clientName}"`); }); test(`pending connection closed when client disconnects`, async ({ startExtensionClient, server, protocolVersion }) => { From 4b48cf03836bc0ce7a5b75010129fa8c1c8afcc3 Mon Sep 17 00:00:00 2001 From: Akshat Date: Sat, 18 Jul 2026 01:50:17 +0530 Subject: [PATCH 2/2] fix(extension): parse client name once from URL search params Address review feedback: avoid a clientNameOverride workaround for the React stale closure by treating the URL-derived client name as a module constant, since it never changes for the page lifetime. --- packages/extension/src/ui/connect.tsx | 44 ++++++++++++--------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/extension/src/ui/connect.tsx b/packages/extension/src/ui/connect.tsx index 2f0da5803de9e..8ddca72b3377c 100644 --- a/packages/extension/src/ui/connect.tsx +++ b/packages/extension/src/ui/connect.tsx @@ -27,11 +27,19 @@ type Status = const SUPPORTED_PROTOCOL_VERSION = 2; +// Client name comes from the URL and never changes for the lifetime of this page. +const clientInfo = (() => { + try { + return JSON.parse(new URLSearchParams(window.location.search).get('client') || '{}').name || 'unknown'; + } catch { + return 'unknown'; + } +})(); + const ConnectApp: React.FC = () => { const [tabs, setTabs] = useState([]); const [status, setStatus] = useState(null); const [showTabList, setShowTabList] = useState(true); - const [clientInfo, setClientInfo] = useState('unknown'); const setError = useCallback((message: string) => { setShowTabList(false); @@ -59,19 +67,10 @@ const ConnectApp: React.FC = () => { return; } - let info: string; - try { - const client = JSON.parse(params.get('client') || '{}'); - info = `${client.name || 'unknown'}`; - setClientInfo(info); - setStatus({ - type: 'connecting', - message: `"${info}" is trying to connect to the Playwright Extension.` - }); - } catch (e) { - setStatus({ type: 'error', message: 'Failed to parse client version.' }); - return; - } + setStatus({ + type: 'connecting', + message: `"${clientInfo}" is trying to connect to the Playwright Extension.` + }); const parsedVersion = parseInt(params.get('protocolVersion') ?? '', 10); const requestedVersion = isNaN(parsedVersion) ? 1 : parsedVersion; @@ -98,9 +97,7 @@ const ConnectApp: React.FC = () => { const expectedToken = getOrCreateAuthToken(); const token = params.get('token'); if (token === expectedToken) { - // Pass `info` explicitly: setClientInfo only schedules a re-render, so - // handleConnectToTab would still close over the initial 'unknown' state. - await handleConnectToTab(undefined, info); + await handleConnectToTab(); return; } if (token) { @@ -131,32 +128,31 @@ const ConnectApp: React.FC = () => { setStatus({ type: 'error', message: 'Failed to load tabs: ' + response.error }); }, []); - const handleConnectToTab = useCallback(async (tab?: chrome.tabs.Tab, clientNameOverride?: string) => { + const handleConnectToTab = useCallback(async (tab?: chrome.tabs.Tab) => { setShowTabList(false); - const name = clientNameOverride ?? clientInfo; try { const response = await chrome.runtime.sendMessage({ type: 'connectToTab', tab, - clientName: name, + clientName: clientInfo, }); if (response?.success) { - setStatus({ type: 'connected', message: `"${name}" connected.` }); + setStatus({ type: 'connected', message: `"${clientInfo}" connected.` }); } else { setStatus({ type: 'error', - message: response?.error || `"${name}" failed to connect.` + message: response?.error || `"${clientInfo}" failed to connect.` }); } } catch (e) { setStatus({ type: 'error', - message: `"${name}" failed to connect: ${e}` + message: `"${clientInfo}" failed to connect: ${e}` }); } - }, [clientInfo]); + }, []); useEffect(() => { const listener = (message: any) => {