@midscene/web 1.7.5-beta-20260421030751.0 → 1.7.5

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.
Files changed (46) hide show
  1. package/dist/es/bridge-mode/io-client.mjs +1 -1
  2. package/dist/es/bridge-mode/io-server.mjs +2 -2
  3. package/dist/es/bridge-mode/io-server.mjs.map +1 -1
  4. package/dist/es/bridge-mode/page-browser-side.mjs +1 -1
  5. package/dist/es/bridge-mode/page-browser-side.mjs.map +1 -1
  6. package/dist/es/cdp-proxy-manager.mjs +219 -0
  7. package/dist/es/cdp-proxy-manager.mjs.map +1 -0
  8. package/dist/es/cdp-target-store.mjs +28 -0
  9. package/dist/es/cdp-target-store.mjs.map +1 -0
  10. package/dist/es/cli.mjs +3 -4
  11. package/dist/es/cli.mjs.map +1 -1
  12. package/dist/es/mcp-server.mjs +1 -1
  13. package/dist/es/mcp-tools-cdp.mjs +4 -218
  14. package/dist/es/mcp-tools-cdp.mjs.map +1 -1
  15. package/dist/es/mcp-tools-puppeteer.mjs +2 -1
  16. package/dist/es/mcp-tools-puppeteer.mjs.map +1 -1
  17. package/dist/es/mcp-tools.mjs +1 -1
  18. package/dist/es/mcp-tools.mjs.map +1 -1
  19. package/dist/es/playwright/ai-fixture.mjs +3 -3
  20. package/dist/es/playwright/ai-fixture.mjs.map +1 -1
  21. package/dist/lib/bridge-mode/io-client.js +1 -1
  22. package/dist/lib/bridge-mode/io-server.js +2 -2
  23. package/dist/lib/bridge-mode/io-server.js.map +1 -1
  24. package/dist/lib/bridge-mode/page-browser-side.js +1 -1
  25. package/dist/lib/bridge-mode/page-browser-side.js.map +1 -1
  26. package/dist/lib/cdp-proxy-manager.js +275 -0
  27. package/dist/lib/cdp-proxy-manager.js.map +1 -0
  28. package/dist/lib/cdp-target-store.js +68 -0
  29. package/dist/lib/cdp-target-store.js.map +1 -0
  30. package/dist/lib/cli.js +2 -3
  31. package/dist/lib/cli.js.map +1 -1
  32. package/dist/lib/mcp-server.js +1 -1
  33. package/dist/lib/mcp-tools-cdp.js +9 -227
  34. package/dist/lib/mcp-tools-cdp.js.map +1 -1
  35. package/dist/lib/mcp-tools-puppeteer.js +4 -3
  36. package/dist/lib/mcp-tools-puppeteer.js.map +1 -1
  37. package/dist/lib/mcp-tools.js +2 -2
  38. package/dist/lib/mcp-tools.js.map +1 -1
  39. package/dist/lib/playwright/ai-fixture.js +3 -3
  40. package/dist/lib/playwright/ai-fixture.js.map +1 -1
  41. package/dist/types/cdp-proxy-manager.d.ts +53 -0
  42. package/dist/types/cdp-target-store.d.ts +26 -0
  43. package/dist/types/mcp-tools-cdp.d.ts +2 -32
  44. package/dist/types/mcp-tools-puppeteer.d.ts +2 -1
  45. package/dist/types/mcp-tools.d.ts +2 -1
  46. package/package.json +4 -4
@@ -1,12 +1,9 @@
1
- import { spawn } from "node:child_process";
2
- import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
- import node_http from "node:http";
4
- import { join } from "node:path";
5
1
  import { ScreenshotItem, z } from "@midscene/core";
6
2
  import { getDebug } from "@midscene/shared/logger";
7
- import { BaseMidsceneTools } from "@midscene/shared/mcp";
3
+ import { BaseMidsceneTools } from "@midscene/shared/mcp/base-tools";
8
4
  import puppeteer_core from "puppeteer-core";
9
- import { PROXY_ENDPOINT_FILE, PROXY_PID_FILE, PROXY_UPSTREAM_FILE, TARGET_ID_FILE } from "./cdp-proxy-constants.mjs";
5
+ import { getProxyEndpoint } from "./cdp-proxy-manager.mjs";
6
+ import { cleanupTargetIdFile, readSavedTargetId, saveTargetId } from "./cdp-target-store.mjs";
10
7
  import { PuppeteerAgent } from "./puppeteer/index.mjs";
11
8
  import { StaticPage } from "./static/index.mjs";
12
9
  function _define_property(obj, key, value) {
@@ -21,214 +18,9 @@ function _define_property(obj, key, value) {
21
18
  }
22
19
  const debug = getDebug('mcp:cdp');
23
20
  const CDP_TARGET_DISCOVERY_DELAY_MS = 500;
24
- function isPageLevelEndpoint(endpoint) {
25
- return /\/devtools\/page\//.test(endpoint);
26
- }
27
- function resolveBrowserEndpoint(pageEndpoint) {
28
- return new Promise((resolve, reject)=>{
29
- let host;
30
- try {
31
- const url = new URL(pageEndpoint);
32
- host = url.host;
33
- } catch {
34
- reject(new Error(`Invalid CDP endpoint URL: ${pageEndpoint}`));
35
- return;
36
- }
37
- const req = node_http.get(`http://${host}/json/version`, {
38
- timeout: 5000
39
- }, (res)=>{
40
- if (res.statusCode && res.statusCode >= 400) {
41
- reject(new Error(`/json/version returned HTTP ${res.statusCode}`));
42
- res.resume();
43
- return;
44
- }
45
- let data = '';
46
- res.on('data', (chunk)=>{
47
- data += chunk.toString();
48
- });
49
- res.on('end', ()=>{
50
- try {
51
- const info = JSON.parse(data);
52
- if (info.webSocketDebuggerUrl) resolve(info.webSocketDebuggerUrl);
53
- else reject(new Error('webSocketDebuggerUrl not found in /json/version response'));
54
- } catch {
55
- reject(new Error(`Failed to parse /json/version response: ${data}`));
56
- }
57
- });
58
- });
59
- req.on('error', (err)=>reject(new Error(`Failed to fetch /json/version: ${err.message}`)));
60
- req.on('timeout', ()=>{
61
- req.destroy();
62
- reject(new Error('Timeout fetching /json/version'));
63
- });
64
- });
65
- }
66
- function isProxyAlive() {
67
- if (!existsSync(PROXY_PID_FILE)) return false;
68
- try {
69
- const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());
70
- process.kill(pid, 0);
71
- return true;
72
- } catch {
73
- return false;
74
- }
75
- }
76
- function readProxyEndpoint() {
77
- if (!existsSync(PROXY_ENDPOINT_FILE)) return null;
78
- try {
79
- return readFileSync(PROXY_ENDPOINT_FILE, 'utf-8').trim();
80
- } catch {
81
- return null;
82
- }
83
- }
84
- function readProxyUpstream() {
85
- if (!existsSync(PROXY_UPSTREAM_FILE)) return null;
86
- try {
87
- return readFileSync(PROXY_UPSTREAM_FILE, 'utf-8').trim();
88
- } catch {
89
- return null;
90
- }
91
- }
92
- function killProxy() {
93
- if (!existsSync(PROXY_PID_FILE)) return;
94
- try {
95
- const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());
96
- process.kill(pid, 'SIGTERM');
97
- debug('Killed proxy pid: %d', pid);
98
- } catch (err) {
99
- debug('killProxy failed: %s', err);
100
- }
101
- try {
102
- if (existsSync(PROXY_ENDPOINT_FILE)) unlinkSync(PROXY_ENDPOINT_FILE);
103
- } catch {}
104
- try {
105
- if (existsSync(PROXY_PID_FILE)) unlinkSync(PROXY_PID_FILE);
106
- } catch {}
107
- try {
108
- if (existsSync(PROXY_UPSTREAM_FILE)) unlinkSync(PROXY_UPSTREAM_FILE);
109
- } catch {}
110
- cleanupTargetIdFile();
111
- }
112
- function readSavedTargetId() {
113
- if (!existsSync(TARGET_ID_FILE)) return null;
114
- try {
115
- return readFileSync(TARGET_ID_FILE, 'utf-8').trim() || null;
116
- } catch {
117
- return null;
118
- }
119
- }
120
- function saveTargetId(targetId) {
121
- try {
122
- writeFileSync(TARGET_ID_FILE, targetId, 'utf-8');
123
- debug('Saved targetId: %s', targetId);
124
- } catch (err) {
125
- debug('Failed to save targetId: %s', err);
126
- }
127
- }
128
- function cleanupTargetIdFile() {
129
- try {
130
- if (existsSync(TARGET_ID_FILE)) unlinkSync(TARGET_ID_FILE);
131
- } catch {}
132
- }
133
21
  function getTargetId(page) {
134
22
  return page.target()._targetId;
135
23
  }
136
- const PROXY_STDERR_BUFFER_LIMIT = 8192;
137
- function spawnProxy(chromeEndpoint) {
138
- return new Promise((resolve, reject)=>{
139
- const proxyScript = join(__dirname, 'cdp-proxy.js');
140
- const proc = spawn(process.execPath, [
141
- proxyScript,
142
- chromeEndpoint
143
- ], {
144
- detached: true,
145
- stdio: [
146
- 'ignore',
147
- 'pipe',
148
- 'pipe'
149
- ]
150
- });
151
- proc.unref();
152
- let output = '';
153
- let stderrBuf = '';
154
- let settled = false;
155
- const appendStderr = (chunk)=>{
156
- stderrBuf += chunk.toString();
157
- if (stderrBuf.length > PROXY_STDERR_BUFFER_LIMIT) stderrBuf = stderrBuf.slice(-PROXY_STDERR_BUFFER_LIMIT);
158
- };
159
- proc.stderr.on('data', appendStderr);
160
- const formatStderr = ()=>{
161
- const trimmed = stderrBuf.trim();
162
- return trimmed ? ` (stderr: ${trimmed})` : '';
163
- };
164
- const timer = setTimeout(()=>{
165
- if (!settled) {
166
- settled = true;
167
- reject(new Error(`Proxy startup timeout (10s)${formatStderr()}`));
168
- }
169
- }, 10000);
170
- const onData = (chunk)=>{
171
- output += chunk.toString();
172
- const lines = output.split('\n');
173
- for (const line of lines)if (line.trim()) try {
174
- const parsed = JSON.parse(line);
175
- if (parsed.endpoint && !settled) {
176
- settled = true;
177
- clearTimeout(timer);
178
- proc.stdout.removeListener('data', onData);
179
- proc.stderr.removeListener('data', appendStderr);
180
- proc.stdout.destroy();
181
- proc.stderr.destroy();
182
- resolve(parsed.endpoint);
183
- return;
184
- }
185
- } catch {}
186
- };
187
- proc.stdout.on('data', onData);
188
- proc.on('error', (err)=>{
189
- if (!settled) {
190
- settled = true;
191
- clearTimeout(timer);
192
- reject(new Error(`Failed to spawn proxy: ${err.message}`));
193
- }
194
- });
195
- proc.on('exit', (code, signal)=>{
196
- if (!settled) {
197
- settled = true;
198
- clearTimeout(timer);
199
- const how = signal ? `signal ${signal}` : `code ${code}`;
200
- reject(new Error(`Proxy exited with ${how} before ready${formatStderr()}`));
201
- }
202
- });
203
- });
204
- }
205
- async function getProxyEndpoint(chromeEndpoint) {
206
- let browserEndpoint = chromeEndpoint;
207
- if (isPageLevelEndpoint(chromeEndpoint)) {
208
- debug('Page-level CDP endpoint detected, resolving via /json/version: %s', chromeEndpoint);
209
- try {
210
- browserEndpoint = await resolveBrowserEndpoint(chromeEndpoint);
211
- debug('Resolved browser endpoint: %s', browserEndpoint);
212
- } catch (err) {
213
- throw new Error(`Cannot use page-level CDP endpoint directly. Puppeteer requires a browser-level endpoint (e.g., ws://host:port/devtools/browser/<id>). Auto-resolution via /json/version failed: ${err.message}. Please provide a browser-level CDP endpoint instead.`);
214
- }
215
- }
216
- if (isProxyAlive()) {
217
- const endpoint = readProxyEndpoint();
218
- const savedUpstream = readProxyUpstream();
219
- if (endpoint) if (!savedUpstream || savedUpstream === browserEndpoint) return endpoint;
220
- else {
221
- debug('Proxy connected to different upstream (%s), killing', savedUpstream);
222
- killProxy();
223
- }
224
- }
225
- try {
226
- return await spawnProxy(browserEndpoint);
227
- } catch (err) {
228
- console.warn(`[cdp] proxy failed, falling back to direct connection: ${err}`);
229
- return browserEndpoint;
230
- }
231
- }
232
24
  class WebCdpMidsceneTools extends BaseMidsceneTools {
233
25
  createTemporaryDevice() {
234
26
  return new StaticPage({
@@ -363,12 +155,6 @@ class WebCdpMidsceneTools extends BaseMidsceneTools {
363
155
  this.cdpEndpoint = cdpEndpoint;
364
156
  }
365
157
  }
366
- const __test__ = {
367
- getProxyEndpoint,
368
- killProxy,
369
- readProxyUpstream,
370
- isProxyAlive
371
- };
372
- export { WebCdpMidsceneTools, __test__ };
158
+ export { WebCdpMidsceneTools };
373
159
 
374
160
  //# sourceMappingURL=mcp-tools-cdp.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tools-cdp.mjs","sources":["../../src/mcp-tools-cdp.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport http from 'node:http';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { getDebug } from '@midscene/shared/logger';\nimport { BaseMidsceneTools, type ToolDefinition } from '@midscene/shared/mcp';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n PROXY_ENDPOINT_FILE,\n PROXY_PID_FILE,\n PROXY_UPSTREAM_FILE,\n TARGET_ID_FILE,\n} from './cdp-proxy-constants';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst debug = getDebug('mcp:cdp');\n\n/** CDP target discovery may need a brief moment after WebSocket open. */\nconst CDP_TARGET_DISCOVERY_DELAY_MS = 500;\n\n/**\n * Check if a CDP endpoint is a page-level URL (e.g., /devtools/page/XXX).\n */\nfunction isPageLevelEndpoint(endpoint: string): boolean {\n return /\\/devtools\\/page\\//.test(endpoint);\n}\n\n/**\n * Try to resolve a page-level CDP endpoint to a browser-level endpoint\n * by fetching /json/version from the same host:port.\n */\nfunction resolveBrowserEndpoint(pageEndpoint: string): Promise<string> {\n return new Promise((resolve, reject) => {\n let host: string;\n try {\n const url = new URL(pageEndpoint);\n host = url.host; // host includes port (e.g. \"127.0.0.1:9222\")\n } catch {\n reject(new Error(`Invalid CDP endpoint URL: ${pageEndpoint}`));\n return;\n }\n\n const req = http.get(\n `http://${host}/json/version`,\n { timeout: 5000 },\n (res) => {\n if (res.statusCode && res.statusCode >= 400) {\n reject(new Error(`/json/version returned HTTP ${res.statusCode}`));\n res.resume();\n return;\n }\n let data = '';\n res.on('data', (chunk: Buffer) => {\n data += chunk.toString();\n });\n res.on('end', () => {\n try {\n const info = JSON.parse(data);\n if (info.webSocketDebuggerUrl) {\n resolve(info.webSocketDebuggerUrl);\n } else {\n reject(\n new Error(\n 'webSocketDebuggerUrl not found in /json/version response',\n ),\n );\n }\n } catch {\n reject(\n new Error(`Failed to parse /json/version response: ${data}`),\n );\n }\n });\n },\n );\n req.on('error', (err) =>\n reject(new Error(`Failed to fetch /json/version: ${err.message}`)),\n );\n req.on('timeout', () => {\n req.destroy();\n reject(new Error('Timeout fetching /json/version'));\n });\n });\n}\n\n/**\n * Check if a previously spawned proxy process is still alive.\n */\nfunction isProxyAlive(): boolean {\n if (!existsSync(PROXY_PID_FILE)) return false;\n try {\n const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n process.kill(pid, 0); // signal 0 = existence check\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the proxy endpoint written by cdp-proxy.ts.\n */\nfunction readProxyEndpoint(): string | null {\n if (!existsSync(PROXY_ENDPOINT_FILE)) return null;\n try {\n return readFileSync(PROXY_ENDPOINT_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Read the Chrome endpoint that the running proxy is connected to.\n */\nfunction readProxyUpstream(): string | null {\n if (!existsSync(PROXY_UPSTREAM_FILE)) return null;\n try {\n return readFileSync(PROXY_UPSTREAM_FILE, 'utf-8').trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Kill the running proxy process and clear all CDP-mode metadata files\n * (proxy endpoint/pid/upstream and the cross-command targetId).\n */\nfunction killProxy(): void {\n if (!existsSync(PROXY_PID_FILE)) return;\n try {\n const pid = Number(readFileSync(PROXY_PID_FILE, 'utf-8').trim());\n process.kill(pid, 'SIGTERM');\n debug('Killed proxy pid: %d', pid);\n } catch (err) {\n // ESRCH (already dead) is the common case; surface anything else\n // (e.g. EPERM) via debug so it does not vanish silently.\n debug('killProxy failed: %s', err);\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 // The saved targetId points into the now-defunct upstream's tab list,\n // so it cannot match anything in a fresh Chrome and must be discarded.\n cleanupTargetIdFile();\n}\n\n/**\n * Read the saved targetId from the temporary file.\n */\nfunction 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 to the temporary file for cross-command tab reuse.\n */\nfunction 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 * Remove the saved targetId file.\n */\nfunction cleanupTargetIdFile(): void {\n try {\n if (existsSync(TARGET_ID_FILE)) unlinkSync(TARGET_ID_FILE);\n } catch {}\n}\n\n/**\n * puppeteer-core does not expose a public method for the underlying\n * CDP target id, so we reach into `_targetId`. Centralised here so that\n * a future puppeteer release exposing this properly only requires one\n * change. Callers must treat the result as optional.\n */\nfunction getTargetId(page: Page): string | undefined {\n return (page.target() as unknown as { _targetId?: string })._targetId;\n}\n\n/** Keep at most this many bytes of proxy stderr for diagnostics. */\nconst PROXY_STDERR_BUFFER_LIMIT = 8 * 1024;\n\n/**\n * Spawn the CDP proxy process and wait for it to print the endpoint.\n *\n * Captures the child's stderr so that when startup fails we can surface\n * the real reason (upstream closed / duplicate proxy / upstream error)\n * instead of the generic \"exited before ready\".\n */\nfunction spawnProxy(chromeEndpoint: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const proxyScript = join(__dirname, 'cdp-proxy.js');\n const proc = spawn(process.execPath, [proxyScript, chromeEndpoint], {\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n proc.unref();\n\n let output = '';\n let stderrBuf = '';\n let settled = false;\n\n const appendStderr = (chunk: Buffer) => {\n stderrBuf += chunk.toString();\n if (stderrBuf.length > PROXY_STDERR_BUFFER_LIMIT) {\n stderrBuf = stderrBuf.slice(-PROXY_STDERR_BUFFER_LIMIT);\n }\n };\n proc.stderr!.on('data', appendStderr);\n\n const formatStderr = () => {\n const trimmed = stderrBuf.trim();\n return trimmed ? ` (stderr: ${trimmed})` : '';\n };\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n reject(new Error(`Proxy startup timeout (10s)${formatStderr()}`));\n }\n }, 10000);\n\n const onData = (chunk: Buffer) => {\n output += chunk.toString();\n const lines = output.split('\\n');\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const parsed = JSON.parse(line);\n if (parsed.endpoint && !settled) {\n settled = true;\n clearTimeout(timer);\n proc.stdout!.removeListener('data', onData);\n proc.stderr!.removeListener('data', appendStderr);\n // Destroy the stdio pipes so they don't keep the parent\n // process event loop alive after we've read the endpoint.\n proc.stdout!.destroy();\n proc.stderr!.destroy();\n resolve(parsed.endpoint);\n return;\n }\n } catch {\n // stdout may contain non-JSON lines during startup — skip them\n }\n }\n };\n proc.stdout!.on('data', onData);\n\n proc.on('error', (err) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn proxy: ${err.message}`));\n }\n });\n proc.on('exit', (code, signal) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n const how = signal ? `signal ${signal}` : `code ${code}`;\n reject(\n new Error(`Proxy exited with ${how} before ready${formatStderr()}`),\n );\n }\n });\n });\n}\n\n/**\n * Get the proxy endpoint, spawning the proxy if needed.\n * Falls back to direct connection if proxy cannot be started.\n *\n * If the user provides a page-level CDP URL, automatically resolves it\n * to a browser-level endpoint via /json/version.\n */\nasync function getProxyEndpoint(chromeEndpoint: string): Promise<string> {\n // If the user passed a page-level endpoint, resolve to browser-level first\n let browserEndpoint = chromeEndpoint;\n if (isPageLevelEndpoint(chromeEndpoint)) {\n debug(\n 'Page-level CDP endpoint detected, resolving via /json/version: %s',\n chromeEndpoint,\n );\n try {\n browserEndpoint = await resolveBrowserEndpoint(chromeEndpoint);\n debug('Resolved browser endpoint: %s', browserEndpoint);\n } catch (err) {\n throw new Error(\n `Cannot use page-level CDP endpoint directly. Puppeteer requires a browser-level endpoint (e.g., ws://host:port/devtools/browser/<id>). Auto-resolution via /json/version failed: ${(err as Error).message}. Please provide a browser-level CDP endpoint instead.`,\n );\n }\n }\n\n // If proxy is alive and connected to the same Chrome, reuse it\n if (isProxyAlive()) {\n const endpoint = readProxyEndpoint();\n const savedUpstream = readProxyUpstream();\n if (endpoint) {\n if (savedUpstream && savedUpstream !== browserEndpoint) {\n // Proxy is connected to a different Chrome — kill it and start fresh\n debug(\n 'Proxy connected to different upstream (%s), killing',\n savedUpstream,\n );\n killProxy();\n } else {\n return endpoint;\n }\n }\n }\n\n // Spawn a new proxy\n try {\n return await spawnProxy(browserEndpoint);\n } catch (err) {\n console.warn(\n `[cdp] proxy failed, falling back to direct connection: ${err}`,\n );\n return browserEndpoint;\n }\n}\n\n/**\n * Tools manager for Web CDP-mode MCP.\n * Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.\n * Unlike WebPuppeteerMidsceneTools which launches its own Chrome, this connects\n * to a browser that is already running with remote debugging enabled.\n *\n * Uses a persistent WebSocket proxy to avoid repeated Chrome permission popups\n * when Chrome's settings-based remote debugging is used.\n */\nexport class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private cdpEndpoint: string;\n private activeBrowser: Browser | null = null;\n\n constructor(cdpEndpoint: string) {\n super();\n this.cdpEndpoint = cdpEndpoint;\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect via proxy to avoid repeated Chrome permission popups\n if (!this.activeBrowser) {\n const endpoint = await getProxyEndpoint(this.cdpEndpoint);\n this.activeBrowser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n }\n\n const browser = this.activeBrowser;\n let pages = await browser.pages();\n\n // If no pages discovered, wait briefly and retry — some CDP targets\n // need a moment to appear after the WebSocket connection is established.\n if (pages.length === 0) {\n await new Promise((r) => setTimeout(r, CDP_TARGET_DISCOVERY_DELAY_MS));\n pages = await browser.pages();\n }\n\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n debug(\n 'Found %d page(s), %d web page(s): %o',\n pages.length,\n webPages.length,\n pages.map((p) => p.url()),\n );\n let page: Page;\n\n if (navigateToUrl) {\n if (webPages.length > 0) {\n // Reuse an existing page and navigate it — avoids creating invisible\n // tabs when Chrome uses settings-based remote debugging (no HTTP\n // discovery endpoints, /devtools/page/* returns 403).\n page = webPages[webPages.length - 1];\n await page.bringToFront();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // No existing web pages — fall back to creating a new tab\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n }\n } else {\n // Try to find the exact tab from a previous `connect` command via saved targetId.\n const savedTargetId = readSavedTargetId();\n let matchedPage: Page | undefined;\n\n if (savedTargetId && pages.length > 0) {\n matchedPage = pages.find((p) => getTargetId(p) === savedTargetId);\n if (matchedPage) {\n debug('Matched saved targetId %s', savedTargetId);\n } else {\n debug(\n 'Saved targetId %s not found among %d pages, falling back',\n savedTargetId,\n pages.length,\n );\n }\n }\n\n if (matchedPage) {\n page = matchedPage;\n } else if (webPages.length > 0) {\n page = webPages[webPages.length - 1];\n } else if (pages.length > 0) {\n page = pages[pages.length - 1];\n } else {\n page = await browser.newPage();\n }\n\n await page.bringToFront();\n }\n\n // Persist the targetId so subsequent CLI commands can find this exact tab\n const targetId = getTargetId(page);\n if (targetId) {\n saveTargetId(targetId);\n } else {\n // If puppeteer ever drops the private _targetId field, this branch\n // makes the regression visible instead of silently disabling the\n // cross-command tab reuse path.\n debug(\n 'No targetId on page.target(); cross-command tab reuse disabled until puppeteer integration is updated.',\n );\n }\n\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage);\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page via CDP. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during connect:', e);\n }\n this.agent = undefined;\n }\n\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected via CDP to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running (managed externally).',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during disconnect:', e);\n }\n this.agent = undefined;\n }\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n cleanupTargetIdFile();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running externally)',\n );\n },\n },\n ];\n }\n}\n\n/**\n * Internal helpers exposed for unit tests. Not part of the public API.\n */\nexport const __test__ = {\n getProxyEndpoint,\n killProxy,\n readProxyUpstream,\n isProxyAlive,\n};\n"],"names":["debug","getDebug","CDP_TARGET_DISCOVERY_DELAY_MS","isPageLevelEndpoint","endpoint","resolveBrowserEndpoint","pageEndpoint","Promise","resolve","reject","host","url","URL","Error","req","http","res","data","chunk","info","JSON","err","isProxyAlive","existsSync","PROXY_PID_FILE","pid","Number","readFileSync","process","readProxyEndpoint","PROXY_ENDPOINT_FILE","readProxyUpstream","PROXY_UPSTREAM_FILE","killProxy","unlinkSync","cleanupTargetIdFile","readSavedTargetId","TARGET_ID_FILE","saveTargetId","targetId","writeFileSync","getTargetId","page","PROXY_STDERR_BUFFER_LIMIT","spawnProxy","chromeEndpoint","proxyScript","join","__dirname","proc","spawn","output","stderrBuf","settled","appendStderr","formatStderr","trimmed","timer","setTimeout","onData","lines","line","parsed","clearTimeout","code","signal","how","getProxyEndpoint","browserEndpoint","savedUpstream","console","WebCdpMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","error","undefined","puppeteer","browser","pages","r","webPages","p","savedTargetId","matchedPage","PuppeteerAgent","z","args","e","screenshot","label","cdpEndpoint","__test__"],"mappings":"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"}
1
+ {"version":3,"file":"mcp-tools-cdp.mjs","sources":["../../src/mcp-tools-cdp.ts"],"sourcesContent":["import { ScreenshotItem, z } from '@midscene/core';\nimport { getDebug } from '@midscene/shared/logger';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { getProxyEndpoint } from './cdp-proxy-manager';\nimport {\n cleanupTargetIdFile,\n readSavedTargetId,\n saveTargetId,\n} from './cdp-target-store';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst debug = getDebug('mcp:cdp');\n\n/** CDP target discovery may need a brief moment after WebSocket open. */\nconst CDP_TARGET_DISCOVERY_DELAY_MS = 500;\n\n/**\n * puppeteer-core does not expose a public method for the underlying CDP\n * target id, so we reach into `_targetId`. Centralised here so a future\n * puppeteer release exposing this properly only requires one change.\n * Callers must treat the result as optional.\n */\nfunction getTargetId(page: Page): string | undefined {\n return (page.target() as unknown as { _targetId?: string })._targetId;\n}\n\n/**\n * Tools manager for Web CDP-mode MCP.\n * Connects to an existing Chrome browser via CDP (Chrome DevTools Protocol) endpoint.\n * Unlike WebPuppeteerMidsceneTools which launches its own Chrome, this connects\n * to a browser that is already running with remote debugging enabled.\n *\n * Uses a persistent WebSocket proxy to avoid repeated Chrome permission popups\n * when Chrome's settings-based remote debugging is used.\n */\nexport class WebCdpMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n private cdpEndpoint: string;\n private activeBrowser: Browser | null = null;\n\n constructor(cdpEndpoint: string) {\n super();\n this.cdpEndpoint = cdpEndpoint;\n }\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect via proxy to avoid repeated Chrome permission popups\n if (!this.activeBrowser) {\n const endpoint = await getProxyEndpoint(this.cdpEndpoint);\n this.activeBrowser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n }\n\n const browser = this.activeBrowser;\n let pages = await browser.pages();\n\n // If no pages discovered, wait briefly and retry — some CDP targets\n // need a moment to appear after the WebSocket connection is established.\n if (pages.length === 0) {\n await new Promise((r) => setTimeout(r, CDP_TARGET_DISCOVERY_DELAY_MS));\n pages = await browser.pages();\n }\n\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n debug(\n 'Found %d page(s), %d web page(s): %o',\n pages.length,\n webPages.length,\n pages.map((p) => p.url()),\n );\n let page: Page;\n\n if (navigateToUrl) {\n if (webPages.length > 0) {\n // Reuse an existing page and navigate it — avoids creating invisible\n // tabs when Chrome uses settings-based remote debugging (no HTTP\n // discovery endpoints, /devtools/page/* returns 403).\n page = webPages[webPages.length - 1];\n await page.bringToFront();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // No existing web pages — fall back to creating a new tab\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n }\n } else {\n // Try to find the exact tab from a previous `connect` command via saved targetId.\n const savedTargetId = readSavedTargetId();\n let matchedPage: Page | undefined;\n\n if (savedTargetId && pages.length > 0) {\n matchedPage = pages.find((p) => getTargetId(p) === savedTargetId);\n if (matchedPage) {\n debug('Matched saved targetId %s', savedTargetId);\n } else {\n debug(\n 'Saved targetId %s not found among %d pages, falling back',\n savedTargetId,\n pages.length,\n );\n }\n }\n\n if (matchedPage) {\n page = matchedPage;\n } else if (webPages.length > 0) {\n page = webPages[webPages.length - 1];\n } else if (pages.length > 0) {\n page = pages[pages.length - 1];\n } else {\n page = await browser.newPage();\n }\n\n await page.bringToFront();\n }\n\n // Persist the targetId so subsequent CLI commands can find this exact tab\n const targetId = getTargetId(page);\n if (targetId) {\n saveTargetId(targetId);\n } else {\n // If puppeteer ever drops the private _targetId field, this branch\n // makes the regression visible instead of silently disabling the\n // cross-command tab reuse path.\n debug(\n 'No targetId on page.target(); cross-command tab reuse disabled until puppeteer integration is updated.',\n );\n }\n\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage);\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page via CDP. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during connect:', e);\n }\n this.agent = undefined;\n }\n\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected via CDP to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running (managed externally).',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch (e) {\n console.debug('Failed to destroy agent during disconnect:', e);\n }\n this.agent = undefined;\n }\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n cleanupTargetIdFile();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running externally)',\n );\n },\n },\n ];\n }\n}\n"],"names":["debug","getDebug","CDP_TARGET_DISCOVERY_DELAY_MS","getTargetId","page","WebCdpMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","error","console","undefined","endpoint","getProxyEndpoint","puppeteer","browser","pages","Promise","r","setTimeout","webPages","p","savedTargetId","readSavedTargetId","matchedPage","targetId","saveTargetId","PuppeteerAgent","z","args","url","e","screenshot","label","cleanupTargetIdFile","cdpEndpoint"],"mappings":"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"}
@@ -4,7 +4,8 @@ import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { ScreenshotItem, z } from "@midscene/core";
7
- import { BaseMidsceneTools, resolveChromePath } from "@midscene/shared/mcp";
7
+ import { BaseMidsceneTools } from "@midscene/shared/mcp/base-tools";
8
+ import { resolveChromePath } from "@midscene/shared/mcp/chrome-path";
8
9
  import puppeteer_core from "puppeteer-core";
9
10
  import { PuppeteerAgent } from "./puppeteer/index.mjs";
10
11
  import { StaticPage } from "./static/index.mjs";
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tools-puppeteer.mjs","sources":["../../src/mcp-tools-puppeteer.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport {\n BaseMidsceneTools,\n type ToolDefinition,\n resolveChromePath,\n} from '@midscene/shared/mcp';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');\nconst USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nconst browserManager = {\n activeBrowser: null as Browser | null,\n\n async getOrLaunch(): Promise<{ browser: Browser; reused: boolean }> {\n if (existsSync(ENDPOINT_FILE)) {\n try {\n const endpoint = (await readFile(ENDPOINT_FILE, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch {\n try {\n await unlink(ENDPOINT_FILE);\n } catch {}\n }\n }\n\n const wsEndpoint = await this.launchDetachedChrome();\n await writeFile(ENDPOINT_FILE, wsEndpoint);\n\n const browser = await puppeteer.connect({\n browserWSEndpoint: wsEndpoint,\n defaultViewport: null,\n });\n return { browser, reused: false };\n },\n\n async closeBrowser(): Promise<void> {\n if (!existsSync(ENDPOINT_FILE)) return;\n try {\n const endpoint = (await readFile(ENDPOINT_FILE, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch {}\n try {\n await unlink(ENDPOINT_FILE);\n } catch {}\n },\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n },\n\n async launchDetachedChrome(): Promise<string> {\n const chromePath = resolveChromePath();\n\n await mkdir(USER_DATA_DIR, { recursive: true });\n\n const args = [\n '--headless=new',\n `--user-data-dir=${USER_DATA_DIR}`,\n '--remote-debugging-port=0',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-extensions',\n '--disable-default-apps',\n '--disable-sync',\n '--disable-background-networking',\n '--password-store=basic',\n '--use-mock-keychain',\n '--window-size=1280,800',\n '--force-color-profile=srgb',\n ];\n\n const proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n proc.unref();\n\n return new Promise<string>((resolve, reject) => {\n let output = '';\n const onData = (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n proc.stderr!.removeListener('data', onData);\n resolve(match[1]);\n }\n };\n proc.stderr!.on('data', onData);\n\n proc.on('exit', (code) => {\n proc.stderr!.removeListener('data', onData);\n reject(\n new Error(\n `Chrome exited with code ${code} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n );\n });\n\n setTimeout(\n () =>\n reject(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n ),\n 15000,\n );\n });\n },\n};\n\n/**\n * Tools manager for Web Puppeteer-mode MCP.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n const { browser, reused } = await browserManager.getOrLaunch();\n browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // Reuse the last web page\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n webPages.length > 0\n ? webPages[webPages.length - 1]\n : pages[pages.length - 1] || (await browser.newPage());\n\n if (reused) {\n await page.bringToFront();\n }\n }\n\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage);\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n browserManager.disconnect();\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running for future calls.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n browserManager.disconnect();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running)',\n );\n },\n },\n {\n name: 'web_close',\n description: 'Close the browser completely and release all resources.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n await browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","browserManager","existsSync","endpoint","readFile","browser","puppeteer","unlink","wsEndpoint","writeFile","chromePath","resolveChromePath","mkdir","args","proc","spawn","Promise","resolve","reject","output","onData","data","match","code","Error","setTimeout","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","undefined","reused","pages","page","webPages","p","PuppeteerAgent","z","url","screenshot","label"],"mappings":"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"}
1
+ {"version":3,"file":"mcp-tools-puppeteer.mjs","sources":["../../src/mcp-tools-puppeteer.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport { resolveChromePath } from '@midscene/shared/mcp/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport { PuppeteerAgent } from './puppeteer';\nimport { StaticPage } from './static';\n\nconst ENDPOINT_FILE = join(tmpdir(), 'midscene-puppeteer-endpoint');\nconst USER_DATA_DIR = join(tmpdir(), 'midscene-puppeteer-profile');\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nconst browserManager = {\n activeBrowser: null as Browser | null,\n\n async getOrLaunch(): Promise<{ browser: Browser; reused: boolean }> {\n if (existsSync(ENDPOINT_FILE)) {\n try {\n const endpoint = (await readFile(ENDPOINT_FILE, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch {\n try {\n await unlink(ENDPOINT_FILE);\n } catch {}\n }\n }\n\n const wsEndpoint = await this.launchDetachedChrome();\n await writeFile(ENDPOINT_FILE, wsEndpoint);\n\n const browser = await puppeteer.connect({\n browserWSEndpoint: wsEndpoint,\n defaultViewport: null,\n });\n return { browser, reused: false };\n },\n\n async closeBrowser(): Promise<void> {\n if (!existsSync(ENDPOINT_FILE)) return;\n try {\n const endpoint = (await readFile(ENDPOINT_FILE, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch {}\n try {\n await unlink(ENDPOINT_FILE);\n } catch {}\n },\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n },\n\n async launchDetachedChrome(): Promise<string> {\n const chromePath = resolveChromePath();\n\n await mkdir(USER_DATA_DIR, { recursive: true });\n\n const args = [\n '--headless=new',\n `--user-data-dir=${USER_DATA_DIR}`,\n '--remote-debugging-port=0',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-extensions',\n '--disable-default-apps',\n '--disable-sync',\n '--disable-background-networking',\n '--password-store=basic',\n '--use-mock-keychain',\n '--window-size=1280,800',\n '--force-color-profile=srgb',\n ];\n\n const proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n proc.unref();\n\n return new Promise<string>((resolve, reject) => {\n let output = '';\n const onData = (data: Buffer) => {\n output += data.toString();\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n proc.stderr!.removeListener('data', onData);\n resolve(match[1]);\n }\n };\n proc.stderr!.on('data', onData);\n\n proc.on('exit', (code) => {\n proc.stderr!.removeListener('data', onData);\n reject(\n new Error(\n `Chrome exited with code ${code} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n );\n });\n\n setTimeout(\n () =>\n reject(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: try setting MIDSCENE_MCP_NO_SANDBOX=1 if running in a container.`,\n ),\n ),\n 15000,\n );\n });\n },\n};\n\n/**\n * Tools manager for Web Puppeteer-mode MCP.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<PuppeteerAgent> {\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(navigateToUrl?: string): Promise<PuppeteerAgent> {\n // Re-init if URL provided\n if (this.agent && navigateToUrl) {\n try {\n await this.agent?.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n const { browser, reused } = await browserManager.getOrLaunch();\n browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n // Reuse the last web page\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n webPages.length > 0\n ? webPages[webPages.length - 1]\n : pages[pages.length - 1] || (await browser.newPage());\n\n if (reused) {\n await page.bringToFront();\n }\n }\n\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage);\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n browserManager.disconnect();\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to a web page. Opens a new tab with the given URL, or reuses the current page.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to use current page)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Destroy existing agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n\n this.agent = await this.ensureAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current page';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page. The browser stays running for future calls.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n browserManager.disconnect();\n return this.buildTextResult(\n 'Disconnected from web page (browser still running)',\n );\n },\n },\n {\n name: 'web_close',\n description: 'Close the browser completely and release all resources.',\n schema: {},\n handler: async () => {\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n await browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","browserManager","existsSync","endpoint","readFile","browser","puppeteer","unlink","wsEndpoint","writeFile","chromePath","resolveChromePath","mkdir","args","proc","spawn","Promise","resolve","reject","output","onData","data","match","code","Error","setTimeout","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","navigateToUrl","undefined","reused","pages","page","webPages","p","PuppeteerAgent","z","url","screenshot","label"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { ScreenshotItem, z } from "@midscene/core";
2
- import { BaseMidsceneTools } from "@midscene/shared/mcp";
2
+ import { BaseMidsceneTools } from "@midscene/shared/mcp/base-tools";
3
3
  import { AgentOverChromeBridge } from "./bridge-mode/index.mjs";
4
4
  import { StaticPage } from "./static/index.mjs";
5
5
  class WebMidsceneTools extends BaseMidsceneTools {
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tools.mjs","sources":["../../src/mcp-tools.ts"],"sourcesContent":["import { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools, type ToolDefinition } from '@midscene/shared/mcp';\nimport { AgentOverChromeBridge } from './bridge-mode';\nimport { StaticPage } from './static';\n\n/**\n * Tools manager for Web bridge-mode MCP\n */\nexport class WebMidsceneTools extends BaseMidsceneTools<AgentOverChromeBridge> {\n protected createTemporaryDevice() {\n // Use require to avoid type incompatibility with DeviceAction vs ActionSpaceItem\n // StaticPage.actionSpace() returns DeviceAction[] which is compatible at runtime\n // Use screenshotBase64 field to avoid async ScreenshotItem.create()\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(\n openNewTabWithUrl?: string,\n ): Promise<AgentOverChromeBridge> {\n // Re-init if URL provided\n if (this.agent && openNewTabWithUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect to current tab when no URL provided (handles CLI stateless calls)\n this.agent = await this.initBridgeModeAgent(openNewTabWithUrl);\n\n return this.agent;\n }\n\n private async initBridgeModeAgent(\n url?: string,\n ): Promise<AgentOverChromeBridge> {\n const agent = new AgentOverChromeBridge({ closeConflictServer: true });\n\n if (!url) {\n await agent.connectCurrentTab();\n } else {\n await agent.connectNewTabWithUrl(url);\n }\n\n return agent;\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to web page. If URL provided, opens new tab; otherwise connects to current tab.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to connect current tab)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Bypass ensureAgent's URL check — directly init bridge agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n this.agent = await this.initBridgeModeAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current tab';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page and release browser resources',\n schema: {},\n handler: this.createDisconnectHandler('web page'),\n },\n ];\n }\n}\n"],"names":["WebMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","openNewTabWithUrl","error","console","undefined","url","agent","AgentOverChromeBridge","z","args","screenshot","label"],"mappings":";;;;AAQO,MAAMA,yBAAyBC;IAC1B,wBAAwB;QAIhC,OAAO,IAAIC,WAAW;YACpB,YAAYC,eAAe,MAAM,CAAC,IAAIC,KAAK,GAAG;YAC9C,UAAU;gBAAE,OAAO;gBAAM,QAAQ;YAAK;YACtC,0BAA0B;QAC5B;IACF;IAEA,MAAgB,YACdC,iBAA0B,EACM;QAEhC,IAAI,IAAI,CAAC,KAAK,IAAIA,mBAAmB;YACnC,IAAI;gBACF,MAAM,IAAI,CAAC,KAAK,EAAE;YACpB,EAAE,OAAOC,OAAO;gBACdC,QAAQ,KAAK,CAAC,2CAA2CD;YAC3D;YACA,IAAI,CAAC,KAAK,GAAGE;QACf;QAEA,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK;QAGjC,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAACH;QAE5C,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAc,oBACZI,GAAY,EACoB;QAChC,MAAMC,QAAQ,IAAIC,sBAAsB;YAAE,qBAAqB;QAAK;QAEpE,IAAKF,KAGH,MAAMC,MAAM,oBAAoB,CAACD;aAFjC,MAAMC,MAAM,iBAAiB;QAK/B,OAAOA;IACT;IAEU,uBAAyC;QACjD,OAAO;YACL;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ;oBACN,KAAKE,EAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CAAC;gBACd;gBACA,SAAS,OAAOC;oBACd,MAAM,EAAEJ,GAAG,EAAE,GAAGI;oBAGhB,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGL;oBACf;oBACA,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAACC;oBAE5C,MAAMK,aAAa,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC1C,MAAMC,QAAQN,OAAO;oBAErB,OAAO;wBACL,SAAS;4BACP;gCAAE,MAAM;gCAAQ,MAAM,CAAC,cAAc,EAAEM,OAAO;4BAAC;+BAC3CD,aAAa,IAAI,CAAC,sBAAsB,CAACA,cAAc,EAAE;yBAC9D;oBACH;gBACF;YACF;YACA;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ,CAAC;gBACT,SAAS,IAAI,CAAC,uBAAuB,CAAC;YACxC;SACD;IACH;AACF"}
1
+ {"version":3,"file":"mcp-tools.mjs","sources":["../../src/mcp-tools.ts"],"sourcesContent":["import { ScreenshotItem, z } from '@midscene/core';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp/base-tools';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport { AgentOverChromeBridge } from './bridge-mode';\nimport { StaticPage } from './static';\n\n/**\n * Tools manager for Web bridge-mode MCP\n */\nexport class WebMidsceneTools extends BaseMidsceneTools<AgentOverChromeBridge> {\n protected createTemporaryDevice() {\n // Use require to avoid type incompatibility with DeviceAction vs ActionSpaceItem\n // StaticPage.actionSpace() returns DeviceAction[] which is compatible at runtime\n // Use screenshotBase64 field to avoid async ScreenshotItem.create()\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: { width: 1920, height: 1080 },\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(\n openNewTabWithUrl?: string,\n ): Promise<AgentOverChromeBridge> {\n // Re-init if URL provided\n if (this.agent && openNewTabWithUrl) {\n try {\n await this.agent?.destroy?.();\n } catch (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect to current tab when no URL provided (handles CLI stateless calls)\n this.agent = await this.initBridgeModeAgent(openNewTabWithUrl);\n\n return this.agent;\n }\n\n private async initBridgeModeAgent(\n url?: string,\n ): Promise<AgentOverChromeBridge> {\n const agent = new AgentOverChromeBridge({ closeConflictServer: true });\n\n if (!url) {\n await agent.connectCurrentTab();\n } else {\n await agent.connectNewTabWithUrl(url);\n }\n\n return agent;\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to web page. If URL provided, opens new tab; otherwise connects to current tab.',\n schema: {\n url: z\n .string()\n .url()\n .optional()\n .describe('URL to open in new tab (omit to connect current tab)'),\n },\n handler: async (args) => {\n const { url } = args as { url?: string };\n\n // Bypass ensureAgent's URL check — directly init bridge agent\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n }\n this.agent = await this.initBridgeModeAgent(url);\n\n const screenshot = await this.agent.page?.screenshotBase64();\n const label = url ?? 'current tab';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page and release browser resources',\n schema: {},\n handler: this.createDisconnectHandler('web page'),\n },\n ];\n }\n}\n"],"names":["WebMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","openNewTabWithUrl","error","console","undefined","url","agent","AgentOverChromeBridge","z","args","screenshot","label"],"mappings":";;;;AASO,MAAMA,yBAAyBC;IAC1B,wBAAwB;QAIhC,OAAO,IAAIC,WAAW;YACpB,YAAYC,eAAe,MAAM,CAAC,IAAIC,KAAK,GAAG;YAC9C,UAAU;gBAAE,OAAO;gBAAM,QAAQ;YAAK;YACtC,0BAA0B;QAC5B;IACF;IAEA,MAAgB,YACdC,iBAA0B,EACM;QAEhC,IAAI,IAAI,CAAC,KAAK,IAAIA,mBAAmB;YACnC,IAAI;gBACF,MAAM,IAAI,CAAC,KAAK,EAAE;YACpB,EAAE,OAAOC,OAAO;gBACdC,QAAQ,KAAK,CAAC,2CAA2CD;YAC3D;YACA,IAAI,CAAC,KAAK,GAAGE;QACf;QAEA,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,KAAK;QAGjC,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAACH;QAE5C,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAc,oBACZI,GAAY,EACoB;QAChC,MAAMC,QAAQ,IAAIC,sBAAsB;YAAE,qBAAqB;QAAK;QAEpE,IAAKF,KAGH,MAAMC,MAAM,oBAAoB,CAACD;aAFjC,MAAMC,MAAM,iBAAiB;QAK/B,OAAOA;IACT;IAEU,uBAAyC;QACjD,OAAO;YACL;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ;oBACN,KAAKE,EAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CAAC;gBACd;gBACA,SAAS,OAAOC;oBACd,MAAM,EAAEJ,GAAG,EAAE,GAAGI;oBAGhB,IAAI,IAAI,CAAC,KAAK,EAAE;wBACd,IAAI;4BACF,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO;wBAC1B,EAAE,OAAM,CAAC;wBACT,IAAI,CAAC,KAAK,GAAGL;oBACf;oBACA,IAAI,CAAC,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAACC;oBAE5C,MAAMK,aAAa,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC1C,MAAMC,QAAQN,OAAO;oBAErB,OAAO;wBACL,SAAS;4BACP;gCAAE,MAAM;gCAAQ,MAAM,CAAC,cAAc,EAAEM,OAAO;4BAAC;+BAC3CD,aAAa,IAAI,CAAC,sBAAsB,CAACA,cAAc,EAAE;yBAC9D;oBACH;gBACF;YACF;YACA;gBACE,MAAM;gBACN,aACE;gBACF,QAAQ,CAAC;gBACT,SAAS,IAAI,CAAC,uBAAuB,CAAC;YACxC;SACD;IACH;AACF"}
@@ -67,12 +67,12 @@ const PlaywrightAiFixture = (options)=>{
67
67
  if (!idForPage) {
68
68
  idForPage = uuid();
69
69
  page[midsceneAgentKeyId] = idForPage;
70
- const { testId } = testInfo;
71
70
  const { file, title } = groupAndCaseForTest(testInfo);
72
71
  const cacheConfig = processTestCacheConfig(testInfo);
72
+ const reportTag = `playwright-${title.replace(/[\\/]/g, '-')}-${idForPage}`;
73
73
  const agent = new PlaywrightAgent(page, {
74
- testId: `playwright-${testId}-${idForPage}`,
75
- reportFileName: `playwright-${testId}-${idForPage}`,
74
+ testId: reportTag,
75
+ reportFileName: reportTag,
76
76
  forceSameTabNavigation,
77
77
  cache: cacheConfig,
78
78
  groupName: title,
@@ -1 +1 @@
1
- {"version":3,"file":"playwright/ai-fixture.mjs","sources":["../../../src/playwright/ai-fixture.ts"],"sourcesContent":["import { PlaywrightAgent, type PlaywrightWebPage } from '@/playwright/index';\nimport type { WebPageAgentOpt } from '@/web-element';\nimport type { Cache } from '@midscene/core';\nimport type { Agent as PageAgent } from '@midscene/core/agent';\nimport { processCacheConfig } from '@midscene/core/utils';\nimport {\n DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,\n} from '@midscene/shared/constants';\nimport { getDebug } from '@midscene/shared/logger';\nimport { uuid } from '@midscene/shared/utils';\nimport { replaceIllegalPathCharsAndSpace } from '@midscene/shared/utils';\nimport { type TestInfo, type TestType, test } from '@playwright/test';\nimport type { Page as OriginPlaywrightPage } from 'playwright';\nexport type APITestType = Pick<TestType<any, any>, 'step'>;\n\nconst debugPage = getDebug('web:playwright:ai-fixture');\n\nconst groupAndCaseForTest = (testInfo: TestInfo) => {\n let taskFile: string;\n let taskTitle: string;\n const titlePath = [...testInfo.titlePath];\n\n if (titlePath.length > 1) {\n taskFile = titlePath.shift() || 'unnamed';\n taskTitle = titlePath.join('__');\n } else if (titlePath.length === 1) {\n taskTitle = titlePath[0];\n taskFile = `${taskTitle}`;\n } else {\n taskTitle = 'unnamed';\n taskFile = 'unnamed';\n }\n\n const taskTitleWithRetry = `${taskTitle}${testInfo.retry ? `(retry #${testInfo.retry})` : ''}`;\n\n return {\n file: taskFile,\n id: replaceIllegalPathCharsAndSpace(`${taskFile}(${taskTitle})`),\n title: replaceIllegalPathCharsAndSpace(taskTitleWithRetry),\n };\n};\n\nconst midsceneAgentKeyId = '_midsceneAgentId';\nexport const midsceneDumpAnnotationId = 'MIDSCENE_DUMP_ANNOTATION';\n\ntype AgentRecord = {\n agent: PageAgent<PlaywrightWebPage>;\n finalizePromise?: Promise<string | undefined>;\n finalReportPath?: string;\n};\n\ntype PlaywrightCacheConfig = {\n strategy?: 'read-only' | 'read-write' | 'write-only';\n id?: string;\n};\ntype PlaywrightCache = false | true | PlaywrightCacheConfig;\n\nexport type PlaywrightAiFixtureOptions = Omit<\n WebPageAgentOpt,\n | 'testId'\n | 'cacheId'\n | 'groupName'\n | 'groupDescription'\n | 'reportFileName'\n | 'cache'\n> & {\n cache?: PlaywrightCache;\n};\n\nexport const PlaywrightAiFixture = (options?: PlaywrightAiFixtureOptions) => {\n const {\n forceSameTabNavigation = true,\n waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,\n waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,\n cache,\n ...sharedAgentOptions\n } = options ?? {};\n\n // Helper function to process cache configuration and auto-generate ID from test info\n const processTestCacheConfig = (testInfo: TestInfo): Cache | undefined => {\n // Generate ID from test info\n const { id } = groupAndCaseForTest(testInfo);\n\n // Use shared processCacheConfig with generated ID as fallback\n return processCacheConfig(cache as Cache, id);\n };\n\n const pageAgentMap: Record<string, PageAgent<PlaywrightWebPage>> = {};\n const testAgentRecords = new Map<string, Map<string, AgentRecord>>();\n\n const getAgentRecordsForTest = (testInfo: TestInfo) => {\n let records = testAgentRecords.get(testInfo.testId);\n if (!records) {\n records = new Map<string, AgentRecord>();\n testAgentRecords.set(testInfo.testId, records);\n }\n return records;\n };\n\n const setReportAnnotation = (testInfo: TestInfo, reportPaths: string[]) => {\n testInfo.annotations = testInfo.annotations.filter((item) => {\n return item.type !== midsceneDumpAnnotationId;\n });\n\n for (const reportPath of reportPaths) {\n testInfo.annotations.push({\n type: midsceneDumpAnnotationId,\n description: reportPath,\n });\n }\n };\n\n const finalizeAgentRecord = async (\n record: AgentRecord,\n ): Promise<string | undefined> => {\n if (!record.finalizePromise) {\n record.finalizePromise = (async () => {\n await record.agent.destroy();\n const reportPath = record.agent.reportFile || undefined;\n record.finalReportPath = reportPath;\n return reportPath;\n })();\n }\n\n return await record.finalizePromise;\n };\n\n const createOrReuseAgentForPage = (\n page: OriginPlaywrightPage,\n testInfo: TestInfo, // { testId: string; taskFile: string; taskTitle: string },\n opts?: WebPageAgentOpt,\n ) => {\n let idForPage = (page as any)[midsceneAgentKeyId];\n if (!idForPage) {\n idForPage = uuid();\n (page as any)[midsceneAgentKeyId] = idForPage;\n const { testId } = testInfo;\n const { file, title } = groupAndCaseForTest(testInfo);\n const cacheConfig = processTestCacheConfig(testInfo);\n\n const agent = new PlaywrightAgent(page, {\n testId: `playwright-${testId}-${idForPage}`,\n reportFileName: `playwright-${testId}-${idForPage}`,\n forceSameTabNavigation,\n cache: cacheConfig,\n groupName: title,\n groupDescription: file,\n generateReport: true,\n ...sharedAgentOptions,\n ...opts,\n });\n pageAgentMap[idForPage] = agent;\n const records = getAgentRecordsForTest(testInfo);\n const record: AgentRecord = { agent };\n records.set(idForPage, record);\n\n page.on('close', async () => {\n debugPage('page closed');\n try {\n await finalizeAgentRecord(record);\n } finally {\n delete pageAgentMap[idForPage];\n }\n });\n }\n\n return pageAgentMap[idForPage];\n };\n\n async function generateAiFunction(options: {\n page: OriginPlaywrightPage;\n testInfo: TestInfo;\n use: any;\n aiActionType:\n | 'ai'\n | 'aiAct'\n | 'aiAction'\n | 'aiHover'\n | 'aiInput'\n | 'aiKeyboardPress'\n | 'aiScroll'\n | 'aiTap'\n | 'aiRightClick'\n | 'aiDoubleClick'\n | 'aiQuery'\n | 'aiAssert'\n | 'aiWaitFor'\n | 'aiLocate'\n | 'aiNumber'\n | 'aiString'\n | 'aiBoolean'\n | 'aiAsk'\n | 'runYaml'\n | 'setAIActionContext'\n | 'evaluateJavaScript'\n | 'recordToReport'\n | 'logScreenshot'\n | 'freezePageContext'\n | 'unfreezePageContext';\n }) {\n const { page, testInfo, use, aiActionType } = options;\n const agent = createOrReuseAgentForPage(page, testInfo, {\n waitForNavigationTimeout,\n waitForNetworkIdleTimeout,\n }) as PlaywrightAgent;\n\n await use(async (taskPrompt: string, ...args: any[]) => {\n return new Promise((resolve, reject) => {\n test.step(`ai-${aiActionType} - ${JSON.stringify(taskPrompt)}`, async () => {\n try {\n debugPage(\n `waitForNetworkIdle timeout: ${waitForNetworkIdleTimeout}`,\n );\n await agent.waitForNetworkIdle(waitForNetworkIdleTimeout);\n } catch (error) {\n console.warn(\n '[midscene:warning] Waiting for network idle has timed out, but Midscene will continue execution. Please check https://midscenejs.com/faq.html#customize-the-network-timeout for more information on customizing the network timeout',\n );\n }\n try {\n type AgentMethod = (\n prompt: string,\n ...restArgs: any[]\n ) => Promise<any>;\n const result = await (agent[aiActionType] as AgentMethod).bind(\n agent,\n )(taskPrompt, ...args);\n resolve(result);\n } catch (error) {\n reject(error);\n }\n });\n });\n });\n }\n\n return {\n _midsceneFinalizeReports: [\n // biome-ignore lint/correctness/noEmptyPattern: Playwright fixture callbacks must use object destructuring for the first parameter even when no fixtures are consumed.\n async ({}: Record<string, unknown>, use: any, testInfo: TestInfo) => {\n await use();\n\n const records = testAgentRecords.get(testInfo.testId);\n if (!records || records.size === 0) {\n return;\n }\n\n const reportPaths = (\n await Promise.all(\n Array.from(records.values()).map((record) =>\n finalizeAgentRecord(record),\n ),\n )\n ).filter((reportPath): reportPath is string => Boolean(reportPath));\n\n if (reportPaths.length > 0) {\n setReportAnnotation(testInfo, reportPaths);\n }\n\n testAgentRecords.delete(testInfo.testId);\n },\n { auto: true },\n ],\n agentForPage: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await use(\n async (\n propsPage?: OriginPlaywrightPage | undefined,\n opts?: WebPageAgentOpt,\n ) => {\n const cacheConfig = processTestCacheConfig(testInfo);\n\n // Handle cache configuration priority:\n // 1. If user provides cache in opts, use it (but auto-generate ID if missing)\n // 2. Otherwise use fixture's cache config\n let finalCacheConfig = cacheConfig;\n if (opts?.cache !== undefined) {\n const userCache = opts.cache;\n if (userCache === false) {\n finalCacheConfig = false;\n } else if (userCache === true) {\n // Auto-generate ID for user's cache: true\n const { id } = groupAndCaseForTest(testInfo);\n finalCacheConfig = { id };\n } else if (typeof userCache === 'object') {\n if (!userCache.id) {\n // Auto-generate ID for user's cache object without ID\n const { id } = groupAndCaseForTest(testInfo);\n finalCacheConfig = { ...userCache, id };\n } else {\n finalCacheConfig = userCache;\n }\n }\n }\n\n const agent = createOrReuseAgentForPage(propsPage || page, testInfo, {\n waitForNavigationTimeout,\n waitForNetworkIdleTimeout,\n cache: finalCacheConfig,\n ...opts,\n });\n return agent;\n },\n );\n },\n ai: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'ai',\n });\n },\n aiAct: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAct',\n });\n },\n /**\n * @deprecated Use {@link PlaywrightAiFixture.aiAct} instead.\n */\n aiAction: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAction',\n });\n },\n aiTap: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiTap',\n });\n },\n aiRightClick: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiRightClick',\n });\n },\n aiDoubleClick: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiDoubleClick',\n });\n },\n aiHover: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiHover',\n });\n },\n aiInput: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiInput',\n });\n },\n aiKeyboardPress: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiKeyboardPress',\n });\n },\n aiScroll: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiScroll',\n });\n },\n aiQuery: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiQuery',\n });\n },\n aiAssert: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAssert',\n });\n },\n aiWaitFor: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiWaitFor',\n });\n },\n aiLocate: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiLocate',\n });\n },\n aiNumber: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiNumber',\n });\n },\n aiString: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiString',\n });\n },\n aiBoolean: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiBoolean',\n });\n },\n aiAsk: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAsk',\n });\n },\n runYaml: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'runYaml',\n });\n },\n setAIActionContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'setAIActionContext',\n });\n },\n evaluateJavaScript: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'evaluateJavaScript',\n });\n },\n recordToReport: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'recordToReport',\n });\n },\n logScreenshot: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'logScreenshot',\n });\n },\n freezePageContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'freezePageContext',\n });\n },\n unfreezePageContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'unfreezePageContext',\n });\n },\n };\n};\n\nexport type PlayWrightAiFixtureType = {\n agentForPage: (\n page?: OriginPlaywrightPage,\n opts?: WebPageAgentOpt,\n ) => Promise<PageAgent<PlaywrightWebPage>>;\n ai: <T = any>(...args: Parameters<PageAgent['ai']>) => Promise<T>;\n aiAct: (\n ...args: Parameters<PageAgent['aiAct']>\n ) => ReturnType<PageAgent['aiAct']>;\n /**\n * @deprecated Use {@link PlayWrightAiFixtureType.aiAct} instead.\n */\n aiAction: (\n ...args: Parameters<PageAgent['aiAction']>\n ) => ReturnType<PageAgent['aiAction']>;\n aiTap: (\n ...args: Parameters<PageAgent['aiTap']>\n ) => ReturnType<PageAgent['aiTap']>;\n aiRightClick: (\n ...args: Parameters<PageAgent['aiRightClick']>\n ) => ReturnType<PageAgent['aiRightClick']>;\n aiDoubleClick: (\n ...args: Parameters<PageAgent['aiDoubleClick']>\n ) => ReturnType<PageAgent['aiDoubleClick']>;\n aiHover: (\n ...args: Parameters<PageAgent['aiHover']>\n ) => ReturnType<PageAgent['aiHover']>;\n aiInput: (\n ...args: Parameters<PageAgent['aiInput']>\n ) => ReturnType<PageAgent['aiInput']>;\n aiKeyboardPress: (\n ...args: Parameters<PageAgent['aiKeyboardPress']>\n ) => ReturnType<PageAgent['aiKeyboardPress']>;\n aiScroll: (\n ...args: Parameters<PageAgent['aiScroll']>\n ) => ReturnType<PageAgent['aiScroll']>;\n aiQuery: <T = any>(...args: Parameters<PageAgent['aiQuery']>) => Promise<T>;\n aiAssert: (\n ...args: Parameters<PageAgent['aiAssert']>\n ) => ReturnType<PageAgent['aiAssert']>;\n aiWaitFor: (...args: Parameters<PageAgent['aiWaitFor']>) => Promise<void>;\n aiLocate: (\n ...args: Parameters<PageAgent['aiLocate']>\n ) => ReturnType<PageAgent['aiLocate']>;\n aiNumber: (\n ...args: Parameters<PageAgent['aiNumber']>\n ) => ReturnType<PageAgent['aiNumber']>;\n aiString: (\n ...args: Parameters<PageAgent['aiString']>\n ) => ReturnType<PageAgent['aiString']>;\n aiBoolean: (\n ...args: Parameters<PageAgent['aiBoolean']>\n ) => ReturnType<PageAgent['aiBoolean']>;\n aiAsk: (\n ...args: Parameters<PageAgent['aiAsk']>\n ) => ReturnType<PageAgent['aiAsk']>;\n runYaml: (\n ...args: Parameters<PageAgent['runYaml']>\n ) => ReturnType<PageAgent['runYaml']>;\n setAIActionContext: (\n ...args: Parameters<PageAgent['setAIActionContext']>\n ) => ReturnType<PageAgent['setAIActionContext']>;\n evaluateJavaScript: (\n ...args: Parameters<PageAgent['evaluateJavaScript']>\n ) => ReturnType<PageAgent['evaluateJavaScript']>;\n recordToReport: (\n ...args: Parameters<PageAgent['recordToReport']>\n ) => ReturnType<PageAgent['recordToReport']>;\n logScreenshot: (\n ...args: Parameters<PageAgent['logScreenshot']>\n ) => ReturnType<PageAgent['logScreenshot']>;\n freezePageContext: (\n ...args: Parameters<PageAgent['freezePageContext']>\n ) => ReturnType<PageAgent['freezePageContext']>;\n unfreezePageContext: (\n ...args: Parameters<PageAgent['unfreezePageContext']>\n ) => ReturnType<PageAgent['unfreezePageContext']>;\n};\n"],"names":["debugPage","getDebug","groupAndCaseForTest","testInfo","taskFile","taskTitle","titlePath","taskTitleWithRetry","replaceIllegalPathCharsAndSpace","midsceneAgentKeyId","midsceneDumpAnnotationId","PlaywrightAiFixture","options","forceSameTabNavigation","waitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","waitForNavigationTimeout","DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT","cache","sharedAgentOptions","processTestCacheConfig","id","processCacheConfig","pageAgentMap","testAgentRecords","Map","getAgentRecordsForTest","records","setReportAnnotation","reportPaths","item","reportPath","finalizeAgentRecord","record","undefined","createOrReuseAgentForPage","page","opts","idForPage","uuid","testId","file","title","cacheConfig","agent","PlaywrightAgent","generateAiFunction","use","aiActionType","taskPrompt","args","Promise","resolve","reject","test","JSON","error","console","result","Array","Boolean","propsPage","finalCacheConfig","userCache"],"mappings":"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"}
1
+ {"version":3,"file":"playwright/ai-fixture.mjs","sources":["../../../src/playwright/ai-fixture.ts"],"sourcesContent":["import { PlaywrightAgent, type PlaywrightWebPage } from '@/playwright/index';\nimport type { WebPageAgentOpt } from '@/web-element';\nimport type { Cache } from '@midscene/core';\nimport type { Agent as PageAgent } from '@midscene/core/agent';\nimport { processCacheConfig } from '@midscene/core/utils';\nimport {\n DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,\n} from '@midscene/shared/constants';\nimport { getDebug } from '@midscene/shared/logger';\nimport { uuid } from '@midscene/shared/utils';\nimport { replaceIllegalPathCharsAndSpace } from '@midscene/shared/utils';\nimport { type TestInfo, type TestType, test } from '@playwright/test';\nimport type { Page as OriginPlaywrightPage } from 'playwright';\nexport type APITestType = Pick<TestType<any, any>, 'step'>;\n\nconst debugPage = getDebug('web:playwright:ai-fixture');\n\nconst groupAndCaseForTest = (testInfo: TestInfo) => {\n let taskFile: string;\n let taskTitle: string;\n const titlePath = [...testInfo.titlePath];\n\n if (titlePath.length > 1) {\n taskFile = titlePath.shift() || 'unnamed';\n taskTitle = titlePath.join('__');\n } else if (titlePath.length === 1) {\n taskTitle = titlePath[0];\n taskFile = `${taskTitle}`;\n } else {\n taskTitle = 'unnamed';\n taskFile = 'unnamed';\n }\n\n const taskTitleWithRetry = `${taskTitle}${testInfo.retry ? `(retry #${testInfo.retry})` : ''}`;\n\n return {\n file: taskFile,\n id: replaceIllegalPathCharsAndSpace(`${taskFile}(${taskTitle})`),\n title: replaceIllegalPathCharsAndSpace(taskTitleWithRetry),\n };\n};\n\nconst midsceneAgentKeyId = '_midsceneAgentId';\nexport const midsceneDumpAnnotationId = 'MIDSCENE_DUMP_ANNOTATION';\n\ntype AgentRecord = {\n agent: PageAgent<PlaywrightWebPage>;\n finalizePromise?: Promise<string | undefined>;\n finalReportPath?: string;\n};\n\ntype PlaywrightCacheConfig = {\n strategy?: 'read-only' | 'read-write' | 'write-only';\n id?: string;\n};\ntype PlaywrightCache = false | true | PlaywrightCacheConfig;\n\nexport type PlaywrightAiFixtureOptions = Omit<\n WebPageAgentOpt,\n | 'testId'\n | 'cacheId'\n | 'groupName'\n | 'groupDescription'\n | 'reportFileName'\n | 'cache'\n> & {\n cache?: PlaywrightCache;\n};\n\nexport const PlaywrightAiFixture = (options?: PlaywrightAiFixtureOptions) => {\n const {\n forceSameTabNavigation = true,\n waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,\n waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,\n cache,\n ...sharedAgentOptions\n } = options ?? {};\n\n // Helper function to process cache configuration and auto-generate ID from test info\n const processTestCacheConfig = (testInfo: TestInfo): Cache | undefined => {\n // Generate ID from test info\n const { id } = groupAndCaseForTest(testInfo);\n\n // Use shared processCacheConfig with generated ID as fallback\n return processCacheConfig(cache as Cache, id);\n };\n\n const pageAgentMap: Record<string, PageAgent<PlaywrightWebPage>> = {};\n const testAgentRecords = new Map<string, Map<string, AgentRecord>>();\n\n const getAgentRecordsForTest = (testInfo: TestInfo) => {\n let records = testAgentRecords.get(testInfo.testId);\n if (!records) {\n records = new Map<string, AgentRecord>();\n testAgentRecords.set(testInfo.testId, records);\n }\n return records;\n };\n\n const setReportAnnotation = (testInfo: TestInfo, reportPaths: string[]) => {\n testInfo.annotations = testInfo.annotations.filter((item) => {\n return item.type !== midsceneDumpAnnotationId;\n });\n\n for (const reportPath of reportPaths) {\n testInfo.annotations.push({\n type: midsceneDumpAnnotationId,\n description: reportPath,\n });\n }\n };\n\n const finalizeAgentRecord = async (\n record: AgentRecord,\n ): Promise<string | undefined> => {\n if (!record.finalizePromise) {\n record.finalizePromise = (async () => {\n await record.agent.destroy();\n const reportPath = record.agent.reportFile || undefined;\n record.finalReportPath = reportPath;\n return reportPath;\n })();\n }\n\n return await record.finalizePromise;\n };\n\n const createOrReuseAgentForPage = (\n page: OriginPlaywrightPage,\n testInfo: TestInfo, // { testId: string; taskFile: string; taskTitle: string },\n opts?: WebPageAgentOpt,\n ) => {\n let idForPage = (page as any)[midsceneAgentKeyId];\n if (!idForPage) {\n idForPage = uuid();\n (page as any)[midsceneAgentKeyId] = idForPage;\n const { file, title } = groupAndCaseForTest(testInfo);\n const cacheConfig = processTestCacheConfig(testInfo);\n // `replaceIllegalPathCharsAndSpace` intentionally preserves `/` and `\\`\n // so groupName/groupDescription can still carry hierarchy. But\n // ReportGenerator rejects path separators in the file name, so strip\n // them here for the report tag only.\n const reportTag = `playwright-${title.replace(/[\\\\/]/g, '-')}-${idForPage}`;\n\n const agent = new PlaywrightAgent(page, {\n testId: reportTag,\n reportFileName: reportTag,\n forceSameTabNavigation,\n cache: cacheConfig,\n groupName: title,\n groupDescription: file,\n generateReport: true,\n ...sharedAgentOptions,\n ...opts,\n });\n pageAgentMap[idForPage] = agent;\n const records = getAgentRecordsForTest(testInfo);\n const record: AgentRecord = { agent };\n records.set(idForPage, record);\n\n page.on('close', async () => {\n debugPage('page closed');\n try {\n await finalizeAgentRecord(record);\n } finally {\n delete pageAgentMap[idForPage];\n }\n });\n }\n\n return pageAgentMap[idForPage];\n };\n\n async function generateAiFunction(options: {\n page: OriginPlaywrightPage;\n testInfo: TestInfo;\n use: any;\n aiActionType:\n | 'ai'\n | 'aiAct'\n | 'aiAction'\n | 'aiHover'\n | 'aiInput'\n | 'aiKeyboardPress'\n | 'aiScroll'\n | 'aiTap'\n | 'aiRightClick'\n | 'aiDoubleClick'\n | 'aiQuery'\n | 'aiAssert'\n | 'aiWaitFor'\n | 'aiLocate'\n | 'aiNumber'\n | 'aiString'\n | 'aiBoolean'\n | 'aiAsk'\n | 'runYaml'\n | 'setAIActionContext'\n | 'evaluateJavaScript'\n | 'recordToReport'\n | 'logScreenshot'\n | 'freezePageContext'\n | 'unfreezePageContext';\n }) {\n const { page, testInfo, use, aiActionType } = options;\n const agent = createOrReuseAgentForPage(page, testInfo, {\n waitForNavigationTimeout,\n waitForNetworkIdleTimeout,\n }) as PlaywrightAgent;\n\n await use(async (taskPrompt: string, ...args: any[]) => {\n return new Promise((resolve, reject) => {\n test.step(`ai-${aiActionType} - ${JSON.stringify(taskPrompt)}`, async () => {\n try {\n debugPage(\n `waitForNetworkIdle timeout: ${waitForNetworkIdleTimeout}`,\n );\n await agent.waitForNetworkIdle(waitForNetworkIdleTimeout);\n } catch (error) {\n console.warn(\n '[midscene:warning] Waiting for network idle has timed out, but Midscene will continue execution. Please check https://midscenejs.com/faq.html#customize-the-network-timeout for more information on customizing the network timeout',\n );\n }\n try {\n type AgentMethod = (\n prompt: string,\n ...restArgs: any[]\n ) => Promise<any>;\n const result = await (agent[aiActionType] as AgentMethod).bind(\n agent,\n )(taskPrompt, ...args);\n resolve(result);\n } catch (error) {\n reject(error);\n }\n });\n });\n });\n }\n\n return {\n _midsceneFinalizeReports: [\n // biome-ignore lint/correctness/noEmptyPattern: Playwright fixture callbacks must use object destructuring for the first parameter even when no fixtures are consumed.\n async ({}: Record<string, unknown>, use: any, testInfo: TestInfo) => {\n await use();\n\n const records = testAgentRecords.get(testInfo.testId);\n if (!records || records.size === 0) {\n return;\n }\n\n const reportPaths = (\n await Promise.all(\n Array.from(records.values()).map((record) =>\n finalizeAgentRecord(record),\n ),\n )\n ).filter((reportPath): reportPath is string => Boolean(reportPath));\n\n if (reportPaths.length > 0) {\n setReportAnnotation(testInfo, reportPaths);\n }\n\n testAgentRecords.delete(testInfo.testId);\n },\n { auto: true },\n ],\n agentForPage: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await use(\n async (\n propsPage?: OriginPlaywrightPage | undefined,\n opts?: WebPageAgentOpt,\n ) => {\n const cacheConfig = processTestCacheConfig(testInfo);\n\n // Handle cache configuration priority:\n // 1. If user provides cache in opts, use it (but auto-generate ID if missing)\n // 2. Otherwise use fixture's cache config\n let finalCacheConfig = cacheConfig;\n if (opts?.cache !== undefined) {\n const userCache = opts.cache;\n if (userCache === false) {\n finalCacheConfig = false;\n } else if (userCache === true) {\n // Auto-generate ID for user's cache: true\n const { id } = groupAndCaseForTest(testInfo);\n finalCacheConfig = { id };\n } else if (typeof userCache === 'object') {\n if (!userCache.id) {\n // Auto-generate ID for user's cache object without ID\n const { id } = groupAndCaseForTest(testInfo);\n finalCacheConfig = { ...userCache, id };\n } else {\n finalCacheConfig = userCache;\n }\n }\n }\n\n const agent = createOrReuseAgentForPage(propsPage || page, testInfo, {\n waitForNavigationTimeout,\n waitForNetworkIdleTimeout,\n cache: finalCacheConfig,\n ...opts,\n });\n return agent;\n },\n );\n },\n ai: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'ai',\n });\n },\n aiAct: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAct',\n });\n },\n /**\n * @deprecated Use {@link PlaywrightAiFixture.aiAct} instead.\n */\n aiAction: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAction',\n });\n },\n aiTap: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiTap',\n });\n },\n aiRightClick: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiRightClick',\n });\n },\n aiDoubleClick: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiDoubleClick',\n });\n },\n aiHover: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiHover',\n });\n },\n aiInput: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiInput',\n });\n },\n aiKeyboardPress: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiKeyboardPress',\n });\n },\n aiScroll: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiScroll',\n });\n },\n aiQuery: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiQuery',\n });\n },\n aiAssert: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAssert',\n });\n },\n aiWaitFor: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiWaitFor',\n });\n },\n aiLocate: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiLocate',\n });\n },\n aiNumber: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiNumber',\n });\n },\n aiString: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiString',\n });\n },\n aiBoolean: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiBoolean',\n });\n },\n aiAsk: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAsk',\n });\n },\n runYaml: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'runYaml',\n });\n },\n setAIActionContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'setAIActionContext',\n });\n },\n evaluateJavaScript: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'evaluateJavaScript',\n });\n },\n recordToReport: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'recordToReport',\n });\n },\n logScreenshot: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'logScreenshot',\n });\n },\n freezePageContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'freezePageContext',\n });\n },\n unfreezePageContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'unfreezePageContext',\n });\n },\n };\n};\n\nexport type PlayWrightAiFixtureType = {\n agentForPage: (\n page?: OriginPlaywrightPage,\n opts?: WebPageAgentOpt,\n ) => Promise<PageAgent<PlaywrightWebPage>>;\n ai: <T = any>(...args: Parameters<PageAgent['ai']>) => Promise<T>;\n aiAct: (\n ...args: Parameters<PageAgent['aiAct']>\n ) => ReturnType<PageAgent['aiAct']>;\n /**\n * @deprecated Use {@link PlayWrightAiFixtureType.aiAct} instead.\n */\n aiAction: (\n ...args: Parameters<PageAgent['aiAction']>\n ) => ReturnType<PageAgent['aiAction']>;\n aiTap: (\n ...args: Parameters<PageAgent['aiTap']>\n ) => ReturnType<PageAgent['aiTap']>;\n aiRightClick: (\n ...args: Parameters<PageAgent['aiRightClick']>\n ) => ReturnType<PageAgent['aiRightClick']>;\n aiDoubleClick: (\n ...args: Parameters<PageAgent['aiDoubleClick']>\n ) => ReturnType<PageAgent['aiDoubleClick']>;\n aiHover: (\n ...args: Parameters<PageAgent['aiHover']>\n ) => ReturnType<PageAgent['aiHover']>;\n aiInput: (\n ...args: Parameters<PageAgent['aiInput']>\n ) => ReturnType<PageAgent['aiInput']>;\n aiKeyboardPress: (\n ...args: Parameters<PageAgent['aiKeyboardPress']>\n ) => ReturnType<PageAgent['aiKeyboardPress']>;\n aiScroll: (\n ...args: Parameters<PageAgent['aiScroll']>\n ) => ReturnType<PageAgent['aiScroll']>;\n aiQuery: <T = any>(...args: Parameters<PageAgent['aiQuery']>) => Promise<T>;\n aiAssert: (\n ...args: Parameters<PageAgent['aiAssert']>\n ) => ReturnType<PageAgent['aiAssert']>;\n aiWaitFor: (...args: Parameters<PageAgent['aiWaitFor']>) => Promise<void>;\n aiLocate: (\n ...args: Parameters<PageAgent['aiLocate']>\n ) => ReturnType<PageAgent['aiLocate']>;\n aiNumber: (\n ...args: Parameters<PageAgent['aiNumber']>\n ) => ReturnType<PageAgent['aiNumber']>;\n aiString: (\n ...args: Parameters<PageAgent['aiString']>\n ) => ReturnType<PageAgent['aiString']>;\n aiBoolean: (\n ...args: Parameters<PageAgent['aiBoolean']>\n ) => ReturnType<PageAgent['aiBoolean']>;\n aiAsk: (\n ...args: Parameters<PageAgent['aiAsk']>\n ) => ReturnType<PageAgent['aiAsk']>;\n runYaml: (\n ...args: Parameters<PageAgent['runYaml']>\n ) => ReturnType<PageAgent['runYaml']>;\n setAIActionContext: (\n ...args: Parameters<PageAgent['setAIActionContext']>\n ) => ReturnType<PageAgent['setAIActionContext']>;\n evaluateJavaScript: (\n ...args: Parameters<PageAgent['evaluateJavaScript']>\n ) => ReturnType<PageAgent['evaluateJavaScript']>;\n recordToReport: (\n ...args: Parameters<PageAgent['recordToReport']>\n ) => ReturnType<PageAgent['recordToReport']>;\n logScreenshot: (\n ...args: Parameters<PageAgent['logScreenshot']>\n ) => ReturnType<PageAgent['logScreenshot']>;\n freezePageContext: (\n ...args: Parameters<PageAgent['freezePageContext']>\n ) => ReturnType<PageAgent['freezePageContext']>;\n unfreezePageContext: (\n ...args: Parameters<PageAgent['unfreezePageContext']>\n ) => ReturnType<PageAgent['unfreezePageContext']>;\n};\n"],"names":["debugPage","getDebug","groupAndCaseForTest","testInfo","taskFile","taskTitle","titlePath","taskTitleWithRetry","replaceIllegalPathCharsAndSpace","midsceneAgentKeyId","midsceneDumpAnnotationId","PlaywrightAiFixture","options","forceSameTabNavigation","waitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","waitForNavigationTimeout","DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT","cache","sharedAgentOptions","processTestCacheConfig","id","processCacheConfig","pageAgentMap","testAgentRecords","Map","getAgentRecordsForTest","records","setReportAnnotation","reportPaths","item","reportPath","finalizeAgentRecord","record","undefined","createOrReuseAgentForPage","page","opts","idForPage","uuid","file","title","cacheConfig","reportTag","agent","PlaywrightAgent","generateAiFunction","use","aiActionType","taskPrompt","args","Promise","resolve","reject","test","JSON","error","console","result","Array","Boolean","propsPage","finalCacheConfig","userCache"],"mappings":"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"}
@@ -51,7 +51,7 @@ class BridgeClient {
51
51
  ]
52
52
  } : {},
53
53
  query: {
54
- version: "1.7.5-beta-20260421030751.0"
54
+ version: "1.7.5"
55
55
  }
56
56
  });
57
57
  const timeout = setTimeout(()=>{