@midscene/web 1.9.8 → 1.10.1-beta-20260624112700.0
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/{mcp-agent-init-args.mjs → agent-init-args.mjs} +2 -2
- package/dist/es/{mcp-agent-init-args.mjs.map → agent-init-args.mjs.map} +1 -1
- package/dist/es/{mcp-tools-cdp.mjs → agent-tools-cdp.mjs} +5 -5
- package/dist/es/agent-tools-cdp.mjs.map +1 -0
- package/dist/es/{mcp-tools-puppeteer.mjs → agent-tools-puppeteer.mjs} +7 -7
- package/dist/es/agent-tools-puppeteer.mjs.map +1 -0
- package/dist/es/{mcp-tools.mjs → agent-tools.mjs} +4 -4
- package/dist/es/agent-tools.mjs.map +1 -0
- 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/cdp-proxy-constants.mjs.map +1 -1
- package/dist/es/cdp-proxy-manager.mjs +1 -1
- package/dist/es/cdp-proxy-manager.mjs.map +1 -1
- package/dist/es/cdp-proxy.mjs.map +1 -1
- package/dist/es/cdp-target-store.mjs +1 -1
- package/dist/es/cdp-target-store.mjs.map +1 -1
- package/dist/es/cli.mjs +4 -4
- package/dist/es/cli.mjs.map +1 -1
- package/dist/es/index.mjs +2 -2
- package/dist/es/playwright/index.mjs +5 -3
- package/dist/es/playwright/index.mjs.map +1 -1
- package/dist/es/puppeteer/base-page.mjs +43 -44
- package/dist/es/puppeteer/base-page.mjs.map +1 -1
- package/dist/es/puppeteer/index.mjs +5 -3
- package/dist/es/puppeteer/index.mjs.map +1 -1
- package/dist/es/static/static-page.mjs +8 -3
- package/dist/es/static/static-page.mjs.map +1 -1
- package/dist/es/web-element.mjs.map +1 -1
- package/dist/lib/{mcp-agent-init-args.js → agent-init-args.js} +2 -2
- package/dist/lib/{mcp-agent-init-args.js.map → agent-init-args.js.map} +1 -1
- package/dist/lib/{mcp-tools-cdp.js → agent-tools-cdp.js} +7 -7
- package/dist/lib/agent-tools-cdp.js.map +1 -0
- package/dist/lib/{mcp-tools-puppeteer.js → agent-tools-puppeteer.js} +9 -9
- package/dist/lib/agent-tools-puppeteer.js.map +1 -0
- package/dist/lib/{mcp-tools.js → agent-tools.js} +6 -6
- package/dist/lib/agent-tools.js.map +1 -0
- 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/cdp-proxy-constants.js.map +1 -1
- package/dist/lib/cdp-proxy-manager.js +1 -1
- package/dist/lib/cdp-proxy-manager.js.map +1 -1
- package/dist/lib/cdp-proxy.js.map +1 -1
- package/dist/lib/cdp-target-store.js +1 -1
- package/dist/lib/cdp-target-store.js.map +1 -1
- package/dist/lib/cli.js +5 -5
- package/dist/lib/cli.js.map +1 -1
- package/dist/lib/index.js +4 -4
- package/dist/lib/playwright/index.js +5 -3
- package/dist/lib/playwright/index.js.map +1 -1
- package/dist/lib/puppeteer/base-page.js +43 -44
- package/dist/lib/puppeteer/base-page.js.map +1 -1
- package/dist/lib/puppeteer/index.js +5 -3
- package/dist/lib/puppeteer/index.js.map +1 -1
- package/dist/lib/static/static-page.js +8 -3
- package/dist/lib/static/static-page.js.map +1 -1
- package/dist/lib/web-element.js.map +1 -1
- package/dist/types/{mcp-agent-init-args.d.ts → agent-init-args.d.ts} +1 -1
- package/dist/types/{mcp-tools-cdp.d.ts → agent-tools-cdp.d.ts} +4 -4
- package/dist/types/{mcp-tools-puppeteer.d.ts → agent-tools-puppeteer.d.ts} +4 -4
- package/dist/types/{mcp-tools.d.ts → agent-tools.d.ts} +4 -4
- package/dist/types/cdp-proxy-manager.d.ts +1 -1
- package/dist/types/cdp-proxy.d.ts +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/puppeteer/base-page.d.ts +2 -9
- package/dist/types/static/static-page.d.ts +9 -2
- package/dist/types/web-element.d.ts +3 -0
- package/package.json +4 -9
- package/dist/es/mcp-server.mjs +0 -35
- package/dist/es/mcp-server.mjs.map +0 -1
- package/dist/es/mcp-tools-cdp.mjs.map +0 -1
- package/dist/es/mcp-tools-puppeteer.mjs.map +0 -1
- package/dist/es/mcp-tools.mjs.map +0 -1
- package/dist/lib/mcp-server.js +0 -75
- package/dist/lib/mcp-server.js.map +0 -1
- package/dist/lib/mcp-tools-cdp.js.map +0 -1
- package/dist/lib/mcp-tools-puppeteer.js.map +0 -1
- package/dist/lib/mcp-tools.js.map +0 -1
- package/dist/types/mcp-server.d.ts +0 -26
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdp-proxy.mjs","sources":["../../src/cdp-proxy.ts"],"sourcesContent":["/**\n * CDP WebSocket Proxy — standalone process.\n *\n * Holds a persistent WebSocket connection to Chrome's CDP endpoint and\n * exposes a local WebSocket server. Midscene CLI processes connect to the\n * proxy instead of Chrome directly, so Chrome's \"Allow remote debugging\"\n * permission popup only fires once (when the proxy connects).\n *\n * Lifecycle notes:\n * - When all downstream clients disconnect the proxy stays running but\n * marks the upstream as needing reconnection. The actual reconnect is\n * deferred to the moment the next client connects, so Chrome's CDP\n * state (notably Target.setDiscoverTargets) is reset and the new\n * client receives all targetCreated events.\n * - On startup, if another proxy is already alive the new instance\n * announces \"duplicate proxy detected\" on stderr and exits 0 without\n * touching the existing metadata files.\n *\n * Exit conditions:\n * 1. Upstream Chrome connection closes or errors.\n * 2. No downstream client message for IDLE_TIMEOUT_MS (default 5 min).\n * 3. SIGTERM / SIGINT.\n * 4. Duplicate proxy detected on startup (exits 0 with stderr notice).\n *\n * Usage (spawned by mcp-tools-cdp.ts):\n * node cdp-proxy.js <chrome-ws-endpoint>\n *\n * On startup, prints the proxy endpoint to stdout as a single JSON line:\n * {\"endpoint\":\"ws://127.0.0.1:<port>/devtools/browser\"}\n * and writes:\n * - PROXY_ENDPOINT_FILE — the local proxy URL above\n * - PROXY_PID_FILE — this process's pid\n * - PROXY_UPSTREAM_FILE — the Chrome endpoint the proxy is connected to,\n * so callers can detect when the requested\n * upstream has changed and replace the proxy.\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { createServer } from 'node:http';\nimport WebSocket, { WebSocketServer } from 'ws';\nimport {\n PROXY_ENDPOINT_FILE,\n PROXY_PID_FILE,\n PROXY_UPSTREAM_FILE,\n} from './cdp-proxy-constants';\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nconst IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\n\nconst chromeEndpoint = process.argv[2];\nif (!chromeEndpoint) {\n process.stderr.write('Usage: node cdp-proxy.js <chrome-ws-endpoint>\\n');\n process.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// Cleanup\n// ---------------------------------------------------------------------------\n\nfunction cleanupIfOwned() {\n // Only clean up if the PID file exists and records *our* PID.\n // If the file is missing (e.g. already deleted by killProxy()) or\n // unreadable, skip cleanup — another process may have taken over.\n try {\n if (!existsSync(PROXY_PID_FILE)) return;\n const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n if (pid !== process.pid) return;\n } catch {\n return;\n }\n try {\n if (existsSync(PROXY_ENDPOINT_FILE)) unlinkSync(PROXY_ENDPOINT_FILE);\n } catch {}\n try {\n if (existsSync(PROXY_PID_FILE)) unlinkSync(PROXY_PID_FILE);\n } catch {}\n try {\n if (existsSync(PROXY_UPSTREAM_FILE)) unlinkSync(PROXY_UPSTREAM_FILE);\n } catch {}\n}\n\n/**\n * Maximum time to wait for the stderr drain callback before forcing exit.\n * The callback should normally fire within microseconds, but if the pipe\n * has been closed by the parent it may never run. 500ms is generous\n * enough to be effectively unreachable in the happy path while still\n * keeping the process from hanging if something goes wrong.\n */\nconst STDERR_FLUSH_FALLBACK_MS = 500;\n\n/**\n * Exit after the stderr diagnostic has been flushed.\n *\n * When the proxy's stderr is a pipe (parent uses stdio 'pipe'), Node's\n * process.stderr.write() is asynchronous on POSIX. Calling process.exit()\n * immediately afterwards can drop the pending write, which would silently\n * lose the very diagnostic the caller is relying on. Wait for the drain\n * callback before exiting, with a short fallback timer in case the\n * callback never fires (e.g. the pipe is already closed).\n */\nfunction exitWithStderr(message: string, code = 0): void {\n let exited = false;\n const doExit = () => {\n if (exited) return;\n exited = true;\n process.exit(code);\n };\n const fallback = setTimeout(doExit, STDERR_FLUSH_FALLBACK_MS);\n fallback.unref?.();\n try {\n process.stderr.write(message, () => doExit());\n } catch {\n doExit();\n }\n}\n\nfunction shutdown(reason: string): void {\n cleanupIfOwned();\n exitWithStderr(`[cdp-proxy] shutting down: ${reason}\\n`, 0);\n}\n\nprocess.on('SIGTERM', () => shutdown('SIGTERM'));\nprocess.on('SIGINT', () => shutdown('SIGINT'));\nprocess.on('uncaughtException', (e) => shutdown(`uncaught: ${e.message}`));\n\n// ---------------------------------------------------------------------------\n// Idle timer\n// ---------------------------------------------------------------------------\n\nlet idleTimer: ReturnType<typeof setTimeout> | null = null;\n\nfunction resetIdleTimer() {\n if (idleTimer) clearTimeout(idleTimer);\n idleTimer = setTimeout(\n () => shutdown('idle timeout (5min)'),\n IDLE_TIMEOUT_MS,\n );\n}\n\nresetIdleTimer();\n\n// ---------------------------------------------------------------------------\n// Upstream: connect to Chrome\n// ---------------------------------------------------------------------------\n\nconst clients = new Set<WebSocket>();\n\n/**\n * Whether we are intentionally reconnecting the upstream WebSocket.\n * When true, the old upstream's close/error events should not trigger shutdown.\n */\nlet reconnecting = false;\n\n/**\n * Whether the upstream WebSocket needs to be reconnected before the next\n * client can use it. Set to true when all downstream clients disconnect;\n * the actual reconnect is deferred until a new client connects so that\n * Chrome's permission popup only fires when someone actually needs it.\n */\nlet needsUpstreamReconnect = false;\n\n/**\n * Messages from downstream clients that arrived while the upstream WebSocket\n * was not yet open (e.g. during a reconnect). Flushed once upstream opens.\n */\nconst pendingUpstreamMessages: {\n data: WebSocket.RawData;\n isBinary: boolean;\n}[] = [];\n\n/**\n * Create a new upstream WebSocket to Chrome and bind its event handlers.\n * Used for both initial connection and reconnection.\n */\nfunction createUpstream(endpoint: string): WebSocket {\n const ws = new WebSocket(endpoint);\n\n ws.on('error', (err) => {\n // A failed reconnect must not leave the flag stuck on.\n reconnecting = false;\n shutdown(`upstream error: ${err.message}`);\n });\n\n ws.on('close', (code, reasonBuf) => {\n if (reconnecting) return;\n const reason = reasonBuf?.toString?.() || '';\n const detail = reason\n ? ` (code=${code}, reason=${reason})`\n : ` (code=${code})`;\n shutdown(`upstream closed${detail}`);\n });\n\n // Forward upstream messages to all downstream clients\n ws.on('message', (data, isBinary) => {\n resetIdleTimer();\n for (const client of clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(data, { binary: isBinary });\n }\n }\n });\n\n ws.on('open', () => {\n reconnecting = false;\n for (const msg of pendingUpstreamMessages) {\n ws.send(msg.data, { binary: msg.isBinary });\n }\n pendingUpstreamMessages.length = 0;\n });\n\n return ws;\n}\n\nlet upstream = createUpstream(chromeEndpoint);\n\n/**\n * Reconnect the upstream WebSocket to Chrome.\n *\n * Called when all downstream clients have disconnected. This resets the CDP\n * protocol state on Chrome's side — critically, the Target.setDiscoverTargets\n * subscription — so the next client that connects gets a fresh session and\n * receives all targetCreated events.\n *\n * `reconnecting` is cleared by the new upstream's `open` (or `error`) handler,\n * not here — the new socket is still CONNECTING when this returns.\n */\nfunction reconnectUpstream() {\n reconnecting = true;\n upstream.removeAllListeners();\n upstream.close();\n upstream = createUpstream(chromeEndpoint);\n resetIdleTimer();\n}\n\n// ---------------------------------------------------------------------------\n// Downstream: local WebSocket server\n// ---------------------------------------------------------------------------\n\nconst httpServer = createServer((_req, res) => {\n res.writeHead(404);\n res.end();\n});\n\nconst wss = new WebSocketServer({ server: httpServer });\n\nwss.on('connection', (clientWs) => {\n // Reconnect the upstream WebSocket if the previous session ended.\n // This resets Chrome's CDP protocol state (Target.setDiscoverTargets, etc.)\n // so the new client receives all targetCreated events.\n if (needsUpstreamReconnect && upstream.readyState === WebSocket.OPEN) {\n reconnectUpstream();\n needsUpstreamReconnect = false;\n }\n\n clients.add(clientWs);\n resetIdleTimer();\n\n // Forward downstream messages to upstream (buffer if upstream is reconnecting)\n clientWs.on('message', (data, isBinary) => {\n resetIdleTimer();\n if (upstream.readyState === WebSocket.OPEN) {\n upstream.send(data, { binary: isBinary });\n } else {\n pendingUpstreamMessages.push({ data, isBinary });\n }\n });\n\n const removeClient = () => {\n clients.delete(clientWs);\n // When all downstream clients disconnect, mark that the upstream needs\n // reconnecting to reset Chrome's CDP protocol state. The actual\n // reconnect is deferred until the next client connects so we don't\n // trigger Chrome's permission popup while nobody is using the proxy.\n if (clients.size === 0) {\n pendingUpstreamMessages.length = 0;\n needsUpstreamReconnect = true;\n }\n };\n\n clientWs.on('close', removeClient);\n clientWs.on('error', removeClient);\n});\n\n// ---------------------------------------------------------------------------\n// Start\n// ---------------------------------------------------------------------------\n\n// Start the HTTP/WebSocket server once the initial upstream connection opens.\n// This listener is added *after* createUpstream() already bound its own 'open'\n// handler (which flushes pendingUpstreamMessages), so both will fire.\nupstream.once('open', () => {\n // Check for duplicate proxy\n if (existsSync(PROXY_PID_FILE)) {\n try {\n const existingPid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n if (existingPid !== process.pid) {\n try {\n process.kill(existingPid, 0);\n // Another proxy is alive — exit without cleanupIfOwned() (we don't\n // own the metadata files). Announce the reason on stderr so the\n // parent process can distinguish this path from upstream failures,\n // then bail out of this open handler so we don't proceed to listen().\n exitWithStderr(\n `[cdp-proxy] duplicate proxy detected (existing pid=${existingPid})\\n`,\n 0,\n );\n return;\n } catch {\n // dead — we take over\n }\n }\n } catch {}\n }\n\n httpServer.listen(0, '127.0.0.1', () => {\n const addr = httpServer.address();\n if (!addr || typeof addr === 'string') {\n shutdown('failed to get server address');\n return;\n }\n\n const proxyEndpoint = `ws://127.0.0.1:${addr.port}/devtools/browser`;\n\n writeFileSync(PROXY_ENDPOINT_FILE, proxyEndpoint);\n writeFileSync(PROXY_PID_FILE, String(process.pid));\n writeFileSync(PROXY_UPSTREAM_FILE, chromeEndpoint);\n\n process.stdout.write(`${JSON.stringify({ endpoint: proxyEndpoint })}\\n`);\n });\n});\n"],"names":["IDLE_TIMEOUT_MS","chromeEndpoint","process","cleanupIfOwned","existsSync","PROXY_PID_FILE","pid","Number","readFileSync","PROXY_ENDPOINT_FILE","unlinkSync","PROXY_UPSTREAM_FILE","STDERR_FLUSH_FALLBACK_MS","exitWithStderr","message","code","exited","doExit","fallback","setTimeout","shutdown","reason","e","idleTimer","resetIdleTimer","clearTimeout","clients","Set","reconnecting","needsUpstreamReconnect","pendingUpstreamMessages","createUpstream","endpoint","ws","WebSocket","err","reasonBuf","detail","data","isBinary","client","msg","upstream","reconnectUpstream","httpServer","createServer","_req","res","wss","WebSocketServer","clientWs","removeClient","existingPid","addr","proxyEndpoint","writeFileSync","String","JSON"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cdp-proxy.mjs","sources":["../../src/cdp-proxy.ts"],"sourcesContent":["/**\n * CDP WebSocket Proxy — standalone process.\n *\n * Holds a persistent WebSocket connection to Chrome's CDP endpoint and\n * exposes a local WebSocket server. Midscene CLI processes connect to the\n * proxy instead of Chrome directly, so Chrome's \"Allow remote debugging\"\n * permission popup only fires once (when the proxy connects).\n *\n * Lifecycle notes:\n * - When all downstream clients disconnect the proxy stays running but\n * marks the upstream as needing reconnection. The actual reconnect is\n * deferred to the moment the next client connects, so Chrome's CDP\n * state (notably Target.setDiscoverTargets) is reset and the new\n * client receives all targetCreated events.\n * - On startup, if another proxy is already alive the new instance\n * announces \"duplicate proxy detected\" on stderr and exits 0 without\n * touching the existing metadata files.\n *\n * Exit conditions:\n * 1. Upstream Chrome connection closes or errors.\n * 2. No downstream client message for IDLE_TIMEOUT_MS (default 5 min).\n * 3. SIGTERM / SIGINT.\n * 4. Duplicate proxy detected on startup (exits 0 with stderr notice).\n *\n * Usage (spawned by agent-tools-cdp.ts):\n * node cdp-proxy.js <chrome-ws-endpoint>\n *\n * On startup, prints the proxy endpoint to stdout as a single JSON line:\n * {\"endpoint\":\"ws://127.0.0.1:<port>/devtools/browser\"}\n * and writes:\n * - PROXY_ENDPOINT_FILE — the local proxy URL above\n * - PROXY_PID_FILE — this process's pid\n * - PROXY_UPSTREAM_FILE — the Chrome endpoint the proxy is connected to,\n * so callers can detect when the requested\n * upstream has changed and replace the proxy.\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { createServer } from 'node:http';\nimport WebSocket, { WebSocketServer } from 'ws';\nimport {\n PROXY_ENDPOINT_FILE,\n PROXY_PID_FILE,\n PROXY_UPSTREAM_FILE,\n} from './cdp-proxy-constants';\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nconst IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\n\nconst chromeEndpoint = process.argv[2];\nif (!chromeEndpoint) {\n process.stderr.write('Usage: node cdp-proxy.js <chrome-ws-endpoint>\\n');\n process.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// Cleanup\n// ---------------------------------------------------------------------------\n\nfunction cleanupIfOwned() {\n // Only clean up if the PID file exists and records *our* PID.\n // If the file is missing (e.g. already deleted by killProxy()) or\n // unreadable, skip cleanup — another process may have taken over.\n try {\n if (!existsSync(PROXY_PID_FILE)) return;\n const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n if (pid !== process.pid) return;\n } catch {\n return;\n }\n try {\n if (existsSync(PROXY_ENDPOINT_FILE)) unlinkSync(PROXY_ENDPOINT_FILE);\n } catch {}\n try {\n if (existsSync(PROXY_PID_FILE)) unlinkSync(PROXY_PID_FILE);\n } catch {}\n try {\n if (existsSync(PROXY_UPSTREAM_FILE)) unlinkSync(PROXY_UPSTREAM_FILE);\n } catch {}\n}\n\n/**\n * Maximum time to wait for the stderr drain callback before forcing exit.\n * The callback should normally fire within microseconds, but if the pipe\n * has been closed by the parent it may never run. 500ms is generous\n * enough to be effectively unreachable in the happy path while still\n * keeping the process from hanging if something goes wrong.\n */\nconst STDERR_FLUSH_FALLBACK_MS = 500;\n\n/**\n * Exit after the stderr diagnostic has been flushed.\n *\n * When the proxy's stderr is a pipe (parent uses stdio 'pipe'), Node's\n * process.stderr.write() is asynchronous on POSIX. Calling process.exit()\n * immediately afterwards can drop the pending write, which would silently\n * lose the very diagnostic the caller is relying on. Wait for the drain\n * callback before exiting, with a short fallback timer in case the\n * callback never fires (e.g. the pipe is already closed).\n */\nfunction exitWithStderr(message: string, code = 0): void {\n let exited = false;\n const doExit = () => {\n if (exited) return;\n exited = true;\n process.exit(code);\n };\n const fallback = setTimeout(doExit, STDERR_FLUSH_FALLBACK_MS);\n fallback.unref?.();\n try {\n process.stderr.write(message, () => doExit());\n } catch {\n doExit();\n }\n}\n\nfunction shutdown(reason: string): void {\n cleanupIfOwned();\n exitWithStderr(`[cdp-proxy] shutting down: ${reason}\\n`, 0);\n}\n\nprocess.on('SIGTERM', () => shutdown('SIGTERM'));\nprocess.on('SIGINT', () => shutdown('SIGINT'));\nprocess.on('uncaughtException', (e) => shutdown(`uncaught: ${e.message}`));\n\n// ---------------------------------------------------------------------------\n// Idle timer\n// ---------------------------------------------------------------------------\n\nlet idleTimer: ReturnType<typeof setTimeout> | null = null;\n\nfunction resetIdleTimer() {\n if (idleTimer) clearTimeout(idleTimer);\n idleTimer = setTimeout(\n () => shutdown('idle timeout (5min)'),\n IDLE_TIMEOUT_MS,\n );\n}\n\nresetIdleTimer();\n\n// ---------------------------------------------------------------------------\n// Upstream: connect to Chrome\n// ---------------------------------------------------------------------------\n\nconst clients = new Set<WebSocket>();\n\n/**\n * Whether we are intentionally reconnecting the upstream WebSocket.\n * When true, the old upstream's close/error events should not trigger shutdown.\n */\nlet reconnecting = false;\n\n/**\n * Whether the upstream WebSocket needs to be reconnected before the next\n * client can use it. Set to true when all downstream clients disconnect;\n * the actual reconnect is deferred until a new client connects so that\n * Chrome's permission popup only fires when someone actually needs it.\n */\nlet needsUpstreamReconnect = false;\n\n/**\n * Messages from downstream clients that arrived while the upstream WebSocket\n * was not yet open (e.g. during a reconnect). Flushed once upstream opens.\n */\nconst pendingUpstreamMessages: {\n data: WebSocket.RawData;\n isBinary: boolean;\n}[] = [];\n\n/**\n * Create a new upstream WebSocket to Chrome and bind its event handlers.\n * Used for both initial connection and reconnection.\n */\nfunction createUpstream(endpoint: string): WebSocket {\n const ws = new WebSocket(endpoint);\n\n ws.on('error', (err) => {\n // A failed reconnect must not leave the flag stuck on.\n reconnecting = false;\n shutdown(`upstream error: ${err.message}`);\n });\n\n ws.on('close', (code, reasonBuf) => {\n if (reconnecting) return;\n const reason = reasonBuf?.toString?.() || '';\n const detail = reason\n ? ` (code=${code}, reason=${reason})`\n : ` (code=${code})`;\n shutdown(`upstream closed${detail}`);\n });\n\n // Forward upstream messages to all downstream clients\n ws.on('message', (data, isBinary) => {\n resetIdleTimer();\n for (const client of clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(data, { binary: isBinary });\n }\n }\n });\n\n ws.on('open', () => {\n reconnecting = false;\n for (const msg of pendingUpstreamMessages) {\n ws.send(msg.data, { binary: msg.isBinary });\n }\n pendingUpstreamMessages.length = 0;\n });\n\n return ws;\n}\n\nlet upstream = createUpstream(chromeEndpoint);\n\n/**\n * Reconnect the upstream WebSocket to Chrome.\n *\n * Called when all downstream clients have disconnected. This resets the CDP\n * protocol state on Chrome's side — critically, the Target.setDiscoverTargets\n * subscription — so the next client that connects gets a fresh session and\n * receives all targetCreated events.\n *\n * `reconnecting` is cleared by the new upstream's `open` (or `error`) handler,\n * not here — the new socket is still CONNECTING when this returns.\n */\nfunction reconnectUpstream() {\n reconnecting = true;\n upstream.removeAllListeners();\n upstream.close();\n upstream = createUpstream(chromeEndpoint);\n resetIdleTimer();\n}\n\n// ---------------------------------------------------------------------------\n// Downstream: local WebSocket server\n// ---------------------------------------------------------------------------\n\nconst httpServer = createServer((_req, res) => {\n res.writeHead(404);\n res.end();\n});\n\nconst wss = new WebSocketServer({ server: httpServer });\n\nwss.on('connection', (clientWs) => {\n // Reconnect the upstream WebSocket if the previous session ended.\n // This resets Chrome's CDP protocol state (Target.setDiscoverTargets, etc.)\n // so the new client receives all targetCreated events.\n if (needsUpstreamReconnect && upstream.readyState === WebSocket.OPEN) {\n reconnectUpstream();\n needsUpstreamReconnect = false;\n }\n\n clients.add(clientWs);\n resetIdleTimer();\n\n // Forward downstream messages to upstream (buffer if upstream is reconnecting)\n clientWs.on('message', (data, isBinary) => {\n resetIdleTimer();\n if (upstream.readyState === WebSocket.OPEN) {\n upstream.send(data, { binary: isBinary });\n } else {\n pendingUpstreamMessages.push({ data, isBinary });\n }\n });\n\n const removeClient = () => {\n clients.delete(clientWs);\n // When all downstream clients disconnect, mark that the upstream needs\n // reconnecting to reset Chrome's CDP protocol state. The actual\n // reconnect is deferred until the next client connects so we don't\n // trigger Chrome's permission popup while nobody is using the proxy.\n if (clients.size === 0) {\n pendingUpstreamMessages.length = 0;\n needsUpstreamReconnect = true;\n }\n };\n\n clientWs.on('close', removeClient);\n clientWs.on('error', removeClient);\n});\n\n// ---------------------------------------------------------------------------\n// Start\n// ---------------------------------------------------------------------------\n\n// Start the HTTP/WebSocket server once the initial upstream connection opens.\n// This listener is added *after* createUpstream() already bound its own 'open'\n// handler (which flushes pendingUpstreamMessages), so both will fire.\nupstream.once('open', () => {\n // Check for duplicate proxy\n if (existsSync(PROXY_PID_FILE)) {\n try {\n const existingPid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n if (existingPid !== process.pid) {\n try {\n process.kill(existingPid, 0);\n // Another proxy is alive — exit without cleanupIfOwned() (we don't\n // own the metadata files). Announce the reason on stderr so the\n // parent process can distinguish this path from upstream failures,\n // then bail out of this open handler so we don't proceed to listen().\n exitWithStderr(\n `[cdp-proxy] duplicate proxy detected (existing pid=${existingPid})\\n`,\n 0,\n );\n return;\n } catch {\n // dead — we take over\n }\n }\n } catch {}\n }\n\n httpServer.listen(0, '127.0.0.1', () => {\n const addr = httpServer.address();\n if (!addr || typeof addr === 'string') {\n shutdown('failed to get server address');\n return;\n }\n\n const proxyEndpoint = `ws://127.0.0.1:${addr.port}/devtools/browser`;\n\n writeFileSync(PROXY_ENDPOINT_FILE, proxyEndpoint);\n writeFileSync(PROXY_PID_FILE, String(process.pid));\n writeFileSync(PROXY_UPSTREAM_FILE, chromeEndpoint);\n\n process.stdout.write(`${JSON.stringify({ endpoint: proxyEndpoint })}\\n`);\n });\n});\n"],"names":["IDLE_TIMEOUT_MS","chromeEndpoint","process","cleanupIfOwned","existsSync","PROXY_PID_FILE","pid","Number","readFileSync","PROXY_ENDPOINT_FILE","unlinkSync","PROXY_UPSTREAM_FILE","STDERR_FLUSH_FALLBACK_MS","exitWithStderr","message","code","exited","doExit","fallback","setTimeout","shutdown","reason","e","idleTimer","resetIdleTimer","clearTimeout","clients","Set","reconnecting","needsUpstreamReconnect","pendingUpstreamMessages","createUpstream","endpoint","ws","WebSocket","err","reasonBuf","detail","data","isBinary","client","msg","upstream","reconnectUpstream","httpServer","createServer","_req","res","wss","WebSocketServer","clientWs","removeClient","existingPid","addr","proxyEndpoint","writeFileSync","String","JSON"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { getDebug } from "@midscene/shared/logger";
|
|
3
3
|
import { TARGET_ID_FILE } from "./cdp-proxy-constants.mjs";
|
|
4
|
-
const debug = getDebug('
|
|
4
|
+
const debug = getDebug('agent-tools:cdp:target-store');
|
|
5
5
|
function readSavedTargetId() {
|
|
6
6
|
if (!existsSync(TARGET_ID_FILE)) return null;
|
|
7
7
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdp-target-store.mjs","sources":["../../src/cdp-target-store.ts"],"sourcesContent":["/**\n * Persistent store for the CDP-mode \"current tab\" targetId.\n *\n * The Midscene CLI runs each command as a fresh Node process, so anything\n * the previous command knew about which tab was being driven must survive\n * across processes. This store writes the chosen targetId to a temp file\n * after `connect`/`act`/etc. succeed, and the next command reads it back\n * to bind to the exact same tab — even when Chrome holds 14 of them.\n *\n * Owns nothing else. The CDP proxy lifecycle and its own metadata files\n * live in `cdp-proxy-manager.ts`.\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { getDebug } from '@midscene/shared/logger';\nimport { TARGET_ID_FILE } from './cdp-proxy-constants';\n\nconst debug = getDebug('
|
|
1
|
+
{"version":3,"file":"cdp-target-store.mjs","sources":["../../src/cdp-target-store.ts"],"sourcesContent":["/**\n * Persistent store for the CDP-mode \"current tab\" targetId.\n *\n * The Midscene CLI runs each command as a fresh Node process, so anything\n * the previous command knew about which tab was being driven must survive\n * across processes. This store writes the chosen targetId to a temp file\n * after `connect`/`act`/etc. succeed, and the next command reads it back\n * to bind to the exact same tab — even when Chrome holds 14 of them.\n *\n * Owns nothing else. The CDP proxy lifecycle and its own metadata files\n * live in `cdp-proxy-manager.ts`.\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { getDebug } from '@midscene/shared/logger';\nimport { TARGET_ID_FILE } from './cdp-proxy-constants';\n\nconst debug = getDebug('agent-tools:cdp:target-store');\n\n/**\n * Read the saved targetId, or null if no command has stored one yet.\n */\nexport function readSavedTargetId(): string | null {\n if (!existsSync(TARGET_ID_FILE)) return null;\n try {\n return readFileSync(TARGET_ID_FILE, 'utf-8').trim() || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Save a targetId so the next CLI command can rebind to the same tab.\n */\nexport function saveTargetId(targetId: string): void {\n try {\n writeFileSync(TARGET_ID_FILE, targetId, 'utf-8');\n debug('Saved targetId: %s', targetId);\n } catch (err) {\n debug('Failed to save targetId: %s', err);\n }\n}\n\n/**\n * Discard the saved targetId — call when disconnecting or when the\n * upstream Chrome changes (the targetId would point into the old\n * browser's tab list).\n */\nexport function cleanupTargetIdFile(): void {\n try {\n if (existsSync(TARGET_ID_FILE)) unlinkSync(TARGET_ID_FILE);\n } catch {}\n}\n"],"names":["debug","getDebug","readSavedTargetId","existsSync","TARGET_ID_FILE","readFileSync","saveTargetId","targetId","writeFileSync","err","cleanupTargetIdFile","unlinkSync"],"mappings":";;;AAiBA,MAAMA,QAAQC,SAAS;AAKhB,SAASC;IACd,IAAI,CAACC,WAAWC,iBAAiB,OAAO;IACxC,IAAI;QACF,OAAOC,aAAaD,gBAAgB,SAAS,IAAI,MAAM;IACzD,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAKO,SAASE,aAAaC,QAAgB;IAC3C,IAAI;QACFC,cAAcJ,gBAAgBG,UAAU;QACxCP,MAAM,sBAAsBO;IAC9B,EAAE,OAAOE,KAAK;QACZT,MAAM,+BAA+BS;IACvC;AACF;AAOO,SAASC;IACd,IAAI;QACF,IAAIP,WAAWC,iBAAiBO,WAAWP;IAC7C,EAAE,OAAM,CAAC;AACX"}
|
package/dist/es/cli.mjs
CHANGED
|
@@ -3,10 +3,10 @@ import { join } from "node:path";
|
|
|
3
3
|
import { createReportCliCommands } from "@midscene/core";
|
|
4
4
|
import { reportCLIError, runToolsCLI } from "@midscene/shared/cli";
|
|
5
5
|
import dotenv from "dotenv";
|
|
6
|
+
import { WebMidsceneTools } from "./agent-tools.mjs";
|
|
7
|
+
import { WebCdpMidsceneTools } from "./agent-tools-cdp.mjs";
|
|
8
|
+
import { WebPuppeteerMidsceneTools } from "./agent-tools-puppeteer.mjs";
|
|
6
9
|
import { parseWebCliOptions } from "./cli-options.mjs";
|
|
7
|
-
import { WebMidsceneTools } from "./mcp-tools.mjs";
|
|
8
|
-
import { WebCdpMidsceneTools } from "./mcp-tools-cdp.mjs";
|
|
9
|
-
import { WebPuppeteerMidsceneTools } from "./mcp-tools-puppeteer.mjs";
|
|
10
10
|
const envFile = join(process.cwd(), '.env');
|
|
11
11
|
if (existsSync(envFile)) dotenv.config({
|
|
12
12
|
path: envFile
|
|
@@ -18,7 +18,7 @@ Promise.resolve().then(()=>{
|
|
|
18
18
|
return runToolsCLI(tools, 'midscene-web', {
|
|
19
19
|
stripPrefix: 'web_',
|
|
20
20
|
argv: parsedOptions.argv,
|
|
21
|
-
version: "1.
|
|
21
|
+
version: "1.10.1-beta-20260624112700.0",
|
|
22
22
|
extraCommands: createReportCliCommands()
|
|
23
23
|
});
|
|
24
24
|
}).catch((e)=>{
|
package/dist/es/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","sources":["../../src/cli.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { createReportCliCommands } from '@midscene/core';\nimport { reportCLIError, runToolsCLI } from '@midscene/shared/cli';\nimport dotenv from 'dotenv';\nimport {
|
|
1
|
+
{"version":3,"file":"cli.mjs","sources":["../../src/cli.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { createReportCliCommands } from '@midscene/core';\nimport { reportCLIError, runToolsCLI } from '@midscene/shared/cli';\nimport dotenv from 'dotenv';\nimport { WebMidsceneTools } from './agent-tools';\nimport { WebCdpMidsceneTools } from './agent-tools-cdp';\nimport { WebPuppeteerMidsceneTools } from './agent-tools-puppeteer';\nimport { parseWebCliOptions } from './cli-options';\n\n// Load .env early so MIDSCENE_CDP_ENDPOINT is available during arg parsing\nconst envFile = join(process.cwd(), '.env');\nif (existsSync(envFile)) {\n dotenv.config({ path: envFile });\n}\n\ndeclare const __VERSION__: string;\n\nPromise.resolve()\n .then(() => {\n const parsedOptions = parseWebCliOptions(process.argv.slice(2));\n\n let tools:\n | WebMidsceneTools\n | WebPuppeteerMidsceneTools\n | WebCdpMidsceneTools;\n if (parsedOptions.mode === 'bridge') {\n tools = new WebMidsceneTools();\n } else if (parsedOptions.mode === 'cdp') {\n tools = new WebCdpMidsceneTools(parsedOptions.cdpEndpoint!);\n } else {\n tools = new WebPuppeteerMidsceneTools(parsedOptions.viewport);\n }\n\n return runToolsCLI(tools, 'midscene-web', {\n stripPrefix: 'web_',\n argv: parsedOptions.argv,\n version: __VERSION__,\n extraCommands: createReportCliCommands(),\n });\n })\n .catch((e) => {\n process.exit(reportCLIError(e));\n });\n"],"names":["envFile","join","process","existsSync","dotenv","Promise","parsedOptions","parseWebCliOptions","tools","WebMidsceneTools","WebCdpMidsceneTools","WebPuppeteerMidsceneTools","runToolsCLI","__VERSION__","createReportCliCommands","e","reportCLIError"],"mappings":";;;;;;;;;AAWA,MAAMA,UAAUC,KAAKC,QAAQ,GAAG,IAAI;AACpC,IAAIC,WAAWH,UACbI,OAAO,MAAM,CAAC;IAAE,MAAMJ;AAAQ;AAKhCK,QAAQ,OAAO,GACZ,IAAI,CAAC;IACJ,MAAMC,gBAAgBC,mBAAmBL,QAAQ,IAAI,CAAC,KAAK,CAAC;IAE5D,IAAIM;IAKFA,QADEF,AAAuB,aAAvBA,cAAc,IAAI,GACZ,IAAIG,qBACHH,AAAuB,UAAvBA,cAAc,IAAI,GACnB,IAAII,oBAAoBJ,cAAc,WAAW,IAEjD,IAAIK,0BAA0BL,cAAc,QAAQ;IAG9D,OAAOM,YAAYJ,OAAO,gBAAgB;QACxC,aAAa;QACb,MAAMF,cAAc,IAAI;QACxB,SAASO;QACT,eAAeC;IACjB;AACF,GACC,KAAK,CAAC,CAACC;IACNb,QAAQ,IAAI,CAACc,eAAeD;AAC9B"}
|
package/dist/es/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { PlaywrightAgent, PlaywrightAiFixture } from "./playwright/index.mjs";
|
|
|
2
2
|
import { Agent } from "@midscene/core/agent";
|
|
3
3
|
import { PuppeteerAgent } from "./puppeteer/index.mjs";
|
|
4
4
|
import { StaticPage, StaticPageAgent } from "./static/index.mjs";
|
|
5
|
-
import { WebMidsceneTools } from "./
|
|
5
|
+
import { WebMidsceneTools } from "./agent-tools.mjs";
|
|
6
6
|
import { webPlaygroundPlatform } from "./platform.mjs";
|
|
7
|
-
import { WebCdpMidsceneTools } from "./
|
|
7
|
+
import { WebCdpMidsceneTools } from "./agent-tools-cdp.mjs";
|
|
8
8
|
export { Agent as PageAgent, PlaywrightAgent, PlaywrightAiFixture, PuppeteerAgent, StaticPage, StaticPageAgent, WebCdpMidsceneTools, WebMidsceneTools, webPlaygroundPlatform };
|
|
@@ -24,11 +24,13 @@ class PlaywrightAgent extends Agent {
|
|
|
24
24
|
if (!page) throw new Error('[midscene] PlaywrightAgent requires a valid Playwright page instance. Please make sure to pass a valid page object.');
|
|
25
25
|
const webPage = new WebPage(page, opts);
|
|
26
26
|
super(webPage, opts);
|
|
27
|
-
const { forceSameTabNavigation = true, forceChromeSelectRendering } = opts ?? {};
|
|
27
|
+
const { forceSameTabNavigation = true, forceChromeSelectRendering = true } = opts ?? {};
|
|
28
28
|
if (forceSameTabNavigation) forceClosePopup(page, debug);
|
|
29
29
|
if (forceChromeSelectRendering) {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (opts?.forceChromeSelectRendering === true) {
|
|
31
|
+
const playwrightVersion = getPlaywrightVersion();
|
|
32
|
+
if (playwrightVersion && !semver.gte(playwrightVersion, '1.52.0')) console.warn(`[midscene:error] forceChromeSelectRendering requires Playwright >= 1.52.0, but current version is ${playwrightVersion}. This feature may not work correctly.`);
|
|
33
|
+
}
|
|
32
34
|
base_page_mjs_forceChromeSelectRendering(page);
|
|
33
35
|
}
|
|
34
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright/index.mjs","sources":["../../../src/playwright/index.ts"],"sourcesContent":["import { Agent as PageAgent } from '@midscene/core/agent';\nimport type { Page as PlaywrightPage } from 'playwright';\nimport { WebPage as PlaywrightWebPage } from './page';\n\nexport type {\n PlayWrightAiFixtureType,\n PlaywrightAiFixtureOptions,\n} from './ai-fixture';\nexport { PlaywrightAiFixture } from './ai-fixture';\nexport { overrideAIConfig } from '@midscene/shared/env';\nexport { WebPage as PlaywrightWebPage } from './page';\nexport type { WebPageAgentOpt } from '@/web-element';\nimport type { WebPageAgentOpt } from '@/web-element';\nimport { getDebug } from '@midscene/shared/logger';\nimport semver from 'semver';\nimport {\n BROWSER_NAVIGATION_ERROR_PATTERN,\n forceChromeSelectRendering as applyChromeSelectRendering,\n forceClosePopup,\n} from '../puppeteer/base-page';\nimport { getWebpackRequire } from '../utils';\n\nconst debug = getDebug('playwright:agent');\n\n/**\n * Get Playwright version from package.json\n */\nfunction getPlaywrightVersion(): string | null {\n try {\n const playwrightPkg = getWebpackRequire()('playwright/package.json');\n return playwrightPkg.version || null;\n } catch (error) {\n console.error('[midscene:error] Failed to get Playwright version', error);\n return null;\n }\n}\n\nexport class PlaywrightAgent extends PageAgent<PlaywrightWebPage> {\n protected isRetryableContextError(error: unknown): boolean {\n return (\n error instanceof Error &&\n BROWSER_NAVIGATION_ERROR_PATTERN.test(error.message)\n );\n }\n\n constructor(page: PlaywrightPage, opts?: WebPageAgentOpt) {\n if (!page) {\n throw new Error(\n '[midscene] PlaywrightAgent requires a valid Playwright page instance. Please make sure to pass a valid page object.',\n );\n }\n const webPage = new PlaywrightWebPage(page, opts);\n super(webPage, opts);\n\n const { forceSameTabNavigation = true, forceChromeSelectRendering } =\n opts ?? {};\n\n if (forceSameTabNavigation) {\n forceClosePopup(page, debug);\n }\n\n if (forceChromeSelectRendering) {\n //
|
|
1
|
+
{"version":3,"file":"playwright/index.mjs","sources":["../../../src/playwright/index.ts"],"sourcesContent":["import { Agent as PageAgent } from '@midscene/core/agent';\nimport type { Page as PlaywrightPage } from 'playwright';\nimport { WebPage as PlaywrightWebPage } from './page';\n\nexport type {\n PlayWrightAiFixtureType,\n PlaywrightAiFixtureOptions,\n} from './ai-fixture';\nexport { PlaywrightAiFixture } from './ai-fixture';\nexport { overrideAIConfig } from '@midscene/shared/env';\nexport { WebPage as PlaywrightWebPage } from './page';\nexport type { WebPageAgentOpt } from '@/web-element';\nimport type { WebPageAgentOpt } from '@/web-element';\nimport { getDebug } from '@midscene/shared/logger';\nimport semver from 'semver';\nimport {\n BROWSER_NAVIGATION_ERROR_PATTERN,\n forceChromeSelectRendering as applyChromeSelectRendering,\n forceClosePopup,\n} from '../puppeteer/base-page';\nimport { getWebpackRequire } from '../utils';\n\nconst debug = getDebug('playwright:agent');\n\n/**\n * Get Playwright version from package.json\n */\nfunction getPlaywrightVersion(): string | null {\n try {\n const playwrightPkg = getWebpackRequire()('playwright/package.json');\n return playwrightPkg.version || null;\n } catch (error) {\n console.error('[midscene:error] Failed to get Playwright version', error);\n return null;\n }\n}\n\nexport class PlaywrightAgent extends PageAgent<PlaywrightWebPage> {\n protected isRetryableContextError(error: unknown): boolean {\n return (\n error instanceof Error &&\n BROWSER_NAVIGATION_ERROR_PATTERN.test(error.message)\n );\n }\n\n constructor(page: PlaywrightPage, opts?: WebPageAgentOpt) {\n if (!page) {\n throw new Error(\n '[midscene] PlaywrightAgent requires a valid Playwright page instance. Please make sure to pass a valid page object.',\n );\n }\n const webPage = new PlaywrightWebPage(page, opts);\n super(webPage, opts);\n\n const { forceSameTabNavigation = true, forceChromeSelectRendering = true } =\n opts ?? {};\n\n if (forceSameTabNavigation) {\n forceClosePopup(page, debug);\n }\n\n if (forceChromeSelectRendering) {\n // Only warn about version requirements when the user explicitly opted in;\n // it is on by default, so we should not nag users on older Playwright.\n if (opts?.forceChromeSelectRendering === true) {\n const playwrightVersion = getPlaywrightVersion();\n if (playwrightVersion && !semver.gte(playwrightVersion, '1.52.0')) {\n console.warn(\n `[midscene:error] forceChromeSelectRendering requires Playwright >= 1.52.0, but current version is ${playwrightVersion}. This feature may not work correctly.`,\n );\n }\n }\n applyChromeSelectRendering(page);\n }\n }\n}\n"],"names":["debug","getDebug","getPlaywrightVersion","playwrightPkg","getWebpackRequire","error","console","PlaywrightAgent","PageAgent","Error","BROWSER_NAVIGATION_ERROR_PATTERN","page","opts","webPage","PlaywrightWebPage","forceSameTabNavigation","forceChromeSelectRendering","forceClosePopup","playwrightVersion","semver","applyChromeSelectRendering"],"mappings":";;;;;;;;AAsBA,MAAMA,QAAQC,SAAS;AAKvB,SAASC;IACP,IAAI;QACF,MAAMC,gBAAgBC,oBAAoB;QAC1C,OAAOD,cAAc,OAAO,IAAI;IAClC,EAAE,OAAOE,OAAO;QACdC,QAAQ,KAAK,CAAC,qDAAqDD;QACnE,OAAO;IACT;AACF;AAEO,MAAME,wBAAwBC;IACzB,wBAAwBH,KAAc,EAAW;QACzD,OACEA,iBAAiBI,SACjBC,iCAAiC,IAAI,CAACL,MAAM,OAAO;IAEvD;IAEA,YAAYM,IAAoB,EAAEC,IAAsB,CAAE;QACxD,IAAI,CAACD,MACH,MAAM,IAAIF,MACR;QAGJ,MAAMI,UAAU,IAAIC,QAAkBH,MAAMC;QAC5C,KAAK,CAACC,SAASD;QAEf,MAAM,EAAEG,yBAAyB,IAAI,EAAEC,6BAA6B,IAAI,EAAE,GACxEJ,QAAQ,CAAC;QAEX,IAAIG,wBACFE,gBAAgBN,MAAMX;QAGxB,IAAIgB,4BAA4B;YAG9B,IAAIJ,MAAM,+BAA+B,MAAM;gBAC7C,MAAMM,oBAAoBhB;gBAC1B,IAAIgB,qBAAqB,CAACC,OAAO,GAAG,CAACD,mBAAmB,WACtDZ,QAAQ,IAAI,CACV,CAAC,kGAAkG,EAAEY,kBAAkB,sCAAsC,CAAC;YAGpK;YACAE,yCAA2BT;QAC7B;IACF;AACF"}
|
|
@@ -179,17 +179,15 @@ class Page {
|
|
|
179
179
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
180
180
|
console.warn(`[Midscene] Playwright screenshot failed: ${errorMsg}. Falling back to CDP screenshot.`);
|
|
181
181
|
debugPage('playwright screenshot failed, trying CDP fallback: %s', error);
|
|
182
|
-
base64 = await this.screenshotBase64ByPlaywrightCdp(
|
|
182
|
+
base64 = await this.screenshotBase64ByPlaywrightCdp(imgType, quality);
|
|
183
183
|
}
|
|
184
184
|
} else throw new Error('Unsupported page type for screenshot');
|
|
185
185
|
const endTime = Date.now();
|
|
186
186
|
debugPage(`screenshotBase64 end, cost: ${endTime - startTime}ms`);
|
|
187
187
|
return base64;
|
|
188
188
|
}
|
|
189
|
-
async screenshotBase64ByPlaywrightCdp(
|
|
190
|
-
const
|
|
191
|
-
if (browserName && 'chromium' !== browserName) throw new Error(`CDP screenshot fallback requires Chromium-based browser, but current browser is "${browserName}".`);
|
|
192
|
-
const client = await page.context().newCDPSession(page);
|
|
189
|
+
async screenshotBase64ByPlaywrightCdp(imgType, quality) {
|
|
190
|
+
const client = await this.createPageCdpSession('CDP screenshot fallback');
|
|
193
191
|
try {
|
|
194
192
|
const result = await new Promise((resolve, reject)=>{
|
|
195
193
|
const timeoutId = setTimeout(()=>{
|
|
@@ -215,14 +213,18 @@ class Page {
|
|
|
215
213
|
});
|
|
216
214
|
}
|
|
217
215
|
}
|
|
218
|
-
async
|
|
216
|
+
async createPageCdpSession(featureName) {
|
|
219
217
|
if ('puppeteer' === this.interfaceType) {
|
|
220
218
|
const page = this.underlyingPage;
|
|
221
|
-
|
|
219
|
+
const pageWithCdp = page;
|
|
220
|
+
if ('function' == typeof pageWithCdp.createCDPSession) return await pageWithCdp.createCDPSession();
|
|
221
|
+
const target = page.target?.();
|
|
222
|
+
if ('function' == typeof target?.createCDPSession) return await target.createCDPSession();
|
|
223
|
+
throw new Error(`${featureName} requires a browser page with CDP session support.`);
|
|
222
224
|
}
|
|
223
225
|
const page = this.underlyingPage;
|
|
224
226
|
const browserName = page.context().browser()?.browserType().name();
|
|
225
|
-
if (browserName && 'chromium' !== browserName) throw new Error(
|
|
227
|
+
if (browserName && 'chromium' !== browserName) throw new Error(`${featureName} requires Chromium-based browser, but current browser is "${browserName}".`);
|
|
226
228
|
return await page.context().newCDPSession(page);
|
|
227
229
|
}
|
|
228
230
|
async waitForDomQuiet(opts) {
|
|
@@ -307,7 +309,7 @@ class Page {
|
|
|
307
309
|
async startMjpegStream(options) {
|
|
308
310
|
const { signal, onFrame, onError } = options;
|
|
309
311
|
if ('function' == typeof this.underlyingPage.bringToFront) await this.underlyingPage.bringToFront();
|
|
310
|
-
const client = await this.
|
|
312
|
+
const client = await this.createPageCdpSession('CDP screencast');
|
|
311
313
|
let stopped = false;
|
|
312
314
|
const streamToken = Symbol('mjpeg-stream');
|
|
313
315
|
const reportStreamError = (error)=>{
|
|
@@ -490,6 +492,22 @@ class Page {
|
|
|
490
492
|
}
|
|
491
493
|
};
|
|
492
494
|
}
|
|
495
|
+
async selectAllByCdp() {
|
|
496
|
+
const client = await this.createPageCdpSession('clearInput');
|
|
497
|
+
try {
|
|
498
|
+
await client.send('Input.dispatchKeyEvent', {
|
|
499
|
+
type: 'rawKeyDown',
|
|
500
|
+
commands: [
|
|
501
|
+
'selectAll'
|
|
502
|
+
]
|
|
503
|
+
});
|
|
504
|
+
await client.send('Input.dispatchKeyEvent', {
|
|
505
|
+
type: 'keyUp'
|
|
506
|
+
});
|
|
507
|
+
} finally{
|
|
508
|
+
await client.detach().catch(()=>void 0);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
493
511
|
async clearInput(element) {
|
|
494
512
|
const backspace = async ()=>{
|
|
495
513
|
await sleep(100);
|
|
@@ -499,28 +517,17 @@ class Page {
|
|
|
499
517
|
}
|
|
500
518
|
]);
|
|
501
519
|
};
|
|
502
|
-
const isMac = 'darwin' === process.platform;
|
|
503
520
|
debugPage('clearInput begin');
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
count: 3
|
|
508
|
-
});
|
|
509
|
-
await backspace();
|
|
510
|
-
}
|
|
511
|
-
element && await this.mouse.click(element.center[0], element.center[1]);
|
|
512
|
-
await this.underlyingPage.keyboard.down('Meta');
|
|
513
|
-
await this.underlyingPage.keyboard.press('a');
|
|
514
|
-
await this.underlyingPage.keyboard.up('Meta');
|
|
515
|
-
await backspace();
|
|
516
|
-
} else {
|
|
517
|
-
element && await this.mouse.click(element.center[0], element.center[1]);
|
|
518
|
-
await this.underlyingPage.keyboard.down('Control');
|
|
519
|
-
await this.underlyingPage.keyboard.press('a');
|
|
520
|
-
await this.underlyingPage.keyboard.up('Control');
|
|
521
|
+
element && await this.mouse.click(element.center[0], element.center[1]);
|
|
522
|
+
try {
|
|
523
|
+
await this.selectAllByCdp();
|
|
521
524
|
await backspace();
|
|
525
|
+
} catch (error) {
|
|
526
|
+
debugPage('clearInput cdp selectAll failed', error);
|
|
527
|
+
throw error;
|
|
528
|
+
} finally{
|
|
529
|
+
debugPage('clearInput end');
|
|
522
530
|
}
|
|
523
|
-
debugPage('clearInput end');
|
|
524
531
|
}
|
|
525
532
|
async moveToPointBeforeScroll(point) {
|
|
526
533
|
if (point) await this.mouse.move(point.left, point.top);
|
|
@@ -598,7 +605,7 @@ class Page {
|
|
|
598
605
|
async stopLoading() {
|
|
599
606
|
debugPage('stop loading');
|
|
600
607
|
if ('puppeteer' === this.interfaceType) {
|
|
601
|
-
const client = await this.
|
|
608
|
+
const client = await this.createPageCdpSession('stopLoading');
|
|
602
609
|
try {
|
|
603
610
|
await client.send('Page.stopLoading');
|
|
604
611
|
} finally{
|
|
@@ -704,17 +711,7 @@ class Page {
|
|
|
704
711
|
const delay = duration / steps;
|
|
705
712
|
const halfStart = startDistance / 2;
|
|
706
713
|
const halfEnd = endDistance / 2;
|
|
707
|
-
|
|
708
|
-
if ('puppeteer' === this.interfaceType) {
|
|
709
|
-
const page = this.underlyingPage;
|
|
710
|
-
client = await page.target().createCDPSession();
|
|
711
|
-
} else {
|
|
712
|
-
if ('playwright' !== this.interfaceType) return;
|
|
713
|
-
const page = this.underlyingPage;
|
|
714
|
-
const browserName = page.context().browser()?.browserType().name();
|
|
715
|
-
if (browserName && 'chromium' !== browserName) throw new Error(`Pinch gesture requires Chromium-based browser, but current browser is "${browserName}". CDP touch events are not supported in Firefox/WebKit.`);
|
|
716
|
-
client = await page.context().newCDPSession(page);
|
|
717
|
-
}
|
|
714
|
+
const client = await this.createPageCdpSession('Pinch gesture');
|
|
718
715
|
try {
|
|
719
716
|
await client.send('Input.dispatchTouchEvent', {
|
|
720
717
|
type: 'touchStart',
|
|
@@ -758,9 +755,9 @@ class Page {
|
|
|
758
755
|
await client.detach();
|
|
759
756
|
}
|
|
760
757
|
}
|
|
761
|
-
async ensurePuppeteerFileChooserSession(
|
|
758
|
+
async ensurePuppeteerFileChooserSession() {
|
|
762
759
|
if (this.puppeteerFileChooserSession) return this.puppeteerFileChooserSession;
|
|
763
|
-
const session = await
|
|
760
|
+
const session = await this.createPageCdpSession('Puppeteer file chooser');
|
|
764
761
|
await session.send('Page.enable');
|
|
765
762
|
await session.send('DOM.enable');
|
|
766
763
|
await session.send('Page.setInterceptFileChooserDialog', {
|
|
@@ -771,8 +768,7 @@ class Page {
|
|
|
771
768
|
}
|
|
772
769
|
async registerFileChooserListener(handler) {
|
|
773
770
|
if ('puppeteer' !== this.interfaceType) throw new Error('registerFileChooserListener is only supported in Puppeteer');
|
|
774
|
-
const
|
|
775
|
-
const session = await this.ensurePuppeteerFileChooserSession(page);
|
|
771
|
+
const session = await this.ensurePuppeteerFileChooserSession();
|
|
776
772
|
if (this.puppeteerFileChooserHandler) session.off('Page.fileChooserOpened', this.puppeteerFileChooserHandler);
|
|
777
773
|
let capturedError;
|
|
778
774
|
this.puppeteerFileChooserHandler = async (event)=>{
|
|
@@ -858,7 +854,10 @@ function forceClosePopup(page, debugProfile) {
|
|
|
858
854
|
}
|
|
859
855
|
});
|
|
860
856
|
}
|
|
857
|
+
const forceSelectRenderingPages = new WeakSet();
|
|
861
858
|
function forceChromeSelectRendering(page) {
|
|
859
|
+
if (forceSelectRenderingPages.has(page)) return;
|
|
860
|
+
forceSelectRenderingPages.add(page);
|
|
862
861
|
const styleContent = `
|
|
863
862
|
/* Add by Midscene because of forceChromeSelectRendering is enabled*/
|
|
864
863
|
select {
|