@midscene/web 1.6.1-beta-20260331120401.0 → 1.6.1
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.
- package/dist/es/bridge-mode/io-client.mjs +1 -1
- package/dist/es/bridge-mode/io-server.mjs +2 -2
- package/dist/es/bridge-mode/io-server.mjs.map +1 -1
- package/dist/es/bridge-mode/page-browser-side.mjs +1 -1
- package/dist/es/bridge-mode/page-browser-side.mjs.map +1 -1
- package/dist/es/cli.mjs +1 -1
- package/dist/es/mcp-server.mjs +1 -1
- package/dist/es/playwright/reporter/index.mjs +3 -3
- package/dist/es/playwright/reporter/index.mjs.map +1 -1
- package/dist/lib/bridge-mode/io-client.js +1 -1
- package/dist/lib/bridge-mode/io-server.js +2 -2
- package/dist/lib/bridge-mode/io-server.js.map +1 -1
- package/dist/lib/bridge-mode/page-browser-side.js +1 -1
- package/dist/lib/bridge-mode/page-browser-side.js.map +1 -1
- package/dist/lib/cli.js +1 -1
- package/dist/lib/mcp-server.js +1 -1
- package/dist/lib/playwright/reporter/index.js +2 -2
- package/dist/lib/playwright/reporter/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -86,7 +86,7 @@ class BridgeServer {
|
|
|
86
86
|
logMsg('one client connected');
|
|
87
87
|
this.socket = socket;
|
|
88
88
|
const clientVersion = socket.handshake.query.version;
|
|
89
|
-
logMsg(`Bridge connected, cli-side version v1.6.1
|
|
89
|
+
logMsg(`Bridge connected, cli-side version v1.6.1, browser-side version v${clientVersion}`);
|
|
90
90
|
socket.on(BridgeEvent.CallResponse, (params)=>{
|
|
91
91
|
const id = params.id;
|
|
92
92
|
const response = params.response;
|
|
@@ -110,7 +110,7 @@ class BridgeServer {
|
|
|
110
110
|
setTimeout(()=>{
|
|
111
111
|
this.onConnect?.();
|
|
112
112
|
const payload = {
|
|
113
|
-
version: "1.6.1
|
|
113
|
+
version: "1.6.1"
|
|
114
114
|
};
|
|
115
115
|
socket.emit(BridgeEvent.Connected, payload);
|
|
116
116
|
Promise.resolve().then(()=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-mode/io-server.mjs","sources":["../../../src/bridge-mode/io-server.ts"],"sourcesContent":["import { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\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\ndeclare const __VERSION__: string;\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 // console.error('failed to kill port', 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 and start listening on the specified host and port\n const httpServer = createServer();\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 // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\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 (this.socket?.connected) {\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":["killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","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 { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\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\ndeclare const __VERSION__: string;\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 // console.error('failed to kill port', 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 and start listening on the specified host and port\n const httpServer = createServer();\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 // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\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 (this.socket?.connected) {\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":["killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","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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"}
|
|
@@ -65,7 +65,7 @@ class ExtensionBridgePageBrowserSide extends page {
|
|
|
65
65
|
throw new Error('Connection denied by user');
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.6.1
|
|
68
|
+
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.6.1`, 'log');
|
|
69
69
|
}
|
|
70
70
|
async connect() {
|
|
71
71
|
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\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 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 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 await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\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":["ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","Promise","resolve","BridgeClient","method","args","allowed","Error","BridgeEvent","tabId","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","url","options","tab","chrome","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\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 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 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 await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\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":["ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","Promise","resolve","BridgeClient","method","args","allowed","Error","BridgeEvent","tabId","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","url","options","tab","chrome","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":"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"}
|
package/dist/es/cli.mjs
CHANGED
|
@@ -39,7 +39,7 @@ tools = isBridge ? new WebMidsceneTools() : isCdp ? new WebCdpMidsceneTools(cdpE
|
|
|
39
39
|
runToolsCLI(tools, 'midscene-web', {
|
|
40
40
|
stripPrefix: 'web_',
|
|
41
41
|
argv,
|
|
42
|
-
version: "1.6.1
|
|
42
|
+
version: "1.6.1"
|
|
43
43
|
}).catch((e)=>{
|
|
44
44
|
if (!(e instanceof CLIError)) console.error(e);
|
|
45
45
|
process.exit(e instanceof CLIError ? e.exitCode : 1);
|
package/dist/es/mcp-server.mjs
CHANGED
|
@@ -7,7 +7,7 @@ class WebMCPServer extends BaseMCPServer {
|
|
|
7
7
|
constructor(toolsManager){
|
|
8
8
|
super({
|
|
9
9
|
name: '@midscene/web-bridge-mcp',
|
|
10
|
-
version: "1.6.1
|
|
10
|
+
version: "1.6.1",
|
|
11
11
|
description: 'Control the browser using natural language commands'
|
|
12
12
|
}, toolsManager);
|
|
13
13
|
}
|
|
@@ -2,7 +2,7 @@ import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { ReportActionDump } from "@midscene/core";
|
|
4
4
|
import { getReportFileName, printReportMsg } from "@midscene/core/agent";
|
|
5
|
-
import { getReportTpl
|
|
5
|
+
import { getReportTpl } from "@midscene/core/utils";
|
|
6
6
|
import { getMidsceneRunSubDir } from "@midscene/shared/common";
|
|
7
7
|
import { escapeScriptTag, replaceIllegalPathCharsAndSpace } from "@midscene/shared/utils";
|
|
8
8
|
var __webpack_modules__ = {
|
|
@@ -106,12 +106,12 @@ class MidsceneReporter {
|
|
|
106
106
|
flag: 'a'
|
|
107
107
|
});
|
|
108
108
|
else {
|
|
109
|
-
(0, external_node_fs_.writeFileSync)(reportPath,
|
|
109
|
+
(0, external_node_fs_.writeFileSync)(reportPath, tpl + dumpScript, {
|
|
110
110
|
flag: 'w'
|
|
111
111
|
});
|
|
112
112
|
this.mergedReportInitialized = true;
|
|
113
113
|
}
|
|
114
|
-
else (0, external_node_fs_.writeFileSync)(reportPath,
|
|
114
|
+
else (0, external_node_fs_.writeFileSync)(reportPath, tpl + dumpScript, {
|
|
115
115
|
flag: 'w'
|
|
116
116
|
});
|
|
117
117
|
printReportMsg(reportPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright/reporter/index.mjs","sources":["../../../../src/playwright/reporter/index.ts"],"sourcesContent":["import {\n copyFileSync,\n existsSync,\n mkdirSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport {\n ReportActionDump,\n type ReportDumpWithAttributes,\n} from '@midscene/core';\nimport { getReportFileName, printReportMsg } from '@midscene/core/agent';\nimport {\n getReportTpl,\n insertContentBeforeClosingHtml,\n} from '@midscene/core/utils';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n escapeScriptTag,\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 /**\n * Output format for the report.\n * - 'single-html': All screenshots embedded as base64 in a single HTML file (default)\n * - 'html-and-external-assets': Screenshots saved as separate PNG files in a screenshots/ subdirectory\n *\n * Note: 'html-and-external-assets' reports must be served via HTTP server due to CORS restrictions.\n */\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 mode?: 'merged' | 'separate';\n outputFormat: 'single-html' | 'html-and-external-assets';\n\n // Track all temp files created during this test run for cleanup\n private tempFiles = new Set<string>();\n\n // Track pending report updates\n private pendingReports = new Set<Promise<void>>();\n\n // Track whether the merged report file has been initialized\n private mergedReportInitialized = false;\n\n // Write queue to serialize file writes and prevent concurrent write conflicts\n private writeQueue: Promise<void> = Promise.resolve();\n\n // Track whether we have multiple projects (browsers)\n private hasMultipleProjects = false;\n\n // Track written screenshots to avoid duplicates (for directory mode)\n private writtenScreenshots = new Set<string>();\n\n constructor(options: MidsceneReporterOptions = {}) {\n // Set mode from constructor options (official Playwright way)\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 } else 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 /**\n * Get the report path - for directory mode, returns a directory path with index.html\n */\n private getReportPath(testTitle?: string): string {\n const fileName = this.getReportFilename(testTitle);\n if (this.outputFormat === 'html-and-external-assets') {\n // Directory mode: report-name/index.html\n return join(getMidsceneRunSubDir('report'), fileName, 'index.html');\n }\n // Inline mode: report-name.html\n return join(getMidsceneRunSubDir('report'), `${fileName}.html`);\n }\n\n /**\n * Copy screenshots from temp location to report screenshots directory\n */\n private copyScreenshotsToReport(\n tempFilePath: string,\n reportPath: string,\n ): void {\n const screenshotsDir = join(dirname(reportPath), 'screenshots');\n const tempScreenshotsDir = `${tempFilePath}.screenshots`;\n\n if (!existsSync(tempScreenshotsDir)) {\n return;\n }\n\n // Ensure screenshots directory exists\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n // Read screenshot map to get all screenshot IDs\n const screenshotMapPath = `${tempFilePath}.screenshots.json`;\n if (!existsSync(screenshotMapPath)) {\n return;\n }\n\n try {\n const { readFileSync } = require('node:fs');\n const screenshotMap: Record<string, string> = JSON.parse(\n readFileSync(screenshotMapPath, 'utf-8'),\n );\n\n for (const [id, srcPath] of Object.entries(screenshotMap)) {\n // In merged mode, skip if already written to avoid duplicates\n // In separate mode, each test has its own screenshots directory\n if (this.mode === 'merged' && this.writtenScreenshots.has(id)) {\n continue;\n }\n\n const destPath = join(screenshotsDir, `${id}.png`);\n\n if (existsSync(srcPath)) {\n copyFileSync(srcPath, destPath);\n if (this.mode === 'merged') {\n this.writtenScreenshots.add(id);\n }\n }\n }\n } catch (error) {\n console.error('Error copying screenshots:', error);\n }\n }\n\n private async updateReport(testData: ReportDumpWithAttributes) {\n if (!testData || !this.mode) return;\n\n // Queue the write operation to prevent concurrent writes to the same file\n this.writeQueue = this.writeQueue.then(async () => {\n const reportPath = this.getReportPath(\n testData.attributes?.playwright_test_title,\n );\n\n // Ensure report directory exists for directory mode\n if (this.outputFormat === 'html-and-external-assets') {\n const reportDir = dirname(reportPath);\n if (!existsSync(reportDir)) {\n mkdirSync(reportDir, { recursive: true });\n }\n }\n\n // Get report template\n const tpl = getReportTpl();\n if (!tpl) {\n throw new Error(\n 'Report template not found. Ensure @midscene/core is built correctly.',\n );\n }\n\n // Parse the dump string and generate dump script tag\n let dumpScript = `<script type=\"midscene_web_dump\">\\n${escapeScriptTag(testData.dumpString)}\\n</script>`;\n\n if (testData.attributes) {\n const attributesArr = Object.keys(testData.attributes).map((key) => {\n return `${key}=\"${encodeURIComponent(testData.attributes![key])}\"`;\n });\n // Add attributes to the script tag\n dumpScript = dumpScript.replace(\n '<script type=\"midscene_web_dump\"',\n `<script type=\"midscene_web_dump\" ${attributesArr.join(' ')}`,\n );\n }\n\n // Write or append to file\n if (this.mode === 'merged') {\n // For merged report, write template + dump on first write, then only append dumps\n if (!this.mergedReportInitialized) {\n writeFileSync(\n reportPath,\n insertContentBeforeClosingHtml(tpl, dumpScript),\n { flag: 'w' },\n );\n this.mergedReportInitialized = true;\n } else {\n // Append only the dump scripts for subsequent tests\n writeFileSync(reportPath, dumpScript, { flag: 'a' });\n }\n } else {\n // For separate reports, write each test to its own file with template\n writeFileSync(\n reportPath,\n insertContentBeforeClosingHtml(tpl, dumpScript),\n { flag: 'w' },\n );\n }\n\n printReportMsg(reportPath);\n });\n\n await this.writeQueue;\n }\n\n async onBegin(config: FullConfig, suite: Suite) {\n // Check if we have multiple projects to determine if we need browser labels\n this.hasMultipleProjects = (config.projects?.length || 0) > 1;\n }\n\n onTestBegin(_test: TestCase, _result: TestResult) {\n // logger(`Starting test ${test.title}`);\n }\n\n onTestEnd(test: TestCase, result: TestResult) {\n const dumpAnnotation = test.annotations.find((annotation) => {\n return annotation.type === 'MIDSCENE_DUMP_ANNOTATION';\n });\n if (!dumpAnnotation?.description) return;\n\n const tempFilePath = dumpAnnotation.description;\n\n // Track temp files for potential cleanup in onEnd\n for (const filePath of ReportActionDump.getFilePaths(tempFilePath)) {\n this.tempFiles.add(filePath);\n }\n\n let dumpString: string | undefined;\n\n try {\n if (this.outputFormat === 'html-and-external-assets') {\n // Directory mode: keep { $screenshot: id } format, copy screenshots to report dir\n const { readFileSync } = require('node:fs');\n dumpString = readFileSync(tempFilePath, 'utf-8');\n\n // Get report path and copy screenshots\n const retry = result.retry ? `(retry #${result.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 reportPath = this.getReportPath(testTitle);\n\n this.copyScreenshotsToReport(tempFilePath, reportPath);\n } else {\n // Inline mode: convert screenshots to base64\n dumpString = ReportActionDump.fromFilesAsInlineJson(tempFilePath);\n }\n } catch (error) {\n console.error(\n `Failed to read Midscene dump file: ${tempFilePath}`,\n error,\n );\n // Don't return here - we still need to clean up the temp file\n }\n\n // Only update report if we successfully read the dump\n if (dumpString) {\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n\n // Get the project name (browser name) only if we have multiple projects\n const projectName = this.hasMultipleProjects\n ? test.parent?.project()?.name\n : undefined;\n const projectSuffix = projectName ? ` [${projectName}]` : '';\n\n const testData: ReportDumpWithAttributes = {\n dumpString,\n attributes: {\n 'data-group-id': testId,\n playwright_test_id: testId,\n playwright_test_title: `${test.title}${projectSuffix}${retry}`,\n playwright_test_status: result.status,\n playwright_test_duration: result.duration,\n },\n };\n\n // Start async report update and track it\n const reportPromise = this.updateReport(testData)\n .catch((error) => {\n console.error('Error updating report:', error);\n })\n .finally(() => {\n this.pendingReports.delete(reportPromise);\n });\n this.pendingReports.add(reportPromise);\n }\n\n // Always try to clean up temp files\n try {\n ReportActionDump.cleanupFiles(tempFilePath);\n for (const filePath of ReportActionDump.getFilePaths(tempFilePath)) {\n this.tempFiles.delete(filePath);\n }\n } catch {\n // Keep in tempFiles for cleanup in onEnd\n }\n }\n\n async onEnd() {\n // Wait for all pending report updates to complete\n if (this.pendingReports.size > 0) {\n console.log(\n `Midscene: Waiting for ${this.pendingReports.size} pending report(s) to complete...`,\n );\n await Promise.all(Array.from(this.pendingReports));\n }\n\n // Print directory mode notice (only for merged mode)\n if (\n this.outputFormat === 'html-and-external-assets' &&\n this.mode === 'merged'\n ) {\n const reportPath = this.getReportPath();\n const reportDir = dirname(reportPath);\n console.log('[Midscene] Directory report generated.');\n console.log(\n '[Midscene] Note: This report must be served via HTTP server due to CORS restrictions.',\n );\n console.log(`[Midscene] Example: npx serve ${reportDir}`);\n } else if (\n this.outputFormat === 'html-and-external-assets' &&\n this.mode === 'separate'\n ) {\n const reportBaseDir = getMidsceneRunSubDir('report');\n console.log('[Midscene] Directory reports generated.');\n console.log(\n '[Midscene] Note: Reports must be served via HTTP server due to CORS restrictions.',\n );\n console.log(`[Midscene] Example: npx serve ${reportBaseDir}`);\n }\n\n // Clean up any remaining temp files that weren't deleted in onTestEnd\n if (this.tempFiles.size > 0) {\n console.log(\n `Midscene: Cleaning up ${this.tempFiles.size} remaining temp file(s)...`,\n );\n\n for (const filePath of this.tempFiles) {\n try {\n rmSync(filePath, { force: true });\n } catch (error) {\n // Silently ignore - file may have been deleted already\n }\n }\n\n this.tempFiles.clear();\n }\n }\n}\n\nexport default MidsceneReporter;\n"],"names":["MidsceneReporter","reporterType","Error","testTitle","baseTag","replaceIllegalPathCharsAndSpace","generatedFilename","getReportFileName","fileName","join","getMidsceneRunSubDir","tempFilePath","reportPath","screenshotsDir","dirname","tempScreenshotsDir","existsSync","mkdirSync","screenshotMapPath","readFileSync","require","screenshotMap","JSON","id","srcPath","Object","destPath","copyFileSync","error","console","testData","reportDir","tpl","getReportTpl","dumpScript","escapeScriptTag","attributesArr","key","encodeURIComponent","writeFileSync","insertContentBeforeClosingHtml","printReportMsg","config","suite","_test","_result","test","result","dumpAnnotation","annotation","filePath","ReportActionDump","dumpString","retry","projectName","undefined","projectSuffix","testId","reportPromise","Promise","Array","reportBaseDir","rmSync","options","Map","Set"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAMA;IA8BJ,OAAe,QAAQC,YAAoB,EAAyB;QAClE,IAAI,CAACA,cACH,OAAO;QAET,IAAIA,AAAiB,aAAjBA,gBAA6BA,AAAiB,eAAjBA,cAC/B,MAAM,IAAIC,MACR,CAAC,4CAA4C,EAAED,aAAa,qCAAqC,CAAC;QAGtG,OAAOA;IACT;IAEQ,qBAAqBE,SAAiB,EAAU;QACtD,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAACA,YAAY;YAC5C,MAAMC,UAAU,CAAC,WAAW,EAAEC,gCAAgCF,YAAY;YAC1E,MAAMG,oBAAoBC,kBAAkBH;YAC5C,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAACD,WAAWG;QAC1C;QACA,OAAO,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAACH;IACtC;IAEQ,kBAAkBA,SAAkB,EAAU;QACpD,IAAI,AAAc,aAAd,IAAI,CAAC,IAAI,EAAe;YAC1B,IAAI,CAAC,IAAI,CAAC,cAAc,EACtB,IAAI,CAAC,cAAc,GAAGI,kBAAkB;YAE1C,OAAO,IAAI,CAAC,cAAc;QAC5B;QAAO,IAAI,AAAc,eAAd,IAAI,CAAC,IAAI,EAAiB;YACnC,IAAI,CAACJ,WAAW,MAAM,IAAID,MAAM;YAChC,OAAO,IAAI,CAAC,oBAAoB,CAACC;QACnC;QACA,MAAM,IAAID,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,EAAE;IAC9C;IAKQ,cAAcC,SAAkB,EAAU;QAChD,MAAMK,WAAW,IAAI,CAAC,iBAAiB,CAACL;QACxC,IAAI,AAAsB,+BAAtB,IAAI,CAAC,YAAY,EAEnB,OAAOM,KAAKC,qBAAqB,WAAWF,UAAU;QAGxD,OAAOC,KAAKC,qBAAqB,WAAW,GAAGF,SAAS,KAAK,CAAC;IAChE;IAKQ,wBACNG,YAAoB,EACpBC,UAAkB,EACZ;QACN,MAAMC,iBAAiBJ,KAAKK,QAAQF,aAAa;QACjD,MAAMG,qBAAqB,GAAGJ,aAAa,YAAY,CAAC;QAExD,IAAI,CAACK,AAAAA,IAAAA,kBAAAA,UAAAA,AAAAA,EAAWD,qBACd;QAIF,IAAI,CAACC,AAAAA,IAAAA,kBAAAA,UAAAA,AAAAA,EAAWH,iBACdI,AAAAA,IAAAA,kBAAAA,SAAAA,AAAAA,EAAUJ,gBAAgB;YAAE,WAAW;QAAK;QAI9C,MAAMK,oBAAoB,GAAGP,aAAa,iBAAiB,CAAC;QAC5D,IAAI,CAACK,AAAAA,IAAAA,kBAAAA,UAAAA,AAAAA,EAAWE,oBACd;QAGF,IAAI;YACF,MAAM,EAAEC,YAAY,EAAE,GAAGC,oBAAQ;YACjC,MAAMC,gBAAwCC,KAAK,KAAK,CACtDH,aAAaD,mBAAmB;YAGlC,KAAK,MAAM,CAACK,IAAIC,QAAQ,IAAIC,OAAO,OAAO,CAACJ,eAAgB;gBAGzD,IAAI,AAAc,aAAd,IAAI,CAAC,IAAI,IAAiB,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAACE,KACxD;gBAGF,MAAMG,WAAWjB,KAAKI,gBAAgB,GAAGU,GAAG,IAAI,CAAC;gBAEjD,IAAIP,AAAAA,IAAAA,kBAAAA,UAAAA,AAAAA,EAAWQ,UAAU;oBACvBG,IAAAA,kBAAAA,YAAAA,AAAAA,EAAaH,SAASE;oBACtB,IAAI,AAAc,aAAd,IAAI,CAAC,IAAI,EACX,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAACH;gBAEhC;YACF;QACF,EAAE,OAAOK,OAAO;YACdC,QAAQ,KAAK,CAAC,8BAA8BD;QAC9C;IACF;IAEA,MAAc,aAAaE,QAAkC,EAAE;QAC7D,IAAI,CAACA,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE;QAG7B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YACrC,MAAMlB,aAAa,IAAI,CAAC,aAAa,CACnCkB,SAAS,UAAU,EAAE;YAIvB,IAAI,AAAsB,+BAAtB,IAAI,CAAC,YAAY,EAAiC;gBACpD,MAAMC,YAAYjB,QAAQF;gBAC1B,IAAI,CAACI,AAAAA,IAAAA,kBAAAA,UAAAA,AAAAA,EAAWe,YACdd,AAAAA,IAAAA,kBAAAA,SAAAA,AAAAA,EAAUc,WAAW;oBAAE,WAAW;gBAAK;YAE3C;YAGA,MAAMC,MAAMC;YACZ,IAAI,CAACD,KACH,MAAM,IAAI9B,MACR;YAKJ,IAAIgC,aAAa,CAAC,mCAAmC,EAAEC,gBAAgBL,SAAS,UAAU,EAAE,WAAW,CAAC;YAExG,IAAIA,SAAS,UAAU,EAAE;gBACvB,MAAMM,gBAAgBX,OAAO,IAAI,CAACK,SAAS,UAAU,EAAE,GAAG,CAAC,CAACO,MACnD,GAAGA,IAAI,EAAE,EAAEC,mBAAmBR,SAAS,UAAW,CAACO,IAAI,EAAE,CAAC,CAAC;gBAGpEH,aAAaA,WAAW,OAAO,CAC7B,oCACA,CAAC,iCAAiC,EAAEE,cAAc,IAAI,CAAC,MAAM;YAEjE;YAGA,IAAI,AAAc,aAAd,IAAI,CAAC,IAAI,EAEX,IAAK,IAAI,CAAC,uBAAuB,EAS/BG,AAAAA,IAAAA,kBAAAA,aAAAA,AAAAA,EAAc3B,YAAYsB,YAAY;gBAAE,MAAM;YAAI;iBATjB;gBACjCK,IAAAA,kBAAAA,aAAAA,AAAAA,EACE3B,YACA4B,+BAA+BR,KAAKE,aACpC;oBAAE,MAAM;gBAAI;gBAEd,IAAI,CAAC,uBAAuB,GAAG;YACjC;iBAMAK,AAAAA,IAAAA,kBAAAA,aAAAA,AAAAA,EACE3B,YACA4B,+BAA+BR,KAAKE,aACpC;gBAAE,MAAM;YAAI;YAIhBO,eAAe7B;QACjB;QAEA,MAAM,IAAI,CAAC,UAAU;IACvB;IAEA,MAAM,QAAQ8B,MAAkB,EAAEC,KAAY,EAAE;QAE9C,IAAI,CAAC,mBAAmB,GAAID,AAAAA,CAAAA,OAAO,QAAQ,EAAE,UAAU,KAAK;IAC9D;IAEA,YAAYE,KAAe,EAAEC,OAAmB,EAAE,CAElD;IAEA,UAAUC,IAAc,EAAEC,MAAkB,EAAE;QAC5C,MAAMC,iBAAiBF,KAAK,WAAW,CAAC,IAAI,CAAC,CAACG,aACrCA,AAAoB,+BAApBA,WAAW,IAAI;QAExB,IAAI,CAACD,gBAAgB,aAAa;QAElC,MAAMrC,eAAeqC,eAAe,WAAW;QAG/C,KAAK,MAAME,YAAYC,iBAAiB,YAAY,CAACxC,cACnD,IAAI,CAAC,SAAS,CAAC,GAAG,CAACuC;QAGrB,IAAIE;QAEJ,IAAI;YACF,IAAI,AAAsB,+BAAtB,IAAI,CAAC,YAAY,EAAiC;gBAEpD,MAAM,EAAEjC,YAAY,EAAE,GAAGC,oBAAQ;gBACjCgC,aAAajC,aAAaR,cAAc;gBAGxC,MAAM0C,QAAQN,OAAO,KAAK,GAAG,CAAC,QAAQ,EAAEA,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG;gBAC1D,MAAMO,cAAc,IAAI,CAAC,mBAAmB,GACxCR,KAAK,MAAM,EAAE,WAAW,OACxBS;gBACJ,MAAMC,gBAAgBF,cAAc,CAAC,EAAE,EAAEA,YAAY,CAAC,CAAC,GAAG;gBAC1D,MAAMnD,YAAY,GAAG2C,KAAK,KAAK,GAAGU,gBAAgBH,OAAO;gBACzD,MAAMzC,aAAa,IAAI,CAAC,aAAa,CAACT;gBAEtC,IAAI,CAAC,uBAAuB,CAACQ,cAAcC;YAC7C,OAEEwC,aAAaD,iBAAiB,qBAAqB,CAACxC;QAExD,EAAE,OAAOiB,OAAO;YACdC,QAAQ,KAAK,CACX,CAAC,mCAAmC,EAAElB,cAAc,EACpDiB;QAGJ;QAGA,IAAIwB,YAAY;YACd,MAAMC,QAAQN,OAAO,KAAK,GAAG,CAAC,QAAQ,EAAEA,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG;YAC1D,MAAMU,SAAS,GAAGX,KAAK,EAAE,GAAGO,OAAO;YAGnC,MAAMC,cAAc,IAAI,CAAC,mBAAmB,GACxCR,KAAK,MAAM,EAAE,WAAW,OACxBS;YACJ,MAAMC,gBAAgBF,cAAc,CAAC,EAAE,EAAEA,YAAY,CAAC,CAAC,GAAG;YAE1D,MAAMxB,WAAqC;gBACzCsB;gBACA,YAAY;oBACV,iBAAiBK;oBACjB,oBAAoBA;oBACpB,uBAAuB,GAAGX,KAAK,KAAK,GAAGU,gBAAgBH,OAAO;oBAC9D,wBAAwBN,OAAO,MAAM;oBACrC,0BAA0BA,OAAO,QAAQ;gBAC3C;YACF;YAGA,MAAMW,gBAAgB,IAAI,CAAC,YAAY,CAAC5B,UACrC,KAAK,CAAC,CAACF;gBACNC,QAAQ,KAAK,CAAC,0BAA0BD;YAC1C,GACC,OAAO,CAAC;gBACP,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC8B;YAC7B;YACF,IAAI,CAAC,cAAc,CAAC,GAAG,CAACA;QAC1B;QAGA,IAAI;YACFP,iBAAiB,YAAY,CAACxC;YAC9B,KAAK,MAAMuC,YAAYC,iBAAiB,YAAY,CAACxC,cACnD,IAAI,CAAC,SAAS,CAAC,MAAM,CAACuC;QAE1B,EAAE,OAAM,CAER;IACF;IAEA,MAAM,QAAQ;QAEZ,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,GAAG,GAAG;YAChCrB,QAAQ,GAAG,CACT,CAAC,sBAAsB,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,iCAAiC,CAAC;YAEtF,MAAM8B,QAAQ,GAAG,CAACC,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc;QAClD;QAGA,IACE,AAAsB,+BAAtB,IAAI,CAAC,YAAY,IACjB,AAAc,aAAd,IAAI,CAAC,IAAI,EACT;YACA,MAAMhD,aAAa,IAAI,CAAC,aAAa;YACrC,MAAMmB,YAAYjB,QAAQF;YAC1BiB,QAAQ,GAAG,CAAC;YACZA,QAAQ,GAAG,CACT;YAEFA,QAAQ,GAAG,CAAC,CAAC,8BAA8B,EAAEE,WAAW;QAC1D,OAAO,IACL,AAAsB,+BAAtB,IAAI,CAAC,YAAY,IACjB,AAAc,eAAd,IAAI,CAAC,IAAI,EACT;YACA,MAAM8B,gBAAgBnD,qBAAqB;YAC3CmB,QAAQ,GAAG,CAAC;YACZA,QAAQ,GAAG,CACT;YAEFA,QAAQ,GAAG,CAAC,CAAC,8BAA8B,EAAEgC,eAAe;QAC9D;QAGA,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,GAAG;YAC3BhC,QAAQ,GAAG,CACT,CAAC,sBAAsB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,0BAA0B,CAAC;YAG1E,KAAK,MAAMqB,YAAY,IAAI,CAAC,SAAS,CACnC,IAAI;gBACFY,IAAAA,kBAAAA,MAAAA,AAAAA,EAAOZ,UAAU;oBAAE,OAAO;gBAAK;YACjC,EAAE,OAAOtB,OAAO,CAEhB;YAGF,IAAI,CAAC,SAAS,CAAC,KAAK;QACtB;IACF;IA9TA,YAAYmC,UAAmC,CAAC,CAAC,CAAE;QAvBnD,uBAAQ,kBAAR;QACA,uBAAQ,uBAAsB,IAAIC;QAClC;QACA;QAGA,uBAAQ,aAAY,IAAIC;QAGxB,uBAAQ,kBAAiB,IAAIA;QAG7B,uBAAQ,2BAA0B;QAGlC,uBAAQ,cAA4BN,QAAQ,OAAO;QAGnD,uBAAQ,uBAAsB;QAG9B,uBAAQ,sBAAqB,IAAIM;QAI/B,IAAI,CAAC,IAAI,GAAGjE,iBAAiB,OAAO,CAAC+D,QAAQ,IAAI,IAAI;QACrD,IAAI,CAAC,YAAY,GAAGA,QAAQ,YAAY,IAAI;IAC9C;AA2TF;AAEA,iBAAe/D"}
|
|
1
|
+
{"version":3,"file":"playwright/reporter/index.mjs","sources":["../../../../src/playwright/reporter/index.ts"],"sourcesContent":["import {\n copyFileSync,\n existsSync,\n mkdirSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport {\n ReportActionDump,\n type ReportDumpWithAttributes,\n} from '@midscene/core';\nimport { getReportFileName, printReportMsg } from '@midscene/core/agent';\nimport { getReportTpl } from '@midscene/core/utils';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n escapeScriptTag,\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 /**\n * Output format for the report.\n * - 'single-html': All screenshots embedded as base64 in a single HTML file (default)\n * - 'html-and-external-assets': Screenshots saved as separate PNG files in a screenshots/ subdirectory\n *\n * Note: 'html-and-external-assets' reports must be served via HTTP server due to CORS restrictions.\n */\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 mode?: 'merged' | 'separate';\n outputFormat: 'single-html' | 'html-and-external-assets';\n\n // Track all temp files created during this test run for cleanup\n private tempFiles = new Set<string>();\n\n // Track pending report updates\n private pendingReports = new Set<Promise<void>>();\n\n // Track whether the merged report file has been initialized\n private mergedReportInitialized = false;\n\n // Write queue to serialize file writes and prevent concurrent write conflicts\n private writeQueue: Promise<void> = Promise.resolve();\n\n // Track whether we have multiple projects (browsers)\n private hasMultipleProjects = false;\n\n // Track written screenshots to avoid duplicates (for directory mode)\n private writtenScreenshots = new Set<string>();\n\n constructor(options: MidsceneReporterOptions = {}) {\n // Set mode from constructor options (official Playwright way)\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 } else 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 /**\n * Get the report path - for directory mode, returns a directory path with index.html\n */\n private getReportPath(testTitle?: string): string {\n const fileName = this.getReportFilename(testTitle);\n if (this.outputFormat === 'html-and-external-assets') {\n // Directory mode: report-name/index.html\n return join(getMidsceneRunSubDir('report'), fileName, 'index.html');\n }\n // Inline mode: report-name.html\n return join(getMidsceneRunSubDir('report'), `${fileName}.html`);\n }\n\n /**\n * Copy screenshots from temp location to report screenshots directory\n */\n private copyScreenshotsToReport(\n tempFilePath: string,\n reportPath: string,\n ): void {\n const screenshotsDir = join(dirname(reportPath), 'screenshots');\n const tempScreenshotsDir = `${tempFilePath}.screenshots`;\n\n if (!existsSync(tempScreenshotsDir)) {\n return;\n }\n\n // Ensure screenshots directory exists\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n // Read screenshot map to get all screenshot IDs\n const screenshotMapPath = `${tempFilePath}.screenshots.json`;\n if (!existsSync(screenshotMapPath)) {\n return;\n }\n\n try {\n const { readFileSync } = require('node:fs');\n const screenshotMap: Record<string, string> = JSON.parse(\n readFileSync(screenshotMapPath, 'utf-8'),\n );\n\n for (const [id, srcPath] of Object.entries(screenshotMap)) {\n // In merged mode, skip if already written to avoid duplicates\n // In separate mode, each test has its own screenshots directory\n if (this.mode === 'merged' && this.writtenScreenshots.has(id)) {\n continue;\n }\n\n const destPath = join(screenshotsDir, `${id}.png`);\n\n if (existsSync(srcPath)) {\n copyFileSync(srcPath, destPath);\n if (this.mode === 'merged') {\n this.writtenScreenshots.add(id);\n }\n }\n }\n } catch (error) {\n console.error('Error copying screenshots:', error);\n }\n }\n\n private async updateReport(testData: ReportDumpWithAttributes) {\n if (!testData || !this.mode) return;\n\n // Queue the write operation to prevent concurrent writes to the same file\n this.writeQueue = this.writeQueue.then(async () => {\n const reportPath = this.getReportPath(\n testData.attributes?.playwright_test_title,\n );\n\n // Ensure report directory exists for directory mode\n if (this.outputFormat === 'html-and-external-assets') {\n const reportDir = dirname(reportPath);\n if (!existsSync(reportDir)) {\n mkdirSync(reportDir, { recursive: true });\n }\n }\n\n // Get report template\n const tpl = getReportTpl();\n if (!tpl) {\n throw new Error(\n 'Report template not found. Ensure @midscene/core is built correctly.',\n );\n }\n\n // Parse the dump string and generate dump script tag\n let dumpScript = `<script type=\"midscene_web_dump\">\\n${escapeScriptTag(testData.dumpString)}\\n</script>`;\n\n if (testData.attributes) {\n const attributesArr = Object.keys(testData.attributes).map((key) => {\n return `${key}=\"${encodeURIComponent(testData.attributes![key])}\"`;\n });\n // Add attributes to the script tag\n dumpScript = dumpScript.replace(\n '<script type=\"midscene_web_dump\"',\n `<script type=\"midscene_web_dump\" ${attributesArr.join(' ')}`,\n );\n }\n\n // Write or append to file\n if (this.mode === 'merged') {\n // For merged report, write template + dump on first write, then only append dumps\n if (!this.mergedReportInitialized) {\n writeFileSync(reportPath, tpl + dumpScript, { flag: 'w' });\n this.mergedReportInitialized = true;\n } else {\n // Append only the dump scripts for subsequent tests\n writeFileSync(reportPath, dumpScript, { flag: 'a' });\n }\n } else {\n // For separate reports, write each test to its own file with template\n writeFileSync(reportPath, tpl + dumpScript, { flag: 'w' });\n }\n\n printReportMsg(reportPath);\n });\n\n await this.writeQueue;\n }\n\n async onBegin(config: FullConfig, suite: Suite) {\n // Check if we have multiple projects to determine if we need browser labels\n this.hasMultipleProjects = (config.projects?.length || 0) > 1;\n }\n\n onTestBegin(_test: TestCase, _result: TestResult) {\n // logger(`Starting test ${test.title}`);\n }\n\n onTestEnd(test: TestCase, result: TestResult) {\n const dumpAnnotation = test.annotations.find((annotation) => {\n return annotation.type === 'MIDSCENE_DUMP_ANNOTATION';\n });\n if (!dumpAnnotation?.description) return;\n\n const tempFilePath = dumpAnnotation.description;\n\n // Track temp files for potential cleanup in onEnd\n for (const filePath of ReportActionDump.getFilePaths(tempFilePath)) {\n this.tempFiles.add(filePath);\n }\n\n let dumpString: string | undefined;\n\n try {\n if (this.outputFormat === 'html-and-external-assets') {\n // Directory mode: keep { $screenshot: id } format, copy screenshots to report dir\n const { readFileSync } = require('node:fs');\n dumpString = readFileSync(tempFilePath, 'utf-8');\n\n // Get report path and copy screenshots\n const retry = result.retry ? `(retry #${result.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 reportPath = this.getReportPath(testTitle);\n\n this.copyScreenshotsToReport(tempFilePath, reportPath);\n } else {\n // Inline mode: convert screenshots to base64\n dumpString = ReportActionDump.fromFilesAsInlineJson(tempFilePath);\n }\n } catch (error) {\n console.error(\n `Failed to read Midscene dump file: ${tempFilePath}`,\n error,\n );\n // Don't return here - we still need to clean up the temp file\n }\n\n // Only update report if we successfully read the dump\n if (dumpString) {\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n\n // Get the project name (browser name) only if we have multiple projects\n const projectName = this.hasMultipleProjects\n ? test.parent?.project()?.name\n : undefined;\n const projectSuffix = projectName ? ` [${projectName}]` : '';\n\n const testData: ReportDumpWithAttributes = {\n dumpString,\n attributes: {\n 'data-group-id': testId,\n playwright_test_id: testId,\n playwright_test_title: `${test.title}${projectSuffix}${retry}`,\n playwright_test_status: result.status,\n playwright_test_duration: result.duration,\n },\n };\n\n // Start async report update and track it\n const reportPromise = this.updateReport(testData)\n .catch((error) => {\n console.error('Error updating report:', error);\n })\n .finally(() => {\n this.pendingReports.delete(reportPromise);\n });\n this.pendingReports.add(reportPromise);\n }\n\n // Always try to clean up temp files\n try {\n ReportActionDump.cleanupFiles(tempFilePath);\n for (const filePath of ReportActionDump.getFilePaths(tempFilePath)) {\n this.tempFiles.delete(filePath);\n }\n } catch {\n // Keep in tempFiles for cleanup in onEnd\n }\n }\n\n async onEnd() {\n // Wait for all pending report updates to complete\n if (this.pendingReports.size > 0) {\n console.log(\n `Midscene: Waiting for ${this.pendingReports.size} pending report(s) to complete...`,\n );\n await Promise.all(Array.from(this.pendingReports));\n }\n\n // Print directory mode notice (only for merged mode)\n if (\n this.outputFormat === 'html-and-external-assets' &&\n this.mode === 'merged'\n ) {\n const reportPath = this.getReportPath();\n const reportDir = dirname(reportPath);\n console.log('[Midscene] Directory report generated.');\n console.log(\n '[Midscene] Note: This report must be served via HTTP server due to CORS restrictions.',\n );\n console.log(`[Midscene] Example: npx serve ${reportDir}`);\n } else if (\n this.outputFormat === 'html-and-external-assets' &&\n this.mode === 'separate'\n ) {\n const reportBaseDir = getMidsceneRunSubDir('report');\n console.log('[Midscene] Directory reports generated.');\n console.log(\n '[Midscene] Note: Reports must be served via HTTP server due to CORS restrictions.',\n );\n console.log(`[Midscene] Example: npx serve ${reportBaseDir}`);\n }\n\n // Clean up any remaining temp files that weren't deleted in onTestEnd\n if (this.tempFiles.size > 0) {\n console.log(\n `Midscene: Cleaning up ${this.tempFiles.size} remaining temp file(s)...`,\n );\n\n for (const filePath of this.tempFiles) {\n try {\n rmSync(filePath, { force: true });\n } catch (error) {\n // Silently ignore - file may have been deleted already\n }\n }\n\n this.tempFiles.clear();\n }\n }\n}\n\nexport default MidsceneReporter;\n"],"names":["MidsceneReporter","reporterType","Error","testTitle","baseTag","replaceIllegalPathCharsAndSpace","generatedFilename","getReportFileName","fileName","join","getMidsceneRunSubDir","tempFilePath","reportPath","screenshotsDir","dirname","tempScreenshotsDir","existsSync","mkdirSync","screenshotMapPath","readFileSync","require","screenshotMap","JSON","id","srcPath","Object","destPath","copyFileSync","error","console","testData","reportDir","tpl","getReportTpl","dumpScript","escapeScriptTag","attributesArr","key","encodeURIComponent","writeFileSync","printReportMsg","config","suite","_test","_result","test","result","dumpAnnotation","annotation","filePath","ReportActionDump","dumpString","retry","projectName","undefined","projectSuffix","testId","reportPromise","Promise","Array","reportBaseDir","rmSync","options","Map","Set"],"mappings":"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"}
|
|
@@ -115,7 +115,7 @@ class BridgeServer {
|
|
|
115
115
|
(0, shared_utils_namespaceObject.logMsg)('one client connected');
|
|
116
116
|
this.socket = socket;
|
|
117
117
|
const clientVersion = socket.handshake.query.version;
|
|
118
|
-
(0, shared_utils_namespaceObject.logMsg)(`Bridge connected, cli-side version v1.6.1
|
|
118
|
+
(0, shared_utils_namespaceObject.logMsg)(`Bridge connected, cli-side version v1.6.1, browser-side version v${clientVersion}`);
|
|
119
119
|
socket.on(external_common_js_namespaceObject.BridgeEvent.CallResponse, (params)=>{
|
|
120
120
|
const id = params.id;
|
|
121
121
|
const response = params.response;
|
|
@@ -139,7 +139,7 @@ class BridgeServer {
|
|
|
139
139
|
setTimeout(()=>{
|
|
140
140
|
this.onConnect?.();
|
|
141
141
|
const payload = {
|
|
142
|
-
version: "1.6.1
|
|
142
|
+
version: "1.6.1"
|
|
143
143
|
};
|
|
144
144
|
socket.emit(external_common_js_namespaceObject.BridgeEvent.Connected, payload);
|
|
145
145
|
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 { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\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\ndeclare const __VERSION__: string;\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 // console.error('failed to kill port', 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 and start listening on the specified host and port\n const httpServer = createServer();\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 // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\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 (this.socket?.connected) {\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","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","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 { createServer } from 'node:http';\nimport { sleep } from '@midscene/core/utils';\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\ndeclare const __VERSION__: string;\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 // console.error('failed to kill port', 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 and start listening on the specified host and port\n const httpServer = createServer();\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 // Always allow kill signal connections through\n if (socket.handshake.url.includes(BridgeSignalKill)) {\n return next();\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 (this.socket?.connected) {\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","killRunningServer","port","host","client","ClientIO","DefaultBridgeServerPort","BridgeSignalKill","sleep","e","BridgeServer","opts","timeout","Promise","resolve","reject","Error","setTimeout","BridgeErrorCodeNoClientConnected","logMsg","httpServer","createServer","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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"}
|
|
@@ -103,7 +103,7 @@ class ExtensionBridgePageBrowserSide extends page_js_default() {
|
|
|
103
103
|
throw new Error('Connection denied by user');
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.6.1
|
|
106
|
+
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.6.1`, 'log');
|
|
107
107
|
}
|
|
108
108
|
async connect() {
|
|
109
109
|
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\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 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 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 await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\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","ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","Promise","resolve","BridgeClient","method","args","allowed","Error","BridgeEvent","tabId","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","url","options","tab","chrome","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":"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"}
|
|
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\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 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 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 await this.setActiveTabId(tabId);\n }\n\n public async connectCurrentTab(\n options: BridgeConnectTabOptions = {\n forceSameTabNavigation: true,\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","ExtensionBridgePageBrowserSide","ChromeExtensionProxyPage","endpoint","DefaultBridgeServerPort","resolveConfirmationGate","Promise","resolve","BridgeClient","method","args","allowed","Error","BridgeEvent","tabId","MouseEvent","actionName","KeyboardEvent","result","e","errorMessage","url","options","tab","chrome","assert","tabs","serverEndpoint","onDisconnect","onLogMessage","forceSameTabNavigation","onConnectionRequest"],"mappings":"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"}
|
package/dist/lib/cli.js
CHANGED
|
@@ -63,7 +63,7 @@ tools = isBridge ? new external_mcp_tools_js_namespaceObject.WebMidsceneTools()
|
|
|
63
63
|
(0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-web', {
|
|
64
64
|
stripPrefix: 'web_',
|
|
65
65
|
argv,
|
|
66
|
-
version: "1.6.1
|
|
66
|
+
version: "1.6.1"
|
|
67
67
|
}).catch((e)=>{
|
|
68
68
|
if (!(e instanceof cli_namespaceObject.CLIError)) console.error(e);
|
|
69
69
|
process.exit(e instanceof cli_namespaceObject.CLIError ? e.exitCode : 1);
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -37,7 +37,7 @@ class WebMCPServer extends mcp_namespaceObject.BaseMCPServer {
|
|
|
37
37
|
constructor(toolsManager){
|
|
38
38
|
super({
|
|
39
39
|
name: '@midscene/web-bridge-mcp',
|
|
40
|
-
version: "1.6.1
|
|
40
|
+
version: "1.6.1",
|
|
41
41
|
description: 'Control the browser using natural language commands'
|
|
42
42
|
}, toolsManager);
|
|
43
43
|
}
|
|
@@ -133,12 +133,12 @@ var __webpack_exports__ = {};
|
|
|
133
133
|
flag: 'a'
|
|
134
134
|
});
|
|
135
135
|
else {
|
|
136
|
-
(0, external_node_fs_.writeFileSync)(reportPath,
|
|
136
|
+
(0, external_node_fs_.writeFileSync)(reportPath, tpl + dumpScript, {
|
|
137
137
|
flag: 'w'
|
|
138
138
|
});
|
|
139
139
|
this.mergedReportInitialized = true;
|
|
140
140
|
}
|
|
141
|
-
else (0, external_node_fs_.writeFileSync)(reportPath,
|
|
141
|
+
else (0, external_node_fs_.writeFileSync)(reportPath, tpl + dumpScript, {
|
|
142
142
|
flag: 'w'
|
|
143
143
|
});
|
|
144
144
|
(0, agent_namespaceObject.printReportMsg)(reportPath);
|
|
@@ -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 {\n copyFileSync,\n existsSync,\n mkdirSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport {\n ReportActionDump,\n type ReportDumpWithAttributes,\n} from '@midscene/core';\nimport { getReportFileName, printReportMsg } from '@midscene/core/agent';\nimport {\n getReportTpl,\n insertContentBeforeClosingHtml,\n} from '@midscene/core/utils';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n escapeScriptTag,\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 /**\n * Output format for the report.\n * - 'single-html': All screenshots embedded as base64 in a single HTML file (default)\n * - 'html-and-external-assets': Screenshots saved as separate PNG files in a screenshots/ subdirectory\n *\n * Note: 'html-and-external-assets' reports must be served via HTTP server due to CORS restrictions.\n */\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 mode?: 'merged' | 'separate';\n outputFormat: 'single-html' | 'html-and-external-assets';\n\n // Track all temp files created during this test run for cleanup\n private tempFiles = new Set<string>();\n\n // Track pending report updates\n private pendingReports = new Set<Promise<void>>();\n\n // Track whether the merged report file has been initialized\n private mergedReportInitialized = false;\n\n // Write queue to serialize file writes and prevent concurrent write conflicts\n private writeQueue: Promise<void> = Promise.resolve();\n\n // Track whether we have multiple projects (browsers)\n private hasMultipleProjects = false;\n\n // Track written screenshots to avoid duplicates (for directory mode)\n private writtenScreenshots = new Set<string>();\n\n constructor(options: MidsceneReporterOptions = {}) {\n // Set mode from constructor options (official Playwright way)\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 } else 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 /**\n * Get the report path - for directory mode, returns a directory path with index.html\n */\n private getReportPath(testTitle?: string): string {\n const fileName = this.getReportFilename(testTitle);\n if (this.outputFormat === 'html-and-external-assets') {\n // Directory mode: report-name/index.html\n return join(getMidsceneRunSubDir('report'), fileName, 'index.html');\n }\n // Inline mode: report-name.html\n return join(getMidsceneRunSubDir('report'), `${fileName}.html`);\n }\n\n /**\n * Copy screenshots from temp location to report screenshots directory\n */\n private copyScreenshotsToReport(\n tempFilePath: string,\n reportPath: string,\n ): void {\n const screenshotsDir = join(dirname(reportPath), 'screenshots');\n const tempScreenshotsDir = `${tempFilePath}.screenshots`;\n\n if (!existsSync(tempScreenshotsDir)) {\n return;\n }\n\n // Ensure screenshots directory exists\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n // Read screenshot map to get all screenshot IDs\n const screenshotMapPath = `${tempFilePath}.screenshots.json`;\n if (!existsSync(screenshotMapPath)) {\n return;\n }\n\n try {\n const { readFileSync } = require('node:fs');\n const screenshotMap: Record<string, string> = JSON.parse(\n readFileSync(screenshotMapPath, 'utf-8'),\n );\n\n for (const [id, srcPath] of Object.entries(screenshotMap)) {\n // In merged mode, skip if already written to avoid duplicates\n // In separate mode, each test has its own screenshots directory\n if (this.mode === 'merged' && this.writtenScreenshots.has(id)) {\n continue;\n }\n\n const destPath = join(screenshotsDir, `${id}.png`);\n\n if (existsSync(srcPath)) {\n copyFileSync(srcPath, destPath);\n if (this.mode === 'merged') {\n this.writtenScreenshots.add(id);\n }\n }\n }\n } catch (error) {\n console.error('Error copying screenshots:', error);\n }\n }\n\n private async updateReport(testData: ReportDumpWithAttributes) {\n if (!testData || !this.mode) return;\n\n // Queue the write operation to prevent concurrent writes to the same file\n this.writeQueue = this.writeQueue.then(async () => {\n const reportPath = this.getReportPath(\n testData.attributes?.playwright_test_title,\n );\n\n // Ensure report directory exists for directory mode\n if (this.outputFormat === 'html-and-external-assets') {\n const reportDir = dirname(reportPath);\n if (!existsSync(reportDir)) {\n mkdirSync(reportDir, { recursive: true });\n }\n }\n\n // Get report template\n const tpl = getReportTpl();\n if (!tpl) {\n throw new Error(\n 'Report template not found. Ensure @midscene/core is built correctly.',\n );\n }\n\n // Parse the dump string and generate dump script tag\n let dumpScript = `<script type=\"midscene_web_dump\">\\n${escapeScriptTag(testData.dumpString)}\\n</script>`;\n\n if (testData.attributes) {\n const attributesArr = Object.keys(testData.attributes).map((key) => {\n return `${key}=\"${encodeURIComponent(testData.attributes![key])}\"`;\n });\n // Add attributes to the script tag\n dumpScript = dumpScript.replace(\n '<script type=\"midscene_web_dump\"',\n `<script type=\"midscene_web_dump\" ${attributesArr.join(' ')}`,\n );\n }\n\n // Write or append to file\n if (this.mode === 'merged') {\n // For merged report, write template + dump on first write, then only append dumps\n if (!this.mergedReportInitialized) {\n writeFileSync(\n reportPath,\n insertContentBeforeClosingHtml(tpl, dumpScript),\n { flag: 'w' },\n );\n this.mergedReportInitialized = true;\n } else {\n // Append only the dump scripts for subsequent tests\n writeFileSync(reportPath, dumpScript, { flag: 'a' });\n }\n } else {\n // For separate reports, write each test to its own file with template\n writeFileSync(\n reportPath,\n insertContentBeforeClosingHtml(tpl, dumpScript),\n { flag: 'w' },\n );\n }\n\n printReportMsg(reportPath);\n });\n\n await this.writeQueue;\n }\n\n async onBegin(config: FullConfig, suite: Suite) {\n // Check if we have multiple projects to determine if we need browser labels\n this.hasMultipleProjects = (config.projects?.length || 0) > 1;\n }\n\n onTestBegin(_test: TestCase, _result: TestResult) {\n // logger(`Starting test ${test.title}`);\n }\n\n onTestEnd(test: TestCase, result: TestResult) {\n const dumpAnnotation = test.annotations.find((annotation) => {\n return annotation.type === 'MIDSCENE_DUMP_ANNOTATION';\n });\n if (!dumpAnnotation?.description) return;\n\n const tempFilePath = dumpAnnotation.description;\n\n // Track temp files for potential cleanup in onEnd\n for (const filePath of ReportActionDump.getFilePaths(tempFilePath)) {\n this.tempFiles.add(filePath);\n }\n\n let dumpString: string | undefined;\n\n try {\n if (this.outputFormat === 'html-and-external-assets') {\n // Directory mode: keep { $screenshot: id } format, copy screenshots to report dir\n const { readFileSync } = require('node:fs');\n dumpString = readFileSync(tempFilePath, 'utf-8');\n\n // Get report path and copy screenshots\n const retry = result.retry ? `(retry #${result.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 reportPath = this.getReportPath(testTitle);\n\n this.copyScreenshotsToReport(tempFilePath, reportPath);\n } else {\n // Inline mode: convert screenshots to base64\n dumpString = ReportActionDump.fromFilesAsInlineJson(tempFilePath);\n }\n } catch (error) {\n console.error(\n `Failed to read Midscene dump file: ${tempFilePath}`,\n error,\n );\n // Don't return here - we still need to clean up the temp file\n }\n\n // Only update report if we successfully read the dump\n if (dumpString) {\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n\n // Get the project name (browser name) only if we have multiple projects\n const projectName = this.hasMultipleProjects\n ? test.parent?.project()?.name\n : undefined;\n const projectSuffix = projectName ? ` [${projectName}]` : '';\n\n const testData: ReportDumpWithAttributes = {\n dumpString,\n attributes: {\n 'data-group-id': testId,\n playwright_test_id: testId,\n playwright_test_title: `${test.title}${projectSuffix}${retry}`,\n playwright_test_status: result.status,\n playwright_test_duration: result.duration,\n },\n };\n\n // Start async report update and track it\n const reportPromise = this.updateReport(testData)\n .catch((error) => {\n console.error('Error updating report:', error);\n })\n .finally(() => {\n this.pendingReports.delete(reportPromise);\n });\n this.pendingReports.add(reportPromise);\n }\n\n // Always try to clean up temp files\n try {\n ReportActionDump.cleanupFiles(tempFilePath);\n for (const filePath of ReportActionDump.getFilePaths(tempFilePath)) {\n this.tempFiles.delete(filePath);\n }\n } catch {\n // Keep in tempFiles for cleanup in onEnd\n }\n }\n\n async onEnd() {\n // Wait for all pending report updates to complete\n if (this.pendingReports.size > 0) {\n console.log(\n `Midscene: Waiting for ${this.pendingReports.size} pending report(s) to complete...`,\n );\n await Promise.all(Array.from(this.pendingReports));\n }\n\n // Print directory mode notice (only for merged mode)\n if (\n this.outputFormat === 'html-and-external-assets' &&\n this.mode === 'merged'\n ) {\n const reportPath = this.getReportPath();\n const reportDir = dirname(reportPath);\n console.log('[Midscene] Directory report generated.');\n console.log(\n '[Midscene] Note: This report must be served via HTTP server due to CORS restrictions.',\n );\n console.log(`[Midscene] Example: npx serve ${reportDir}`);\n } else if (\n this.outputFormat === 'html-and-external-assets' &&\n this.mode === 'separate'\n ) {\n const reportBaseDir = getMidsceneRunSubDir('report');\n console.log('[Midscene] Directory reports generated.');\n console.log(\n '[Midscene] Note: Reports must be served via HTTP server due to CORS restrictions.',\n );\n console.log(`[Midscene] Example: npx serve ${reportBaseDir}`);\n }\n\n // Clean up any remaining temp files that weren't deleted in onTestEnd\n if (this.tempFiles.size > 0) {\n console.log(\n `Midscene: Cleaning up ${this.tempFiles.size} remaining temp file(s)...`,\n );\n\n for (const filePath of this.tempFiles) {\n try {\n rmSync(filePath, { force: true });\n } catch (error) {\n // Silently ignore - file may have been deleted already\n }\n }\n\n this.tempFiles.clear();\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","tempFilePath","reportPath","screenshotsDir","dirname","tempScreenshotsDir","existsSync","mkdirSync","screenshotMapPath","readFileSync","require","screenshotMap","JSON","id","srcPath","destPath","copyFileSync","error","console","testData","reportDir","tpl","getReportTpl","dumpScript","escapeScriptTag","attributesArr","encodeURIComponent","writeFileSync","insertContentBeforeClosingHtml","printReportMsg","config","suite","_test","_result","test","result","dumpAnnotation","annotation","filePath","ReportActionDump","dumpString","retry","projectName","undefined","projectSuffix","testId","reportPromise","Promise","Array","reportBaseDir","rmSync","options","Map","Set"],"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 {\n copyFileSync,\n existsSync,\n mkdirSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport {\n ReportActionDump,\n type ReportDumpWithAttributes,\n} from '@midscene/core';\nimport { getReportFileName, printReportMsg } from '@midscene/core/agent';\nimport { getReportTpl } from '@midscene/core/utils';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n escapeScriptTag,\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 /**\n * Output format for the report.\n * - 'single-html': All screenshots embedded as base64 in a single HTML file (default)\n * - 'html-and-external-assets': Screenshots saved as separate PNG files in a screenshots/ subdirectory\n *\n * Note: 'html-and-external-assets' reports must be served via HTTP server due to CORS restrictions.\n */\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 mode?: 'merged' | 'separate';\n outputFormat: 'single-html' | 'html-and-external-assets';\n\n // Track all temp files created during this test run for cleanup\n private tempFiles = new Set<string>();\n\n // Track pending report updates\n private pendingReports = new Set<Promise<void>>();\n\n // Track whether the merged report file has been initialized\n private mergedReportInitialized = false;\n\n // Write queue to serialize file writes and prevent concurrent write conflicts\n private writeQueue: Promise<void> = Promise.resolve();\n\n // Track whether we have multiple projects (browsers)\n private hasMultipleProjects = false;\n\n // Track written screenshots to avoid duplicates (for directory mode)\n private writtenScreenshots = new Set<string>();\n\n constructor(options: MidsceneReporterOptions = {}) {\n // Set mode from constructor options (official Playwright way)\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 } else 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 /**\n * Get the report path - for directory mode, returns a directory path with index.html\n */\n private getReportPath(testTitle?: string): string {\n const fileName = this.getReportFilename(testTitle);\n if (this.outputFormat === 'html-and-external-assets') {\n // Directory mode: report-name/index.html\n return join(getMidsceneRunSubDir('report'), fileName, 'index.html');\n }\n // Inline mode: report-name.html\n return join(getMidsceneRunSubDir('report'), `${fileName}.html`);\n }\n\n /**\n * Copy screenshots from temp location to report screenshots directory\n */\n private copyScreenshotsToReport(\n tempFilePath: string,\n reportPath: string,\n ): void {\n const screenshotsDir = join(dirname(reportPath), 'screenshots');\n const tempScreenshotsDir = `${tempFilePath}.screenshots`;\n\n if (!existsSync(tempScreenshotsDir)) {\n return;\n }\n\n // Ensure screenshots directory exists\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n // Read screenshot map to get all screenshot IDs\n const screenshotMapPath = `${tempFilePath}.screenshots.json`;\n if (!existsSync(screenshotMapPath)) {\n return;\n }\n\n try {\n const { readFileSync } = require('node:fs');\n const screenshotMap: Record<string, string> = JSON.parse(\n readFileSync(screenshotMapPath, 'utf-8'),\n );\n\n for (const [id, srcPath] of Object.entries(screenshotMap)) {\n // In merged mode, skip if already written to avoid duplicates\n // In separate mode, each test has its own screenshots directory\n if (this.mode === 'merged' && this.writtenScreenshots.has(id)) {\n continue;\n }\n\n const destPath = join(screenshotsDir, `${id}.png`);\n\n if (existsSync(srcPath)) {\n copyFileSync(srcPath, destPath);\n if (this.mode === 'merged') {\n this.writtenScreenshots.add(id);\n }\n }\n }\n } catch (error) {\n console.error('Error copying screenshots:', error);\n }\n }\n\n private async updateReport(testData: ReportDumpWithAttributes) {\n if (!testData || !this.mode) return;\n\n // Queue the write operation to prevent concurrent writes to the same file\n this.writeQueue = this.writeQueue.then(async () => {\n const reportPath = this.getReportPath(\n testData.attributes?.playwright_test_title,\n );\n\n // Ensure report directory exists for directory mode\n if (this.outputFormat === 'html-and-external-assets') {\n const reportDir = dirname(reportPath);\n if (!existsSync(reportDir)) {\n mkdirSync(reportDir, { recursive: true });\n }\n }\n\n // Get report template\n const tpl = getReportTpl();\n if (!tpl) {\n throw new Error(\n 'Report template not found. Ensure @midscene/core is built correctly.',\n );\n }\n\n // Parse the dump string and generate dump script tag\n let dumpScript = `<script type=\"midscene_web_dump\">\\n${escapeScriptTag(testData.dumpString)}\\n</script>`;\n\n if (testData.attributes) {\n const attributesArr = Object.keys(testData.attributes).map((key) => {\n return `${key}=\"${encodeURIComponent(testData.attributes![key])}\"`;\n });\n // Add attributes to the script tag\n dumpScript = dumpScript.replace(\n '<script type=\"midscene_web_dump\"',\n `<script type=\"midscene_web_dump\" ${attributesArr.join(' ')}`,\n );\n }\n\n // Write or append to file\n if (this.mode === 'merged') {\n // For merged report, write template + dump on first write, then only append dumps\n if (!this.mergedReportInitialized) {\n writeFileSync(reportPath, tpl + dumpScript, { flag: 'w' });\n this.mergedReportInitialized = true;\n } else {\n // Append only the dump scripts for subsequent tests\n writeFileSync(reportPath, dumpScript, { flag: 'a' });\n }\n } else {\n // For separate reports, write each test to its own file with template\n writeFileSync(reportPath, tpl + dumpScript, { flag: 'w' });\n }\n\n printReportMsg(reportPath);\n });\n\n await this.writeQueue;\n }\n\n async onBegin(config: FullConfig, suite: Suite) {\n // Check if we have multiple projects to determine if we need browser labels\n this.hasMultipleProjects = (config.projects?.length || 0) > 1;\n }\n\n onTestBegin(_test: TestCase, _result: TestResult) {\n // logger(`Starting test ${test.title}`);\n }\n\n onTestEnd(test: TestCase, result: TestResult) {\n const dumpAnnotation = test.annotations.find((annotation) => {\n return annotation.type === 'MIDSCENE_DUMP_ANNOTATION';\n });\n if (!dumpAnnotation?.description) return;\n\n const tempFilePath = dumpAnnotation.description;\n\n // Track temp files for potential cleanup in onEnd\n for (const filePath of ReportActionDump.getFilePaths(tempFilePath)) {\n this.tempFiles.add(filePath);\n }\n\n let dumpString: string | undefined;\n\n try {\n if (this.outputFormat === 'html-and-external-assets') {\n // Directory mode: keep { $screenshot: id } format, copy screenshots to report dir\n const { readFileSync } = require('node:fs');\n dumpString = readFileSync(tempFilePath, 'utf-8');\n\n // Get report path and copy screenshots\n const retry = result.retry ? `(retry #${result.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 reportPath = this.getReportPath(testTitle);\n\n this.copyScreenshotsToReport(tempFilePath, reportPath);\n } else {\n // Inline mode: convert screenshots to base64\n dumpString = ReportActionDump.fromFilesAsInlineJson(tempFilePath);\n }\n } catch (error) {\n console.error(\n `Failed to read Midscene dump file: ${tempFilePath}`,\n error,\n );\n // Don't return here - we still need to clean up the temp file\n }\n\n // Only update report if we successfully read the dump\n if (dumpString) {\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n\n // Get the project name (browser name) only if we have multiple projects\n const projectName = this.hasMultipleProjects\n ? test.parent?.project()?.name\n : undefined;\n const projectSuffix = projectName ? ` [${projectName}]` : '';\n\n const testData: ReportDumpWithAttributes = {\n dumpString,\n attributes: {\n 'data-group-id': testId,\n playwright_test_id: testId,\n playwright_test_title: `${test.title}${projectSuffix}${retry}`,\n playwright_test_status: result.status,\n playwright_test_duration: result.duration,\n },\n };\n\n // Start async report update and track it\n const reportPromise = this.updateReport(testData)\n .catch((error) => {\n console.error('Error updating report:', error);\n })\n .finally(() => {\n this.pendingReports.delete(reportPromise);\n });\n this.pendingReports.add(reportPromise);\n }\n\n // Always try to clean up temp files\n try {\n ReportActionDump.cleanupFiles(tempFilePath);\n for (const filePath of ReportActionDump.getFilePaths(tempFilePath)) {\n this.tempFiles.delete(filePath);\n }\n } catch {\n // Keep in tempFiles for cleanup in onEnd\n }\n }\n\n async onEnd() {\n // Wait for all pending report updates to complete\n if (this.pendingReports.size > 0) {\n console.log(\n `Midscene: Waiting for ${this.pendingReports.size} pending report(s) to complete...`,\n );\n await Promise.all(Array.from(this.pendingReports));\n }\n\n // Print directory mode notice (only for merged mode)\n if (\n this.outputFormat === 'html-and-external-assets' &&\n this.mode === 'merged'\n ) {\n const reportPath = this.getReportPath();\n const reportDir = dirname(reportPath);\n console.log('[Midscene] Directory report generated.');\n console.log(\n '[Midscene] Note: This report must be served via HTTP server due to CORS restrictions.',\n );\n console.log(`[Midscene] Example: npx serve ${reportDir}`);\n } else if (\n this.outputFormat === 'html-and-external-assets' &&\n this.mode === 'separate'\n ) {\n const reportBaseDir = getMidsceneRunSubDir('report');\n console.log('[Midscene] Directory reports generated.');\n console.log(\n '[Midscene] Note: Reports must be served via HTTP server due to CORS restrictions.',\n );\n console.log(`[Midscene] Example: npx serve ${reportBaseDir}`);\n }\n\n // Clean up any remaining temp files that weren't deleted in onTestEnd\n if (this.tempFiles.size > 0) {\n console.log(\n `Midscene: Cleaning up ${this.tempFiles.size} remaining temp file(s)...`,\n );\n\n for (const filePath of this.tempFiles) {\n try {\n rmSync(filePath, { force: true });\n } catch (error) {\n // Silently ignore - file may have been deleted already\n }\n }\n\n this.tempFiles.clear();\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","tempFilePath","reportPath","screenshotsDir","dirname","tempScreenshotsDir","existsSync","mkdirSync","screenshotMapPath","readFileSync","require","screenshotMap","JSON","id","srcPath","destPath","copyFileSync","error","console","testData","reportDir","tpl","getReportTpl","dumpScript","escapeScriptTag","attributesArr","encodeURIComponent","writeFileSync","printReportMsg","config","suite","_test","_result","test","result","dumpAnnotation","annotation","filePath","ReportActionDump","dumpString","retry","projectName","undefined","projectSuffix","testId","reportPromise","Promise","Array","reportBaseDir","rmSync","options","Map","Set"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"Browser use",
|
|
9
9
|
"Android use"
|
|
10
10
|
],
|
|
11
|
-
"version": "1.6.1
|
|
11
|
+
"version": "1.6.1",
|
|
12
12
|
"repository": "https://github.com/web-infra-dev/midscene",
|
|
13
13
|
"homepage": "https://midscenejs.com/",
|
|
14
14
|
"main": "./dist/lib/index.js",
|
|
@@ -110,9 +110,9 @@
|
|
|
110
110
|
"socket.io": "^4.8.1",
|
|
111
111
|
"socket.io-client": "4.8.1",
|
|
112
112
|
"ws": "^8.18.1",
|
|
113
|
-
"@midscene/core": "1.6.1
|
|
114
|
-
"@midscene/playground": "1.6.1
|
|
115
|
-
"@midscene/shared": "1.6.1
|
|
113
|
+
"@midscene/core": "1.6.1",
|
|
114
|
+
"@midscene/playground": "1.6.1",
|
|
115
|
+
"@midscene/shared": "1.6.1"
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|
|
118
118
|
"@playwright/test": "^1.45.0",
|