@midscene/web 1.10.9-beta-20260804015945.0 → 1.10.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,7 @@ class BridgeClient {
23
23
  ]
24
24
  } : {},
25
25
  query: {
26
- version: "1.10.9-beta-20260804015945.0"
26
+ version: "1.10.9"
27
27
  }
28
28
  });
29
29
  const timeout = setTimeout(()=>{
@@ -102,7 +102,7 @@ class BridgeServer {
102
102
  logMsg('one client connected');
103
103
  this.socket = socket;
104
104
  const clientVersion = socket.handshake.query.version;
105
- logMsg(`Bridge connected, cli-side version v1.10.9-beta-20260804015945.0, browser-side version v${clientVersion}`);
105
+ logMsg(`Bridge connected, cli-side version v1.10.9, browser-side version v${clientVersion}`);
106
106
  socket.on(BridgeEvent.CallResponse, (params)=>{
107
107
  const id = params.id;
108
108
  const response = params.response;
@@ -126,7 +126,7 @@ class BridgeServer {
126
126
  setTimeout(()=>{
127
127
  this.onConnect?.();
128
128
  const payload = {
129
- version: "1.10.9-beta-20260804015945.0"
129
+ version: "1.10.9"
130
130
  };
131
131
  socket.emit(BridgeEvent.Connected, payload);
132
132
  Promise.resolve().then(()=>{
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mode/io-server.mjs","sources":["../../../src/bridge-mode/io-server.ts"],"sourcesContent":["import {\n type IncomingMessage,\n type ServerResponse,\n createServer,\n} from 'node:http';\nimport { sleep } from '@midscene/core/utils';\nimport { getDebug } from '@midscene/shared/logger';\nimport { logMsg } from '@midscene/shared/utils';\nimport { Server, type Socket as ServerSocket } from 'socket.io';\nimport { io as ClientIO } from 'socket.io-client';\n\nimport {\n type BridgeCall,\n type BridgeCallResponse,\n BridgeCallTimeout,\n type BridgeConnectedEventPayload,\n BridgeErrorCodeNoClientConnected,\n BridgeEvent,\n BridgeSignalKill,\n DefaultBridgeServerPort,\n} from './common';\n\nconst debug = getDebug('web:bridge:io-server');\n\ndeclare const __VERSION__: string;\n\n/**\n * Returns true if the given Origin header value is allowed to connect\n * to the bridge server. Trusted origins are:\n * - No Origin header (local Node.js processes like killRunningServer)\n * - chrome-extension:// origins (the Midscene Chrome extension)\n *\n * Browser pages (https://evil.com, etc.) are rejected to prevent\n * Cross-Site WebSocket Hijacking (CSWSH) attacks.\n */\nfunction isTrustedOrigin(origin: string | undefined): boolean {\n if (!origin) return true;\n if (origin.startsWith('chrome-extension://')) return true;\n return false;\n}\n\nexport const killRunningServer = async (port?: number, host = 'localhost') => {\n try {\n const client = ClientIO(`ws://${host}:${port || DefaultBridgeServerPort}`, {\n query: {\n [BridgeSignalKill]: 1,\n },\n });\n await sleep(300);\n await client.close();\n } catch (e) {\n debug('failed to kill port: %O', e);\n }\n};\n\n// ws server, this is where the request is sent\nexport class BridgeServer {\n private callId = 0;\n private io: Server | null = null;\n private socket: ServerSocket | null = null;\n private listeningTimeoutId: NodeJS.Timeout | null = null;\n private listeningTimerFlag = false;\n private connectionTipTimer: NodeJS.Timeout | null = null;\n public calls: Record<string, BridgeCall> = {};\n\n private connectionLost = false;\n private connectionLostReason = '';\n\n constructor(\n public host: string,\n public port: number,\n public onConnect?: () => void,\n public onDisconnect?: (reason: string) => void,\n public closeConflictServer?: boolean,\n ) {}\n\n async listen(\n opts: {\n timeout?: number | false;\n } = {},\n ): Promise<void> {\n const { timeout = 30000 } = opts;\n\n if (this.closeConflictServer) {\n await killRunningServer(this.port, this.host);\n }\n\n return new Promise((resolve, reject) => {\n if (this.listeningTimerFlag) {\n return reject(new Error('already listening'));\n }\n this.listeningTimerFlag = true;\n\n this.listeningTimeoutId = timeout\n ? setTimeout(() => {\n reject(\n new Error(\n `no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`,\n ),\n );\n }, timeout)\n : null;\n\n this.connectionTipTimer =\n !timeout || timeout > 3000\n ? setTimeout(() => {\n logMsg('waiting for bridge to connect...');\n }, 2000)\n : null;\n\n // Create HTTP server — no /bridge-auth endpoint needed since we use\n // pure Origin checking in the Socket.IO middleware below.\n const httpServer = createServer(\n (_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(404);\n res.end();\n },\n );\n\n // Set up HTTP server event listeners FIRST\n httpServer.once('listening', () => {\n resolve();\n });\n\n httpServer.once('error', (err: Error) => {\n reject(new Error(`Bridge Listening Error: ${err.message}`));\n });\n\n // Start listening BEFORE creating Socket.IO Server\n // When host is 127.0.0.1 (default), don't specify host to listen on all local interfaces (IPv4 + IPv6)\n // This ensures localhost resolves correctly in both IPv4 and IPv6 environments\n if (this.host === '127.0.0.1') {\n httpServer.listen(this.port);\n } else {\n httpServer.listen(this.port, this.host);\n }\n\n // Now create Socket.IO Server attached to the already-listening HTTP server\n this.io = new Server(httpServer, {\n maxHttpBufferSize: 100 * 1024 * 1024, // 100MB\n // Increase pingTimeout to tolerate Chrome MV3 Service Worker suspension.\n // The SW keepalive alarm fires every ~24s; default pingTimeout (20s) may\n // be too short if the SW is suspended between alarm pings.\n pingTimeout: 60000,\n });\n\n this.io.use((socket, next) => {\n // Reject connections from untrusted browser origins to prevent\n // Cross-Site WebSocket Hijacking (CSWSH) and unauthorized kill signals.\n // The browser automatically sets the Origin header on WebSocket\n // handshakes, and JavaScript cannot override it.\n const origin = socket.handshake.headers.origin;\n if (!isTrustedOrigin(origin)) {\n debug('connection rejected: untrusted origin=%s', origin);\n return next(new Error('origin not allowed'));\n }\n\n // Allow new connections to replace old ones (reconnection after\n // extension Stop→Start). If the old socket is already disconnected\n // or unresponsive, accept the new connection immediately.\n if (\n socket.handshake.url.includes(BridgeSignalKill) === false &&\n this.socket?.connected\n ) {\n return next(new Error('server already connected by another client'));\n }\n next();\n });\n\n this.io.on('connection', (socket) => {\n // check the connection url\n const url = socket.handshake.url;\n if (url.includes(BridgeSignalKill)) {\n console.warn('kill signal received, closing bridge server');\n return this.close();\n }\n\n this.connectionLost = false;\n this.connectionLostReason = '';\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.listeningTimeoutId = null;\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n this.connectionTipTimer = null;\n if (this.socket?.connected) {\n socket.emit(BridgeEvent.Refused);\n socket.disconnect();\n logMsg(\n 'refused new connection: server already connected by another client',\n );\n return;\n }\n\n // Clean up stale old socket if it exists but is no longer connected\n if (this.socket) {\n try {\n this.socket.disconnect();\n } catch (e) {\n logMsg(`failed to disconnect stale socket: ${e}`);\n }\n this.socket = null;\n }\n\n try {\n logMsg('one client connected');\n this.socket = socket;\n\n const clientVersion = socket.handshake.query.version;\n logMsg(\n `Bridge connected, cli-side version v${__VERSION__}, browser-side version v${clientVersion}`,\n );\n\n socket.on(BridgeEvent.CallResponse, (params: BridgeCallResponse) => {\n const id = params.id;\n const response = params.response;\n const error = params.error;\n\n this.triggerCallResponseCallback(id, error, response);\n });\n\n socket.on('disconnect', (reason: string) => {\n this.connectionLost = true;\n this.connectionLostReason = reason;\n this.socket = null;\n\n // flush all pending calls as error and clean up completed calls\n for (const id in this.calls) {\n const call = this.calls[id];\n\n if (!call.responseTime) {\n const errorMessage = this.connectionLostErrorMsg();\n this.triggerCallResponseCallback(\n id,\n new Error(errorMessage),\n null,\n );\n }\n }\n\n // Clean up completed calls to prevent memory leaks in long-running sessions\n for (const id in this.calls) {\n if (this.calls[id].responseTime) {\n delete this.calls[id];\n }\n }\n\n this.onDisconnect?.(reason);\n });\n\n setTimeout(() => {\n this.onConnect?.();\n\n const payload = {\n version: __VERSION__,\n } as BridgeConnectedEventPayload;\n socket.emit(BridgeEvent.Connected, payload);\n Promise.resolve().then(() => {\n for (const id in this.calls) {\n if (this.calls[id].callTime === 0) {\n this.emitCall(id);\n }\n }\n });\n }, 0);\n } catch (e) {\n logMsg(`failed to handle connection event: ${e}`);\n }\n });\n\n this.io.on('close', () => {\n this.close();\n });\n });\n }\n\n private connectionLostErrorMsg = () => {\n return `Connection lost, reason: ${this.connectionLostReason}`;\n };\n\n private async triggerCallResponseCallback(\n id: string | number,\n error: Error | string | null,\n response: any,\n ) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n // Ensure error is always an Error object (bridge client may send strings)\n if (error) {\n call.error =\n error instanceof Error\n ? error\n : new Error(typeof error === 'string' ? error : String(error));\n } else {\n call.error = undefined;\n }\n call.response = response;\n call.responseTime = Date.now();\n\n call.callback(call.error, response);\n }\n\n private async emitCall(id: string) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n\n if (this.connectionLost) {\n const message = `Connection lost, reason: ${this.connectionLostReason}`;\n call.callback(new Error(message), null);\n return;\n }\n\n if (this.socket) {\n this.socket.emit(BridgeEvent.Call, {\n id,\n method: call.method,\n args: call.args,\n });\n call.callTime = Date.now();\n }\n }\n\n async call<T = any>(\n method: string,\n args: any[],\n timeout = BridgeCallTimeout,\n ): Promise<T> {\n const id = `${this.callId++}`;\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);\n this.calls[id].error = new Error(\n `Bridge call timeout after ${timeout}ms: ${method}`,\n );\n reject(this.calls[id].error);\n }, timeout);\n\n this.calls[id] = {\n method,\n args,\n response: null,\n callTime: 0,\n responseTime: 0,\n callback: (error: Error | undefined, response: any) => {\n clearTimeout(timeoutId);\n if (error) {\n reject(error);\n } else {\n resolve(response);\n }\n },\n };\n\n this.emitCall(id);\n });\n }\n\n // do NOT restart after close\n async close() {\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n const closeProcess = this.io?.close();\n this.io = null;\n\n return closeProcess;\n }\n}\n"],"names":["debug","getDebug","isTrustedOrigin","origin","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","_req","res","err","Server","socket","next","url","console","clearTimeout","BridgeEvent","clientVersion","params","id","response","error","reason","call","errorMessage","payload","__VERSION__","String","undefined","Date","message","method","args","BridgeCallTimeout","timeoutId","closeProcess","onConnect","onDisconnect","closeConflictServer"],"mappings":"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"}
1
+ {"version":3,"file":"bridge-mode/io-server.mjs","sources":["../../../src/bridge-mode/io-server.ts"],"sourcesContent":["import {\n type IncomingMessage,\n type ServerResponse,\n createServer,\n} from 'node:http';\nimport { sleep } from '@midscene/core/utils';\nimport { getDebug } from '@midscene/shared/logger';\nimport { logMsg } from '@midscene/shared/utils';\nimport { Server, type Socket as ServerSocket } from 'socket.io';\nimport { io as ClientIO } from 'socket.io-client';\n\nimport {\n type BridgeCall,\n type BridgeCallResponse,\n BridgeCallTimeout,\n type BridgeConnectedEventPayload,\n BridgeErrorCodeNoClientConnected,\n BridgeEvent,\n BridgeSignalKill,\n DefaultBridgeServerPort,\n} from './common';\n\nconst debug = getDebug('web:bridge:io-server');\n\ndeclare const __VERSION__: string;\n\n/**\n * Returns true if the given Origin header value is allowed to connect\n * to the bridge server. Trusted origins are:\n * - No Origin header (local Node.js processes like killRunningServer)\n * - chrome-extension:// origins (the Midscene Chrome extension)\n *\n * Browser pages (https://evil.com, etc.) are rejected to prevent\n * Cross-Site WebSocket Hijacking (CSWSH) attacks.\n */\nfunction isTrustedOrigin(origin: string | undefined): boolean {\n if (!origin) return true;\n if (origin.startsWith('chrome-extension://')) return true;\n return false;\n}\n\nexport const killRunningServer = async (port?: number, host = 'localhost') => {\n try {\n const client = ClientIO(`ws://${host}:${port || DefaultBridgeServerPort}`, {\n query: {\n [BridgeSignalKill]: 1,\n },\n });\n await sleep(300);\n await client.close();\n } catch (e) {\n debug('failed to kill port: %O', e);\n }\n};\n\n// ws server, this is where the request is sent\nexport class BridgeServer {\n private callId = 0;\n private io: Server | null = null;\n private socket: ServerSocket | null = null;\n private listeningTimeoutId: NodeJS.Timeout | null = null;\n private listeningTimerFlag = false;\n private connectionTipTimer: NodeJS.Timeout | null = null;\n public calls: Record<string, BridgeCall> = {};\n\n private connectionLost = false;\n private connectionLostReason = '';\n\n constructor(\n public host: string,\n public port: number,\n public onConnect?: () => void,\n public onDisconnect?: (reason: string) => void,\n public closeConflictServer?: boolean,\n ) {}\n\n async listen(\n opts: {\n timeout?: number | false;\n } = {},\n ): Promise<void> {\n const { timeout = 30000 } = opts;\n\n if (this.closeConflictServer) {\n await killRunningServer(this.port, this.host);\n }\n\n return new Promise((resolve, reject) => {\n if (this.listeningTimerFlag) {\n return reject(new Error('already listening'));\n }\n this.listeningTimerFlag = true;\n\n this.listeningTimeoutId = timeout\n ? setTimeout(() => {\n reject(\n new Error(\n `no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`,\n ),\n );\n }, timeout)\n : null;\n\n this.connectionTipTimer =\n !timeout || timeout > 3000\n ? setTimeout(() => {\n logMsg('waiting for bridge to connect...');\n }, 2000)\n : null;\n\n // Create HTTP server — no /bridge-auth endpoint needed since we use\n // pure Origin checking in the Socket.IO middleware below.\n const httpServer = createServer(\n (_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(404);\n res.end();\n },\n );\n\n // Set up HTTP server event listeners FIRST\n httpServer.once('listening', () => {\n resolve();\n });\n\n httpServer.once('error', (err: Error) => {\n reject(new Error(`Bridge Listening Error: ${err.message}`));\n });\n\n // Start listening BEFORE creating Socket.IO Server\n // When host is 127.0.0.1 (default), don't specify host to listen on all local interfaces (IPv4 + IPv6)\n // This ensures localhost resolves correctly in both IPv4 and IPv6 environments\n if (this.host === '127.0.0.1') {\n httpServer.listen(this.port);\n } else {\n httpServer.listen(this.port, this.host);\n }\n\n // Now create Socket.IO Server attached to the already-listening HTTP server\n this.io = new Server(httpServer, {\n maxHttpBufferSize: 100 * 1024 * 1024, // 100MB\n // Increase pingTimeout to tolerate Chrome MV3 Service Worker suspension.\n // The SW keepalive alarm fires every ~24s; default pingTimeout (20s) may\n // be too short if the SW is suspended between alarm pings.\n pingTimeout: 60000,\n });\n\n this.io.use((socket, next) => {\n // Reject connections from untrusted browser origins to prevent\n // Cross-Site WebSocket Hijacking (CSWSH) and unauthorized kill signals.\n // The browser automatically sets the Origin header on WebSocket\n // handshakes, and JavaScript cannot override it.\n const origin = socket.handshake.headers.origin;\n if (!isTrustedOrigin(origin)) {\n debug('connection rejected: untrusted origin=%s', origin);\n return next(new Error('origin not allowed'));\n }\n\n // Allow new connections to replace old ones (reconnection after\n // extension Stop→Start). If the old socket is already disconnected\n // or unresponsive, accept the new connection immediately.\n if (\n socket.handshake.url.includes(BridgeSignalKill) === false &&\n this.socket?.connected\n ) {\n return next(new Error('server already connected by another client'));\n }\n next();\n });\n\n this.io.on('connection', (socket) => {\n // check the connection url\n const url = socket.handshake.url;\n if (url.includes(BridgeSignalKill)) {\n console.warn('kill signal received, closing bridge server');\n return this.close();\n }\n\n this.connectionLost = false;\n this.connectionLostReason = '';\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.listeningTimeoutId = null;\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n this.connectionTipTimer = null;\n if (this.socket?.connected) {\n socket.emit(BridgeEvent.Refused);\n socket.disconnect();\n logMsg(\n 'refused new connection: server already connected by another client',\n );\n return;\n }\n\n // Clean up stale old socket if it exists but is no longer connected\n if (this.socket) {\n try {\n this.socket.disconnect();\n } catch (e) {\n logMsg(`failed to disconnect stale socket: ${e}`);\n }\n this.socket = null;\n }\n\n try {\n logMsg('one client connected');\n this.socket = socket;\n\n const clientVersion = socket.handshake.query.version;\n logMsg(\n `Bridge connected, cli-side version v${__VERSION__}, browser-side version v${clientVersion}`,\n );\n\n socket.on(BridgeEvent.CallResponse, (params: BridgeCallResponse) => {\n const id = params.id;\n const response = params.response;\n const error = params.error;\n\n this.triggerCallResponseCallback(id, error, response);\n });\n\n socket.on('disconnect', (reason: string) => {\n this.connectionLost = true;\n this.connectionLostReason = reason;\n this.socket = null;\n\n // flush all pending calls as error and clean up completed calls\n for (const id in this.calls) {\n const call = this.calls[id];\n\n if (!call.responseTime) {\n const errorMessage = this.connectionLostErrorMsg();\n this.triggerCallResponseCallback(\n id,\n new Error(errorMessage),\n null,\n );\n }\n }\n\n // Clean up completed calls to prevent memory leaks in long-running sessions\n for (const id in this.calls) {\n if (this.calls[id].responseTime) {\n delete this.calls[id];\n }\n }\n\n this.onDisconnect?.(reason);\n });\n\n setTimeout(() => {\n this.onConnect?.();\n\n const payload = {\n version: __VERSION__,\n } as BridgeConnectedEventPayload;\n socket.emit(BridgeEvent.Connected, payload);\n Promise.resolve().then(() => {\n for (const id in this.calls) {\n if (this.calls[id].callTime === 0) {\n this.emitCall(id);\n }\n }\n });\n }, 0);\n } catch (e) {\n logMsg(`failed to handle connection event: ${e}`);\n }\n });\n\n this.io.on('close', () => {\n this.close();\n });\n });\n }\n\n private connectionLostErrorMsg = () => {\n return `Connection lost, reason: ${this.connectionLostReason}`;\n };\n\n private async triggerCallResponseCallback(\n id: string | number,\n error: Error | string | null,\n response: any,\n ) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n // Ensure error is always an Error object (bridge client may send strings)\n if (error) {\n call.error =\n error instanceof Error\n ? error\n : new Error(typeof error === 'string' ? error : String(error));\n } else {\n call.error = undefined;\n }\n call.response = response;\n call.responseTime = Date.now();\n\n call.callback(call.error, response);\n }\n\n private async emitCall(id: string) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n\n if (this.connectionLost) {\n const message = `Connection lost, reason: ${this.connectionLostReason}`;\n call.callback(new Error(message), null);\n return;\n }\n\n if (this.socket) {\n this.socket.emit(BridgeEvent.Call, {\n id,\n method: call.method,\n args: call.args,\n });\n call.callTime = Date.now();\n }\n }\n\n async call<T = any>(\n method: string,\n args: any[],\n timeout = BridgeCallTimeout,\n ): Promise<T> {\n const id = `${this.callId++}`;\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);\n this.calls[id].error = new Error(\n `Bridge call timeout after ${timeout}ms: ${method}`,\n );\n reject(this.calls[id].error);\n }, timeout);\n\n this.calls[id] = {\n method,\n args,\n response: null,\n callTime: 0,\n responseTime: 0,\n callback: (error: Error | undefined, response: any) => {\n clearTimeout(timeoutId);\n if (error) {\n reject(error);\n } else {\n resolve(response);\n }\n },\n };\n\n this.emitCall(id);\n });\n }\n\n // do NOT restart after close\n async close() {\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n const closeProcess = this.io?.close();\n this.io = null;\n\n return closeProcess;\n }\n}\n"],"names":["debug","getDebug","isTrustedOrigin","origin","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","_req","res","err","Server","socket","next","url","console","clearTimeout","BridgeEvent","clientVersion","params","id","response","error","reason","call","errorMessage","payload","__VERSION__","String","undefined","Date","message","method","args","BridgeCallTimeout","timeoutId","closeProcess","onConnect","onDisconnect","closeConflictServer"],"mappings":"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"}
@@ -100,7 +100,7 @@ class ExtensionBridgePageBrowserSide extends page {
100
100
  throw new Error('Connection denied by user');
101
101
  }
102
102
  }
103
- this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.10.9-beta-20260804015945.0`, 'log');
103
+ this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.10.9`, 'log');
104
104
  }
105
105
  async connect() {
106
106
  return await this.setupBridgeClient();
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mode/page-browser-side.mjs","sources":["../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["import { assert } from '@midscene/shared/utils';\nimport ChromeExtensionProxyPage from '../chrome-extension/page';\nimport type {\n ChromePageDestroyOptions,\n KeyboardAction,\n MouseAction,\n} from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n} from './common';\nimport { BridgeClient } from './io-client';\n\ndeclare const __VERSION__: string;\n\nconst NEW_TAB_LOAD_TIMEOUT_MS = 30_000;\n\nfunction isBlankUrl(url: string | undefined): boolean {\n if (!url) return true;\n return url === 'about:blank' || url.startsWith('chrome://newtab');\n}\n\n// Wait until the freshly created tab has navigated away from about:blank\n// and reached `status === 'complete'`. Resolves on timeout instead of\n// throwing so callers degrade to the existing lazy-attach behavior.\nfunction waitForTabNavigationComplete(\n tabId: number,\n targetUrl: string,\n timeoutMs = NEW_TAB_LOAD_TIMEOUT_MS,\n): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = () => {\n if (settled) return;\n settled = true;\n try {\n chrome.tabs.onUpdated.removeListener(onUpdated);\n } catch {}\n clearTimeout(timer);\n resolve();\n };\n\n const isReady = (tab: chrome.tabs.Tab | undefined): boolean => {\n if (!tab) return false;\n if (tab.status !== 'complete') return false;\n const currentUrl = tab.url || tab.pendingUrl || '';\n // Skip the initial about:blank \"complete\" that fires before\n // the target URL navigation kicks in.\n if (isBlankUrl(currentUrl) && !isBlankUrl(targetUrl)) return false;\n return true;\n };\n\n const onUpdated = (\n id: number,\n _info: chrome.tabs.TabChangeInfo,\n tab: chrome.tabs.Tab,\n ) => {\n if (id !== tabId) return;\n if (isReady(tab)) finish();\n };\n\n chrome.tabs.onUpdated.addListener(onUpdated);\n const timer = setTimeout(finish, timeoutMs);\n\n // Handle the race where the tab already finished loading before\n // we registered the listener.\n chrome.tabs\n .get(tabId)\n .then((tab) => {\n if (isReady(tab)) finish();\n })\n .catch(() => {});\n });\n}\n\nexport class ExtensionBridgePageBrowserSide extends ChromeExtensionProxyPage {\n public bridgeClient: BridgeClient | null = null;\n\n private destroyOptions?: ChromePageDestroyOptions;\n\n private newlyCreatedTabIds: number[] = [];\n\n // Connection confirmation state\n private confirmationPromise: Promise<boolean> | null = null;\n\n constructor(\n public serverEndpoint?: string,\n public onDisconnect: () => void = () => {},\n public onLogMessage: (\n message: string,\n type: 'log' | 'status',\n ) => void = () => {},\n forceSameTabNavigation = true,\n public onConnectionRequest?: () => Promise<boolean>,\n ) {\n super(forceSameTabNavigation);\n }\n\n private async setupBridgeClient() {\n const endpoint =\n this.serverEndpoint || `ws://localhost:${DefaultBridgeServerPort}`;\n\n // Create confirmation gate BEFORE establishing connection,\n // so that any calls received immediately after connection are blocked\n // until user confirms. This prevents a race condition where server-side\n // queued calls bypass the confirmation dialog.\n let resolveConfirmationGate: (allowed: boolean) => void = () => {};\n if (this.onConnectionRequest) {\n this.confirmationPromise = new Promise<boolean>((resolve) => {\n resolveConfirmationGate = resolve;\n });\n }\n\n this.bridgeClient = new BridgeClient(\n endpoint,\n async (method, args: any[]) => {\n // Wait for user confirmation before processing any commands\n if (this.confirmationPromise) {\n const allowed = await this.confirmationPromise;\n if (!allowed) {\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(`bridge call from cli side: ${method}`, 'log');\n if (method === BridgeEvent.ConnectNewTabWithUrl) {\n return this.connectNewTabWithUrl.apply(\n this,\n args as unknown as [string],\n );\n }\n\n if (method === BridgeEvent.GetBrowserTabList) {\n return this.getBrowserTabList.apply(this, args as any);\n }\n\n if (method === BridgeEvent.SetActiveTabId) {\n return this.setActiveTabId.apply(this, args as any);\n }\n\n if (method === BridgeEvent.ConnectCurrentTab) {\n return this.connectCurrentTab.apply(this, args as any);\n }\n\n if (method === BridgeEvent.UpdateAgentStatus) {\n return this.onLogMessage(args[0] as string, 'status');\n }\n\n const tabId = await this.getActiveTabId();\n if (!tabId || tabId === 0) {\n throw new Error('no tab is connected');\n }\n\n // this.onLogMessage(`calling method: ${method}`);\n\n if (method.startsWith(MouseEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof MouseAction;\n if (actionName === 'drag') {\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n\n if (method.startsWith(KeyboardEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof KeyboardAction;\n if (actionName === 'press') {\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n\n // Generic method lookup. Methods like `setWaterFlowAnimationEnabled`\n // and `getWaterFlowAnimationEnabled` are inherited from\n // ChromeExtensionProxyPage and found via `this[method]`.\n if (!this[method as keyof ChromeExtensionProxyPage]) {\n this.onLogMessage(`method not found: ${method}`, 'log');\n return undefined;\n }\n\n try {\n // @ts-expect-error\n const result = await this[method as keyof ChromeExtensionProxyPage](\n ...args,\n );\n return result;\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Unknown error';\n this.onLogMessage(\n `Error calling method: ${method}, ${errorMessage}`,\n 'log',\n );\n throw new Error(errorMessage, { cause: e });\n }\n },\n // on disconnect\n () => {\n return this.destroy();\n },\n );\n await this.bridgeClient.connect();\n\n // Show confirmation dialog after connection is established\n if (this.onConnectionRequest) {\n this.onLogMessage('Waiting for user confirmation...', 'log');\n const allowed = await this.onConnectionRequest();\n resolveConfirmationGate(allowed);\n this.confirmationPromise = null;\n\n if (!allowed) {\n this.onLogMessage('Connection denied by user', 'log');\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(\n `Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v${__VERSION__}`,\n 'log',\n );\n }\n\n public async connect() {\n return await this.setupBridgeClient();\n }\n\n public async connectNewTabWithUrl(\n url: string,\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n // Otherwise the animation gets injected during the connection flow\n // before the CLI side's setWaterFlowAnimationEnabled(false) arrives.\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tab = await chrome.tabs.create({ url });\n const tabId = tab.id;\n assert(tabId, 'failed to get tabId after creating a new tab');\n\n // new tab\n this.onLogMessage(`Creating new tab: ${url}`, 'log');\n this.newlyCreatedTabIds.push(tabId);\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n // chrome.tabs.create returns immediately with an about:blank target,\n // then navigates to `url`. If we attach the debugger during that\n // cross-origin transition Site Isolation will detach it again, leaving\n // the first CDP command to fail with \"Debugger is not attached to the\n // tab\". Wait for navigation to settle so the lazy attach lands on a\n // stable target.\n await waitForTabNavigationComplete(tabId, url);\n\n await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tabs = await chrome.tabs.query({ active: true, currentWindow: true });\n const tabId = tabs[0]?.id;\n assert(tabId, 'failed to get tabId');\n\n this.onLogMessage(`Connected to current tab: ${tabs[0]?.url}`, 'log');\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async setDestroyOptions(options: ChromePageDestroyOptions) {\n this.destroyOptions = options;\n }\n\n async destroy() {\n if (this.destroyOptions?.closeTab && this.newlyCreatedTabIds.length > 0) {\n this.onLogMessage('Closing all newly created tabs by bridge...', 'log');\n for (const tabId of this.newlyCreatedTabIds) {\n await chrome.tabs.remove(tabId);\n }\n this.newlyCreatedTabIds = [];\n }\n\n await super.destroy();\n\n if (this.bridgeClient) {\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n this.onDisconnect();\n }\n }\n}\n"],"names":["NEW_TAB_LOAD_TIMEOUT_MS","isBlankUrl","url","waitForTabNavigationComplete","tabId","targetUrl","timeoutMs","Promise","resolve","settled","finish","chrome","onUpdated","clearTimeout","timer","isReady","tab","currentUrl","id","_info","setTimeout","ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","BridgeClient","method","args","allowed","Error","BridgeEvent","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","options","undefined","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":"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"}
1
+ {"version":3,"file":"bridge-mode/page-browser-side.mjs","sources":["../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["import { assert } from '@midscene/shared/utils';\nimport ChromeExtensionProxyPage from '../chrome-extension/page';\nimport type {\n ChromePageDestroyOptions,\n KeyboardAction,\n MouseAction,\n} from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n} from './common';\nimport { BridgeClient } from './io-client';\n\ndeclare const __VERSION__: string;\n\nconst NEW_TAB_LOAD_TIMEOUT_MS = 30_000;\n\nfunction isBlankUrl(url: string | undefined): boolean {\n if (!url) return true;\n return url === 'about:blank' || url.startsWith('chrome://newtab');\n}\n\n// Wait until the freshly created tab has navigated away from about:blank\n// and reached `status === 'complete'`. Resolves on timeout instead of\n// throwing so callers degrade to the existing lazy-attach behavior.\nfunction waitForTabNavigationComplete(\n tabId: number,\n targetUrl: string,\n timeoutMs = NEW_TAB_LOAD_TIMEOUT_MS,\n): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = () => {\n if (settled) return;\n settled = true;\n try {\n chrome.tabs.onUpdated.removeListener(onUpdated);\n } catch {}\n clearTimeout(timer);\n resolve();\n };\n\n const isReady = (tab: chrome.tabs.Tab | undefined): boolean => {\n if (!tab) return false;\n if (tab.status !== 'complete') return false;\n const currentUrl = tab.url || tab.pendingUrl || '';\n // Skip the initial about:blank \"complete\" that fires before\n // the target URL navigation kicks in.\n if (isBlankUrl(currentUrl) && !isBlankUrl(targetUrl)) return false;\n return true;\n };\n\n const onUpdated = (\n id: number,\n _info: chrome.tabs.TabChangeInfo,\n tab: chrome.tabs.Tab,\n ) => {\n if (id !== tabId) return;\n if (isReady(tab)) finish();\n };\n\n chrome.tabs.onUpdated.addListener(onUpdated);\n const timer = setTimeout(finish, timeoutMs);\n\n // Handle the race where the tab already finished loading before\n // we registered the listener.\n chrome.tabs\n .get(tabId)\n .then((tab) => {\n if (isReady(tab)) finish();\n })\n .catch(() => {});\n });\n}\n\nexport class ExtensionBridgePageBrowserSide extends ChromeExtensionProxyPage {\n public bridgeClient: BridgeClient | null = null;\n\n private destroyOptions?: ChromePageDestroyOptions;\n\n private newlyCreatedTabIds: number[] = [];\n\n // Connection confirmation state\n private confirmationPromise: Promise<boolean> | null = null;\n\n constructor(\n public serverEndpoint?: string,\n public onDisconnect: () => void = () => {},\n public onLogMessage: (\n message: string,\n type: 'log' | 'status',\n ) => void = () => {},\n forceSameTabNavigation = true,\n public onConnectionRequest?: () => Promise<boolean>,\n ) {\n super(forceSameTabNavigation);\n }\n\n private async setupBridgeClient() {\n const endpoint =\n this.serverEndpoint || `ws://localhost:${DefaultBridgeServerPort}`;\n\n // Create confirmation gate BEFORE establishing connection,\n // so that any calls received immediately after connection are blocked\n // until user confirms. This prevents a race condition where server-side\n // queued calls bypass the confirmation dialog.\n let resolveConfirmationGate: (allowed: boolean) => void = () => {};\n if (this.onConnectionRequest) {\n this.confirmationPromise = new Promise<boolean>((resolve) => {\n resolveConfirmationGate = resolve;\n });\n }\n\n this.bridgeClient = new BridgeClient(\n endpoint,\n async (method, args: any[]) => {\n // Wait for user confirmation before processing any commands\n if (this.confirmationPromise) {\n const allowed = await this.confirmationPromise;\n if (!allowed) {\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(`bridge call from cli side: ${method}`, 'log');\n if (method === BridgeEvent.ConnectNewTabWithUrl) {\n return this.connectNewTabWithUrl.apply(\n this,\n args as unknown as [string],\n );\n }\n\n if (method === BridgeEvent.GetBrowserTabList) {\n return this.getBrowserTabList.apply(this, args as any);\n }\n\n if (method === BridgeEvent.SetActiveTabId) {\n return this.setActiveTabId.apply(this, args as any);\n }\n\n if (method === BridgeEvent.ConnectCurrentTab) {\n return this.connectCurrentTab.apply(this, args as any);\n }\n\n if (method === BridgeEvent.UpdateAgentStatus) {\n return this.onLogMessage(args[0] as string, 'status');\n }\n\n const tabId = await this.getActiveTabId();\n if (!tabId || tabId === 0) {\n throw new Error('no tab is connected');\n }\n\n // this.onLogMessage(`calling method: ${method}`);\n\n if (method.startsWith(MouseEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof MouseAction;\n if (actionName === 'drag') {\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n\n if (method.startsWith(KeyboardEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof KeyboardAction;\n if (actionName === 'press') {\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n\n // Generic method lookup. Methods like `setWaterFlowAnimationEnabled`\n // and `getWaterFlowAnimationEnabled` are inherited from\n // ChromeExtensionProxyPage and found via `this[method]`.\n if (!this[method as keyof ChromeExtensionProxyPage]) {\n this.onLogMessage(`method not found: ${method}`, 'log');\n return undefined;\n }\n\n try {\n // @ts-expect-error\n const result = await this[method as keyof ChromeExtensionProxyPage](\n ...args,\n );\n return result;\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Unknown error';\n this.onLogMessage(\n `Error calling method: ${method}, ${errorMessage}`,\n 'log',\n );\n throw new Error(errorMessage, { cause: e });\n }\n },\n // on disconnect\n () => {\n return this.destroy();\n },\n );\n await this.bridgeClient.connect();\n\n // Show confirmation dialog after connection is established\n if (this.onConnectionRequest) {\n this.onLogMessage('Waiting for user confirmation...', 'log');\n const allowed = await this.onConnectionRequest();\n resolveConfirmationGate(allowed);\n this.confirmationPromise = null;\n\n if (!allowed) {\n this.onLogMessage('Connection denied by user', 'log');\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(\n `Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v${__VERSION__}`,\n 'log',\n );\n }\n\n public async connect() {\n return await this.setupBridgeClient();\n }\n\n public async connectNewTabWithUrl(\n url: string,\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n // Otherwise the animation gets injected during the connection flow\n // before the CLI side's setWaterFlowAnimationEnabled(false) arrives.\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tab = await chrome.tabs.create({ url });\n const tabId = tab.id;\n assert(tabId, 'failed to get tabId after creating a new tab');\n\n // new tab\n this.onLogMessage(`Creating new tab: ${url}`, 'log');\n this.newlyCreatedTabIds.push(tabId);\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n // chrome.tabs.create returns immediately with an about:blank target,\n // then navigates to `url`. If we attach the debugger during that\n // cross-origin transition Site Isolation will detach it again, leaving\n // the first CDP command to fail with \"Debugger is not attached to the\n // tab\". Wait for navigation to settle so the lazy attach lands on a\n // stable target.\n await waitForTabNavigationComplete(tabId, url);\n\n await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tabs = await chrome.tabs.query({ active: true, currentWindow: true });\n const tabId = tabs[0]?.id;\n assert(tabId, 'failed to get tabId');\n\n this.onLogMessage(`Connected to current tab: ${tabs[0]?.url}`, 'log');\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async setDestroyOptions(options: ChromePageDestroyOptions) {\n this.destroyOptions = options;\n }\n\n async destroy() {\n if (this.destroyOptions?.closeTab && this.newlyCreatedTabIds.length > 0) {\n this.onLogMessage('Closing all newly created tabs by bridge...', 'log');\n for (const tabId of this.newlyCreatedTabIds) {\n await chrome.tabs.remove(tabId);\n }\n this.newlyCreatedTabIds = [];\n }\n\n await super.destroy();\n\n if (this.bridgeClient) {\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n this.onDisconnect();\n }\n }\n}\n"],"names":["NEW_TAB_LOAD_TIMEOUT_MS","isBlankUrl","url","waitForTabNavigationComplete","tabId","targetUrl","timeoutMs","Promise","resolve","settled","finish","chrome","onUpdated","clearTimeout","timer","isReady","tab","currentUrl","id","_info","setTimeout","ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","BridgeClient","method","args","allowed","Error","BridgeEvent","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","options","undefined","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":"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"}
package/dist/es/cli.mjs CHANGED
@@ -18,7 +18,7 @@ Promise.resolve().then(()=>{
18
18
  return runToolsCLI(tools, 'midscene-web', {
19
19
  stripPrefix: 'web_',
20
20
  argv: parsedOptions.argv,
21
- version: "1.10.9-beta-20260804015945.0",
21
+ version: "1.10.9",
22
22
  extraCommands: createReportCliCommands()
23
23
  });
24
24
  }).catch((e)=>{
@@ -1,7 +1,7 @@
1
1
  import { copyFileSync, cpSync, existsSync, mkdirSync } from "node:fs";
2
- import { dirname, join } from "node:path";
2
+ import { dirname, join, resolve } from "node:path";
3
3
  import { getReportFileName, printReportMsg } from "@midscene/core/agent";
4
- import { ReportMergingTool, isDirectoryModeReport } from "@midscene/core/report";
4
+ import { ReportMergingTool, isDirectoryModeReport, removeReportArtifact } from "@midscene/core/report";
5
5
  import { getMidsceneRunSubDir } from "@midscene/shared/common";
6
6
  import { logMsg, replaceIllegalPathCharsAndSpace } from "@midscene/shared/utils";
7
7
  function _define_property(obj, key, value) {
@@ -49,7 +49,8 @@ class MidsceneReporter {
49
49
  recursive: true
50
50
  });
51
51
  }
52
- copyReport(reportFilePath, targetPath) {
52
+ finalizeSingleReport(reportFilePath, targetPath) {
53
+ if (resolve(reportFilePath) === resolve(targetPath)) return;
53
54
  if (isDirectoryModeReport(reportFilePath)) {
54
55
  const targetDir = dirname(targetPath);
55
56
  mkdirSync(targetDir, {
@@ -59,12 +60,23 @@ class MidsceneReporter {
59
60
  recursive: true,
60
61
  force: true
61
62
  });
62
- return;
63
+ } else {
64
+ mkdirSync(dirname(targetPath), {
65
+ recursive: true
66
+ });
67
+ copyFileSync(reportFilePath, targetPath);
63
68
  }
64
- mkdirSync(dirname(targetPath), {
65
- recursive: true
69
+ try {
70
+ removeReportArtifact(reportFilePath);
71
+ } catch (error) {
72
+ logMsg(`Error deleting report ${reportFilePath}: ${error}`);
73
+ }
74
+ }
75
+ mergeOwnedReports(tool, targetName) {
76
+ return tool.mergeReports(targetName, {
77
+ overwrite: true,
78
+ rmOriginalReports: true
66
79
  });
67
- copyFileSync(reportFilePath, targetPath);
68
80
  }
69
81
  collectReportInfo(test, result) {
70
82
  const reportAnnotations = test.annotations.filter((annotation)=>'MIDSCENE_DUMP_ANNOTATION' === annotation.type && annotation.description);
@@ -109,19 +121,15 @@ class MidsceneReporter {
109
121
  if (!firstReport) return;
110
122
  if (firstReport.reportFilePath) {
111
123
  const targetPath = this.getReportPath();
112
- this.copyReport(firstReport.reportFilePath, targetPath);
124
+ this.finalizeSingleReport(firstReport.reportFilePath, targetPath);
113
125
  printReportMsg(targetPath);
114
126
  return;
115
127
  }
116
- const mergedReportPath = tool.mergeReports(targetName, {
117
- overwrite: true
118
- });
128
+ const mergedReportPath = this.mergeOwnedReports(tool, targetName);
119
129
  if (mergedReportPath) printReportMsg(mergedReportPath);
120
130
  return;
121
131
  }
122
- const mergedReportPath = tool.mergeReports(targetName, {
123
- overwrite: true
124
- });
132
+ const mergedReportPath = this.mergeOwnedReports(tool, targetName);
125
133
  if (mergedReportPath) printReportMsg(mergedReportPath);
126
134
  }
127
135
  finalizeSeparateReports() {
@@ -132,23 +140,19 @@ class MidsceneReporter {
132
140
  const firstReport = entry.reports[0];
133
141
  if (firstReport.reportFilePath) {
134
142
  const targetPath = this.getReportPath(entry.testTitle);
135
- this.copyReport(firstReport.reportFilePath, targetPath);
143
+ this.finalizeSingleReport(firstReport.reportFilePath, targetPath);
136
144
  printReportMsg(targetPath);
137
145
  continue;
138
146
  }
139
147
  const tool = new ReportMergingTool();
140
148
  tool.append(firstReport);
141
- const reportPath = tool.mergeReports(targetName, {
142
- overwrite: true
143
- });
149
+ const reportPath = this.mergeOwnedReports(tool, targetName);
144
150
  if (reportPath) printReportMsg(reportPath);
145
151
  continue;
146
152
  }
147
153
  const tool = new ReportMergingTool();
148
154
  for (const report of entry.reports)tool.append(report);
149
- const reportPath = tool.mergeReports(targetName, {
150
- overwrite: true
151
- });
155
+ const reportPath = this.mergeOwnedReports(tool, targetName);
152
156
  if (reportPath) printReportMsg(reportPath);
153
157
  }
154
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"playwright/reporter/index.mjs","sources":["../../../../src/playwright/reporter/index.ts"],"sourcesContent":["import { copyFileSync, cpSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type { ReportFileWithAttributes, TestStatus } from '@midscene/core';\nimport { getReportFileName, printReportMsg } from '@midscene/core/agent';\nimport {\n ReportMergingTool,\n isDirectoryModeReport,\n} from '@midscene/core/report';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n logMsg,\n replaceIllegalPathCharsAndSpace,\n} from '@midscene/shared/utils';\nimport type {\n FullConfig,\n Reporter,\n Suite,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\n\ninterface MidsceneReporterOptions {\n type?: 'merged' | 'separate';\n outputFormat?: 'single-html' | 'html-and-external-assets';\n}\n\nclass MidsceneReporter implements Reporter {\n private mergedFilename?: string;\n private testTitleToFilename = new Map<string, string>();\n private reportsByTestId = new Map<\n string,\n {\n testTitle: string;\n reports: ReportFileWithAttributes[];\n }\n >();\n mode?: 'merged' | 'separate';\n outputFormat: 'single-html' | 'html-and-external-assets';\n private hasMultipleProjects = false;\n\n constructor(options: MidsceneReporterOptions = {}) {\n this.mode = MidsceneReporter.getMode(options.type ?? 'merged');\n this.outputFormat = options.outputFormat ?? 'single-html';\n }\n\n private static getMode(reporterType: string): 'merged' | 'separate' {\n if (!reporterType) {\n return 'merged';\n }\n if (reporterType !== 'merged' && reporterType !== 'separate') {\n throw new Error(\n `Unknown reporter type in playwright config: ${reporterType}, only support 'merged' or 'separate'`,\n );\n }\n return reporterType;\n }\n\n private getSeparatedFilename(testTitle: string): string {\n if (!this.testTitleToFilename.has(testTitle)) {\n const baseTag = `playwright-${replaceIllegalPathCharsAndSpace(testTitle)}`;\n const generatedFilename = getReportFileName(baseTag);\n this.testTitleToFilename.set(testTitle, generatedFilename);\n }\n return this.testTitleToFilename.get(testTitle)!;\n }\n\n private getReportFilename(testTitle?: string): string {\n if (this.mode === 'merged') {\n if (!this.mergedFilename) {\n this.mergedFilename = getReportFileName('playwright-merged');\n }\n return this.mergedFilename;\n }\n if (this.mode === 'separate') {\n if (!testTitle) throw new Error('testTitle is required in separate mode');\n return this.getSeparatedFilename(testTitle);\n }\n throw new Error(`Unknown mode: ${this.mode}`);\n }\n\n private getReportPath(testTitle?: string): string {\n const fileName = this.getReportFilename(testTitle);\n if (this.outputFormat === 'html-and-external-assets') {\n return join(getMidsceneRunSubDir('report'), fileName, 'index.html');\n }\n return join(getMidsceneRunSubDir('report'), `${fileName}.html`);\n }\n\n private ensureOutputRoot(): void {\n mkdirSync(getMidsceneRunSubDir('report'), { recursive: true });\n }\n\n private copyReport(reportFilePath: string, targetPath: string): void {\n if (isDirectoryModeReport(reportFilePath)) {\n const targetDir = dirname(targetPath);\n mkdirSync(targetDir, { recursive: true });\n cpSync(dirname(reportFilePath), targetDir, {\n recursive: true,\n force: true,\n });\n return;\n }\n\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(reportFilePath, targetPath);\n }\n\n private collectReportInfo(test: TestCase, result: TestResult) {\n const reportAnnotations = test.annotations.filter((annotation) => {\n return (\n annotation.type === 'MIDSCENE_DUMP_ANNOTATION' && annotation.description\n );\n });\n if (reportAnnotations.length === 0 || !this.mode) {\n return;\n }\n\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n const projectName = this.hasMultipleProjects\n ? test.parent?.project()?.name\n : undefined;\n const projectSuffix = projectName ? ` [${projectName}]` : '';\n const testTitle = `${test.title}${projectSuffix}${retry}`;\n const reports = reportAnnotations\n .map((annotation) => annotation.description!)\n .filter((reportFilePath) => {\n if (existsSync(reportFilePath)) {\n return true;\n }\n logMsg(\n `Failed to read Midscene report file: ${reportFilePath}`,\n new Error('Report file does not exist'),\n );\n return false;\n })\n .map((reportFilePath): ReportFileWithAttributes => {\n return {\n reportFilePath,\n reportAttributes: {\n testDuration: result.duration,\n testStatus: result.status as TestStatus,\n testTitle,\n testId,\n testDescription: test.parent?.title || '',\n },\n };\n });\n\n if (reports.length === 0) {\n return;\n }\n\n this.reportsByTestId.set(testId, {\n testTitle,\n reports,\n });\n }\n\n private finalizeMergedReport(): void {\n this.ensureOutputRoot();\n const tool = new ReportMergingTool();\n let reportCount = 0;\n for (const entry of this.reportsByTestId.values()) {\n for (const report of entry.reports) {\n tool.append(report);\n reportCount += 1;\n }\n }\n\n if (reportCount === 0) {\n return;\n }\n\n const targetName = this.getReportFilename();\n if (reportCount === 1) {\n const firstReport = Array.from(this.reportsByTestId.values())[0]\n ?.reports[0];\n if (!firstReport) {\n return;\n }\n if (firstReport.reportFilePath) {\n const targetPath = this.getReportPath();\n this.copyReport(firstReport.reportFilePath, targetPath);\n printReportMsg(targetPath);\n return;\n }\n\n const mergedReportPath = tool.mergeReports(targetName, {\n overwrite: true,\n });\n if (mergedReportPath) {\n printReportMsg(mergedReportPath);\n }\n return;\n }\n\n const mergedReportPath = tool.mergeReports(targetName, {\n overwrite: true,\n });\n if (mergedReportPath) {\n printReportMsg(mergedReportPath);\n }\n }\n\n private finalizeSeparateReports(): void {\n this.ensureOutputRoot();\n for (const entry of this.reportsByTestId.values()) {\n const targetName = this.getReportFilename(entry.testTitle);\n if (entry.reports.length === 1) {\n const firstReport = entry.reports[0];\n if (firstReport.reportFilePath) {\n const targetPath = this.getReportPath(entry.testTitle);\n this.copyReport(firstReport.reportFilePath, targetPath);\n printReportMsg(targetPath);\n continue;\n }\n\n const tool = new ReportMergingTool();\n tool.append(firstReport);\n const reportPath = tool.mergeReports(targetName, {\n overwrite: true,\n });\n if (reportPath) {\n printReportMsg(reportPath);\n }\n continue;\n }\n\n const tool = new ReportMergingTool();\n for (const report of entry.reports) {\n tool.append(report);\n }\n const reportPath = tool.mergeReports(targetName, {\n overwrite: true,\n });\n if (reportPath) {\n printReportMsg(reportPath);\n }\n }\n }\n\n async onBegin(config: FullConfig, _suite: Suite) {\n this.hasMultipleProjects = (config.projects?.length || 0) > 1;\n }\n\n onTestBegin(_test: TestCase, _result: TestResult) {}\n\n onTestEnd(test: TestCase, result: TestResult) {\n this.collectReportInfo(test, result);\n }\n\n async onEnd() {\n if (this.mode === 'merged') {\n this.finalizeMergedReport();\n return;\n }\n\n if (this.mode === 'separate') {\n this.finalizeSeparateReports();\n }\n }\n}\n\nexport default MidsceneReporter;\n"],"names":["MidsceneReporter","reporterType","Error","testTitle","baseTag","replaceIllegalPathCharsAndSpace","generatedFilename","getReportFileName","fileName","join","getMidsceneRunSubDir","mkdirSync","reportFilePath","targetPath","isDirectoryModeReport","targetDir","dirname","cpSync","copyFileSync","test","result","reportAnnotations","annotation","retry","testId","projectName","undefined","projectSuffix","reports","existsSync","logMsg","tool","ReportMergingTool","reportCount","entry","report","targetName","firstReport","Array","printReportMsg","mergedReportPath","reportPath","config","_suite","_test","_result","options","Map"],"mappings":"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"}
1
+ {"version":3,"file":"playwright/reporter/index.mjs","sources":["../../../../src/playwright/reporter/index.ts"],"sourcesContent":["import { copyFileSync, cpSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport type { ReportFileWithAttributes, TestStatus } from '@midscene/core';\nimport { getReportFileName, printReportMsg } from '@midscene/core/agent';\nimport {\n ReportMergingTool,\n isDirectoryModeReport,\n removeReportArtifact,\n} from '@midscene/core/report';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n logMsg,\n replaceIllegalPathCharsAndSpace,\n} from '@midscene/shared/utils';\nimport type {\n FullConfig,\n Reporter,\n Suite,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\n\ninterface MidsceneReporterOptions {\n type?: 'merged' | 'separate';\n outputFormat?: 'single-html' | 'html-and-external-assets';\n}\n\nclass MidsceneReporter implements Reporter {\n private mergedFilename?: string;\n private testTitleToFilename = new Map<string, string>();\n private reportsByTestId = new Map<\n string,\n {\n testTitle: string;\n reports: ReportFileWithAttributes[];\n }\n >();\n mode?: 'merged' | 'separate';\n outputFormat: 'single-html' | 'html-and-external-assets';\n private hasMultipleProjects = false;\n\n constructor(options: MidsceneReporterOptions = {}) {\n this.mode = MidsceneReporter.getMode(options.type ?? 'merged');\n this.outputFormat = options.outputFormat ?? 'single-html';\n }\n\n private static getMode(reporterType: string): 'merged' | 'separate' {\n if (!reporterType) {\n return 'merged';\n }\n if (reporterType !== 'merged' && reporterType !== 'separate') {\n throw new Error(\n `Unknown reporter type in playwright config: ${reporterType}, only support 'merged' or 'separate'`,\n );\n }\n return reporterType;\n }\n\n private getSeparatedFilename(testTitle: string): string {\n if (!this.testTitleToFilename.has(testTitle)) {\n const baseTag = `playwright-${replaceIllegalPathCharsAndSpace(testTitle)}`;\n const generatedFilename = getReportFileName(baseTag);\n this.testTitleToFilename.set(testTitle, generatedFilename);\n }\n return this.testTitleToFilename.get(testTitle)!;\n }\n\n private getReportFilename(testTitle?: string): string {\n if (this.mode === 'merged') {\n if (!this.mergedFilename) {\n this.mergedFilename = getReportFileName('playwright-merged');\n }\n return this.mergedFilename;\n }\n if (this.mode === 'separate') {\n if (!testTitle) throw new Error('testTitle is required in separate mode');\n return this.getSeparatedFilename(testTitle);\n }\n throw new Error(`Unknown mode: ${this.mode}`);\n }\n\n private getReportPath(testTitle?: string): string {\n const fileName = this.getReportFilename(testTitle);\n if (this.outputFormat === 'html-and-external-assets') {\n return join(getMidsceneRunSubDir('report'), fileName, 'index.html');\n }\n return join(getMidsceneRunSubDir('report'), `${fileName}.html`);\n }\n\n private ensureOutputRoot(): void {\n mkdirSync(getMidsceneRunSubDir('report'), { recursive: true });\n }\n\n private finalizeSingleReport(\n reportFilePath: string,\n targetPath: string,\n ): void {\n if (resolve(reportFilePath) === resolve(targetPath)) {\n return;\n }\n\n if (isDirectoryModeReport(reportFilePath)) {\n const targetDir = dirname(targetPath);\n mkdirSync(targetDir, { recursive: true });\n cpSync(dirname(reportFilePath), targetDir, {\n recursive: true,\n force: true,\n });\n } else {\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(reportFilePath, targetPath);\n }\n\n try {\n removeReportArtifact(reportFilePath);\n } catch (error) {\n logMsg(`Error deleting report ${reportFilePath}: ${error}`);\n }\n }\n\n private mergeOwnedReports(\n tool: ReportMergingTool,\n targetName: string,\n ): string | null {\n return tool.mergeReports(targetName, {\n overwrite: true,\n rmOriginalReports: true,\n });\n }\n\n private collectReportInfo(test: TestCase, result: TestResult) {\n const reportAnnotations = test.annotations.filter((annotation) => {\n return (\n annotation.type === 'MIDSCENE_DUMP_ANNOTATION' && annotation.description\n );\n });\n if (reportAnnotations.length === 0 || !this.mode) {\n return;\n }\n\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n const projectName = this.hasMultipleProjects\n ? test.parent?.project()?.name\n : undefined;\n const projectSuffix = projectName ? ` [${projectName}]` : '';\n const testTitle = `${test.title}${projectSuffix}${retry}`;\n const reports = reportAnnotations\n .map((annotation) => annotation.description!)\n .filter((reportFilePath) => {\n if (existsSync(reportFilePath)) {\n return true;\n }\n logMsg(\n `Failed to read Midscene report file: ${reportFilePath}`,\n new Error('Report file does not exist'),\n );\n return false;\n })\n .map((reportFilePath): ReportFileWithAttributes => {\n return {\n reportFilePath,\n reportAttributes: {\n testDuration: result.duration,\n testStatus: result.status as TestStatus,\n testTitle,\n testId,\n testDescription: test.parent?.title || '',\n },\n };\n });\n\n if (reports.length === 0) {\n return;\n }\n\n this.reportsByTestId.set(testId, {\n testTitle,\n reports,\n });\n }\n\n private finalizeMergedReport(): void {\n this.ensureOutputRoot();\n const tool = new ReportMergingTool();\n let reportCount = 0;\n for (const entry of this.reportsByTestId.values()) {\n for (const report of entry.reports) {\n tool.append(report);\n reportCount += 1;\n }\n }\n\n if (reportCount === 0) {\n return;\n }\n\n const targetName = this.getReportFilename();\n if (reportCount === 1) {\n const firstReport = Array.from(this.reportsByTestId.values())[0]\n ?.reports[0];\n if (!firstReport) {\n return;\n }\n if (firstReport.reportFilePath) {\n const targetPath = this.getReportPath();\n this.finalizeSingleReport(firstReport.reportFilePath, targetPath);\n printReportMsg(targetPath);\n return;\n }\n\n const mergedReportPath = this.mergeOwnedReports(tool, targetName);\n if (mergedReportPath) {\n printReportMsg(mergedReportPath);\n }\n return;\n }\n\n const mergedReportPath = this.mergeOwnedReports(tool, targetName);\n if (mergedReportPath) {\n printReportMsg(mergedReportPath);\n }\n }\n\n private finalizeSeparateReports(): void {\n this.ensureOutputRoot();\n for (const entry of this.reportsByTestId.values()) {\n const targetName = this.getReportFilename(entry.testTitle);\n if (entry.reports.length === 1) {\n const firstReport = entry.reports[0];\n if (firstReport.reportFilePath) {\n const targetPath = this.getReportPath(entry.testTitle);\n this.finalizeSingleReport(firstReport.reportFilePath, targetPath);\n printReportMsg(targetPath);\n continue;\n }\n\n const tool = new ReportMergingTool();\n tool.append(firstReport);\n const reportPath = this.mergeOwnedReports(tool, targetName);\n if (reportPath) {\n printReportMsg(reportPath);\n }\n continue;\n }\n\n const tool = new ReportMergingTool();\n for (const report of entry.reports) {\n tool.append(report);\n }\n const reportPath = this.mergeOwnedReports(tool, targetName);\n if (reportPath) {\n printReportMsg(reportPath);\n }\n }\n }\n\n async onBegin(config: FullConfig, _suite: Suite) {\n this.hasMultipleProjects = (config.projects?.length || 0) > 1;\n }\n\n onTestBegin(_test: TestCase, _result: TestResult) {}\n\n onTestEnd(test: TestCase, result: TestResult) {\n this.collectReportInfo(test, result);\n }\n\n async onEnd() {\n if (this.mode === 'merged') {\n this.finalizeMergedReport();\n return;\n }\n\n if (this.mode === 'separate') {\n this.finalizeSeparateReports();\n }\n }\n}\n\nexport default MidsceneReporter;\n"],"names":["MidsceneReporter","reporterType","Error","testTitle","baseTag","replaceIllegalPathCharsAndSpace","generatedFilename","getReportFileName","fileName","join","getMidsceneRunSubDir","mkdirSync","reportFilePath","targetPath","resolve","isDirectoryModeReport","targetDir","dirname","cpSync","copyFileSync","removeReportArtifact","error","logMsg","tool","targetName","test","result","reportAnnotations","annotation","retry","testId","projectName","undefined","projectSuffix","reports","existsSync","ReportMergingTool","reportCount","entry","report","firstReport","Array","printReportMsg","mergedReportPath","reportPath","config","_suite","_test","_result","options","Map"],"mappings":"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"}
@@ -51,7 +51,7 @@ class BridgeClient {
51
51
  ]
52
52
  } : {},
53
53
  query: {
54
- version: "1.10.9-beta-20260804015945.0"
54
+ version: "1.10.9"
55
55
  }
56
56
  });
57
57
  const timeout = setTimeout(()=>{
@@ -131,7 +131,7 @@ class BridgeServer {
131
131
  (0, shared_utils_namespaceObject.logMsg)('one client connected');
132
132
  this.socket = socket;
133
133
  const clientVersion = socket.handshake.query.version;
134
- (0, shared_utils_namespaceObject.logMsg)(`Bridge connected, cli-side version v1.10.9-beta-20260804015945.0, browser-side version v${clientVersion}`);
134
+ (0, shared_utils_namespaceObject.logMsg)(`Bridge connected, cli-side version v1.10.9, browser-side version v${clientVersion}`);
135
135
  socket.on(external_common_js_namespaceObject.BridgeEvent.CallResponse, (params)=>{
136
136
  const id = params.id;
137
137
  const response = params.response;
@@ -155,7 +155,7 @@ class BridgeServer {
155
155
  setTimeout(()=>{
156
156
  this.onConnect?.();
157
157
  const payload = {
158
- version: "1.10.9-beta-20260804015945.0"
158
+ version: "1.10.9"
159
159
  };
160
160
  socket.emit(external_common_js_namespaceObject.BridgeEvent.Connected, payload);
161
161
  Promise.resolve().then(()=>{
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mode/io-server.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/bridge-mode/io-server.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import {\n type IncomingMessage,\n type ServerResponse,\n createServer,\n} from 'node:http';\nimport { sleep } from '@midscene/core/utils';\nimport { getDebug } from '@midscene/shared/logger';\nimport { logMsg } from '@midscene/shared/utils';\nimport { Server, type Socket as ServerSocket } from 'socket.io';\nimport { io as ClientIO } from 'socket.io-client';\n\nimport {\n type BridgeCall,\n type BridgeCallResponse,\n BridgeCallTimeout,\n type BridgeConnectedEventPayload,\n BridgeErrorCodeNoClientConnected,\n BridgeEvent,\n BridgeSignalKill,\n DefaultBridgeServerPort,\n} from './common';\n\nconst debug = getDebug('web:bridge:io-server');\n\ndeclare const __VERSION__: string;\n\n/**\n * Returns true if the given Origin header value is allowed to connect\n * to the bridge server. Trusted origins are:\n * - No Origin header (local Node.js processes like killRunningServer)\n * - chrome-extension:// origins (the Midscene Chrome extension)\n *\n * Browser pages (https://evil.com, etc.) are rejected to prevent\n * Cross-Site WebSocket Hijacking (CSWSH) attacks.\n */\nfunction isTrustedOrigin(origin: string | undefined): boolean {\n if (!origin) return true;\n if (origin.startsWith('chrome-extension://')) return true;\n return false;\n}\n\nexport const killRunningServer = async (port?: number, host = 'localhost') => {\n try {\n const client = ClientIO(`ws://${host}:${port || DefaultBridgeServerPort}`, {\n query: {\n [BridgeSignalKill]: 1,\n },\n });\n await sleep(300);\n await client.close();\n } catch (e) {\n debug('failed to kill port: %O', e);\n }\n};\n\n// ws server, this is where the request is sent\nexport class BridgeServer {\n private callId = 0;\n private io: Server | null = null;\n private socket: ServerSocket | null = null;\n private listeningTimeoutId: NodeJS.Timeout | null = null;\n private listeningTimerFlag = false;\n private connectionTipTimer: NodeJS.Timeout | null = null;\n public calls: Record<string, BridgeCall> = {};\n\n private connectionLost = false;\n private connectionLostReason = '';\n\n constructor(\n public host: string,\n public port: number,\n public onConnect?: () => void,\n public onDisconnect?: (reason: string) => void,\n public closeConflictServer?: boolean,\n ) {}\n\n async listen(\n opts: {\n timeout?: number | false;\n } = {},\n ): Promise<void> {\n const { timeout = 30000 } = opts;\n\n if (this.closeConflictServer) {\n await killRunningServer(this.port, this.host);\n }\n\n return new Promise((resolve, reject) => {\n if (this.listeningTimerFlag) {\n return reject(new Error('already listening'));\n }\n this.listeningTimerFlag = true;\n\n this.listeningTimeoutId = timeout\n ? setTimeout(() => {\n reject(\n new Error(\n `no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`,\n ),\n );\n }, timeout)\n : null;\n\n this.connectionTipTimer =\n !timeout || timeout > 3000\n ? setTimeout(() => {\n logMsg('waiting for bridge to connect...');\n }, 2000)\n : null;\n\n // Create HTTP server — no /bridge-auth endpoint needed since we use\n // pure Origin checking in the Socket.IO middleware below.\n const httpServer = createServer(\n (_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(404);\n res.end();\n },\n );\n\n // Set up HTTP server event listeners FIRST\n httpServer.once('listening', () => {\n resolve();\n });\n\n httpServer.once('error', (err: Error) => {\n reject(new Error(`Bridge Listening Error: ${err.message}`));\n });\n\n // Start listening BEFORE creating Socket.IO Server\n // When host is 127.0.0.1 (default), don't specify host to listen on all local interfaces (IPv4 + IPv6)\n // This ensures localhost resolves correctly in both IPv4 and IPv6 environments\n if (this.host === '127.0.0.1') {\n httpServer.listen(this.port);\n } else {\n httpServer.listen(this.port, this.host);\n }\n\n // Now create Socket.IO Server attached to the already-listening HTTP server\n this.io = new Server(httpServer, {\n maxHttpBufferSize: 100 * 1024 * 1024, // 100MB\n // Increase pingTimeout to tolerate Chrome MV3 Service Worker suspension.\n // The SW keepalive alarm fires every ~24s; default pingTimeout (20s) may\n // be too short if the SW is suspended between alarm pings.\n pingTimeout: 60000,\n });\n\n this.io.use((socket, next) => {\n // Reject connections from untrusted browser origins to prevent\n // Cross-Site WebSocket Hijacking (CSWSH) and unauthorized kill signals.\n // The browser automatically sets the Origin header on WebSocket\n // handshakes, and JavaScript cannot override it.\n const origin = socket.handshake.headers.origin;\n if (!isTrustedOrigin(origin)) {\n debug('connection rejected: untrusted origin=%s', origin);\n return next(new Error('origin not allowed'));\n }\n\n // Allow new connections to replace old ones (reconnection after\n // extension Stop→Start). If the old socket is already disconnected\n // or unresponsive, accept the new connection immediately.\n if (\n socket.handshake.url.includes(BridgeSignalKill) === false &&\n this.socket?.connected\n ) {\n return next(new Error('server already connected by another client'));\n }\n next();\n });\n\n this.io.on('connection', (socket) => {\n // check the connection url\n const url = socket.handshake.url;\n if (url.includes(BridgeSignalKill)) {\n console.warn('kill signal received, closing bridge server');\n return this.close();\n }\n\n this.connectionLost = false;\n this.connectionLostReason = '';\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.listeningTimeoutId = null;\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n this.connectionTipTimer = null;\n if (this.socket?.connected) {\n socket.emit(BridgeEvent.Refused);\n socket.disconnect();\n logMsg(\n 'refused new connection: server already connected by another client',\n );\n return;\n }\n\n // Clean up stale old socket if it exists but is no longer connected\n if (this.socket) {\n try {\n this.socket.disconnect();\n } catch (e) {\n logMsg(`failed to disconnect stale socket: ${e}`);\n }\n this.socket = null;\n }\n\n try {\n logMsg('one client connected');\n this.socket = socket;\n\n const clientVersion = socket.handshake.query.version;\n logMsg(\n `Bridge connected, cli-side version v${__VERSION__}, browser-side version v${clientVersion}`,\n );\n\n socket.on(BridgeEvent.CallResponse, (params: BridgeCallResponse) => {\n const id = params.id;\n const response = params.response;\n const error = params.error;\n\n this.triggerCallResponseCallback(id, error, response);\n });\n\n socket.on('disconnect', (reason: string) => {\n this.connectionLost = true;\n this.connectionLostReason = reason;\n this.socket = null;\n\n // flush all pending calls as error and clean up completed calls\n for (const id in this.calls) {\n const call = this.calls[id];\n\n if (!call.responseTime) {\n const errorMessage = this.connectionLostErrorMsg();\n this.triggerCallResponseCallback(\n id,\n new Error(errorMessage),\n null,\n );\n }\n }\n\n // Clean up completed calls to prevent memory leaks in long-running sessions\n for (const id in this.calls) {\n if (this.calls[id].responseTime) {\n delete this.calls[id];\n }\n }\n\n this.onDisconnect?.(reason);\n });\n\n setTimeout(() => {\n this.onConnect?.();\n\n const payload = {\n version: __VERSION__,\n } as BridgeConnectedEventPayload;\n socket.emit(BridgeEvent.Connected, payload);\n Promise.resolve().then(() => {\n for (const id in this.calls) {\n if (this.calls[id].callTime === 0) {\n this.emitCall(id);\n }\n }\n });\n }, 0);\n } catch (e) {\n logMsg(`failed to handle connection event: ${e}`);\n }\n });\n\n this.io.on('close', () => {\n this.close();\n });\n });\n }\n\n private connectionLostErrorMsg = () => {\n return `Connection lost, reason: ${this.connectionLostReason}`;\n };\n\n private async triggerCallResponseCallback(\n id: string | number,\n error: Error | string | null,\n response: any,\n ) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n // Ensure error is always an Error object (bridge client may send strings)\n if (error) {\n call.error =\n error instanceof Error\n ? error\n : new Error(typeof error === 'string' ? error : String(error));\n } else {\n call.error = undefined;\n }\n call.response = response;\n call.responseTime = Date.now();\n\n call.callback(call.error, response);\n }\n\n private async emitCall(id: string) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n\n if (this.connectionLost) {\n const message = `Connection lost, reason: ${this.connectionLostReason}`;\n call.callback(new Error(message), null);\n return;\n }\n\n if (this.socket) {\n this.socket.emit(BridgeEvent.Call, {\n id,\n method: call.method,\n args: call.args,\n });\n call.callTime = Date.now();\n }\n }\n\n async call<T = any>(\n method: string,\n args: any[],\n timeout = BridgeCallTimeout,\n ): Promise<T> {\n const id = `${this.callId++}`;\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);\n this.calls[id].error = new Error(\n `Bridge call timeout after ${timeout}ms: ${method}`,\n );\n reject(this.calls[id].error);\n }, timeout);\n\n this.calls[id] = {\n method,\n args,\n response: null,\n callTime: 0,\n responseTime: 0,\n callback: (error: Error | undefined, response: any) => {\n clearTimeout(timeoutId);\n if (error) {\n reject(error);\n } else {\n resolve(response);\n }\n },\n };\n\n this.emitCall(id);\n });\n }\n\n // do NOT restart after close\n async close() {\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n const closeProcess = this.io?.close();\n this.io = null;\n\n return closeProcess;\n }\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","debug","getDebug","isTrustedOrigin","origin","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","_req","res","err","Server","socket","next","url","console","clearTimeout","BridgeEvent","clientVersion","params","id","response","error","reason","call","errorMessage","payload","__VERSION__","String","undefined","Date","message","method","args","BridgeCallTimeout","timeoutId","closeProcess","onConnect","onDisconnect","closeConflictServer"],"mappings":"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"}
1
+ {"version":3,"file":"bridge-mode/io-server.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/bridge-mode/io-server.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import {\n type IncomingMessage,\n type ServerResponse,\n createServer,\n} from 'node:http';\nimport { sleep } from '@midscene/core/utils';\nimport { getDebug } from '@midscene/shared/logger';\nimport { logMsg } from '@midscene/shared/utils';\nimport { Server, type Socket as ServerSocket } from 'socket.io';\nimport { io as ClientIO } from 'socket.io-client';\n\nimport {\n type BridgeCall,\n type BridgeCallResponse,\n BridgeCallTimeout,\n type BridgeConnectedEventPayload,\n BridgeErrorCodeNoClientConnected,\n BridgeEvent,\n BridgeSignalKill,\n DefaultBridgeServerPort,\n} from './common';\n\nconst debug = getDebug('web:bridge:io-server');\n\ndeclare const __VERSION__: string;\n\n/**\n * Returns true if the given Origin header value is allowed to connect\n * to the bridge server. Trusted origins are:\n * - No Origin header (local Node.js processes like killRunningServer)\n * - chrome-extension:// origins (the Midscene Chrome extension)\n *\n * Browser pages (https://evil.com, etc.) are rejected to prevent\n * Cross-Site WebSocket Hijacking (CSWSH) attacks.\n */\nfunction isTrustedOrigin(origin: string | undefined): boolean {\n if (!origin) return true;\n if (origin.startsWith('chrome-extension://')) return true;\n return false;\n}\n\nexport const killRunningServer = async (port?: number, host = 'localhost') => {\n try {\n const client = ClientIO(`ws://${host}:${port || DefaultBridgeServerPort}`, {\n query: {\n [BridgeSignalKill]: 1,\n },\n });\n await sleep(300);\n await client.close();\n } catch (e) {\n debug('failed to kill port: %O', e);\n }\n};\n\n// ws server, this is where the request is sent\nexport class BridgeServer {\n private callId = 0;\n private io: Server | null = null;\n private socket: ServerSocket | null = null;\n private listeningTimeoutId: NodeJS.Timeout | null = null;\n private listeningTimerFlag = false;\n private connectionTipTimer: NodeJS.Timeout | null = null;\n public calls: Record<string, BridgeCall> = {};\n\n private connectionLost = false;\n private connectionLostReason = '';\n\n constructor(\n public host: string,\n public port: number,\n public onConnect?: () => void,\n public onDisconnect?: (reason: string) => void,\n public closeConflictServer?: boolean,\n ) {}\n\n async listen(\n opts: {\n timeout?: number | false;\n } = {},\n ): Promise<void> {\n const { timeout = 30000 } = opts;\n\n if (this.closeConflictServer) {\n await killRunningServer(this.port, this.host);\n }\n\n return new Promise((resolve, reject) => {\n if (this.listeningTimerFlag) {\n return reject(new Error('already listening'));\n }\n this.listeningTimerFlag = true;\n\n this.listeningTimeoutId = timeout\n ? setTimeout(() => {\n reject(\n new Error(\n `no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`,\n ),\n );\n }, timeout)\n : null;\n\n this.connectionTipTimer =\n !timeout || timeout > 3000\n ? setTimeout(() => {\n logMsg('waiting for bridge to connect...');\n }, 2000)\n : null;\n\n // Create HTTP server — no /bridge-auth endpoint needed since we use\n // pure Origin checking in the Socket.IO middleware below.\n const httpServer = createServer(\n (_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(404);\n res.end();\n },\n );\n\n // Set up HTTP server event listeners FIRST\n httpServer.once('listening', () => {\n resolve();\n });\n\n httpServer.once('error', (err: Error) => {\n reject(new Error(`Bridge Listening Error: ${err.message}`));\n });\n\n // Start listening BEFORE creating Socket.IO Server\n // When host is 127.0.0.1 (default), don't specify host to listen on all local interfaces (IPv4 + IPv6)\n // This ensures localhost resolves correctly in both IPv4 and IPv6 environments\n if (this.host === '127.0.0.1') {\n httpServer.listen(this.port);\n } else {\n httpServer.listen(this.port, this.host);\n }\n\n // Now create Socket.IO Server attached to the already-listening HTTP server\n this.io = new Server(httpServer, {\n maxHttpBufferSize: 100 * 1024 * 1024, // 100MB\n // Increase pingTimeout to tolerate Chrome MV3 Service Worker suspension.\n // The SW keepalive alarm fires every ~24s; default pingTimeout (20s) may\n // be too short if the SW is suspended between alarm pings.\n pingTimeout: 60000,\n });\n\n this.io.use((socket, next) => {\n // Reject connections from untrusted browser origins to prevent\n // Cross-Site WebSocket Hijacking (CSWSH) and unauthorized kill signals.\n // The browser automatically sets the Origin header on WebSocket\n // handshakes, and JavaScript cannot override it.\n const origin = socket.handshake.headers.origin;\n if (!isTrustedOrigin(origin)) {\n debug('connection rejected: untrusted origin=%s', origin);\n return next(new Error('origin not allowed'));\n }\n\n // Allow new connections to replace old ones (reconnection after\n // extension Stop→Start). If the old socket is already disconnected\n // or unresponsive, accept the new connection immediately.\n if (\n socket.handshake.url.includes(BridgeSignalKill) === false &&\n this.socket?.connected\n ) {\n return next(new Error('server already connected by another client'));\n }\n next();\n });\n\n this.io.on('connection', (socket) => {\n // check the connection url\n const url = socket.handshake.url;\n if (url.includes(BridgeSignalKill)) {\n console.warn('kill signal received, closing bridge server');\n return this.close();\n }\n\n this.connectionLost = false;\n this.connectionLostReason = '';\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.listeningTimeoutId = null;\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n this.connectionTipTimer = null;\n if (this.socket?.connected) {\n socket.emit(BridgeEvent.Refused);\n socket.disconnect();\n logMsg(\n 'refused new connection: server already connected by another client',\n );\n return;\n }\n\n // Clean up stale old socket if it exists but is no longer connected\n if (this.socket) {\n try {\n this.socket.disconnect();\n } catch (e) {\n logMsg(`failed to disconnect stale socket: ${e}`);\n }\n this.socket = null;\n }\n\n try {\n logMsg('one client connected');\n this.socket = socket;\n\n const clientVersion = socket.handshake.query.version;\n logMsg(\n `Bridge connected, cli-side version v${__VERSION__}, browser-side version v${clientVersion}`,\n );\n\n socket.on(BridgeEvent.CallResponse, (params: BridgeCallResponse) => {\n const id = params.id;\n const response = params.response;\n const error = params.error;\n\n this.triggerCallResponseCallback(id, error, response);\n });\n\n socket.on('disconnect', (reason: string) => {\n this.connectionLost = true;\n this.connectionLostReason = reason;\n this.socket = null;\n\n // flush all pending calls as error and clean up completed calls\n for (const id in this.calls) {\n const call = this.calls[id];\n\n if (!call.responseTime) {\n const errorMessage = this.connectionLostErrorMsg();\n this.triggerCallResponseCallback(\n id,\n new Error(errorMessage),\n null,\n );\n }\n }\n\n // Clean up completed calls to prevent memory leaks in long-running sessions\n for (const id in this.calls) {\n if (this.calls[id].responseTime) {\n delete this.calls[id];\n }\n }\n\n this.onDisconnect?.(reason);\n });\n\n setTimeout(() => {\n this.onConnect?.();\n\n const payload = {\n version: __VERSION__,\n } as BridgeConnectedEventPayload;\n socket.emit(BridgeEvent.Connected, payload);\n Promise.resolve().then(() => {\n for (const id in this.calls) {\n if (this.calls[id].callTime === 0) {\n this.emitCall(id);\n }\n }\n });\n }, 0);\n } catch (e) {\n logMsg(`failed to handle connection event: ${e}`);\n }\n });\n\n this.io.on('close', () => {\n this.close();\n });\n });\n }\n\n private connectionLostErrorMsg = () => {\n return `Connection lost, reason: ${this.connectionLostReason}`;\n };\n\n private async triggerCallResponseCallback(\n id: string | number,\n error: Error | string | null,\n response: any,\n ) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n // Ensure error is always an Error object (bridge client may send strings)\n if (error) {\n call.error =\n error instanceof Error\n ? error\n : new Error(typeof error === 'string' ? error : String(error));\n } else {\n call.error = undefined;\n }\n call.response = response;\n call.responseTime = Date.now();\n\n call.callback(call.error, response);\n }\n\n private async emitCall(id: string) {\n const call = this.calls[id];\n if (!call) {\n throw new Error(`call ${id} not found`);\n }\n\n if (this.connectionLost) {\n const message = `Connection lost, reason: ${this.connectionLostReason}`;\n call.callback(new Error(message), null);\n return;\n }\n\n if (this.socket) {\n this.socket.emit(BridgeEvent.Call, {\n id,\n method: call.method,\n args: call.args,\n });\n call.callTime = Date.now();\n }\n }\n\n async call<T = any>(\n method: string,\n args: any[],\n timeout = BridgeCallTimeout,\n ): Promise<T> {\n const id = `${this.callId++}`;\n\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);\n this.calls[id].error = new Error(\n `Bridge call timeout after ${timeout}ms: ${method}`,\n );\n reject(this.calls[id].error);\n }, timeout);\n\n this.calls[id] = {\n method,\n args,\n response: null,\n callTime: 0,\n responseTime: 0,\n callback: (error: Error | undefined, response: any) => {\n clearTimeout(timeoutId);\n if (error) {\n reject(error);\n } else {\n resolve(response);\n }\n },\n };\n\n this.emitCall(id);\n });\n }\n\n // do NOT restart after close\n async close() {\n this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);\n this.connectionTipTimer && clearTimeout(this.connectionTipTimer);\n const closeProcess = this.io?.close();\n this.io = null;\n\n return closeProcess;\n }\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","debug","getDebug","isTrustedOrigin","origin","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","_req","res","err","Server","socket","next","url","console","clearTimeout","BridgeEvent","clientVersion","params","id","response","error","reason","call","errorMessage","payload","__VERSION__","String","undefined","Date","message","method","args","BridgeCallTimeout","timeoutId","closeProcess","onConnect","onDisconnect","closeConflictServer"],"mappings":"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"}
@@ -138,7 +138,7 @@ class ExtensionBridgePageBrowserSide extends page_js_default() {
138
138
  throw new Error('Connection denied by user');
139
139
  }
140
140
  }
141
- this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.10.9-beta-20260804015945.0`, 'log');
141
+ this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.10.9`, 'log');
142
142
  }
143
143
  async connect() {
144
144
  return await this.setupBridgeClient();
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mode/page-browser-side.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { assert } from '@midscene/shared/utils';\nimport ChromeExtensionProxyPage from '../chrome-extension/page';\nimport type {\n ChromePageDestroyOptions,\n KeyboardAction,\n MouseAction,\n} from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n} from './common';\nimport { BridgeClient } from './io-client';\n\ndeclare const __VERSION__: string;\n\nconst NEW_TAB_LOAD_TIMEOUT_MS = 30_000;\n\nfunction isBlankUrl(url: string | undefined): boolean {\n if (!url) return true;\n return url === 'about:blank' || url.startsWith('chrome://newtab');\n}\n\n// Wait until the freshly created tab has navigated away from about:blank\n// and reached `status === 'complete'`. Resolves on timeout instead of\n// throwing so callers degrade to the existing lazy-attach behavior.\nfunction waitForTabNavigationComplete(\n tabId: number,\n targetUrl: string,\n timeoutMs = NEW_TAB_LOAD_TIMEOUT_MS,\n): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = () => {\n if (settled) return;\n settled = true;\n try {\n chrome.tabs.onUpdated.removeListener(onUpdated);\n } catch {}\n clearTimeout(timer);\n resolve();\n };\n\n const isReady = (tab: chrome.tabs.Tab | undefined): boolean => {\n if (!tab) return false;\n if (tab.status !== 'complete') return false;\n const currentUrl = tab.url || tab.pendingUrl || '';\n // Skip the initial about:blank \"complete\" that fires before\n // the target URL navigation kicks in.\n if (isBlankUrl(currentUrl) && !isBlankUrl(targetUrl)) return false;\n return true;\n };\n\n const onUpdated = (\n id: number,\n _info: chrome.tabs.TabChangeInfo,\n tab: chrome.tabs.Tab,\n ) => {\n if (id !== tabId) return;\n if (isReady(tab)) finish();\n };\n\n chrome.tabs.onUpdated.addListener(onUpdated);\n const timer = setTimeout(finish, timeoutMs);\n\n // Handle the race where the tab already finished loading before\n // we registered the listener.\n chrome.tabs\n .get(tabId)\n .then((tab) => {\n if (isReady(tab)) finish();\n })\n .catch(() => {});\n });\n}\n\nexport class ExtensionBridgePageBrowserSide extends ChromeExtensionProxyPage {\n public bridgeClient: BridgeClient | null = null;\n\n private destroyOptions?: ChromePageDestroyOptions;\n\n private newlyCreatedTabIds: number[] = [];\n\n // Connection confirmation state\n private confirmationPromise: Promise<boolean> | null = null;\n\n constructor(\n public serverEndpoint?: string,\n public onDisconnect: () => void = () => {},\n public onLogMessage: (\n message: string,\n type: 'log' | 'status',\n ) => void = () => {},\n forceSameTabNavigation = true,\n public onConnectionRequest?: () => Promise<boolean>,\n ) {\n super(forceSameTabNavigation);\n }\n\n private async setupBridgeClient() {\n const endpoint =\n this.serverEndpoint || `ws://localhost:${DefaultBridgeServerPort}`;\n\n // Create confirmation gate BEFORE establishing connection,\n // so that any calls received immediately after connection are blocked\n // until user confirms. This prevents a race condition where server-side\n // queued calls bypass the confirmation dialog.\n let resolveConfirmationGate: (allowed: boolean) => void = () => {};\n if (this.onConnectionRequest) {\n this.confirmationPromise = new Promise<boolean>((resolve) => {\n resolveConfirmationGate = resolve;\n });\n }\n\n this.bridgeClient = new BridgeClient(\n endpoint,\n async (method, args: any[]) => {\n // Wait for user confirmation before processing any commands\n if (this.confirmationPromise) {\n const allowed = await this.confirmationPromise;\n if (!allowed) {\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(`bridge call from cli side: ${method}`, 'log');\n if (method === BridgeEvent.ConnectNewTabWithUrl) {\n return this.connectNewTabWithUrl.apply(\n this,\n args as unknown as [string],\n );\n }\n\n if (method === BridgeEvent.GetBrowserTabList) {\n return this.getBrowserTabList.apply(this, args as any);\n }\n\n if (method === BridgeEvent.SetActiveTabId) {\n return this.setActiveTabId.apply(this, args as any);\n }\n\n if (method === BridgeEvent.ConnectCurrentTab) {\n return this.connectCurrentTab.apply(this, args as any);\n }\n\n if (method === BridgeEvent.UpdateAgentStatus) {\n return this.onLogMessage(args[0] as string, 'status');\n }\n\n const tabId = await this.getActiveTabId();\n if (!tabId || tabId === 0) {\n throw new Error('no tab is connected');\n }\n\n // this.onLogMessage(`calling method: ${method}`);\n\n if (method.startsWith(MouseEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof MouseAction;\n if (actionName === 'drag') {\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n\n if (method.startsWith(KeyboardEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof KeyboardAction;\n if (actionName === 'press') {\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n\n // Generic method lookup. Methods like `setWaterFlowAnimationEnabled`\n // and `getWaterFlowAnimationEnabled` are inherited from\n // ChromeExtensionProxyPage and found via `this[method]`.\n if (!this[method as keyof ChromeExtensionProxyPage]) {\n this.onLogMessage(`method not found: ${method}`, 'log');\n return undefined;\n }\n\n try {\n // @ts-expect-error\n const result = await this[method as keyof ChromeExtensionProxyPage](\n ...args,\n );\n return result;\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Unknown error';\n this.onLogMessage(\n `Error calling method: ${method}, ${errorMessage}`,\n 'log',\n );\n throw new Error(errorMessage, { cause: e });\n }\n },\n // on disconnect\n () => {\n return this.destroy();\n },\n );\n await this.bridgeClient.connect();\n\n // Show confirmation dialog after connection is established\n if (this.onConnectionRequest) {\n this.onLogMessage('Waiting for user confirmation...', 'log');\n const allowed = await this.onConnectionRequest();\n resolveConfirmationGate(allowed);\n this.confirmationPromise = null;\n\n if (!allowed) {\n this.onLogMessage('Connection denied by user', 'log');\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(\n `Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v${__VERSION__}`,\n 'log',\n );\n }\n\n public async connect() {\n return await this.setupBridgeClient();\n }\n\n public async connectNewTabWithUrl(\n url: string,\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n // Otherwise the animation gets injected during the connection flow\n // before the CLI side's setWaterFlowAnimationEnabled(false) arrives.\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tab = await chrome.tabs.create({ url });\n const tabId = tab.id;\n assert(tabId, 'failed to get tabId after creating a new tab');\n\n // new tab\n this.onLogMessage(`Creating new tab: ${url}`, 'log');\n this.newlyCreatedTabIds.push(tabId);\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n // chrome.tabs.create returns immediately with an about:blank target,\n // then navigates to `url`. If we attach the debugger during that\n // cross-origin transition Site Isolation will detach it again, leaving\n // the first CDP command to fail with \"Debugger is not attached to the\n // tab\". Wait for navigation to settle so the lazy attach lands on a\n // stable target.\n await waitForTabNavigationComplete(tabId, url);\n\n await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tabs = await chrome.tabs.query({ active: true, currentWindow: true });\n const tabId = tabs[0]?.id;\n assert(tabId, 'failed to get tabId');\n\n this.onLogMessage(`Connected to current tab: ${tabs[0]?.url}`, 'log');\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async setDestroyOptions(options: ChromePageDestroyOptions) {\n this.destroyOptions = options;\n }\n\n async destroy() {\n if (this.destroyOptions?.closeTab && this.newlyCreatedTabIds.length > 0) {\n this.onLogMessage('Closing all newly created tabs by bridge...', 'log');\n for (const tabId of this.newlyCreatedTabIds) {\n await chrome.tabs.remove(tabId);\n }\n this.newlyCreatedTabIds = [];\n }\n\n await super.destroy();\n\n if (this.bridgeClient) {\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n this.onDisconnect();\n }\n }\n}\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","NEW_TAB_LOAD_TIMEOUT_MS","isBlankUrl","url","waitForTabNavigationComplete","tabId","targetUrl","timeoutMs","Promise","resolve","settled","finish","chrome","onUpdated","clearTimeout","timer","isReady","tab","currentUrl","id","_info","setTimeout","ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","BridgeClient","method","args","allowed","Error","BridgeEvent","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","options","undefined","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":";;;IACAA,oBAAoB,CAAC,GAAG,CAACC;QACxB,IAAIC,SAASD,UAAUA,OAAO,UAAU,GACvC,IAAOA,MAAM,CAAC,UAAU,GACxB,IAAOA;QACRD,oBAAoB,CAAC,CAACE,QAAQ;YAAE,GAAGA;QAAO;QAC1C,OAAOA;IACR;;;ICPAF,oBAAoB,CAAC,GAAG,CAAC,UAASG;QACjC,IAAI,IAAIC,OAAOD,WACR,IAAGH,oBAAoB,CAAC,CAACG,YAAYC,QAAQ,CAACJ,oBAAoB,CAAC,CAAC,UAASI,MACzEC,OAAO,cAAc,CAAC,UAASD,KAAK;YAAE,YAAY;YAAM,KAAKD,UAAU,CAACC,IAAI;QAAC;IAGzF;;;ICNAJ,oBAAoB,CAAC,GAAG,CAACM,KAAKC,OAAUF,OAAO,SAAS,CAAC,cAAc,CAAC,IAAI,CAACC,KAAKC;;;ICClFP,oBAAoB,CAAC,GAAG,CAAC;QACxB,IAAG,AAAkB,eAAlB,OAAOQ,UAA0BA,OAAO,WAAW,EACrDH,OAAO,cAAc,CAAC,UAASG,OAAO,WAAW,EAAE;YAAE,OAAO;QAAS;QAEtEH,OAAO,cAAc,CAAC,UAAS,cAAc;YAAE,OAAO;QAAK;IAC5D;;;;;;;;;;;;;;;;;;;;;;ACYA,MAAMI,0BAA0B;AAEhC,SAASC,WAAWC,GAAuB;IACzC,IAAI,CAACA,KAAK,OAAO;IACjB,OAAOA,AAAQ,kBAARA,OAAyBA,IAAI,UAAU,CAAC;AACjD;AAKA,SAASC,6BACPC,KAAa,EACbC,SAAiB,EACjBC,YAAYN,uBAAuB;IAEnC,OAAO,IAAIO,QAAQ,CAACC;QAClB,IAAIC,UAAU;QACd,MAAMC,SAAS;YACb,IAAID,SAAS;YACbA,UAAU;YACV,IAAI;gBACFE,OAAO,IAAI,CAAC,SAAS,CAAC,cAAc,CAACC;YACvC,EAAE,OAAM,CAAC;YACTC,aAAaC;YACbN;QACF;QAEA,MAAMO,UAAU,CAACC;YACf,IAAI,CAACA,KAAK,OAAO;YACjB,IAAIA,AAAe,eAAfA,IAAI,MAAM,EAAiB,OAAO;YACtC,MAAMC,aAAaD,IAAI,GAAG,IAAIA,IAAI,UAAU,IAAI;YAGhD,IAAIf,WAAWgB,eAAe,CAAChB,WAAWI,YAAY,OAAO;YAC7D,OAAO;QACT;QAEA,MAAMO,YAAY,CAChBM,IACAC,OACAH;YAEA,IAAIE,OAAOd,OAAO;YAClB,IAAIW,QAAQC,MAAMN;QACpB;QAEAC,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAACC;QAClC,MAAME,QAAQM,WAAWV,QAAQJ;QAIjCK,OAAO,IAAI,CACR,GAAG,CAACP,OACJ,IAAI,CAAC,CAACY;YACL,IAAID,QAAQC,MAAMN;QACpB,GACC,KAAK,CAAC,KAAO;IAClB;AACF;AAEO,MAAMW,uCAAuCC;IAuBlD,MAAc,oBAAoB;QAChC,MAAMC,WACJ,IAAI,CAAC,cAAc,IAAI,CAAC,eAAe,EAAEC,mCAAAA,uBAAuBA,EAAE;QAMpE,IAAIC,0BAAsD,KAAO;QACjE,IAAI,IAAI,CAAC,mBAAmB,EAC1B,IAAI,CAAC,mBAAmB,GAAG,IAAIlB,QAAiB,CAACC;YAC/CiB,0BAA0BjB;QAC5B;QAGF,IAAI,CAAC,YAAY,GAAG,IAAIkB,sCAAAA,YAAYA,CAClCH,UACA,OAAOI,QAAQC;YAEb,IAAI,IAAI,CAAC,mBAAmB,EAAE;gBAC5B,MAAMC,UAAU,MAAM,IAAI,CAAC,mBAAmB;gBAC9C,IAAI,CAACA,SACH,MAAM,IAAIC,MAAM;YAEpB;YAEA,IAAI,CAAC,YAAY,CAAC,CAAC,2BAA2B,EAAEH,QAAQ,EAAE;YAC1D,IAAIA,WAAWI,mCAAAA,WAAAA,CAAAA,oBAAgC,EAC7C,OAAO,IAAI,CAAC,oBAAoB,CAAC,KAAK,CACpC,IAAI,EACJH;YAIJ,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,iBAA6B,EAC1C,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAEH;YAG5C,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,cAA0B,EACvC,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAEH;YAGzC,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,iBAA6B,EAC1C,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAEH;YAG5C,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,iBAA6B,EAC1C,OAAO,IAAI,CAAC,YAAY,CAACH,IAAI,CAAC,EAAE,EAAY;YAG9C,MAAMxB,QAAQ,MAAM,IAAI,CAAC,cAAc;YACvC,IAAI,CAACA,SAASA,AAAU,MAAVA,OACZ,MAAM,IAAI0B,MAAM;YAKlB,IAAIH,OAAO,UAAU,CAACK,mCAAAA,UAAAA,CAAAA,MAAiB,GAAG;gBACxC,MAAMC,aAAaN,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE;gBAIvC,OAAO,IAAI,CAAC,KAAK,CAACM,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAEL;YAClD;YAEA,IAAID,OAAO,UAAU,CAACO,mCAAAA,aAAAA,CAAAA,MAAoB,GAAG;gBAC3C,MAAMD,aAAaN,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE;gBAIvC,OAAO,IAAI,CAAC,QAAQ,CAACM,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAEL;YACxD;YAKA,IAAI,CAAC,IAAI,CAACD,OAAyC,EAAE,YACnD,IAAI,CAAC,YAAY,CAAC,CAAC,kBAAkB,EAAEA,QAAQ,EAAE;YAInD,IAAI;gBAEF,MAAMQ,SAAS,MAAM,IAAI,CAACR,OAAyC,IAC9DC;gBAEL,OAAOO;YACT,EAAE,OAAOC,GAAG;gBACV,MAAMC,eAAeD,aAAaN,QAAQM,EAAE,OAAO,GAAG;gBACtD,IAAI,CAAC,YAAY,CACf,CAAC,sBAAsB,EAAET,OAAO,EAAE,EAAEU,cAAc,EAClD;gBAEF,MAAM,IAAIP,MAAMO,cAAc;oBAAE,OAAOD;gBAAE;YAC3C;QACF,GAEA,IACS,IAAI,CAAC,OAAO;QAGvB,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO;QAG/B,IAAI,IAAI,CAAC,mBAAmB,EAAE;YAC5B,IAAI,CAAC,YAAY,CAAC,oCAAoC;YACtD,MAAMP,UAAU,MAAM,IAAI,CAAC,mBAAmB;YAC9CJ,wBAAwBI;YACxB,IAAI,CAAC,mBAAmB,GAAG;YAE3B,IAAI,CAACA,SAAS;gBACZ,IAAI,CAAC,YAAY,CAAC,6BAA6B;gBAC/C,IAAI,CAAC,YAAY,CAAC,UAAU;gBAC5B,IAAI,CAAC,YAAY,GAAG;gBACpB,MAAM,IAAIC,MAAM;YAClB;QACF;QAEA,IAAI,CAAC,YAAY,CACf,uCAAuC,IAAI,CAAC,YAAY,CAAC,aAAa,sDAAwC,EAC9G;IAEJ;IAEA,MAAa,UAAU;QACrB,OAAO,MAAM,IAAI,CAAC,iBAAiB;IACrC;IAEA,MAAa,qBACX5B,GAAW,EACXoC,UAAmC;QACjC,wBAAwB;IAC1B,CAAC,EACD;QAKA,IAAIA,AAAqCC,WAArCD,QAAQ,wBAAwB,EAClC,IAAI,CAAC,yBAAyB,GAAGA,QAAQ,wBAAwB;QAGnE,MAAMtB,MAAM,MAAML,OAAO,IAAI,CAAC,MAAM,CAAC;YAAET;QAAI;QAC3C,MAAME,QAAQY,IAAI,EAAE;QACpBwB,IAAAA,sBAAAA,MAAAA,AAAAA,EAAOpC,OAAO;QAGd,IAAI,CAAC,YAAY,CAAC,CAAC,kBAAkB,EAAEF,KAAK,EAAE;QAC9C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAACE;QAE7B,IAAIkC,SAAS,wBACX,IAAI,CAAC,sBAAsB,GAAG;QAShC,MAAMnC,6BAA6BC,OAAOF;QAE1C,MAAM,IAAI,CAAC,cAAc,CAACE;IAC5B;IAEA,MAAa,kBACXkC,UAAmC;QACjC,wBAAwB;IAC1B,CAAC,EACD;QAGA,IAAIA,AAAqCC,WAArCD,QAAQ,wBAAwB,EAClC,IAAI,CAAC,yBAAyB,GAAGA,QAAQ,wBAAwB;QAGnE,MAAMG,OAAO,MAAM9B,OAAO,IAAI,CAAC,KAAK,CAAC;YAAE,QAAQ;YAAM,eAAe;QAAK;QACzE,MAAMP,QAAQqC,IAAI,CAAC,EAAE,EAAE;QACvBD,IAAAA,sBAAAA,MAAAA,AAAAA,EAAOpC,OAAO;QAEd,IAAI,CAAC,YAAY,CAAC,CAAC,0BAA0B,EAAEqC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE;QAE/D,IAAIH,SAAS,wBACX,IAAI,CAAC,sBAAsB,GAAG;QAGhC,MAAM,IAAI,CAAC,cAAc,CAAClC;IAC5B;IAEA,MAAa,kBAAkBkC,OAAiC,EAAE;QAChE,IAAI,CAAC,cAAc,GAAGA;IACxB;IAEA,MAAM,UAAU;QACd,IAAI,IAAI,CAAC,cAAc,EAAE,YAAY,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,GAAG;YACvE,IAAI,CAAC,YAAY,CAAC,+CAA+C;YACjE,KAAK,MAAMlC,SAAS,IAAI,CAAC,kBAAkB,CACzC,MAAMO,OAAO,IAAI,CAAC,MAAM,CAACP;YAE3B,IAAI,CAAC,kBAAkB,GAAG,EAAE;QAC9B;QAEA,MAAM,KAAK,CAAC;QAEZ,IAAI,IAAI,CAAC,YAAY,EAAE;YACrB,IAAI,CAAC,YAAY,CAAC,UAAU;YAC5B,IAAI,CAAC,YAAY,GAAG;YACpB,IAAI,CAAC,YAAY;QACnB;IACF;IA9NA,YACSsC,cAAuB,EACvBC,eAA2B,KAAO,CAAC,EACnCC,eAGK,KAAO,CAAC,EACpBC,yBAAyB,IAAI,EACtBC,mBAA4C,CACnD;QACA,KAAK,CAACD,yBAAAA,iBAAAA,IAAAA,EAAAA,kBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,gBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,gBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,uBAAAA,KAAAA,IAnBR,uBAAO,gBAAP,SAEA,uBAAQ,kBAAR,SAEA,uBAAQ,sBAAR,SAGA,uBAAQ,uBAAR,cAGSH,cAAc,GAAdA,gBAAAA,IAAAA,CACAC,YAAY,GAAZA,cAAAA,IAAAA,CACAC,YAAY,GAAZA,cAAAA,IAAAA,CAKAE,mBAAmB,GAAnBA,qBAAAA,IAAAA,CAjBF,YAAY,GAAwB,WAInC,kBAAkB,GAAa,EAAE,OAGjC,mBAAmB,GAA4B;IAavD;AAoNF"}
1
+ {"version":3,"file":"bridge-mode/page-browser-side.js","sources":["webpack/runtime/compat_get_default_export","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/bridge-mode/page-browser-side.ts"],"sourcesContent":["// getDefaultExport function for compatibility with non-ESM modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { assert } from '@midscene/shared/utils';\nimport ChromeExtensionProxyPage from '../chrome-extension/page';\nimport type {\n ChromePageDestroyOptions,\n KeyboardAction,\n MouseAction,\n} from '../web-page';\nimport {\n type BridgeConnectTabOptions,\n BridgeEvent,\n DefaultBridgeServerPort,\n KeyboardEvent,\n MouseEvent,\n} from './common';\nimport { BridgeClient } from './io-client';\n\ndeclare const __VERSION__: string;\n\nconst NEW_TAB_LOAD_TIMEOUT_MS = 30_000;\n\nfunction isBlankUrl(url: string | undefined): boolean {\n if (!url) return true;\n return url === 'about:blank' || url.startsWith('chrome://newtab');\n}\n\n// Wait until the freshly created tab has navigated away from about:blank\n// and reached `status === 'complete'`. Resolves on timeout instead of\n// throwing so callers degrade to the existing lazy-attach behavior.\nfunction waitForTabNavigationComplete(\n tabId: number,\n targetUrl: string,\n timeoutMs = NEW_TAB_LOAD_TIMEOUT_MS,\n): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = () => {\n if (settled) return;\n settled = true;\n try {\n chrome.tabs.onUpdated.removeListener(onUpdated);\n } catch {}\n clearTimeout(timer);\n resolve();\n };\n\n const isReady = (tab: chrome.tabs.Tab | undefined): boolean => {\n if (!tab) return false;\n if (tab.status !== 'complete') return false;\n const currentUrl = tab.url || tab.pendingUrl || '';\n // Skip the initial about:blank \"complete\" that fires before\n // the target URL navigation kicks in.\n if (isBlankUrl(currentUrl) && !isBlankUrl(targetUrl)) return false;\n return true;\n };\n\n const onUpdated = (\n id: number,\n _info: chrome.tabs.TabChangeInfo,\n tab: chrome.tabs.Tab,\n ) => {\n if (id !== tabId) return;\n if (isReady(tab)) finish();\n };\n\n chrome.tabs.onUpdated.addListener(onUpdated);\n const timer = setTimeout(finish, timeoutMs);\n\n // Handle the race where the tab already finished loading before\n // we registered the listener.\n chrome.tabs\n .get(tabId)\n .then((tab) => {\n if (isReady(tab)) finish();\n })\n .catch(() => {});\n });\n}\n\nexport class ExtensionBridgePageBrowserSide extends ChromeExtensionProxyPage {\n public bridgeClient: BridgeClient | null = null;\n\n private destroyOptions?: ChromePageDestroyOptions;\n\n private newlyCreatedTabIds: number[] = [];\n\n // Connection confirmation state\n private confirmationPromise: Promise<boolean> | null = null;\n\n constructor(\n public serverEndpoint?: string,\n public onDisconnect: () => void = () => {},\n public onLogMessage: (\n message: string,\n type: 'log' | 'status',\n ) => void = () => {},\n forceSameTabNavigation = true,\n public onConnectionRequest?: () => Promise<boolean>,\n ) {\n super(forceSameTabNavigation);\n }\n\n private async setupBridgeClient() {\n const endpoint =\n this.serverEndpoint || `ws://localhost:${DefaultBridgeServerPort}`;\n\n // Create confirmation gate BEFORE establishing connection,\n // so that any calls received immediately after connection are blocked\n // until user confirms. This prevents a race condition where server-side\n // queued calls bypass the confirmation dialog.\n let resolveConfirmationGate: (allowed: boolean) => void = () => {};\n if (this.onConnectionRequest) {\n this.confirmationPromise = new Promise<boolean>((resolve) => {\n resolveConfirmationGate = resolve;\n });\n }\n\n this.bridgeClient = new BridgeClient(\n endpoint,\n async (method, args: any[]) => {\n // Wait for user confirmation before processing any commands\n if (this.confirmationPromise) {\n const allowed = await this.confirmationPromise;\n if (!allowed) {\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(`bridge call from cli side: ${method}`, 'log');\n if (method === BridgeEvent.ConnectNewTabWithUrl) {\n return this.connectNewTabWithUrl.apply(\n this,\n args as unknown as [string],\n );\n }\n\n if (method === BridgeEvent.GetBrowserTabList) {\n return this.getBrowserTabList.apply(this, args as any);\n }\n\n if (method === BridgeEvent.SetActiveTabId) {\n return this.setActiveTabId.apply(this, args as any);\n }\n\n if (method === BridgeEvent.ConnectCurrentTab) {\n return this.connectCurrentTab.apply(this, args as any);\n }\n\n if (method === BridgeEvent.UpdateAgentStatus) {\n return this.onLogMessage(args[0] as string, 'status');\n }\n\n const tabId = await this.getActiveTabId();\n if (!tabId || tabId === 0) {\n throw new Error('no tab is connected');\n }\n\n // this.onLogMessage(`calling method: ${method}`);\n\n if (method.startsWith(MouseEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof MouseAction;\n if (actionName === 'drag') {\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n return this.mouse[actionName].apply(this.mouse, args as any);\n }\n\n if (method.startsWith(KeyboardEvent.PREFIX)) {\n const actionName = method.split('.')[1] as keyof KeyboardAction;\n if (actionName === 'press') {\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n return this.keyboard[actionName].apply(this.keyboard, args as any);\n }\n\n // Generic method lookup. Methods like `setWaterFlowAnimationEnabled`\n // and `getWaterFlowAnimationEnabled` are inherited from\n // ChromeExtensionProxyPage and found via `this[method]`.\n if (!this[method as keyof ChromeExtensionProxyPage]) {\n this.onLogMessage(`method not found: ${method}`, 'log');\n return undefined;\n }\n\n try {\n // @ts-expect-error\n const result = await this[method as keyof ChromeExtensionProxyPage](\n ...args,\n );\n return result;\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : 'Unknown error';\n this.onLogMessage(\n `Error calling method: ${method}, ${errorMessage}`,\n 'log',\n );\n throw new Error(errorMessage, { cause: e });\n }\n },\n // on disconnect\n () => {\n return this.destroy();\n },\n );\n await this.bridgeClient.connect();\n\n // Show confirmation dialog after connection is established\n if (this.onConnectionRequest) {\n this.onLogMessage('Waiting for user confirmation...', 'log');\n const allowed = await this.onConnectionRequest();\n resolveConfirmationGate(allowed);\n this.confirmationPromise = null;\n\n if (!allowed) {\n this.onLogMessage('Connection denied by user', 'log');\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n throw new Error('Connection denied by user');\n }\n }\n\n this.onLogMessage(\n `Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v${__VERSION__}`,\n 'log',\n );\n }\n\n public async connect() {\n return await this.setupBridgeClient();\n }\n\n public async connectNewTabWithUrl(\n url: string,\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n // Otherwise the animation gets injected during the connection flow\n // before the CLI side's setWaterFlowAnimationEnabled(false) arrives.\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tab = await chrome.tabs.create({ url });\n const tabId = tab.id;\n assert(tabId, 'failed to get tabId after creating a new tab');\n\n // new tab\n this.onLogMessage(`Creating new tab: ${url}`, 'log');\n this.newlyCreatedTabIds.push(tabId);\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n // chrome.tabs.create returns immediately with an about:blank target,\n // then navigates to `url`. If we attach the debugger during that\n // cross-origin transition Site Isolation will detach it again, leaving\n // the first CDP command to fail with \"Debugger is not attached to the\n // tab\". Wait for navigation to settle so the lazy attach lands on a\n // stable target.\n await waitForTabNavigationComplete(tabId, url);\n\n await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\n },\n ) {\n // Set the water-flow animation flag BEFORE any debugger commands are\n // sent (which trigger enableWaterFlowAnimation via sendCommandToDebugger).\n if (options.enableWaterFlowAnimation !== undefined) {\n this.waterFlowAnimationEnabled = options.enableWaterFlowAnimation;\n }\n\n const tabs = await chrome.tabs.query({ active: true, currentWindow: true });\n const tabId = tabs[0]?.id;\n assert(tabId, 'failed to get tabId');\n\n this.onLogMessage(`Connected to current tab: ${tabs[0]?.url}`, 'log');\n\n if (options?.forceSameTabNavigation) {\n this.forceSameTabNavigation = true;\n }\n\n await this.setActiveTabId(tabId);\n }\n\n public async setDestroyOptions(options: ChromePageDestroyOptions) {\n this.destroyOptions = options;\n }\n\n async destroy() {\n if (this.destroyOptions?.closeTab && this.newlyCreatedTabIds.length > 0) {\n this.onLogMessage('Closing all newly created tabs by bridge...', 'log');\n for (const tabId of this.newlyCreatedTabIds) {\n await chrome.tabs.remove(tabId);\n }\n this.newlyCreatedTabIds = [];\n }\n\n await super.destroy();\n\n if (this.bridgeClient) {\n this.bridgeClient.disconnect();\n this.bridgeClient = null;\n this.onDisconnect();\n }\n }\n}\n"],"names":["__webpack_require__","module","getter","definition","key","Object","obj","prop","Symbol","NEW_TAB_LOAD_TIMEOUT_MS","isBlankUrl","url","waitForTabNavigationComplete","tabId","targetUrl","timeoutMs","Promise","resolve","settled","finish","chrome","onUpdated","clearTimeout","timer","isReady","tab","currentUrl","id","_info","setTimeout","ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","BridgeClient","method","args","allowed","Error","BridgeEvent","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","options","undefined","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":";;;IACAA,oBAAoB,CAAC,GAAG,CAACC;QACxB,IAAIC,SAASD,UAAUA,OAAO,UAAU,GACvC,IAAOA,MAAM,CAAC,UAAU,GACxB,IAAOA;QACRD,oBAAoB,CAAC,CAACE,QAAQ;YAAE,GAAGA;QAAO;QAC1C,OAAOA;IACR;;;ICPAF,oBAAoB,CAAC,GAAG,CAAC,UAASG;QACjC,IAAI,IAAIC,OAAOD,WACR,IAAGH,oBAAoB,CAAC,CAACG,YAAYC,QAAQ,CAACJ,oBAAoB,CAAC,CAAC,UAASI,MACzEC,OAAO,cAAc,CAAC,UAASD,KAAK;YAAE,YAAY;YAAM,KAAKD,UAAU,CAACC,IAAI;QAAC;IAGzF;;;ICNAJ,oBAAoB,CAAC,GAAG,CAACM,KAAKC,OAAUF,OAAO,SAAS,CAAC,cAAc,CAAC,IAAI,CAACC,KAAKC;;;ICClFP,oBAAoB,CAAC,GAAG,CAAC;QACxB,IAAG,AAAkB,eAAlB,OAAOQ,UAA0BA,OAAO,WAAW,EACrDH,OAAO,cAAc,CAAC,UAASG,OAAO,WAAW,EAAE;YAAE,OAAO;QAAS;QAEtEH,OAAO,cAAc,CAAC,UAAS,cAAc;YAAE,OAAO;QAAK;IAC5D;;;;;;;;;;;;;;;;;;;;;;ACYA,MAAMI,0BAA0B;AAEhC,SAASC,WAAWC,GAAuB;IACzC,IAAI,CAACA,KAAK,OAAO;IACjB,OAAOA,AAAQ,kBAARA,OAAyBA,IAAI,UAAU,CAAC;AACjD;AAKA,SAASC,6BACPC,KAAa,EACbC,SAAiB,EACjBC,YAAYN,uBAAuB;IAEnC,OAAO,IAAIO,QAAQ,CAACC;QAClB,IAAIC,UAAU;QACd,MAAMC,SAAS;YACb,IAAID,SAAS;YACbA,UAAU;YACV,IAAI;gBACFE,OAAO,IAAI,CAAC,SAAS,CAAC,cAAc,CAACC;YACvC,EAAE,OAAM,CAAC;YACTC,aAAaC;YACbN;QACF;QAEA,MAAMO,UAAU,CAACC;YACf,IAAI,CAACA,KAAK,OAAO;YACjB,IAAIA,AAAe,eAAfA,IAAI,MAAM,EAAiB,OAAO;YACtC,MAAMC,aAAaD,IAAI,GAAG,IAAIA,IAAI,UAAU,IAAI;YAGhD,IAAIf,WAAWgB,eAAe,CAAChB,WAAWI,YAAY,OAAO;YAC7D,OAAO;QACT;QAEA,MAAMO,YAAY,CAChBM,IACAC,OACAH;YAEA,IAAIE,OAAOd,OAAO;YAClB,IAAIW,QAAQC,MAAMN;QACpB;QAEAC,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAACC;QAClC,MAAME,QAAQM,WAAWV,QAAQJ;QAIjCK,OAAO,IAAI,CACR,GAAG,CAACP,OACJ,IAAI,CAAC,CAACY;YACL,IAAID,QAAQC,MAAMN;QACpB,GACC,KAAK,CAAC,KAAO;IAClB;AACF;AAEO,MAAMW,uCAAuCC;IAuBlD,MAAc,oBAAoB;QAChC,MAAMC,WACJ,IAAI,CAAC,cAAc,IAAI,CAAC,eAAe,EAAEC,mCAAAA,uBAAuBA,EAAE;QAMpE,IAAIC,0BAAsD,KAAO;QACjE,IAAI,IAAI,CAAC,mBAAmB,EAC1B,IAAI,CAAC,mBAAmB,GAAG,IAAIlB,QAAiB,CAACC;YAC/CiB,0BAA0BjB;QAC5B;QAGF,IAAI,CAAC,YAAY,GAAG,IAAIkB,sCAAAA,YAAYA,CAClCH,UACA,OAAOI,QAAQC;YAEb,IAAI,IAAI,CAAC,mBAAmB,EAAE;gBAC5B,MAAMC,UAAU,MAAM,IAAI,CAAC,mBAAmB;gBAC9C,IAAI,CAACA,SACH,MAAM,IAAIC,MAAM;YAEpB;YAEA,IAAI,CAAC,YAAY,CAAC,CAAC,2BAA2B,EAAEH,QAAQ,EAAE;YAC1D,IAAIA,WAAWI,mCAAAA,WAAAA,CAAAA,oBAAgC,EAC7C,OAAO,IAAI,CAAC,oBAAoB,CAAC,KAAK,CACpC,IAAI,EACJH;YAIJ,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,iBAA6B,EAC1C,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAEH;YAG5C,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,cAA0B,EACvC,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAEH;YAGzC,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,iBAA6B,EAC1C,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAEH;YAG5C,IAAID,WAAWI,mCAAAA,WAAAA,CAAAA,iBAA6B,EAC1C,OAAO,IAAI,CAAC,YAAY,CAACH,IAAI,CAAC,EAAE,EAAY;YAG9C,MAAMxB,QAAQ,MAAM,IAAI,CAAC,cAAc;YACvC,IAAI,CAACA,SAASA,AAAU,MAAVA,OACZ,MAAM,IAAI0B,MAAM;YAKlB,IAAIH,OAAO,UAAU,CAACK,mCAAAA,UAAAA,CAAAA,MAAiB,GAAG;gBACxC,MAAMC,aAAaN,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE;gBAIvC,OAAO,IAAI,CAAC,KAAK,CAACM,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAEL;YAClD;YAEA,IAAID,OAAO,UAAU,CAACO,mCAAAA,aAAAA,CAAAA,MAAoB,GAAG;gBAC3C,MAAMD,aAAaN,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE;gBAIvC,OAAO,IAAI,CAAC,QAAQ,CAACM,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAEL;YACxD;YAKA,IAAI,CAAC,IAAI,CAACD,OAAyC,EAAE,YACnD,IAAI,CAAC,YAAY,CAAC,CAAC,kBAAkB,EAAEA,QAAQ,EAAE;YAInD,IAAI;gBAEF,MAAMQ,SAAS,MAAM,IAAI,CAACR,OAAyC,IAC9DC;gBAEL,OAAOO;YACT,EAAE,OAAOC,GAAG;gBACV,MAAMC,eAAeD,aAAaN,QAAQM,EAAE,OAAO,GAAG;gBACtD,IAAI,CAAC,YAAY,CACf,CAAC,sBAAsB,EAAET,OAAO,EAAE,EAAEU,cAAc,EAClD;gBAEF,MAAM,IAAIP,MAAMO,cAAc;oBAAE,OAAOD;gBAAE;YAC3C;QACF,GAEA,IACS,IAAI,CAAC,OAAO;QAGvB,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO;QAG/B,IAAI,IAAI,CAAC,mBAAmB,EAAE;YAC5B,IAAI,CAAC,YAAY,CAAC,oCAAoC;YACtD,MAAMP,UAAU,MAAM,IAAI,CAAC,mBAAmB;YAC9CJ,wBAAwBI;YACxB,IAAI,CAAC,mBAAmB,GAAG;YAE3B,IAAI,CAACA,SAAS;gBACZ,IAAI,CAAC,YAAY,CAAC,6BAA6B;gBAC/C,IAAI,CAAC,YAAY,CAAC,UAAU;gBAC5B,IAAI,CAAC,YAAY,GAAG;gBACpB,MAAM,IAAIC,MAAM;YAClB;QACF;QAEA,IAAI,CAAC,YAAY,CACf,uCAAuC,IAAI,CAAC,YAAY,CAAC,aAAa,gCAAwC,EAC9G;IAEJ;IAEA,MAAa,UAAU;QACrB,OAAO,MAAM,IAAI,CAAC,iBAAiB;IACrC;IAEA,MAAa,qBACX5B,GAAW,EACXoC,UAAmC;QACjC,wBAAwB;IAC1B,CAAC,EACD;QAKA,IAAIA,AAAqCC,WAArCD,QAAQ,wBAAwB,EAClC,IAAI,CAAC,yBAAyB,GAAGA,QAAQ,wBAAwB;QAGnE,MAAMtB,MAAM,MAAML,OAAO,IAAI,CAAC,MAAM,CAAC;YAAET;QAAI;QAC3C,MAAME,QAAQY,IAAI,EAAE;QACpBwB,IAAAA,sBAAAA,MAAAA,AAAAA,EAAOpC,OAAO;QAGd,IAAI,CAAC,YAAY,CAAC,CAAC,kBAAkB,EAAEF,KAAK,EAAE;QAC9C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAACE;QAE7B,IAAIkC,SAAS,wBACX,IAAI,CAAC,sBAAsB,GAAG;QAShC,MAAMnC,6BAA6BC,OAAOF;QAE1C,MAAM,IAAI,CAAC,cAAc,CAACE;IAC5B;IAEA,MAAa,kBACXkC,UAAmC;QACjC,wBAAwB;IAC1B,CAAC,EACD;QAGA,IAAIA,AAAqCC,WAArCD,QAAQ,wBAAwB,EAClC,IAAI,CAAC,yBAAyB,GAAGA,QAAQ,wBAAwB;QAGnE,MAAMG,OAAO,MAAM9B,OAAO,IAAI,CAAC,KAAK,CAAC;YAAE,QAAQ;YAAM,eAAe;QAAK;QACzE,MAAMP,QAAQqC,IAAI,CAAC,EAAE,EAAE;QACvBD,IAAAA,sBAAAA,MAAAA,AAAAA,EAAOpC,OAAO;QAEd,IAAI,CAAC,YAAY,CAAC,CAAC,0BAA0B,EAAEqC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE;QAE/D,IAAIH,SAAS,wBACX,IAAI,CAAC,sBAAsB,GAAG;QAGhC,MAAM,IAAI,CAAC,cAAc,CAAClC;IAC5B;IAEA,MAAa,kBAAkBkC,OAAiC,EAAE;QAChE,IAAI,CAAC,cAAc,GAAGA;IACxB;IAEA,MAAM,UAAU;QACd,IAAI,IAAI,CAAC,cAAc,EAAE,YAAY,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,GAAG;YACvE,IAAI,CAAC,YAAY,CAAC,+CAA+C;YACjE,KAAK,MAAMlC,SAAS,IAAI,CAAC,kBAAkB,CACzC,MAAMO,OAAO,IAAI,CAAC,MAAM,CAACP;YAE3B,IAAI,CAAC,kBAAkB,GAAG,EAAE;QAC9B;QAEA,MAAM,KAAK,CAAC;QAEZ,IAAI,IAAI,CAAC,YAAY,EAAE;YACrB,IAAI,CAAC,YAAY,CAAC,UAAU;YAC5B,IAAI,CAAC,YAAY,GAAG;YACpB,IAAI,CAAC,YAAY;QACnB;IACF;IA9NA,YACSsC,cAAuB,EACvBC,eAA2B,KAAO,CAAC,EACnCC,eAGK,KAAO,CAAC,EACpBC,yBAAyB,IAAI,EACtBC,mBAA4C,CACnD;QACA,KAAK,CAACD,yBAAAA,iBAAAA,IAAAA,EAAAA,kBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,gBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,gBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,EAAAA,uBAAAA,KAAAA,IAnBR,uBAAO,gBAAP,SAEA,uBAAQ,kBAAR,SAEA,uBAAQ,sBAAR,SAGA,uBAAQ,uBAAR,cAGSH,cAAc,GAAdA,gBAAAA,IAAAA,CACAC,YAAY,GAAZA,cAAAA,IAAAA,CACAC,YAAY,GAAZA,cAAAA,IAAAA,CAKAE,mBAAmB,GAAnBA,qBAAAA,IAAAA,CAjBF,YAAY,GAAwB,WAInC,kBAAkB,GAAa,EAAE,OAGjC,mBAAmB,GAA4B;IAavD;AAoNF"}
package/dist/lib/cli.js CHANGED
@@ -42,7 +42,7 @@ Promise.resolve().then(()=>{
42
42
  return (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-web', {
43
43
  stripPrefix: 'web_',
44
44
  argv: parsedOptions.argv,
45
- version: "1.10.9-beta-20260804015945.0",
45
+ version: "1.10.9",
46
46
  extraCommands: (0, core_namespaceObject.createReportCliCommands)()
47
47
  });
48
48
  }).catch((e)=>{
@@ -77,7 +77,8 @@ class MidsceneReporter {
77
77
  recursive: true
78
78
  });
79
79
  }
80
- copyReport(reportFilePath, targetPath) {
80
+ finalizeSingleReport(reportFilePath, targetPath) {
81
+ if ((0, external_node_path_namespaceObject.resolve)(reportFilePath) === (0, external_node_path_namespaceObject.resolve)(targetPath)) return;
81
82
  if ((0, report_namespaceObject.isDirectoryModeReport)(reportFilePath)) {
82
83
  const targetDir = (0, external_node_path_namespaceObject.dirname)(targetPath);
83
84
  (0, external_node_fs_namespaceObject.mkdirSync)(targetDir, {
@@ -87,12 +88,23 @@ class MidsceneReporter {
87
88
  recursive: true,
88
89
  force: true
89
90
  });
90
- return;
91
+ } else {
92
+ (0, external_node_fs_namespaceObject.mkdirSync)((0, external_node_path_namespaceObject.dirname)(targetPath), {
93
+ recursive: true
94
+ });
95
+ (0, external_node_fs_namespaceObject.copyFileSync)(reportFilePath, targetPath);
91
96
  }
92
- (0, external_node_fs_namespaceObject.mkdirSync)((0, external_node_path_namespaceObject.dirname)(targetPath), {
93
- recursive: true
97
+ try {
98
+ (0, report_namespaceObject.removeReportArtifact)(reportFilePath);
99
+ } catch (error) {
100
+ (0, utils_namespaceObject.logMsg)(`Error deleting report ${reportFilePath}: ${error}`);
101
+ }
102
+ }
103
+ mergeOwnedReports(tool, targetName) {
104
+ return tool.mergeReports(targetName, {
105
+ overwrite: true,
106
+ rmOriginalReports: true
94
107
  });
95
- (0, external_node_fs_namespaceObject.copyFileSync)(reportFilePath, targetPath);
96
108
  }
97
109
  collectReportInfo(test, result) {
98
110
  const reportAnnotations = test.annotations.filter((annotation)=>'MIDSCENE_DUMP_ANNOTATION' === annotation.type && annotation.description);
@@ -137,19 +149,15 @@ class MidsceneReporter {
137
149
  if (!firstReport) return;
138
150
  if (firstReport.reportFilePath) {
139
151
  const targetPath = this.getReportPath();
140
- this.copyReport(firstReport.reportFilePath, targetPath);
152
+ this.finalizeSingleReport(firstReport.reportFilePath, targetPath);
141
153
  (0, agent_namespaceObject.printReportMsg)(targetPath);
142
154
  return;
143
155
  }
144
- const mergedReportPath = tool.mergeReports(targetName, {
145
- overwrite: true
146
- });
156
+ const mergedReportPath = this.mergeOwnedReports(tool, targetName);
147
157
  if (mergedReportPath) (0, agent_namespaceObject.printReportMsg)(mergedReportPath);
148
158
  return;
149
159
  }
150
- const mergedReportPath = tool.mergeReports(targetName, {
151
- overwrite: true
152
- });
160
+ const mergedReportPath = this.mergeOwnedReports(tool, targetName);
153
161
  if (mergedReportPath) (0, agent_namespaceObject.printReportMsg)(mergedReportPath);
154
162
  }
155
163
  finalizeSeparateReports() {
@@ -160,23 +168,19 @@ class MidsceneReporter {
160
168
  const firstReport = entry.reports[0];
161
169
  if (firstReport.reportFilePath) {
162
170
  const targetPath = this.getReportPath(entry.testTitle);
163
- this.copyReport(firstReport.reportFilePath, targetPath);
171
+ this.finalizeSingleReport(firstReport.reportFilePath, targetPath);
164
172
  (0, agent_namespaceObject.printReportMsg)(targetPath);
165
173
  continue;
166
174
  }
167
175
  const tool = new report_namespaceObject.ReportMergingTool();
168
176
  tool.append(firstReport);
169
- const reportPath = tool.mergeReports(targetName, {
170
- overwrite: true
171
- });
177
+ const reportPath = this.mergeOwnedReports(tool, targetName);
172
178
  if (reportPath) (0, agent_namespaceObject.printReportMsg)(reportPath);
173
179
  continue;
174
180
  }
175
181
  const tool = new report_namespaceObject.ReportMergingTool();
176
182
  for (const report of entry.reports)tool.append(report);
177
- const reportPath = tool.mergeReports(targetName, {
178
- overwrite: true
179
- });
183
+ const reportPath = this.mergeOwnedReports(tool, targetName);
180
184
  if (reportPath) (0, agent_namespaceObject.printReportMsg)(reportPath);
181
185
  }
182
186
  }
@@ -1 +1 @@
1
- {"version":3,"file":"playwright/reporter/index.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../../src/playwright/reporter/index.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { copyFileSync, cpSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type { ReportFileWithAttributes, TestStatus } from '@midscene/core';\nimport { getReportFileName, printReportMsg } from '@midscene/core/agent';\nimport {\n ReportMergingTool,\n isDirectoryModeReport,\n} from '@midscene/core/report';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n logMsg,\n replaceIllegalPathCharsAndSpace,\n} from '@midscene/shared/utils';\nimport type {\n FullConfig,\n Reporter,\n Suite,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\n\ninterface MidsceneReporterOptions {\n type?: 'merged' | 'separate';\n outputFormat?: 'single-html' | 'html-and-external-assets';\n}\n\nclass MidsceneReporter implements Reporter {\n private mergedFilename?: string;\n private testTitleToFilename = new Map<string, string>();\n private reportsByTestId = new Map<\n string,\n {\n testTitle: string;\n reports: ReportFileWithAttributes[];\n }\n >();\n mode?: 'merged' | 'separate';\n outputFormat: 'single-html' | 'html-and-external-assets';\n private hasMultipleProjects = false;\n\n constructor(options: MidsceneReporterOptions = {}) {\n this.mode = MidsceneReporter.getMode(options.type ?? 'merged');\n this.outputFormat = options.outputFormat ?? 'single-html';\n }\n\n private static getMode(reporterType: string): 'merged' | 'separate' {\n if (!reporterType) {\n return 'merged';\n }\n if (reporterType !== 'merged' && reporterType !== 'separate') {\n throw new Error(\n `Unknown reporter type in playwright config: ${reporterType}, only support 'merged' or 'separate'`,\n );\n }\n return reporterType;\n }\n\n private getSeparatedFilename(testTitle: string): string {\n if (!this.testTitleToFilename.has(testTitle)) {\n const baseTag = `playwright-${replaceIllegalPathCharsAndSpace(testTitle)}`;\n const generatedFilename = getReportFileName(baseTag);\n this.testTitleToFilename.set(testTitle, generatedFilename);\n }\n return this.testTitleToFilename.get(testTitle)!;\n }\n\n private getReportFilename(testTitle?: string): string {\n if (this.mode === 'merged') {\n if (!this.mergedFilename) {\n this.mergedFilename = getReportFileName('playwright-merged');\n }\n return this.mergedFilename;\n }\n if (this.mode === 'separate') {\n if (!testTitle) throw new Error('testTitle is required in separate mode');\n return this.getSeparatedFilename(testTitle);\n }\n throw new Error(`Unknown mode: ${this.mode}`);\n }\n\n private getReportPath(testTitle?: string): string {\n const fileName = this.getReportFilename(testTitle);\n if (this.outputFormat === 'html-and-external-assets') {\n return join(getMidsceneRunSubDir('report'), fileName, 'index.html');\n }\n return join(getMidsceneRunSubDir('report'), `${fileName}.html`);\n }\n\n private ensureOutputRoot(): void {\n mkdirSync(getMidsceneRunSubDir('report'), { recursive: true });\n }\n\n private copyReport(reportFilePath: string, targetPath: string): void {\n if (isDirectoryModeReport(reportFilePath)) {\n const targetDir = dirname(targetPath);\n mkdirSync(targetDir, { recursive: true });\n cpSync(dirname(reportFilePath), targetDir, {\n recursive: true,\n force: true,\n });\n return;\n }\n\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(reportFilePath, targetPath);\n }\n\n private collectReportInfo(test: TestCase, result: TestResult) {\n const reportAnnotations = test.annotations.filter((annotation) => {\n return (\n annotation.type === 'MIDSCENE_DUMP_ANNOTATION' && annotation.description\n );\n });\n if (reportAnnotations.length === 0 || !this.mode) {\n return;\n }\n\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n const projectName = this.hasMultipleProjects\n ? test.parent?.project()?.name\n : undefined;\n const projectSuffix = projectName ? ` [${projectName}]` : '';\n const testTitle = `${test.title}${projectSuffix}${retry}`;\n const reports = reportAnnotations\n .map((annotation) => annotation.description!)\n .filter((reportFilePath) => {\n if (existsSync(reportFilePath)) {\n return true;\n }\n logMsg(\n `Failed to read Midscene report file: ${reportFilePath}`,\n new Error('Report file does not exist'),\n );\n return false;\n })\n .map((reportFilePath): ReportFileWithAttributes => {\n return {\n reportFilePath,\n reportAttributes: {\n testDuration: result.duration,\n testStatus: result.status as TestStatus,\n testTitle,\n testId,\n testDescription: test.parent?.title || '',\n },\n };\n });\n\n if (reports.length === 0) {\n return;\n }\n\n this.reportsByTestId.set(testId, {\n testTitle,\n reports,\n });\n }\n\n private finalizeMergedReport(): void {\n this.ensureOutputRoot();\n const tool = new ReportMergingTool();\n let reportCount = 0;\n for (const entry of this.reportsByTestId.values()) {\n for (const report of entry.reports) {\n tool.append(report);\n reportCount += 1;\n }\n }\n\n if (reportCount === 0) {\n return;\n }\n\n const targetName = this.getReportFilename();\n if (reportCount === 1) {\n const firstReport = Array.from(this.reportsByTestId.values())[0]\n ?.reports[0];\n if (!firstReport) {\n return;\n }\n if (firstReport.reportFilePath) {\n const targetPath = this.getReportPath();\n this.copyReport(firstReport.reportFilePath, targetPath);\n printReportMsg(targetPath);\n return;\n }\n\n const mergedReportPath = tool.mergeReports(targetName, {\n overwrite: true,\n });\n if (mergedReportPath) {\n printReportMsg(mergedReportPath);\n }\n return;\n }\n\n const mergedReportPath = tool.mergeReports(targetName, {\n overwrite: true,\n });\n if (mergedReportPath) {\n printReportMsg(mergedReportPath);\n }\n }\n\n private finalizeSeparateReports(): void {\n this.ensureOutputRoot();\n for (const entry of this.reportsByTestId.values()) {\n const targetName = this.getReportFilename(entry.testTitle);\n if (entry.reports.length === 1) {\n const firstReport = entry.reports[0];\n if (firstReport.reportFilePath) {\n const targetPath = this.getReportPath(entry.testTitle);\n this.copyReport(firstReport.reportFilePath, targetPath);\n printReportMsg(targetPath);\n continue;\n }\n\n const tool = new ReportMergingTool();\n tool.append(firstReport);\n const reportPath = tool.mergeReports(targetName, {\n overwrite: true,\n });\n if (reportPath) {\n printReportMsg(reportPath);\n }\n continue;\n }\n\n const tool = new ReportMergingTool();\n for (const report of entry.reports) {\n tool.append(report);\n }\n const reportPath = tool.mergeReports(targetName, {\n overwrite: true,\n });\n if (reportPath) {\n printReportMsg(reportPath);\n }\n }\n }\n\n async onBegin(config: FullConfig, _suite: Suite) {\n this.hasMultipleProjects = (config.projects?.length || 0) > 1;\n }\n\n onTestBegin(_test: TestCase, _result: TestResult) {}\n\n onTestEnd(test: TestCase, result: TestResult) {\n this.collectReportInfo(test, result);\n }\n\n async onEnd() {\n if (this.mode === 'merged') {\n this.finalizeMergedReport();\n return;\n }\n\n if (this.mode === 'separate') {\n this.finalizeSeparateReports();\n }\n }\n}\n\nexport default MidsceneReporter;\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","MidsceneReporter","reporterType","Error","testTitle","baseTag","replaceIllegalPathCharsAndSpace","generatedFilename","getReportFileName","fileName","join","getMidsceneRunSubDir","mkdirSync","reportFilePath","targetPath","isDirectoryModeReport","targetDir","dirname","cpSync","copyFileSync","test","result","reportAnnotations","annotation","retry","testId","projectName","undefined","projectSuffix","reports","existsSync","logMsg","tool","ReportMergingTool","reportCount","entry","report","targetName","firstReport","Array","printReportMsg","mergedReportPath","reportPath","config","_suite","_test","_result","options","Map"],"mappings":"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"}
1
+ {"version":3,"file":"playwright/reporter/index.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../../src/playwright/reporter/index.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { copyFileSync, cpSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport type { ReportFileWithAttributes, TestStatus } from '@midscene/core';\nimport { getReportFileName, printReportMsg } from '@midscene/core/agent';\nimport {\n ReportMergingTool,\n isDirectoryModeReport,\n removeReportArtifact,\n} from '@midscene/core/report';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n logMsg,\n replaceIllegalPathCharsAndSpace,\n} from '@midscene/shared/utils';\nimport type {\n FullConfig,\n Reporter,\n Suite,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\n\ninterface MidsceneReporterOptions {\n type?: 'merged' | 'separate';\n outputFormat?: 'single-html' | 'html-and-external-assets';\n}\n\nclass MidsceneReporter implements Reporter {\n private mergedFilename?: string;\n private testTitleToFilename = new Map<string, string>();\n private reportsByTestId = new Map<\n string,\n {\n testTitle: string;\n reports: ReportFileWithAttributes[];\n }\n >();\n mode?: 'merged' | 'separate';\n outputFormat: 'single-html' | 'html-and-external-assets';\n private hasMultipleProjects = false;\n\n constructor(options: MidsceneReporterOptions = {}) {\n this.mode = MidsceneReporter.getMode(options.type ?? 'merged');\n this.outputFormat = options.outputFormat ?? 'single-html';\n }\n\n private static getMode(reporterType: string): 'merged' | 'separate' {\n if (!reporterType) {\n return 'merged';\n }\n if (reporterType !== 'merged' && reporterType !== 'separate') {\n throw new Error(\n `Unknown reporter type in playwright config: ${reporterType}, only support 'merged' or 'separate'`,\n );\n }\n return reporterType;\n }\n\n private getSeparatedFilename(testTitle: string): string {\n if (!this.testTitleToFilename.has(testTitle)) {\n const baseTag = `playwright-${replaceIllegalPathCharsAndSpace(testTitle)}`;\n const generatedFilename = getReportFileName(baseTag);\n this.testTitleToFilename.set(testTitle, generatedFilename);\n }\n return this.testTitleToFilename.get(testTitle)!;\n }\n\n private getReportFilename(testTitle?: string): string {\n if (this.mode === 'merged') {\n if (!this.mergedFilename) {\n this.mergedFilename = getReportFileName('playwright-merged');\n }\n return this.mergedFilename;\n }\n if (this.mode === 'separate') {\n if (!testTitle) throw new Error('testTitle is required in separate mode');\n return this.getSeparatedFilename(testTitle);\n }\n throw new Error(`Unknown mode: ${this.mode}`);\n }\n\n private getReportPath(testTitle?: string): string {\n const fileName = this.getReportFilename(testTitle);\n if (this.outputFormat === 'html-and-external-assets') {\n return join(getMidsceneRunSubDir('report'), fileName, 'index.html');\n }\n return join(getMidsceneRunSubDir('report'), `${fileName}.html`);\n }\n\n private ensureOutputRoot(): void {\n mkdirSync(getMidsceneRunSubDir('report'), { recursive: true });\n }\n\n private finalizeSingleReport(\n reportFilePath: string,\n targetPath: string,\n ): void {\n if (resolve(reportFilePath) === resolve(targetPath)) {\n return;\n }\n\n if (isDirectoryModeReport(reportFilePath)) {\n const targetDir = dirname(targetPath);\n mkdirSync(targetDir, { recursive: true });\n cpSync(dirname(reportFilePath), targetDir, {\n recursive: true,\n force: true,\n });\n } else {\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(reportFilePath, targetPath);\n }\n\n try {\n removeReportArtifact(reportFilePath);\n } catch (error) {\n logMsg(`Error deleting report ${reportFilePath}: ${error}`);\n }\n }\n\n private mergeOwnedReports(\n tool: ReportMergingTool,\n targetName: string,\n ): string | null {\n return tool.mergeReports(targetName, {\n overwrite: true,\n rmOriginalReports: true,\n });\n }\n\n private collectReportInfo(test: TestCase, result: TestResult) {\n const reportAnnotations = test.annotations.filter((annotation) => {\n return (\n annotation.type === 'MIDSCENE_DUMP_ANNOTATION' && annotation.description\n );\n });\n if (reportAnnotations.length === 0 || !this.mode) {\n return;\n }\n\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n const projectName = this.hasMultipleProjects\n ? test.parent?.project()?.name\n : undefined;\n const projectSuffix = projectName ? ` [${projectName}]` : '';\n const testTitle = `${test.title}${projectSuffix}${retry}`;\n const reports = reportAnnotations\n .map((annotation) => annotation.description!)\n .filter((reportFilePath) => {\n if (existsSync(reportFilePath)) {\n return true;\n }\n logMsg(\n `Failed to read Midscene report file: ${reportFilePath}`,\n new Error('Report file does not exist'),\n );\n return false;\n })\n .map((reportFilePath): ReportFileWithAttributes => {\n return {\n reportFilePath,\n reportAttributes: {\n testDuration: result.duration,\n testStatus: result.status as TestStatus,\n testTitle,\n testId,\n testDescription: test.parent?.title || '',\n },\n };\n });\n\n if (reports.length === 0) {\n return;\n }\n\n this.reportsByTestId.set(testId, {\n testTitle,\n reports,\n });\n }\n\n private finalizeMergedReport(): void {\n this.ensureOutputRoot();\n const tool = new ReportMergingTool();\n let reportCount = 0;\n for (const entry of this.reportsByTestId.values()) {\n for (const report of entry.reports) {\n tool.append(report);\n reportCount += 1;\n }\n }\n\n if (reportCount === 0) {\n return;\n }\n\n const targetName = this.getReportFilename();\n if (reportCount === 1) {\n const firstReport = Array.from(this.reportsByTestId.values())[0]\n ?.reports[0];\n if (!firstReport) {\n return;\n }\n if (firstReport.reportFilePath) {\n const targetPath = this.getReportPath();\n this.finalizeSingleReport(firstReport.reportFilePath, targetPath);\n printReportMsg(targetPath);\n return;\n }\n\n const mergedReportPath = this.mergeOwnedReports(tool, targetName);\n if (mergedReportPath) {\n printReportMsg(mergedReportPath);\n }\n return;\n }\n\n const mergedReportPath = this.mergeOwnedReports(tool, targetName);\n if (mergedReportPath) {\n printReportMsg(mergedReportPath);\n }\n }\n\n private finalizeSeparateReports(): void {\n this.ensureOutputRoot();\n for (const entry of this.reportsByTestId.values()) {\n const targetName = this.getReportFilename(entry.testTitle);\n if (entry.reports.length === 1) {\n const firstReport = entry.reports[0];\n if (firstReport.reportFilePath) {\n const targetPath = this.getReportPath(entry.testTitle);\n this.finalizeSingleReport(firstReport.reportFilePath, targetPath);\n printReportMsg(targetPath);\n continue;\n }\n\n const tool = new ReportMergingTool();\n tool.append(firstReport);\n const reportPath = this.mergeOwnedReports(tool, targetName);\n if (reportPath) {\n printReportMsg(reportPath);\n }\n continue;\n }\n\n const tool = new ReportMergingTool();\n for (const report of entry.reports) {\n tool.append(report);\n }\n const reportPath = this.mergeOwnedReports(tool, targetName);\n if (reportPath) {\n printReportMsg(reportPath);\n }\n }\n }\n\n async onBegin(config: FullConfig, _suite: Suite) {\n this.hasMultipleProjects = (config.projects?.length || 0) > 1;\n }\n\n onTestBegin(_test: TestCase, _result: TestResult) {}\n\n onTestEnd(test: TestCase, result: TestResult) {\n this.collectReportInfo(test, result);\n }\n\n async onEnd() {\n if (this.mode === 'merged') {\n this.finalizeMergedReport();\n return;\n }\n\n if (this.mode === 'separate') {\n this.finalizeSeparateReports();\n }\n }\n}\n\nexport default MidsceneReporter;\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","MidsceneReporter","reporterType","Error","testTitle","baseTag","replaceIllegalPathCharsAndSpace","generatedFilename","getReportFileName","fileName","join","getMidsceneRunSubDir","mkdirSync","reportFilePath","targetPath","resolve","isDirectoryModeReport","targetDir","dirname","cpSync","copyFileSync","removeReportArtifact","error","logMsg","tool","targetName","test","result","reportAnnotations","annotation","retry","testId","projectName","undefined","projectSuffix","reports","existsSync","ReportMergingTool","reportCount","entry","report","firstReport","Array","printReportMsg","mergedReportPath","reportPath","config","_suite","_test","_result","options","Map"],"mappings":"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"}
@@ -16,7 +16,8 @@ declare class MidsceneReporter implements Reporter {
16
16
  private getReportFilename;
17
17
  private getReportPath;
18
18
  private ensureOutputRoot;
19
- private copyReport;
19
+ private finalizeSingleReport;
20
+ private mergeOwnedReports;
20
21
  private collectReportInfo;
21
22
  private finalizeMergedReport;
22
23
  private finalizeSeparateReports;
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "Browser use",
9
9
  "Android use"
10
10
  ],
11
- "version": "1.10.9-beta-20260804015945.0",
11
+ "version": "1.10.9",
12
12
  "repository": "https://github.com/web-infra-dev/midscene",
13
13
  "homepage": "https://midscenejs.com/",
14
14
  "main": "./dist/lib/index.js",
@@ -106,9 +106,9 @@
106
106
  "socket.io": "^4.8.1",
107
107
  "socket.io-client": "4.8.1",
108
108
  "ws": "^8.18.1",
109
- "@midscene/core": "1.10.9-beta-20260804015945.0",
110
- "@midscene/shared": "1.10.9-beta-20260804015945.0",
111
- "@midscene/playground": "1.10.9-beta-20260804015945.0"
109
+ "@midscene/core": "1.10.9",
110
+ "@midscene/shared": "1.10.9",
111
+ "@midscene/playground": "1.10.9"
112
112
  },
113
113
  "devDependencies": {
114
114
  "@playwright/test": "^1.45.0",