diff --git a/extension-playwright/src/background.ts b/extension-playwright/src/background.ts index 8f0a37d..b05180d 100644 --- a/extension-playwright/src/background.ts +++ b/extension-playwright/src/background.ts @@ -5,13 +5,14 @@ import { create } from 'zustand'; // Relay URL - fixed port for MCP bridge const RELAY_URL = 'ws://localhost:9988/extension'; -type ConnectionState = 'disconnected' | 'reconnecting' | 'connected'; +type ConnectionState = 'disconnected' | 'reconnecting' | 'connected' | 'error'; interface ExtensionState { connection: RelayConnection | undefined; connectedTabs: Map; connectionState: ConnectionState; currentTabId: number | undefined; + errorText: string | undefined; } const useExtensionStore = create(() => ({ @@ -19,110 +20,81 @@ const useExtensionStore = create(() => ({ connectedTabs: new Map(), connectionState: 'disconnected', currentTabId: undefined, + errorText: undefined, })); -async function updateIcon(tabId: number, state: 'connected' | 'disconnected' | 'connecting'): Promise { - try { - switch (state) { - case 'connected': - await chrome.action.setIcon({ - tabId, - path: { - '16': '/icons/icon-green-16.png', - '32': '/icons/icon-green-32.png', - '48': '/icons/icon-green-48.png', - '128': '/icons/icon-green-128.png' - } - }); - await chrome.action.setBadgeText({ tabId, text: '' }); - await chrome.action.setTitle({ tabId, title: 'Connected - Click to disconnect' }); - break; - - case 'connecting': - await chrome.action.setIcon({ - tabId, - path: { - '16': '/icons/icon-gray-16.png', - '32': '/icons/icon-gray-32.png', - '48': '/icons/icon-gray-48.png', - '128': '/icons/icon-gray-128.png' - } - }); - await chrome.action.setBadgeText({ tabId, text: '...' }); - await chrome.action.setBadgeBackgroundColor({ tabId, color: '#FF9800' }); - await chrome.action.setTitle({ tabId, title: 'Connecting...' }); - break; - - case 'disconnected': - default: - await chrome.action.setIcon({ - tabId, - path: { - '16': '/icons/icon-gray-16.png', - '32': '/icons/icon-gray-32.png', - '48': '/icons/icon-gray-48.png', - '128': '/icons/icon-gray-128.png' - } - }); - await chrome.action.setBadgeText({ tabId, text: '' }); - await chrome.action.setTitle({ tabId, title: 'Click to attach debugger' }); - break; - } - } catch (error: any) { - debugLog(`Error updating icon: ${error.message}`); +const icons = { + connected: { + path: { + '16': '/icons/icon-green-16.png', + '32': '/icons/icon-green-32.png', + '48': '/icons/icon-green-48.png', + '128': '/icons/icon-green-128.png' + }, + title: 'Connected - Click to disconnect', + badgeText: '', + badgeColor: undefined + }, + connecting: { + path: { + '16': '/icons/icon-gray-16.png', + '32': '/icons/icon-gray-32.png', + '48': '/icons/icon-gray-48.png', + '128': '/icons/icon-gray-128.png' + }, + title: 'Connecting...', + badgeText: '...', + badgeColor: '#FF9800' + }, + disconnected: { + path: { + '16': '/icons/icon-gray-16.png', + '32': '/icons/icon-gray-32.png', + '48': '/icons/icon-gray-48.png', + '128': '/icons/icon-gray-48.png' + }, + title: 'Click to attach debugger', + badgeText: '', + badgeColor: undefined + }, + error: { + path: { + '16': '/icons/icon-gray-16.png', + '32': '/icons/icon-gray-32.png', + '48': '/icons/icon-gray-48.png', + '128': '/icons/icon-gray-48.png' + }, + title: 'Error', + badgeText: '!', + badgeColor: '#f44336' } -} +} as const; -useExtensionStore.subscribe((state, prevState) => { - const prevTabs = new Set(prevState.connectedTabs.keys()); - const currentTabs = new Set(state.connectedTabs.keys()); - const prevConnectionState = prevState.connectionState; - const currentConnectionState = state.connectionState; - const prevCurrentTabId = prevState.currentTabId; - const currentTabId = state.currentTabId; +useExtensionStore.subscribe(async (state, prevState) => { + console.log(state) + const { connectionState, connectedTabs, errorText } = state; - const connectionStateChanged = prevConnectionState !== currentConnectionState; - const currentTabChanged = prevCurrentTabId !== currentTabId; + const tabs = await chrome.tabs.query({}); + const allTabIds = [undefined, ...tabs.map(tab => tab.id).filter((id): id is number => id !== undefined)]; - const tabsToUpdate = new Set(); - - if (connectionStateChanged) { - debugLog('Connection state changed:', prevConnectionState, '->', currentConnectionState); - for (const tabId of currentTabs) { - tabsToUpdate.add(tabId); - } - } - - const addedTabs = [...currentTabs].filter(id => !prevTabs.has(id)); - const removedTabs = [...prevTabs].filter(id => !currentTabs.has(id)); - - for (const tabId of addedTabs) { - tabsToUpdate.add(tabId); - } - - for (const tabId of removedTabs) { - tabsToUpdate.add(tabId); - } - - if (currentTabChanged && currentTabId !== undefined) { - tabsToUpdate.add(currentTabId); - } - - for (const tabId of tabsToUpdate) { - const isTracked = currentTabs.has(tabId); - let iconState: 'connected' | 'disconnected' | 'connecting'; - - if (currentConnectionState === 'disconnected') { - iconState = 'disconnected'; - } else if (!isTracked) { - iconState = 'disconnected'; - } else if (currentConnectionState === 'connected') { - iconState = 'connected'; - } else { - iconState = 'connecting'; - } - - void updateIcon(tabId, iconState); + for (const tabId of allTabIds) { + const iconConfig = (() => { + if (connectionState === 'error') { + return icons.error; + } + if (connectionState === 'reconnecting') { + return icons.connecting; + } + if (tabId !== undefined && connectedTabs.has(tabId) && connectionState === 'connected') { + return icons.connected; + } + return icons.disconnected; + })(); + const title = connectionState === 'error' && errorText ? errorText : iconConfig.title; + void chrome.action.setIcon({ tabId, path: iconConfig.path }); + void chrome.action.setTitle({ tabId, title }); + if (iconConfig.badgeColor) void chrome.action.setBadgeBackgroundColor({ tabId, color: iconConfig.badgeColor }); + void chrome.action.setBadgeText({ tabId, text: iconConfig.badgeText }); } }); @@ -133,11 +105,12 @@ async function ensureConnection(): Promise { return; } - useExtensionStore.setState({ connectionState: 'reconnecting' }); + debugLog('No existing connection, creating new relay connection'); debugLog('Waiting for server at http://localhost:9988...'); - while (useExtensionStore.getState().connectionState !== 'disconnected') { + useExtensionStore.setState({ connectionState: 'reconnecting' }); + while (true) { try { await fetch('http://localhost:9988', { method: 'HEAD' }); debugLog('Server is available'); @@ -148,11 +121,6 @@ async function ensureConnection(): Promise { } } - if (useExtensionStore.getState().connectionState === 'disconnected') { - debugLog('Connection cancelled by user'); - return; - } - debugLog('Server is ready, creating WebSocket connection to:', RELAY_URL); const socket = new WebSocket(RELAY_URL); debugLog('WebSocket created, initial readyState:', socket.readyState, '(0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED)'); @@ -220,15 +188,19 @@ async function ensureConnection(): Promise { debugLog('No tabs to reconnect'); } }, - onTabDetached: (tabId) => { + onTabDetached: (tabId, reason) => { debugLog('=== Manual tab detachment detected for tab:', tabId, '==='); debugLog('User closed debugger via Chrome automation bar'); useExtensionStore.setState((state) => { const newTabs = new Map(state.connectedTabs); newTabs.delete(tabId); - return { connectionState: 'disconnected', connectedTabs: newTabs }; + return { connectedTabs: newTabs }; }); + if (reason=== chrome.debugger.DetachReason.CANCELED_BY_USER) { + // if user cancels debugger. disconnect everything + useExtensionStore.setState({ connectionState: 'disconnected' }); + } debugLog('Removed tab from _connectedTabs map'); } }); @@ -270,20 +242,8 @@ async function connectTab(tabId: number): Promise { useExtensionStore.setState((state) => { const newTabs = new Map(state.connectedTabs); newTabs.delete(tabId); - return { connectedTabs: newTabs }; + return { connectedTabs: newTabs, connectionState: 'error', errorText: `Error: ${error.message}` }; }); - - chrome.action.setBadgeText({ tabId, text: '!' }); - chrome.action.setBadgeBackgroundColor({ tabId, color: '#f44336' }); - chrome.action.setTitle({ tabId, title: `Error: ${error.message}` }); - - setTimeout(() => { - const { connectedTabs } = useExtensionStore.getState(); - if (!connectedTabs.has(tabId)) { - chrome.action.setBadgeText({ tabId, text: '' }); - chrome.action.setTitle({ tabId, title: 'Click to attach debugger' }); - } - }, 3000); } } @@ -370,14 +330,11 @@ async function reconnect(): Promise { } catch (error: any) { debugLog('=== Reconnection failed ===', error); - const { connectedTabs: failedTabs } = useExtensionStore.getState(); - for (const tabId of failedTabs.keys()) { - chrome.action.setBadgeText({ tabId, text: '!' }); - chrome.action.setBadgeBackgroundColor({ tabId, color: '#f44336' }); - chrome.action.setTitle({ tabId, title: 'Reconnection failed - Click to retry' }); - } - - useExtensionStore.setState({ connectedTabs: new Map(), connectionState: 'disconnected' }); + useExtensionStore.setState({ + connectedTabs: new Map(), + connectionState: 'error', + errorText: 'Reconnection failed - Click to retry' + }); } } @@ -403,8 +360,8 @@ async function onActionClicked(tab: chrome.tabs.Tab): Promise { const { connectedTabs, connectionState, connection } = useExtensionStore.getState(); - if (connectionState === 'reconnecting') { - debugLog('User clicked during reconnection, canceling reconnection and disconnecting all tabs'); + if (connectionState === 'reconnecting' || connectionState === 'error') { + debugLog('User clicked during reconnection/error, canceling and disconnecting all tabs'); const tabsToDisconnect = Array.from(connectedTabs.keys()); @@ -412,7 +369,7 @@ async function onActionClicked(tab: chrome.tabs.Tab): Promise { connection?.detachTab(tabId); } - useExtensionStore.setState({ connectionState: 'disconnected', connectedTabs: new Map() }); + useExtensionStore.setState({ connectionState: 'disconnected', connectedTabs: new Map(), errorText: undefined }); if (connection) { connection.close('User cancelled reconnection'); diff --git a/playwriter/src/snapshots/shadcn-ui-accessibility.md b/playwriter/src/snapshots/shadcn-ui-accessibility.md index 565a0d5..6fe4a44 100644 --- a/playwriter/src/snapshots/shadcn-ui-accessibility.md +++ b/playwriter/src/snapshots/shadcn-ui-accessibility.md @@ -28,10 +28,10 @@ Return value: - generic [ref=e21]: - generic: ⌘ - generic: K - - link "98.7k" [ref=e22] [cursor=pointer]: + - link "99.8k" [ref=e22] [cursor=pointer]: - /url: https://github.com/shadcn-ui/ui - img - - generic [ref=e23]: 98.7k + - generic [ref=e23]: 99.8k - button "Toggle layout" [ref=e24]: - generic [ref=e25]: Toggle layout - img