@midscene/web 1.15.4 → 1.15.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.
- package/dist/es/agent-tools-puppeteer.mjs +26 -2
- package/dist/es/agent-tools-puppeteer.mjs.map +1 -1
- package/dist/es/bridge-mode/io-client.mjs +1 -1
- package/dist/es/bridge-mode/io-server.mjs +2 -2
- package/dist/es/bridge-mode/page-browser-side.mjs +1 -1
- package/dist/es/cli.mjs +1 -1
- package/dist/es/{playwright → common}/test-runner/cookies.mjs +56 -79
- package/dist/es/common/test-runner/cookies.mjs.map +1 -0
- package/dist/es/common/test-runner/driver.mjs +0 -0
- package/dist/es/common/test-runner/nodes.mjs +139 -0
- package/dist/es/common/test-runner/nodes.mjs.map +1 -0
- package/dist/es/common/test-runner/utils.mjs +24 -0
- package/dist/es/common/test-runner/utils.mjs.map +1 -0
- package/dist/es/playwright/test-runner/agent-nodes.mjs +3 -10
- package/dist/es/playwright/test-runner/agent-nodes.mjs.map +1 -1
- package/dist/es/playwright/test-runner/driver.mjs +42 -0
- package/dist/es/playwright/test-runner/driver.mjs.map +1 -0
- package/dist/es/playwright/test-runner/index.mjs +1 -4
- package/dist/es/playwright/test-runner/utils.mjs +1 -21
- package/dist/es/playwright/test-runner/utils.mjs.map +1 -1
- package/dist/es/puppeteer/browser-agent.mjs +9 -1
- package/dist/es/puppeteer/browser-agent.mjs.map +1 -1
- package/dist/es/puppeteer/page-agent.mjs +19 -1
- package/dist/es/puppeteer/page-agent.mjs.map +1 -1
- package/dist/es/puppeteer/test-runner/agent-nodes.mjs +6 -0
- package/dist/es/puppeteer/test-runner/agent-nodes.mjs.map +1 -0
- package/dist/es/puppeteer/test-runner/driver.mjs +62 -0
- package/dist/es/puppeteer/test-runner/driver.mjs.map +1 -0
- package/dist/es/puppeteer/test-runner/index.mjs +3 -0
- package/dist/es/puppeteer/test-runner/types.mjs +0 -0
- package/dist/es/puppeteer/test-runner/utils.mjs +8 -0
- package/dist/es/puppeteer/test-runner/utils.mjs.map +1 -0
- package/dist/lib/agent-tools-puppeteer.js +26 -2
- package/dist/lib/agent-tools-puppeteer.js.map +1 -1
- package/dist/lib/bridge-mode/io-client.js +1 -1
- package/dist/lib/bridge-mode/io-server.js +2 -2
- package/dist/lib/bridge-mode/page-browser-side.js +1 -1
- package/dist/lib/cli.js +1 -1
- package/dist/lib/{playwright → common}/test-runner/cookies.js +58 -81
- package/dist/lib/common/test-runner/cookies.js.map +1 -0
- package/dist/lib/common/test-runner/driver.js +20 -0
- package/dist/lib/common/test-runner/driver.js.map +1 -0
- package/dist/lib/common/test-runner/nodes.js +185 -0
- package/dist/lib/common/test-runner/nodes.js.map +1 -0
- package/dist/lib/common/test-runner/utils.js +61 -0
- package/dist/lib/common/test-runner/utils.js.map +1 -0
- package/dist/lib/playwright/test-runner/agent-nodes.js +3 -10
- package/dist/lib/playwright/test-runner/agent-nodes.js.map +1 -1
- package/dist/lib/playwright/test-runner/driver.js +76 -0
- package/dist/lib/playwright/test-runner/driver.js.map +1 -0
- package/dist/lib/playwright/test-runner/index.js +5 -8
- package/dist/lib/playwright/test-runner/utils.js +4 -24
- package/dist/lib/playwright/test-runner/utils.js.map +1 -1
- package/dist/lib/puppeteer/browser-agent.js +9 -1
- package/dist/lib/puppeteer/browser-agent.js.map +1 -1
- package/dist/lib/puppeteer/page-agent.js +19 -1
- package/dist/lib/puppeteer/page-agent.js.map +1 -1
- package/dist/lib/puppeteer/test-runner/agent-nodes.js +40 -0
- package/dist/lib/puppeteer/test-runner/agent-nodes.js.map +1 -0
- package/dist/lib/puppeteer/test-runner/driver.js +96 -0
- package/dist/lib/puppeteer/test-runner/driver.js.map +1 -0
- package/dist/lib/puppeteer/test-runner/index.js +51 -0
- package/dist/lib/puppeteer/test-runner/index.js.map +1 -0
- package/dist/lib/puppeteer/test-runner/types.js +20 -0
- package/dist/lib/puppeteer/test-runner/types.js.map +1 -0
- package/dist/lib/puppeteer/test-runner/utils.js +48 -0
- package/dist/lib/puppeteer/test-runner/utils.js.map +1 -0
- package/dist/types/agent-tools-puppeteer.d.ts +1 -0
- package/dist/types/agent-tools-puppeteer.d.ts.map +1 -1
- package/dist/types/common/test-runner/cookies.d.ts +52 -0
- package/dist/types/common/test-runner/cookies.d.ts.map +1 -0
- package/dist/types/common/test-runner/driver.d.ts +38 -0
- package/dist/types/common/test-runner/driver.d.ts.map +1 -0
- package/dist/types/common/test-runner/nodes.d.ts +38 -0
- package/dist/types/common/test-runner/nodes.d.ts.map +1 -0
- package/dist/types/common/test-runner/utils.d.ts +3 -0
- package/dist/types/common/test-runner/utils.d.ts.map +1 -0
- package/dist/types/playwright/test-runner/agent-nodes.d.ts +1 -2
- package/dist/types/playwright/test-runner/agent-nodes.d.ts.map +1 -1
- package/dist/types/playwright/test-runner/driver.d.ts +4 -0
- package/dist/types/playwright/test-runner/driver.d.ts.map +1 -0
- package/dist/types/playwright/test-runner/index.d.ts +2 -8
- package/dist/types/playwright/test-runner/index.d.ts.map +1 -1
- package/dist/types/playwright/test-runner/types.d.ts +5 -14
- package/dist/types/playwright/test-runner/types.d.ts.map +1 -1
- package/dist/types/playwright/test-runner/utils.d.ts +1 -2
- package/dist/types/playwright/test-runner/utils.d.ts.map +1 -1
- package/dist/types/puppeteer/browser-agent.d.ts +5 -0
- package/dist/types/puppeteer/browser-agent.d.ts.map +1 -1
- package/dist/types/puppeteer/index.d.ts +1 -1
- package/dist/types/puppeteer/index.d.ts.map +1 -1
- package/dist/types/puppeteer/page-agent.d.ts +8 -1
- package/dist/types/puppeteer/page-agent.d.ts.map +1 -1
- package/dist/types/puppeteer/test-runner/agent-nodes.d.ts +2 -0
- package/dist/types/puppeteer/test-runner/agent-nodes.d.ts.map +1 -0
- package/dist/types/puppeteer/test-runner/driver.d.ts +4 -0
- package/dist/types/puppeteer/test-runner/driver.d.ts.map +1 -0
- package/dist/types/puppeteer/test-runner/index.d.ts +5 -0
- package/dist/types/puppeteer/test-runner/index.d.ts.map +1 -0
- package/dist/types/puppeteer/test-runner/types.d.ts +18 -0
- package/dist/types/puppeteer/test-runner/types.d.ts.map +1 -0
- package/dist/types/puppeteer/test-runner/utils.d.ts +4 -0
- package/dist/types/puppeteer/test-runner/utils.d.ts.map +1 -0
- package/package.json +9 -4
- package/dist/es/playwright/test-runner/clear-cookies.mjs +0 -45
- package/dist/es/playwright/test-runner/clear-cookies.mjs.map +0 -1
- package/dist/es/playwright/test-runner/cookies.mjs.map +0 -1
- package/dist/es/playwright/test-runner/goto-url.mjs +0 -60
- package/dist/es/playwright/test-runner/goto-url.mjs.map +0 -1
- package/dist/es/playwright/test-runner/viewport.mjs +0 -35
- package/dist/es/playwright/test-runner/viewport.mjs.map +0 -1
- package/dist/lib/playwright/test-runner/clear-cookies.js +0 -82
- package/dist/lib/playwright/test-runner/clear-cookies.js.map +0 -1
- package/dist/lib/playwright/test-runner/cookies.js.map +0 -1
- package/dist/lib/playwright/test-runner/goto-url.js +0 -97
- package/dist/lib/playwright/test-runner/goto-url.js.map +0 -1
- package/dist/lib/playwright/test-runner/viewport.js +0 -72
- package/dist/lib/playwright/test-runner/viewport.js.map +0 -1
- package/dist/types/playwright/test-runner/clear-cookies.d.ts +0 -17
- package/dist/types/playwright/test-runner/clear-cookies.d.ts.map +0 -1
- package/dist/types/playwright/test-runner/cookies.d.ts +0 -33
- package/dist/types/playwright/test-runner/cookies.d.ts.map +0 -1
- package/dist/types/playwright/test-runner/goto-url.d.ts +0 -26
- package/dist/types/playwright/test-runner/goto-url.d.ts.map +0 -1
- package/dist/types/playwright/test-runner/viewport.d.ts +0 -14
- package/dist/types/playwright/test-runner/viewport.d.ts.map +0 -1
|
@@ -302,7 +302,7 @@ class WebPuppeteerMidsceneTools extends BaseMidsceneTools {
|
|
|
302
302
|
let page;
|
|
303
303
|
if (navigateToUrl) {
|
|
304
304
|
page = await browser.newPage();
|
|
305
|
-
if (this.viewport) await
|
|
305
|
+
if (this.viewport) await this.resizePersistentViewport(page);
|
|
306
306
|
await page.goto(navigateToUrl, {
|
|
307
307
|
timeout: 30000,
|
|
308
308
|
waitUntil: 'domcontentloaded'
|
|
@@ -313,7 +313,7 @@ class WebPuppeteerMidsceneTools extends BaseMidsceneTools {
|
|
|
313
313
|
const webPages = pages.filter((p)=>/^https?:\/\//.test(p.url()));
|
|
314
314
|
page = matchedPage ?? (webPages.length > 0 ? webPages[webPages.length - 1] : pages[pages.length - 1] || await browser.newPage());
|
|
315
315
|
if (reused) await page.bringToFront();
|
|
316
|
-
if (this.viewport) await
|
|
316
|
+
if (this.viewport) await this.resizePersistentViewport(page);
|
|
317
317
|
}
|
|
318
318
|
const targetId = getTargetId(page);
|
|
319
319
|
if (!targetId) throw new Error('Failed to persist Puppeteer page session because Puppeteer did not expose a Chrome targetId.');
|
|
@@ -326,6 +326,30 @@ class WebPuppeteerMidsceneTools extends BaseMidsceneTools {
|
|
|
326
326
|
this.lastInitArgsSignature = nextSignature;
|
|
327
327
|
return this.agent;
|
|
328
328
|
}
|
|
329
|
+
async resizePersistentViewport(page) {
|
|
330
|
+
const viewport = this.viewport;
|
|
331
|
+
if (!viewport) return;
|
|
332
|
+
await page.setViewport(null);
|
|
333
|
+
const client = await page.createCDPSession();
|
|
334
|
+
try {
|
|
335
|
+
const { windowId, bounds } = await client.send('Browser.getWindowForTarget');
|
|
336
|
+
const content = await page.evaluate('({ width: window.innerWidth, height: window.innerHeight })');
|
|
337
|
+
if (void 0 === bounds.width || void 0 === bounds.height) throw new Error('Chrome did not return window dimensions');
|
|
338
|
+
await client.send('Browser.setWindowBounds', {
|
|
339
|
+
windowId,
|
|
340
|
+
bounds: {
|
|
341
|
+
width: bounds.width + viewport.width - content.width,
|
|
342
|
+
height: bounds.height + viewport.height - content.height
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
await page.waitForFunction(`window.innerWidth === ${JSON.stringify(viewport.width)} && window.innerHeight === ${JSON.stringify(viewport.height)}`, {
|
|
346
|
+
timeout: 10000
|
|
347
|
+
});
|
|
348
|
+
} finally{
|
|
349
|
+
await client.detach();
|
|
350
|
+
}
|
|
351
|
+
await page.setViewport(viewport);
|
|
352
|
+
}
|
|
329
353
|
async destroy() {
|
|
330
354
|
await super.destroy();
|
|
331
355
|
this.browserManager.disconnect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-tools-puppeteer.mjs","sources":["../../src/agent-tools-puppeteer.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, open, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem } from '@midscene/core';\nimport {\n extractAgentBehaviorInitArgs,\n getAgentInitArgsSignature,\n shouldRebuildAgentForInitArgs,\n} from '@midscene/shared/agent-tools/agent-behavior-init-args';\nimport {\n BaseMidsceneTools,\n type InitArgSpec,\n} from '@midscene/shared/agent-tools/base-tools';\nimport { resolveChromePath } from '@midscene/shared/agent-tools/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/agent-tools/types';\nimport { getDebug } from '@midscene/shared/logger';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n type WebAgentInitArgs,\n adaptWebAgentInitArgs,\n webAgentInitArgShape,\n} from './agent-init-args';\nimport {\n type ViewportSize,\n defaultPuppeteerWindowViewportSize,\n defaultStaticPageViewportSize,\n} from './common/viewport';\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');\nconst TARGET_ID_FILE = join(tmpdir(), 'midscene-puppeteer-target-id');\nconst DETACHED_CHROME_LAUNCH_TIMEOUT_MS = 30_000;\nconst DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS = 25;\nconst debug = getDebug('agent-tools:puppeteer');\n\nexport const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;\n\nexport interface PuppeteerPersistenceOptions {\n endpointFile?: string;\n userDataDir?: string;\n targetIdFile?: string;\n}\n\nexport interface WebPuppeteerMidsceneToolsOptions {\n persistence?: PuppeteerPersistenceOptions;\n}\n\nexport function buildDetachedChromeArgs(options: {\n userDataDir: string;\n viewport?: ViewportSize;\n}): string[] {\n const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;\n\n return [\n '--headless=new',\n `--user-data-dir=${options.userDataDir}`,\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=${viewport.width},${viewport.height}`,\n '--force-color-profile=srgb',\n ];\n}\n\nfunction terminateDetachedChrome(proc: ChildProcess): void {\n if (proc.killed || proc.exitCode !== null || proc.signalCode !== null) {\n return;\n }\n\n if (process.platform !== 'win32' && proc.pid) {\n try {\n process.kill(-proc.pid, 'SIGKILL');\n return;\n } catch {}\n }\n\n try {\n proc.kill('SIGKILL');\n } catch {}\n}\n\nfunction getTargetId(page: Page): string | undefined {\n return (page.target() as unknown as { _targetId?: string })._targetId;\n}\n\nexport function waitForDetachedChromeEndpoint(\n proc: ChildProcess,\n stderrFile: string,\n timeoutMs = DETACHED_CHROME_LAUNCH_TIMEOUT_MS,\n): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n let output = '';\n let settled = false;\n let exited = false;\n let pollTimer: NodeJS.Timeout | undefined;\n const cleanup = () => {\n clearTimeout(timeout);\n if (pollTimer) {\n clearTimeout(pollTimer);\n }\n proc.removeListener('exit', onExit);\n proc.removeListener('error', onError);\n };\n const resolveOnce = (value: string) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n };\n const rejectOnce = (error: Error, terminate = false) => {\n if (settled) return;\n settled = true;\n if (terminate) {\n terminateDetachedChrome(proc);\n }\n cleanup();\n reject(error);\n };\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n exited = true;\n void readFile(stderrFile, 'utf-8')\n .catch(() => output)\n .then((latestOutput) => {\n output = latestOutput;\n rejectOnce(\n new Error(\n `Chrome exited with code ${code ?? signal} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`,\n ),\n );\n });\n };\n const onError = (error: Error) => {\n rejectOnce(\n new Error(`Failed to launch Chrome. Stderr log: \"${stderrFile}\".`, {\n cause: error,\n }),\n true,\n );\n };\n const pollForEndpoint = async (): Promise<void> => {\n if (settled || exited) return;\n try {\n output = await readFile(stderrFile, 'utf-8');\n } catch (error) {\n rejectOnce(\n new Error(`Failed to read Chrome stderr log \"${stderrFile}\".`, {\n cause: error,\n }),\n true,\n );\n return;\n }\n\n if (settled || exited) return;\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n resolveOnce(match[1]);\n return;\n }\n pollTimer = setTimeout(\n () => void pollForEndpoint(),\n DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS,\n );\n };\n const timeout = setTimeout(\n () =>\n rejectOnce(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`,\n ),\n true,\n ),\n timeoutMs,\n );\n\n proc.on('exit', onExit);\n proc.on('error', onError);\n void pollForEndpoint();\n });\n}\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nclass PuppeteerBrowserManager {\n activeBrowser: Browser | null = null;\n\n constructor(private readonly persistence: PuppeteerPersistenceOptions = {}) {}\n\n private get endpointFile() {\n return this.persistence.endpointFile || ENDPOINT_FILE;\n }\n\n private get userDataDir() {\n return this.persistence.userDataDir || USER_DATA_DIR;\n }\n\n private get targetIdFile() {\n return this.persistence.targetIdFile || TARGET_ID_FILE;\n }\n\n private get chromeStderrFile() {\n return join(this.userDataDir, 'chrome-stderr.log');\n }\n\n async readSavedTargetId(): Promise<string | null> {\n let content: string;\n try {\n content = await readFile(this.targetIdFile, 'utf-8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw new Error(\n `Failed to read Puppeteer targetId from \"${this.targetIdFile}\".`,\n { cause: error },\n );\n }\n\n const targetId = content.trim();\n if (!targetId) {\n throw new Error(\n `Puppeteer targetId file \"${this.targetIdFile}\" is empty.`,\n );\n }\n return targetId;\n }\n\n async saveTargetId(targetId: string): Promise<void> {\n try {\n await writeFile(this.targetIdFile, targetId, 'utf-8');\n debug('Saved Puppeteer targetId: %s', targetId);\n } catch (error) {\n throw new Error(\n `Failed to save Puppeteer targetId to \"${this.targetIdFile}\".`,\n { cause: error },\n );\n }\n }\n\n async cleanupTargetIdFile(): Promise<void> {\n try {\n await unlink(this.targetIdFile);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw new Error(\n `Failed to remove Puppeteer targetId file \"${this.targetIdFile}\".`,\n { cause: error },\n );\n }\n }\n\n async cleanupChromeStderrFile(): Promise<void> {\n try {\n await unlink(this.chromeStderrFile);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n debug('Failed to clean Chrome stderr log: %s', error);\n }\n }\n }\n\n async getOrLaunch(\n viewport?: ViewportSize,\n ): Promise<{ browser: Browser; reused: boolean }> {\n const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch (error) {\n debug('Failed to reuse persisted Puppeteer endpoint: %s', error);\n try {\n await unlink(endpointFile);\n } catch {}\n await this.cleanupTargetIdFile();\n }\n }\n\n await this.cleanupTargetIdFile();\n const wsEndpoint = await this.launchDetachedChrome(viewport);\n await writeFile(endpointFile, 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 const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch (error) {\n debug('Failed to close persisted Puppeteer browser: %s', error);\n }\n }\n try {\n if (existsSync(endpointFile)) {\n await unlink(endpointFile);\n }\n } catch {}\n await this.cleanupTargetIdFile();\n await this.cleanupChromeStderrFile();\n }\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n async launchDetachedChrome(viewport?: ViewportSize): Promise<string> {\n const chromePath = resolveChromePath();\n const userDataDir = this.userDataDir;\n\n await mkdir(userDataDir, { recursive: true });\n\n const args = buildDetachedChromeArgs({\n userDataDir,\n viewport,\n });\n\n const stderrFile = this.chromeStderrFile;\n const stderrHandle = await open(stderrFile, 'w');\n let proc: ChildProcess;\n try {\n proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', stderrHandle.fd],\n });\n } catch (error) {\n await stderrHandle.close();\n throw error;\n }\n const endpointPromise = waitForDetachedChromeEndpoint(proc, stderrFile);\n proc.unref();\n try {\n await stderrHandle.close();\n } catch (error) {\n terminateDetachedChrome(proc);\n await endpointPromise.catch(() => undefined);\n throw new Error(`Failed to close Chrome stderr log \"${stderrFile}\".`, {\n cause: error,\n });\n }\n\n return endpointPromise;\n }\n}\n\nconst defaultBrowserManager = new PuppeteerBrowserManager();\n\n/**\n * Tools manager for Web Puppeteer mode.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<\n PuppeteerAgent,\n WebAgentInitArgs\n> {\n private readonly viewport?: ViewportSize;\n private readonly browserManager: PuppeteerBrowserManager;\n private lastInitArgsSignature?: string;\n\n constructor(\n viewport?: ViewportSize,\n options: WebPuppeteerMidsceneToolsOptions = {},\n ) {\n super();\n this.viewport = viewport ? { ...viewport } : undefined;\n this.browserManager = options.persistence\n ? new PuppeteerBrowserManager(options.persistence)\n : defaultBrowserManager;\n }\n\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected readonly initArgSpec: InitArgSpec<WebAgentInitArgs> = {\n namespace: 'web',\n shape: webAgentInitArgShape,\n cli: {\n preferBareKeys: true,\n },\n adapt: adaptWebAgentInitArgs,\n };\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: this.viewport ?? defaultStaticPageViewportSize,\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(\n initArgs?: WebAgentInitArgs,\n ): Promise<PuppeteerAgent> {\n const navigateToUrl = initArgs?.url;\n const nextSignature = getAgentInitArgsSignature(initArgs);\n const shouldOpenUrl = typeof navigateToUrl === 'string';\n\n if (\n this.agent &&\n (shouldOpenUrl ||\n shouldRebuildAgentForInitArgs(\n this.lastInitArgsSignature,\n nextSignature,\n ))\n ) {\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 this.browserManager.getOrLaunch(\n this.viewport,\n );\n this.browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n const savedTargetId = await this.browserManager.readSavedTargetId();\n const matchedPage = savedTargetId\n ? pages.find((candidate) => getTargetId(candidate) === savedTargetId)\n : undefined;\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n matchedPage ??\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 if (this.viewport) {\n await page.setViewport(this.viewport);\n }\n }\n\n const targetId = getTargetId(page);\n if (!targetId) {\n throw new Error(\n 'Failed to persist Puppeteer page session because Puppeteer did not expose a Chrome targetId.',\n );\n }\n await this.browserManager.saveTargetId(targetId);\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(extractAgentBehaviorInitArgs(initArgs) ?? {}),\n ...(reportOptions ?? {}),\n });\n this.lastInitArgsSignature = nextSignature;\n return this.agent;\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n this.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: this.getAgentInitArgSchema(),\n cli: this.getAgentInitArgCliMetadata(),\n handler: async (args) => {\n const initArgs = this.extractAgentInitParam(args);\n const url = initArgs?.url;\n\n // Explicit connect always starts a fresh page session.\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n this.lastInitArgsSignature = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(initArgs);\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 this.lastInitArgsSignature = undefined;\n }\n this.browserManager.disconnect();\n await this.browserManager.cleanupTargetIdFile();\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 this.lastInitArgsSignature = undefined;\n }\n await this.browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","TARGET_ID_FILE","DETACHED_CHROME_LAUNCH_TIMEOUT_MS","DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS","debug","getDebug","PUPPETEER_ENDPOINT_FILE","buildDetachedChromeArgs","options","viewport","defaultPuppeteerWindowViewportSize","terminateDetachedChrome","proc","process","getTargetId","page","waitForDetachedChromeEndpoint","stderrFile","timeoutMs","Promise","resolve","reject","output","settled","exited","pollTimer","cleanup","clearTimeout","timeout","onExit","onError","resolveOnce","value","rejectOnce","error","terminate","code","signal","readFile","latestOutput","Error","pollForEndpoint","match","setTimeout","PuppeteerBrowserManager","content","targetId","writeFile","unlink","endpointFile","existsSync","endpoint","browser","puppeteer","wsEndpoint","chromePath","resolveChromePath","userDataDir","mkdir","args","stderrHandle","open","spawn","endpointPromise","undefined","persistence","defaultBrowserManager","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","defaultStaticPageViewportSize","initArgs","navigateToUrl","nextSignature","getAgentInitArgsSignature","shouldOpenUrl","shouldRebuildAgentForInitArgs","reused","pages","savedTargetId","matchedPage","candidate","webPages","p","reportOptions","PuppeteerAgent","extractAgentBehaviorInitArgs","url","reportSession","screenshot","label","webAgentInitArgShape","adaptWebAgentInitArgs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agent-tools-puppeteer.mjs","sources":["../../src/agent-tools-puppeteer.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, open, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { ScreenshotItem } from '@midscene/core';\nimport {\n extractAgentBehaviorInitArgs,\n getAgentInitArgsSignature,\n shouldRebuildAgentForInitArgs,\n} from '@midscene/shared/agent-tools/agent-behavior-init-args';\nimport {\n BaseMidsceneTools,\n type InitArgSpec,\n} from '@midscene/shared/agent-tools/base-tools';\nimport { resolveChromePath } from '@midscene/shared/agent-tools/chrome-path';\nimport type { ToolDefinition } from '@midscene/shared/agent-tools/types';\nimport { getDebug } from '@midscene/shared/logger';\nimport type { Page as PuppeteerPage } from 'puppeteer';\nimport puppeteer from 'puppeteer-core';\nimport type { Browser, Page } from 'puppeteer-core';\nimport {\n type WebAgentInitArgs,\n adaptWebAgentInitArgs,\n webAgentInitArgShape,\n} from './agent-init-args';\nimport {\n type ViewportSize,\n defaultPuppeteerWindowViewportSize,\n defaultStaticPageViewportSize,\n} from './common/viewport';\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');\nconst TARGET_ID_FILE = join(tmpdir(), 'midscene-puppeteer-target-id');\nconst DETACHED_CHROME_LAUNCH_TIMEOUT_MS = 30_000;\nconst DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS = 25;\nconst debug = getDebug('agent-tools:puppeteer');\n\nexport const PUPPETEER_ENDPOINT_FILE = ENDPOINT_FILE;\n\nexport interface PuppeteerPersistenceOptions {\n endpointFile?: string;\n userDataDir?: string;\n targetIdFile?: string;\n}\n\nexport interface WebPuppeteerMidsceneToolsOptions {\n persistence?: PuppeteerPersistenceOptions;\n}\n\nexport function buildDetachedChromeArgs(options: {\n userDataDir: string;\n viewport?: ViewportSize;\n}): string[] {\n const viewport = options.viewport ?? defaultPuppeteerWindowViewportSize;\n\n return [\n '--headless=new',\n `--user-data-dir=${options.userDataDir}`,\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=${viewport.width},${viewport.height}`,\n '--force-color-profile=srgb',\n ];\n}\n\nfunction terminateDetachedChrome(proc: ChildProcess): void {\n if (proc.killed || proc.exitCode !== null || proc.signalCode !== null) {\n return;\n }\n\n if (process.platform !== 'win32' && proc.pid) {\n try {\n process.kill(-proc.pid, 'SIGKILL');\n return;\n } catch {}\n }\n\n try {\n proc.kill('SIGKILL');\n } catch {}\n}\n\nfunction getTargetId(page: Page): string | undefined {\n return (page.target() as unknown as { _targetId?: string })._targetId;\n}\n\nexport function waitForDetachedChromeEndpoint(\n proc: ChildProcess,\n stderrFile: string,\n timeoutMs = DETACHED_CHROME_LAUNCH_TIMEOUT_MS,\n): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n let output = '';\n let settled = false;\n let exited = false;\n let pollTimer: NodeJS.Timeout | undefined;\n const cleanup = () => {\n clearTimeout(timeout);\n if (pollTimer) {\n clearTimeout(pollTimer);\n }\n proc.removeListener('exit', onExit);\n proc.removeListener('error', onError);\n };\n const resolveOnce = (value: string) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n };\n const rejectOnce = (error: Error, terminate = false) => {\n if (settled) return;\n settled = true;\n if (terminate) {\n terminateDetachedChrome(proc);\n }\n cleanup();\n reject(error);\n };\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n exited = true;\n void readFile(stderrFile, 'utf-8')\n .catch(() => output)\n .then((latestOutput) => {\n output = latestOutput;\n rejectOnce(\n new Error(\n `Chrome exited with code ${code ?? signal} before DevTools was ready.\\nChrome stderr: ${output}\\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`,\n ),\n );\n });\n };\n const onError = (error: Error) => {\n rejectOnce(\n new Error(`Failed to launch Chrome. Stderr log: \"${stderrFile}\".`, {\n cause: error,\n }),\n true,\n );\n };\n const pollForEndpoint = async (): Promise<void> => {\n if (settled || exited) return;\n try {\n output = await readFile(stderrFile, 'utf-8');\n } catch (error) {\n rejectOnce(\n new Error(`Failed to read Chrome stderr log \"${stderrFile}\".`, {\n cause: error,\n }),\n true,\n );\n return;\n }\n\n if (settled || exited) return;\n const match = output.match(/DevTools listening on (ws:\\/\\/[^\\s]+)/);\n if (match) {\n resolveOnce(match[1]);\n return;\n }\n pollTimer = setTimeout(\n () => void pollForEndpoint(),\n DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS,\n );\n };\n const timeout = setTimeout(\n () =>\n rejectOnce(\n new Error(\n `Chrome launch timeout.\\nChrome stderr: ${output}\\nTip: if running in a container, launch Chrome with sandbox-compatible arguments.`,\n ),\n true,\n ),\n timeoutMs,\n );\n\n proc.on('exit', onExit);\n proc.on('error', onError);\n void pollForEndpoint();\n });\n}\n\n/**\n * Persistent Puppeteer browser manager.\n * Launches a detached Chrome and persists the WS endpoint across CLI calls.\n */\nclass PuppeteerBrowserManager {\n activeBrowser: Browser | null = null;\n\n constructor(private readonly persistence: PuppeteerPersistenceOptions = {}) {}\n\n private get endpointFile() {\n return this.persistence.endpointFile || ENDPOINT_FILE;\n }\n\n private get userDataDir() {\n return this.persistence.userDataDir || USER_DATA_DIR;\n }\n\n private get targetIdFile() {\n return this.persistence.targetIdFile || TARGET_ID_FILE;\n }\n\n private get chromeStderrFile() {\n return join(this.userDataDir, 'chrome-stderr.log');\n }\n\n async readSavedTargetId(): Promise<string | null> {\n let content: string;\n try {\n content = await readFile(this.targetIdFile, 'utf-8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw new Error(\n `Failed to read Puppeteer targetId from \"${this.targetIdFile}\".`,\n { cause: error },\n );\n }\n\n const targetId = content.trim();\n if (!targetId) {\n throw new Error(\n `Puppeteer targetId file \"${this.targetIdFile}\" is empty.`,\n );\n }\n return targetId;\n }\n\n async saveTargetId(targetId: string): Promise<void> {\n try {\n await writeFile(this.targetIdFile, targetId, 'utf-8');\n debug('Saved Puppeteer targetId: %s', targetId);\n } catch (error) {\n throw new Error(\n `Failed to save Puppeteer targetId to \"${this.targetIdFile}\".`,\n { cause: error },\n );\n }\n }\n\n async cleanupTargetIdFile(): Promise<void> {\n try {\n await unlink(this.targetIdFile);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw new Error(\n `Failed to remove Puppeteer targetId file \"${this.targetIdFile}\".`,\n { cause: error },\n );\n }\n }\n\n async cleanupChromeStderrFile(): Promise<void> {\n try {\n await unlink(this.chromeStderrFile);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n debug('Failed to clean Chrome stderr log: %s', error);\n }\n }\n }\n\n async getOrLaunch(\n viewport?: ViewportSize,\n ): Promise<{ browser: Browser; reused: boolean }> {\n const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n defaultViewport: null,\n });\n return { browser, reused: true };\n } catch (error) {\n debug('Failed to reuse persisted Puppeteer endpoint: %s', error);\n try {\n await unlink(endpointFile);\n } catch {}\n await this.cleanupTargetIdFile();\n }\n }\n\n await this.cleanupTargetIdFile();\n const wsEndpoint = await this.launchDetachedChrome(viewport);\n await writeFile(endpointFile, 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 const endpointFile = this.endpointFile;\n if (existsSync(endpointFile)) {\n try {\n const endpoint = (await readFile(endpointFile, 'utf-8')).trim();\n const browser = await puppeteer.connect({\n browserWSEndpoint: endpoint,\n });\n await browser.close();\n } catch (error) {\n debug('Failed to close persisted Puppeteer browser: %s', error);\n }\n }\n try {\n if (existsSync(endpointFile)) {\n await unlink(endpointFile);\n }\n } catch {}\n await this.cleanupTargetIdFile();\n await this.cleanupChromeStderrFile();\n }\n\n disconnect(): void {\n if (this.activeBrowser) {\n this.activeBrowser.disconnect();\n this.activeBrowser = null;\n }\n }\n\n async launchDetachedChrome(viewport?: ViewportSize): Promise<string> {\n const chromePath = resolveChromePath();\n const userDataDir = this.userDataDir;\n\n await mkdir(userDataDir, { recursive: true });\n\n const args = buildDetachedChromeArgs({\n userDataDir,\n viewport,\n });\n\n const stderrFile = this.chromeStderrFile;\n const stderrHandle = await open(stderrFile, 'w');\n let proc: ChildProcess;\n try {\n proc = spawn(chromePath, args, {\n detached: true,\n stdio: ['ignore', 'ignore', stderrHandle.fd],\n });\n } catch (error) {\n await stderrHandle.close();\n throw error;\n }\n const endpointPromise = waitForDetachedChromeEndpoint(proc, stderrFile);\n proc.unref();\n try {\n await stderrHandle.close();\n } catch (error) {\n terminateDetachedChrome(proc);\n await endpointPromise.catch(() => undefined);\n throw new Error(`Failed to close Chrome stderr log \"${stderrFile}\".`, {\n cause: error,\n });\n }\n\n return endpointPromise;\n }\n}\n\nconst defaultBrowserManager = new PuppeteerBrowserManager();\n\n/**\n * Tools manager for Web Puppeteer mode.\n * Uses a persistent headless Chrome browser that survives across CLI calls.\n */\nexport class WebPuppeteerMidsceneTools extends BaseMidsceneTools<\n PuppeteerAgent,\n WebAgentInitArgs\n> {\n private readonly viewport?: ViewportSize;\n private readonly browserManager: PuppeteerBrowserManager;\n private lastInitArgsSignature?: string;\n\n constructor(\n viewport?: ViewportSize,\n options: WebPuppeteerMidsceneToolsOptions = {},\n ) {\n super();\n this.viewport = viewport ? { ...viewport } : undefined;\n this.browserManager = options.persistence\n ? new PuppeteerBrowserManager(options.persistence)\n : defaultBrowserManager;\n }\n\n protected getCliReportSessionName() {\n return 'midscene-web';\n }\n\n protected readonly initArgSpec: InitArgSpec<WebAgentInitArgs> = {\n namespace: 'web',\n shape: webAgentInitArgShape,\n cli: {\n preferBareKeys: true,\n },\n adapt: adaptWebAgentInitArgs,\n };\n\n protected createTemporaryDevice() {\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: this.viewport ?? defaultStaticPageViewportSize,\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(\n initArgs?: WebAgentInitArgs,\n ): Promise<PuppeteerAgent> {\n const navigateToUrl = initArgs?.url;\n const nextSignature = getAgentInitArgsSignature(initArgs);\n const shouldOpenUrl = typeof navigateToUrl === 'string';\n\n if (\n this.agent &&\n (shouldOpenUrl ||\n shouldRebuildAgentForInitArgs(\n this.lastInitArgsSignature,\n nextSignature,\n ))\n ) {\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 this.browserManager.getOrLaunch(\n this.viewport,\n );\n this.browserManager.activeBrowser = browser;\n\n const pages = await browser.pages();\n let page: Page;\n\n if (navigateToUrl) {\n page = await browser.newPage();\n if (this.viewport) {\n await this.resizePersistentViewport(page);\n }\n await page.goto(navigateToUrl, {\n timeout: 30000,\n waitUntil: 'domcontentloaded',\n });\n } else {\n const savedTargetId = await this.browserManager.readSavedTargetId();\n const matchedPage = savedTargetId\n ? pages.find((candidate) => getTargetId(candidate) === savedTargetId)\n : undefined;\n const webPages = pages.filter((p) => /^https?:\\/\\//.test(p.url()));\n page =\n matchedPage ??\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 if (this.viewport) {\n await this.resizePersistentViewport(page);\n }\n }\n\n const targetId = getTargetId(page);\n if (!targetId) {\n throw new Error(\n 'Failed to persist Puppeteer page session because Puppeteer did not expose a Chrome targetId.',\n );\n }\n await this.browserManager.saveTargetId(targetId);\n\n const reportOptions = this.readCliReportAgentOptions();\n this.agent = new PuppeteerAgent(page as unknown as PuppeteerPage, {\n ...(extractAgentBehaviorInitArgs(initArgs) ?? {}),\n ...(reportOptions ?? {}),\n });\n this.lastInitArgsSignature = nextSignature;\n return this.agent;\n }\n\n private async resizePersistentViewport(page: Page): Promise<void> {\n const viewport = this.viewport;\n if (!viewport) return;\n\n // Emulation is scoped to the CDP connection. Resize the actual content area\n // so the requested dimensions also survive CLI disconnect and reconnect.\n await page.setViewport(null);\n const client = await page.createCDPSession();\n try {\n const { windowId, bounds } = await client.send(\n 'Browser.getWindowForTarget',\n );\n // Browser snippets must remain free of Node-side coverage counters.\n const content = (await page.evaluate(\n '({ width: window.innerWidth, height: window.innerHeight })',\n )) as { width: number; height: number };\n if (bounds.width === undefined || bounds.height === undefined) {\n throw new Error('Chrome did not return window dimensions');\n }\n await client.send('Browser.setWindowBounds', {\n windowId,\n bounds: {\n width: bounds.width + viewport.width - content.width,\n height: bounds.height + viewport.height - content.height,\n },\n });\n await page.waitForFunction(\n `window.innerWidth === ${JSON.stringify(viewport.width)} && window.innerHeight === ${JSON.stringify(viewport.height)}`,\n { timeout: 10_000 },\n );\n } finally {\n await client.detach();\n }\n await page.setViewport(viewport);\n }\n\n public async destroy(): Promise<void> {\n await super.destroy();\n this.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: this.getAgentInitArgSchema(),\n cli: this.getAgentInitArgCliMetadata(),\n handler: async (args) => {\n const initArgs = this.extractAgentInitParam(args);\n const url = initArgs?.url;\n\n // Explicit connect always starts a fresh page session.\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n this.lastInitArgsSignature = undefined;\n }\n\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-page',\n );\n this.commitCliReportSession(reportSession);\n this.agent = await this.ensureAgent(initArgs);\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 this.lastInitArgsSignature = undefined;\n }\n this.browserManager.disconnect();\n await this.browserManager.cleanupTargetIdFile();\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 this.lastInitArgsSignature = undefined;\n }\n await this.browserManager.closeBrowser();\n return this.buildTextResult('Browser closed');\n },\n },\n ];\n }\n}\n"],"names":["ENDPOINT_FILE","join","tmpdir","USER_DATA_DIR","TARGET_ID_FILE","DETACHED_CHROME_LAUNCH_TIMEOUT_MS","DETACHED_CHROME_ENDPOINT_POLL_INTERVAL_MS","debug","getDebug","PUPPETEER_ENDPOINT_FILE","buildDetachedChromeArgs","options","viewport","defaultPuppeteerWindowViewportSize","terminateDetachedChrome","proc","process","getTargetId","page","waitForDetachedChromeEndpoint","stderrFile","timeoutMs","Promise","resolve","reject","output","settled","exited","pollTimer","cleanup","clearTimeout","timeout","onExit","onError","resolveOnce","value","rejectOnce","error","terminate","code","signal","readFile","latestOutput","Error","pollForEndpoint","match","setTimeout","PuppeteerBrowserManager","content","targetId","writeFile","unlink","endpointFile","existsSync","endpoint","browser","puppeteer","wsEndpoint","chromePath","resolveChromePath","userDataDir","mkdir","args","stderrHandle","open","spawn","endpointPromise","undefined","persistence","defaultBrowserManager","WebPuppeteerMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","defaultStaticPageViewportSize","initArgs","navigateToUrl","nextSignature","getAgentInitArgsSignature","shouldOpenUrl","shouldRebuildAgentForInitArgs","reused","pages","savedTargetId","matchedPage","candidate","webPages","p","reportOptions","PuppeteerAgent","extractAgentBehaviorInitArgs","client","windowId","bounds","JSON","url","reportSession","screenshot","label","webAgentInitArgShape","adaptWebAgentInitArgs"],"mappings":"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"}
|
|
@@ -101,7 +101,7 @@ class BridgeServer {
|
|
|
101
101
|
logMsg('one client connected');
|
|
102
102
|
this.socket = socket;
|
|
103
103
|
const clientVersion = socket.handshake.query.version;
|
|
104
|
-
logMsg(`Bridge connected, cli-side version v1.15.
|
|
104
|
+
logMsg(`Bridge connected, cli-side version v1.15.5, browser-side version v${clientVersion}`);
|
|
105
105
|
socket.on(BridgeEvent.CallResponse, (params)=>{
|
|
106
106
|
const id = params.id;
|
|
107
107
|
const response = params.response;
|
|
@@ -125,7 +125,7 @@ class BridgeServer {
|
|
|
125
125
|
setTimeout(()=>{
|
|
126
126
|
this.onConnect?.();
|
|
127
127
|
const payload = {
|
|
128
|
-
version: "1.15.
|
|
128
|
+
version: "1.15.5"
|
|
129
129
|
};
|
|
130
130
|
socket.emit(BridgeEvent.Connected, payload);
|
|
131
131
|
Promise.resolve().then(()=>{
|
|
@@ -100,7 +100,7 @@ class ExtensionBridgePageBrowserSide extends page {
|
|
|
100
100
|
throw new Error('Connection denied by user');
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.15.
|
|
103
|
+
this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.15.5`, 'log');
|
|
104
104
|
}
|
|
105
105
|
async connect() {
|
|
106
106
|
return await this.setupBridgeClient();
|
package/dist/es/cli.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { z } from "zod/v4";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveWebUrl } from "./utils.mjs";
|
|
5
5
|
const setCookiesInputSchema = z.strictObject({
|
|
6
|
-
cookiesEnv: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional().describe('The environment variable containing a Cookie header, cookie array JSON, or
|
|
6
|
+
cookiesEnv: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).optional().describe('The environment variable containing a Cookie header, cookie array JSON, or browser storage-state JSON.'),
|
|
7
7
|
profile: z.string().regex(/\S/).optional().describe('A cookie profile resolved by getCookieProfile().'),
|
|
8
|
-
storageStatePath: z.string().regex(/\S/).optional().describe('A path to a
|
|
8
|
+
storageStatePath: z.string().regex(/\S/).optional().describe('A path to a browser storage-state JSON file.'),
|
|
9
9
|
url: z.string().url().optional().describe('The HTTP(S) URL used to scope Cookie-header values or cookies without domain/path.')
|
|
10
10
|
}).superRefine((input, ctx)=>{
|
|
11
11
|
const sourceCount = [
|
|
@@ -57,48 +57,6 @@ const parseCookieJson = (raw)=>{
|
|
|
57
57
|
throw new TypeError('setCookies source must contain valid JSON or a Cookie header.');
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
-
const resolveCookieSource = async (reference, defaultUrl, ctx, options)=>{
|
|
61
|
-
switch(reference.kind){
|
|
62
|
-
case 'env':
|
|
63
|
-
{
|
|
64
|
-
let env;
|
|
65
|
-
try {
|
|
66
|
-
env = await options.getEnv?.(ctx) ?? process.env;
|
|
67
|
-
} catch {
|
|
68
|
-
throw new Error(`Failed to resolve cookie environment ${reference.name}; the original error was redacted because it may contain cookie values.`);
|
|
69
|
-
}
|
|
70
|
-
const raw = env[reference.name];
|
|
71
|
-
if (!raw?.trim()) throw new Error(`Environment variable ${reference.name} is missing or blank.`);
|
|
72
|
-
const trimmed = raw.trim();
|
|
73
|
-
return {
|
|
74
|
-
reference,
|
|
75
|
-
cookies: /^[\[{]/.test(trimmed) ? parseCookieJson(trimmed) : parseCookieHeader(trimmed, defaultUrl)
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
case 'profile':
|
|
79
|
-
if (!options.getCookieProfile) throw new Error('setCookies.profile requires a configured getCookieProfile callback.');
|
|
80
|
-
try {
|
|
81
|
-
return {
|
|
82
|
-
reference,
|
|
83
|
-
cookies: await options.getCookieProfile({
|
|
84
|
-
profile: reference.name,
|
|
85
|
-
context: ctx.context,
|
|
86
|
-
signal: ctx.signal
|
|
87
|
-
})
|
|
88
|
-
};
|
|
89
|
-
} catch {
|
|
90
|
-
throw new Error(`Failed to resolve cookie profile ${reference.name}; the original error was redacted because it may contain cookie values.`);
|
|
91
|
-
}
|
|
92
|
-
case 'storage-state':
|
|
93
|
-
{
|
|
94
|
-
const path = options.resolveStorageStatePath ? await options.resolveStorageStatePath(reference.name, ctx) : resolve(process.cwd(), reference.name);
|
|
95
|
-
return {
|
|
96
|
-
reference,
|
|
97
|
-
cookies: parseCookieJson(await readFile(path, 'utf8'))
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
60
|
const normalizeCookie = (candidate, defaultUrl, index)=>{
|
|
103
61
|
if (!candidate || 'object' != typeof candidate || Array.isArray(candidate)) throw new TypeError(`setCookies cookie at index ${index} must be an object.`);
|
|
104
62
|
const cookie = candidate;
|
|
@@ -122,41 +80,60 @@ const normalizeCookies = (source, defaultUrl)=>{
|
|
|
122
80
|
if (0 === candidates.length) throw new TypeError('setCookies source contains no cookies.');
|
|
123
81
|
return candidates.map((cookie, index)=>normalizeCookie(cookie, defaultUrl, index));
|
|
124
82
|
};
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
83
|
+
const resolveCookieInput = async (input, ctx, options)=>{
|
|
84
|
+
const defaultUrl = void 0 === input.url ? void 0 : resolveWebUrl(input.url, void 0, 'setCookies.url');
|
|
85
|
+
const reference = toCookieSourceReference(input);
|
|
86
|
+
let source;
|
|
87
|
+
switch(reference.kind){
|
|
88
|
+
case 'env':
|
|
89
|
+
{
|
|
90
|
+
let env;
|
|
91
|
+
try {
|
|
92
|
+
env = await options.getEnv?.(ctx) ?? process.env;
|
|
93
|
+
} catch {
|
|
94
|
+
throw new Error(`Failed to resolve cookie environment ${reference.name}; the original error was redacted because it may contain cookie values.`);
|
|
95
|
+
}
|
|
96
|
+
const raw = env[reference.name];
|
|
97
|
+
if (!raw?.trim()) throw new Error(`Environment variable ${reference.name} is missing or blank.`);
|
|
98
|
+
const trimmed = raw.trim();
|
|
99
|
+
source = /^[\[{]/.test(trimmed) ? parseCookieJson(trimmed) : parseCookieHeader(trimmed, defaultUrl);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'profile':
|
|
103
|
+
if (!options.getCookieProfile) throw new Error('setCookies.profile requires a configured getCookieProfile callback.');
|
|
104
|
+
try {
|
|
105
|
+
source = await options.getCookieProfile({
|
|
106
|
+
profile: reference.name,
|
|
107
|
+
context: ctx.context,
|
|
108
|
+
signal: ctx.signal
|
|
109
|
+
});
|
|
110
|
+
} catch {
|
|
111
|
+
throw new Error(`Failed to resolve cookie profile ${reference.name}; the original error was redacted because it may contain cookie values.`);
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case 'storage-state':
|
|
115
|
+
{
|
|
116
|
+
let contents;
|
|
117
|
+
try {
|
|
118
|
+
const path = options.resolveStorageStatePath ? await options.resolveStorageStatePath(reference.name, ctx) : resolve(process.cwd(), reference.name);
|
|
119
|
+
contents = await readFile(path, 'utf8');
|
|
120
|
+
} catch {
|
|
121
|
+
throw new Error(`Failed to read cookie storage state ${reference.name}; the original error was redacted because it may contain cookie values.`);
|
|
122
|
+
}
|
|
123
|
+
source = parseCookieJson(contents);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
158
126
|
}
|
|
127
|
+
const cookies = normalizeCookies(source, defaultUrl);
|
|
128
|
+
return {
|
|
129
|
+
cookies,
|
|
130
|
+
result: {
|
|
131
|
+
source: reference.kind,
|
|
132
|
+
sourceName: reference.name,
|
|
133
|
+
count: cookies.length
|
|
134
|
+
}
|
|
135
|
+
};
|
|
159
136
|
};
|
|
160
|
-
export {
|
|
137
|
+
export { resolveCookieInput, setCookiesInputSchema };
|
|
161
138
|
|
|
162
139
|
//# sourceMappingURL=cookies.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common/test-runner/cookies.mjs","sources":["../../../../src/common/test-runner/cookies.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { z } from 'zod/v4';\nimport { resolveWebUrl } from './utils';\n\ntype Awaitable<T> = T | Promise<T>;\n\nexport type CookieSourceReference =\n | { kind: 'env'; name: string }\n | { kind: 'profile'; name: string }\n | { kind: 'storage-state'; name: string };\n\nexport interface WebNodeContext<TContext> {\n input: unknown;\n context: TContext;\n signal: AbortSignal;\n}\n\nexport interface CookieProfileContext<TContext> {\n profile: string;\n context: TContext;\n signal: AbortSignal;\n}\n\nexport interface WebCookieSourceOptions<TAgent> {\n getEnv?(\n ctx: WebNodeContext<TAgent>,\n ): Awaitable<Readonly<Record<string, string | undefined>>>;\n getCookieProfile?(input: CookieProfileContext<TAgent>): Awaitable<unknown>;\n resolveStorageStatePath?(\n path: string,\n ctx: WebNodeContext<TAgent>,\n ): Awaitable<string>;\n}\n\nexport interface PortableWebCookie extends Record<string, unknown> {\n name: string;\n value: string;\n url?: string;\n domain?: string;\n path?: string;\n}\n\nexport const setCookiesInputSchema = z\n .strictObject({\n cookiesEnv: z\n .string()\n .regex(/^[A-Za-z_][A-Za-z0-9_]*$/)\n .optional()\n .describe(\n 'The environment variable containing a Cookie header, cookie array JSON, or browser storage-state JSON.',\n ),\n profile: z\n .string()\n .regex(/\\S/)\n .optional()\n .describe('A cookie profile resolved by getCookieProfile().'),\n storageStatePath: z\n .string()\n .regex(/\\S/)\n .optional()\n .describe('A path to a browser storage-state JSON file.'),\n url: z\n .string()\n .url()\n .optional()\n .describe(\n 'The HTTP(S) URL used to scope Cookie-header values or cookies without domain/path.',\n ),\n })\n .superRefine((input, ctx) => {\n const sourceCount = [\n input.cookiesEnv,\n input.profile,\n input.storageStatePath,\n ].filter((value) => value !== undefined).length;\n if (sourceCount !== 1) {\n ctx.addIssue({\n code: 'custom',\n message:\n 'exactly one of cookiesEnv, profile, and storageStatePath is required',\n });\n }\n });\n\nexport type SetCookiesNodeInput = z.infer<typeof setCookiesInputSchema>;\n\nexport interface SetCookiesNodeResult {\n source: CookieSourceReference['kind'];\n sourceName: string;\n count: number;\n}\n\nconst toCookieSourceReference = (\n input: SetCookiesNodeInput,\n): CookieSourceReference => {\n if (input.cookiesEnv !== undefined) {\n return { kind: 'env', name: input.cookiesEnv };\n }\n if (input.profile !== undefined) {\n return { kind: 'profile', name: input.profile };\n }\n if (input.storageStatePath !== undefined) {\n return { kind: 'storage-state', name: input.storageStatePath };\n }\n throw new TypeError('setCookies requires one cookie source.');\n};\n\nconst parseCookieHeader = (raw: string, url: string | undefined) => {\n if (url === undefined) {\n throw new TypeError(\n 'setCookies.url is required when cookiesEnv contains a Cookie header.',\n );\n }\n const segments = raw\n .split(';')\n .map((segment) => segment.trim())\n .filter(Boolean);\n if (segments.length === 0) {\n throw new TypeError('setCookies Cookie header contains no cookies.');\n }\n return segments.map((segment) => {\n const separator = segment.indexOf('=');\n if (separator <= 0) {\n throw new TypeError(\n 'setCookies Cookie header contains an invalid name/value pair.',\n );\n }\n const name = segment.slice(0, separator).trim();\n const value = segment.slice(separator + 1).trim();\n if (!name) {\n throw new TypeError('setCookies Cookie header contains an empty name.');\n }\n return { name, value, url } satisfies PortableWebCookie;\n });\n};\n\nconst parseCookieJson = (raw: string): unknown => {\n try {\n return JSON.parse(raw);\n } catch {\n throw new TypeError(\n 'setCookies source must contain valid JSON or a Cookie header.',\n );\n }\n};\n\nconst normalizeCookie = (\n candidate: unknown,\n defaultUrl: string | undefined,\n index: number,\n): PortableWebCookie => {\n if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {\n throw new TypeError(\n `setCookies cookie at index ${index} must be an object.`,\n );\n }\n const cookie = candidate as Record<string, unknown>;\n if (typeof cookie.name !== 'string' || !cookie.name.trim()) {\n throw new TypeError(\n `setCookies cookie at index ${index} requires a non-blank name.`,\n );\n }\n if (typeof cookie.value !== 'string') {\n throw new TypeError(\n `setCookies cookie at index ${index} requires a string value.`,\n );\n }\n\n const normalized = { ...cookie } as PortableWebCookie;\n const hasUrl = typeof cookie.url === 'string' && cookie.url.length > 0;\n const hasDomain =\n typeof cookie.domain === 'string' && cookie.domain.length > 0;\n if (!hasUrl && !hasDomain) {\n if (defaultUrl === undefined) {\n throw new TypeError(\n `setCookies cookie at index ${index} requires url or domain, or setCookies.url must be provided.`,\n );\n }\n normalized.url = defaultUrl;\n }\n if (hasDomain && typeof cookie.path !== 'string') normalized.path = '/';\n return normalized;\n};\n\nconst normalizeCookies = (\n source: unknown,\n defaultUrl: string | undefined,\n): PortableWebCookie[] => {\n const candidates = Array.isArray(source)\n ? source\n : source &&\n typeof source === 'object' &&\n Array.isArray((source as { cookies?: unknown }).cookies)\n ? (source as { cookies: unknown[] }).cookies\n : undefined;\n if (!candidates) {\n throw new TypeError(\n 'setCookies JSON must be a cookie array or an object with a cookies array.',\n );\n }\n if (candidates.length === 0) {\n throw new TypeError('setCookies source contains no cookies.');\n }\n return candidates.map((cookie, index) =>\n normalizeCookie(cookie, defaultUrl, index),\n );\n};\n\nexport const resolveCookieInput = async <TAgent>(\n input: SetCookiesNodeInput,\n ctx: WebNodeContext<TAgent>,\n options: WebCookieSourceOptions<TAgent>,\n): Promise<{\n cookies: PortableWebCookie[];\n result: SetCookiesNodeResult;\n}> => {\n const defaultUrl =\n input.url === undefined\n ? undefined\n : resolveWebUrl(input.url, undefined, 'setCookies.url');\n const reference = toCookieSourceReference(input);\n let source: unknown;\n switch (reference.kind) {\n case 'env': {\n let env: Readonly<Record<string, string | undefined>>;\n try {\n env = (await options.getEnv?.(ctx)) ?? process.env;\n } catch {\n throw new Error(\n `Failed to resolve cookie environment ${reference.name}; the original error was redacted because it may contain cookie values.`,\n );\n }\n const raw = env[reference.name];\n if (!raw?.trim()) {\n throw new Error(\n `Environment variable ${reference.name} is missing or blank.`,\n );\n }\n const trimmed = raw.trim();\n source = /^[\\[{]/.test(trimmed)\n ? parseCookieJson(trimmed)\n : parseCookieHeader(trimmed, defaultUrl);\n break;\n }\n case 'profile': {\n if (!options.getCookieProfile) {\n throw new Error(\n 'setCookies.profile requires a configured getCookieProfile callback.',\n );\n }\n try {\n source = await options.getCookieProfile({\n profile: reference.name,\n context: ctx.context,\n signal: ctx.signal,\n });\n } catch {\n throw new Error(\n `Failed to resolve cookie profile ${reference.name}; the original error was redacted because it may contain cookie values.`,\n );\n }\n break;\n }\n case 'storage-state': {\n let contents: string;\n try {\n const path = options.resolveStorageStatePath\n ? await options.resolveStorageStatePath(reference.name, ctx)\n : resolve(process.cwd(), reference.name);\n contents = await readFile(path, 'utf8');\n } catch {\n throw new Error(\n `Failed to read cookie storage state ${reference.name}; the original error was redacted because it may contain cookie values.`,\n );\n }\n source = parseCookieJson(contents);\n break;\n }\n }\n const cookies = normalizeCookies(source, defaultUrl);\n return {\n cookies,\n result: {\n source: reference.kind,\n sourceName: reference.name,\n count: cookies.length,\n },\n };\n};\n"],"names":["setCookiesInputSchema","z","input","ctx","sourceCount","value","undefined","toCookieSourceReference","TypeError","parseCookieHeader","raw","url","segments","segment","Boolean","separator","name","parseCookieJson","JSON","normalizeCookie","candidate","defaultUrl","index","Array","cookie","normalized","hasUrl","hasDomain","normalizeCookies","source","candidates","resolveCookieInput","options","resolveWebUrl","reference","env","process","Error","trimmed","contents","path","resolve","readFile","cookies"],"mappings":";;;;AA2CO,MAAMA,wBAAwBC,EAAAA,YACtB,CAAC;IACZ,YAAYA,EAAAA,MACH,GACN,KAAK,CAAC,4BACN,QAAQ,GACR,QAAQ,CACP;IAEJ,SAASA,EAAAA,MACA,GACN,KAAK,CAAC,MACN,QAAQ,GACR,QAAQ,CAAC;IACZ,kBAAkBA,EAAAA,MACT,GACN,KAAK,CAAC,MACN,QAAQ,GACR,QAAQ,CAAC;IACZ,KAAKA,EAAAA,MACI,GACN,GAAG,GACH,QAAQ,GACR,QAAQ,CACP;AAEN,GACC,WAAW,CAAC,CAACC,OAAOC;IACnB,MAAMC,cAAc;QAClBF,MAAM,UAAU;QAChBA,MAAM,OAAO;QACbA,MAAM,gBAAgB;KACvB,CAAC,MAAM,CAAC,CAACG,QAAUA,AAAUC,WAAVD,OAAqB,MAAM;IAC/C,IAAID,AAAgB,MAAhBA,aACFD,IAAI,QAAQ,CAAC;QACX,MAAM;QACN,SACE;IACJ;AAEJ;AAUF,MAAMI,0BAA0B,CAC9BL;IAEA,IAAIA,AAAqBI,WAArBJ,MAAM,UAAU,EAClB,OAAO;QAAE,MAAM;QAAO,MAAMA,MAAM,UAAU;IAAC;IAE/C,IAAIA,AAAkBI,WAAlBJ,MAAM,OAAO,EACf,OAAO;QAAE,MAAM;QAAW,MAAMA,MAAM,OAAO;IAAC;IAEhD,IAAIA,AAA2BI,WAA3BJ,MAAM,gBAAgB,EACxB,OAAO;QAAE,MAAM;QAAiB,MAAMA,MAAM,gBAAgB;IAAC;IAE/D,MAAM,IAAIM,UAAU;AACtB;AAEA,MAAMC,oBAAoB,CAACC,KAAaC;IACtC,IAAIA,AAAQL,WAARK,KACF,MAAM,IAAIH,UACR;IAGJ,MAAMI,WAAWF,IACd,KAAK,CAAC,KACN,GAAG,CAAC,CAACG,UAAYA,QAAQ,IAAI,IAC7B,MAAM,CAACC;IACV,IAAIF,AAAoB,MAApBA,SAAS,MAAM,EACjB,MAAM,IAAIJ,UAAU;IAEtB,OAAOI,SAAS,GAAG,CAAC,CAACC;QACnB,MAAME,YAAYF,QAAQ,OAAO,CAAC;QAClC,IAAIE,aAAa,GACf,MAAM,IAAIP,UACR;QAGJ,MAAMQ,OAAOH,QAAQ,KAAK,CAAC,GAAGE,WAAW,IAAI;QAC7C,MAAMV,QAAQQ,QAAQ,KAAK,CAACE,YAAY,GAAG,IAAI;QAC/C,IAAI,CAACC,MACH,MAAM,IAAIR,UAAU;QAEtB,OAAO;YAAEQ;YAAMX;YAAOM;QAAI;IAC5B;AACF;AAEA,MAAMM,kBAAkB,CAACP;IACvB,IAAI;QACF,OAAOQ,KAAK,KAAK,CAACR;IACpB,EAAE,OAAM;QACN,MAAM,IAAIF,UACR;IAEJ;AACF;AAEA,MAAMW,kBAAkB,CACtBC,WACAC,YACAC;IAEA,IAAI,CAACF,aAAa,AAAqB,YAArB,OAAOA,aAA0BG,MAAM,OAAO,CAACH,YAC/D,MAAM,IAAIZ,UACR,CAAC,2BAA2B,EAAEc,MAAM,mBAAmB,CAAC;IAG5D,MAAME,SAASJ;IACf,IAAI,AAAuB,YAAvB,OAAOI,OAAO,IAAI,IAAiB,CAACA,OAAO,IAAI,CAAC,IAAI,IACtD,MAAM,IAAIhB,UACR,CAAC,2BAA2B,EAAEc,MAAM,2BAA2B,CAAC;IAGpE,IAAI,AAAwB,YAAxB,OAAOE,OAAO,KAAK,EACrB,MAAM,IAAIhB,UACR,CAAC,2BAA2B,EAAEc,MAAM,yBAAyB,CAAC;IAIlE,MAAMG,aAAa;QAAE,GAAGD,MAAM;IAAC;IAC/B,MAAME,SAAS,AAAsB,YAAtB,OAAOF,OAAO,GAAG,IAAiBA,OAAO,GAAG,CAAC,MAAM,GAAG;IACrE,MAAMG,YACJ,AAAyB,YAAzB,OAAOH,OAAO,MAAM,IAAiBA,OAAO,MAAM,CAAC,MAAM,GAAG;IAC9D,IAAI,CAACE,UAAU,CAACC,WAAW;QACzB,IAAIN,AAAef,WAAfe,YACF,MAAM,IAAIb,UACR,CAAC,2BAA2B,EAAEc,MAAM,4DAA4D,CAAC;QAGrGG,WAAW,GAAG,GAAGJ;IACnB;IACA,IAAIM,aAAa,AAAuB,YAAvB,OAAOH,OAAO,IAAI,EAAeC,WAAW,IAAI,GAAG;IACpE,OAAOA;AACT;AAEA,MAAMG,mBAAmB,CACvBC,QACAR;IAEA,MAAMS,aAAaP,MAAM,OAAO,CAACM,UAC7BA,SACAA,UACE,AAAkB,YAAlB,OAAOA,UACPN,MAAM,OAAO,CAAEM,OAAiC,OAAO,IACtDA,OAAkC,OAAO,GAC1CvB;IACN,IAAI,CAACwB,YACH,MAAM,IAAItB,UACR;IAGJ,IAAIsB,AAAsB,MAAtBA,WAAW,MAAM,EACnB,MAAM,IAAItB,UAAU;IAEtB,OAAOsB,WAAW,GAAG,CAAC,CAACN,QAAQF,QAC7BH,gBAAgBK,QAAQH,YAAYC;AAExC;AAEO,MAAMS,qBAAqB,OAChC7B,OACAC,KACA6B;IAKA,MAAMX,aACJnB,AAAcI,WAAdJ,MAAM,GAAG,GACLI,SACA2B,cAAc/B,MAAM,GAAG,EAAEI,QAAW;IAC1C,MAAM4B,YAAY3B,wBAAwBL;IAC1C,IAAI2B;IACJ,OAAQK,UAAU,IAAI;QACpB,KAAK;YAAO;gBACV,IAAIC;gBACJ,IAAI;oBACFA,MAAO,MAAMH,QAAQ,MAAM,GAAG7B,QAASiC,QAAQ,GAAG;gBACpD,EAAE,OAAM;oBACN,MAAM,IAAIC,MACR,CAAC,qCAAqC,EAAEH,UAAU,IAAI,CAAC,uEAAuE,CAAC;gBAEnI;gBACA,MAAMxB,MAAMyB,GAAG,CAACD,UAAU,IAAI,CAAC;gBAC/B,IAAI,CAACxB,KAAK,QACR,MAAM,IAAI2B,MACR,CAAC,qBAAqB,EAAEH,UAAU,IAAI,CAAC,qBAAqB,CAAC;gBAGjE,MAAMI,UAAU5B,IAAI,IAAI;gBACxBmB,SAAS,SAAS,IAAI,CAACS,WACnBrB,gBAAgBqB,WAChB7B,kBAAkB6B,SAASjB;gBAC/B;YACF;QACA,KAAK;YACH,IAAI,CAACW,QAAQ,gBAAgB,EAC3B,MAAM,IAAIK,MACR;YAGJ,IAAI;gBACFR,SAAS,MAAMG,QAAQ,gBAAgB,CAAC;oBACtC,SAASE,UAAU,IAAI;oBACvB,SAAS/B,IAAI,OAAO;oBACpB,QAAQA,IAAI,MAAM;gBACpB;YACF,EAAE,OAAM;gBACN,MAAM,IAAIkC,MACR,CAAC,iCAAiC,EAAEH,UAAU,IAAI,CAAC,uEAAuE,CAAC;YAE/H;YACA;QAEF,KAAK;YAAiB;gBACpB,IAAIK;gBACJ,IAAI;oBACF,MAAMC,OAAOR,QAAQ,uBAAuB,GACxC,MAAMA,QAAQ,uBAAuB,CAACE,UAAU,IAAI,EAAE/B,OACtDsC,QAAQL,QAAQ,GAAG,IAAIF,UAAU,IAAI;oBACzCK,WAAW,MAAMG,SAASF,MAAM;gBAClC,EAAE,OAAM;oBACN,MAAM,IAAIH,MACR,CAAC,oCAAoC,EAAEH,UAAU,IAAI,CAAC,uEAAuE,CAAC;gBAElI;gBACAL,SAASZ,gBAAgBsB;gBACzB;YACF;IACF;IACA,MAAMI,UAAUf,iBAAiBC,QAAQR;IACzC,OAAO;QACLsB;QACA,QAAQ;YACN,QAAQT,UAAU,IAAI;YACtB,YAAYA,UAAU,IAAI;YAC1B,OAAOS,QAAQ,MAAM;QACvB;IACF;AACF"}
|
|
File without changes
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { resolveCookieInput, setCookiesInputSchema } from "./cookies.mjs";
|
|
3
|
+
import { resolveWebUrl, throwIfAborted } from "./utils.mjs";
|
|
4
|
+
const DEFAULT_NAVIGATION_TIMEOUT_MS = 60000;
|
|
5
|
+
const DEFAULT_WAIT_UNTIL = 'domcontentloaded';
|
|
6
|
+
const gotoUrlInputSchema = z.strictObject({
|
|
7
|
+
url: z.string().min(1).describe('An absolute HTTP(S) URL or a path relative to testRunner.baseURL or the current page URL.'),
|
|
8
|
+
waitUntil: z["enum"]([
|
|
9
|
+
'commit',
|
|
10
|
+
'domcontentloaded',
|
|
11
|
+
'load',
|
|
12
|
+
'networkidle',
|
|
13
|
+
'networkidle0',
|
|
14
|
+
'networkidle2'
|
|
15
|
+
]).default(DEFAULT_WAIT_UNTIL).describe('The navigation lifecycle event. domcontentloaded, load, and networkidle are portable across browser drivers.'),
|
|
16
|
+
timeoutMs: z.number().positive().max(600000).default(DEFAULT_NAVIGATION_TIMEOUT_MS).describe('The navigation timeout in milliseconds.')
|
|
17
|
+
});
|
|
18
|
+
const clearCookiesInputSchema = z.strictObject({
|
|
19
|
+
name: z.string().regex(/\S/).optional().describe('Cookie name to clear.'),
|
|
20
|
+
domain: z.string().regex(/\S/).optional().describe('Cookie domain to clear.'),
|
|
21
|
+
path: z.string().regex(/\S/).optional().describe('Cookie path to clear.')
|
|
22
|
+
});
|
|
23
|
+
const setViewportSizeInputSchema = z.strictObject({
|
|
24
|
+
width: z.number().int().positive().describe('Viewport width in CSS pixels.'),
|
|
25
|
+
height: z.number().int().positive().describe('Viewport height in CSS pixels.')
|
|
26
|
+
});
|
|
27
|
+
const resolveNavigationUrl = (target, currentUrl, configuredBase)=>{
|
|
28
|
+
const trimmed = target.trim();
|
|
29
|
+
if (!trimmed) throw new TypeError('gotoUrl.url must not be blank.');
|
|
30
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(trimmed)) return resolveWebUrl(trimmed, void 0, 'gotoUrl.url');
|
|
31
|
+
if (void 0 !== configuredBase) return resolveWebUrl(trimmed, configuredBase, 'gotoUrl.url');
|
|
32
|
+
if (!/^https?:\/\//i.test(currentUrl)) throw new TypeError(`gotoUrl.url cannot resolve a relative URL from ${JSON.stringify(currentUrl)}. Use an absolute HTTP(S) URL for the first navigation or configure testRunner.baseURL.`);
|
|
33
|
+
return resolveWebUrl(trimmed, currentUrl, 'gotoUrl.url');
|
|
34
|
+
};
|
|
35
|
+
const createWebTestRunnerNodeDefinitions = (adapter)=>{
|
|
36
|
+
const gotoUrlNode = {
|
|
37
|
+
name: 'gotoUrl',
|
|
38
|
+
title: 'Open a Web URL',
|
|
39
|
+
description: 'Navigate the current browser Page to an absolute HTTP(S) URL or a path relative to testRunner.baseURL or the current page URL.',
|
|
40
|
+
stringInputKey: 'url',
|
|
41
|
+
inputSchema: gotoUrlInputSchema,
|
|
42
|
+
async execute (agent, input, executionContext) {
|
|
43
|
+
const runtimeAgent = adapter.requireAgent(agent);
|
|
44
|
+
const driver = adapter.createDriver(runtimeAgent);
|
|
45
|
+
throwIfAborted(executionContext.signal, 'gotoUrl');
|
|
46
|
+
const url = resolveNavigationUrl(input.url, driver.currentUrl(), adapter.getOptions(runtimeAgent)?.baseURL ?? driver.baseUrl());
|
|
47
|
+
const response = await driver.goto(url, {
|
|
48
|
+
waitUntil: input.waitUntil,
|
|
49
|
+
timeoutMs: input.timeoutMs
|
|
50
|
+
});
|
|
51
|
+
throwIfAborted(executionContext.signal, 'gotoUrl');
|
|
52
|
+
const result = {
|
|
53
|
+
url: driver.currentUrl(),
|
|
54
|
+
status: response.status,
|
|
55
|
+
title: await driver.title()
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
summary: `Navigated to ${result.url}`,
|
|
59
|
+
data: result
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const setCookiesNode = {
|
|
64
|
+
name: 'setCookies',
|
|
65
|
+
title: 'Set browser cookies',
|
|
66
|
+
description: 'Load cookies from an environment variable, configured profile, or browser storage-state file without persisting cookie values in workflow input or output.',
|
|
67
|
+
stringInputKey: false,
|
|
68
|
+
inputSchema: setCookiesInputSchema,
|
|
69
|
+
async execute (agent, input, executionContext) {
|
|
70
|
+
const runtimeAgent = adapter.requireAgent(agent);
|
|
71
|
+
const driver = adapter.createDriver(runtimeAgent);
|
|
72
|
+
throwIfAborted(executionContext.signal, 'setCookies');
|
|
73
|
+
const { cookies, result } = await resolveCookieInput(input, {
|
|
74
|
+
...executionContext,
|
|
75
|
+
input,
|
|
76
|
+
context: runtimeAgent
|
|
77
|
+
}, adapter.getOptions(runtimeAgent) ?? {});
|
|
78
|
+
throwIfAborted(executionContext.signal, 'setCookies');
|
|
79
|
+
try {
|
|
80
|
+
await driver.setCookies(cookies);
|
|
81
|
+
} catch {
|
|
82
|
+
throw new Error(`Failed to set ${cookies.length} browser cookie(s); the browser error was redacted because it may contain cookie values.`);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
summary: `Set ${result.count} browser cookie(s) from ${result.source} ${result.sourceName}`,
|
|
86
|
+
data: result
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const clearCookiesNode = {
|
|
91
|
+
name: 'clearCookies',
|
|
92
|
+
title: 'Clear browser cookies',
|
|
93
|
+
description: 'Clear all cookies from the current browser context, or only cookies matching name, domain, or path.',
|
|
94
|
+
stringInputKey: false,
|
|
95
|
+
inputSchema: clearCookiesInputSchema,
|
|
96
|
+
async execute (agent, input, executionContext) {
|
|
97
|
+
const runtimeAgent = adapter.requireAgent(agent);
|
|
98
|
+
const driver = adapter.createDriver(runtimeAgent);
|
|
99
|
+
throwIfAborted(executionContext.signal, 'clearCookies');
|
|
100
|
+
const filters = {
|
|
101
|
+
...input
|
|
102
|
+
};
|
|
103
|
+
await driver.clearCookies(filters);
|
|
104
|
+
const filterNames = Object.keys(filters);
|
|
105
|
+
return {
|
|
106
|
+
summary: 0 === filterNames.length ? 'Cleared all browser cookies' : `Cleared browser cookies matching ${filterNames.join(', ')}`,
|
|
107
|
+
data: {
|
|
108
|
+
filters
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const setViewportSizeNode = {
|
|
114
|
+
name: 'setViewportSize',
|
|
115
|
+
title: 'Set the browser viewport size',
|
|
116
|
+
description: 'Set the current browser Page viewport size in CSS pixels and return the effective size.',
|
|
117
|
+
stringInputKey: false,
|
|
118
|
+
inputSchema: setViewportSizeInputSchema,
|
|
119
|
+
async execute (agent, input, executionContext) {
|
|
120
|
+
const runtimeAgent = adapter.requireAgent(agent);
|
|
121
|
+
const driver = adapter.createDriver(runtimeAgent);
|
|
122
|
+
throwIfAborted(executionContext.signal, 'setViewportSize');
|
|
123
|
+
const viewport = await driver.setViewportSize(input);
|
|
124
|
+
return {
|
|
125
|
+
summary: `Set viewport to ${viewport.width}x${viewport.height}`,
|
|
126
|
+
data: viewport
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return [
|
|
131
|
+
gotoUrlNode,
|
|
132
|
+
setCookiesNode,
|
|
133
|
+
clearCookiesNode,
|
|
134
|
+
setViewportSizeNode
|
|
135
|
+
];
|
|
136
|
+
};
|
|
137
|
+
export { clearCookiesInputSchema, createWebTestRunnerNodeDefinitions, gotoUrlInputSchema, setCookiesInputSchema, setViewportSizeInputSchema };
|
|
138
|
+
|
|
139
|
+
//# sourceMappingURL=nodes.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common/test-runner/nodes.mjs","sources":["../../../../src/common/test-runner/nodes.ts"],"sourcesContent":["import type { AgentTestRunnerNodeDefinition } from '@midscene/core/agent';\nimport { z } from 'zod/v4';\nimport {\n type SetCookiesNodeResult,\n resolveCookieInput,\n setCookiesInputSchema,\n} from './cookies';\nimport type {\n WebCookieFilters,\n WebTestDriverAdapter,\n WebTestRunnerOptions,\n WebViewportSize,\n} from './driver';\nimport { resolveWebUrl, throwIfAborted } from './utils';\n\nconst DEFAULT_NAVIGATION_TIMEOUT_MS = 60_000;\nconst DEFAULT_WAIT_UNTIL = 'domcontentloaded' as const;\n\nexport const gotoUrlInputSchema = z.strictObject({\n url: z\n .string()\n .min(1)\n .describe(\n 'An absolute HTTP(S) URL or a path relative to testRunner.baseURL or the current page URL.',\n ),\n waitUntil: z\n .enum([\n 'commit',\n 'domcontentloaded',\n 'load',\n 'networkidle',\n 'networkidle0',\n 'networkidle2',\n ])\n .default(DEFAULT_WAIT_UNTIL)\n .describe(\n 'The navigation lifecycle event. domcontentloaded, load, and networkidle are portable across browser drivers.',\n ),\n timeoutMs: z\n .number()\n .positive()\n .max(10 * 60_000)\n .default(DEFAULT_NAVIGATION_TIMEOUT_MS)\n .describe('The navigation timeout in milliseconds.'),\n});\n\nexport type GotoUrlNodeInput = z.infer<typeof gotoUrlInputSchema>;\n\nexport interface GotoUrlNodeResult {\n url: string;\n status: number | null;\n title: string;\n}\n\nexport const clearCookiesInputSchema = z.strictObject({\n name: z.string().regex(/\\S/).optional().describe('Cookie name to clear.'),\n domain: z.string().regex(/\\S/).optional().describe('Cookie domain to clear.'),\n path: z.string().regex(/\\S/).optional().describe('Cookie path to clear.'),\n});\n\nexport type ClearCookiesNodeInput = z.infer<typeof clearCookiesInputSchema>;\n\nexport const setViewportSizeInputSchema = z.strictObject({\n width: z.number().int().positive().describe('Viewport width in CSS pixels.'),\n height: z\n .number()\n .int()\n .positive()\n .describe('Viewport height in CSS pixels.'),\n});\n\nexport type SetViewportSizeNodeInput = z.infer<\n typeof setViewportSizeInputSchema\n>;\n\nconst resolveNavigationUrl = (\n target: string,\n currentUrl: string,\n configuredBase: string | undefined,\n): string => {\n const trimmed = target.trim();\n if (!trimmed) throw new TypeError('gotoUrl.url must not be blank.');\n if (/^[a-z][a-z\\d+.-]*:/i.test(trimmed)) {\n return resolveWebUrl(trimmed, undefined, 'gotoUrl.url');\n }\n if (configuredBase !== undefined) {\n return resolveWebUrl(trimmed, configuredBase, 'gotoUrl.url');\n }\n if (!/^https?:\\/\\//i.test(currentUrl)) {\n throw new TypeError(\n `gotoUrl.url cannot resolve a relative URL from ${JSON.stringify(currentUrl)}. Use an absolute HTTP(S) URL for the first navigation or configure testRunner.baseURL.`,\n );\n }\n return resolveWebUrl(trimmed, currentUrl, 'gotoUrl.url');\n};\n\n/** Create the browser-neutral built-in Web Nodes for one browser adapter. */\nexport const createWebTestRunnerNodeDefinitions = <\n TAgent,\n TOptions extends WebTestRunnerOptions<TAgent>,\n>(\n adapter: WebTestDriverAdapter<TAgent, TOptions>,\n): readonly AgentTestRunnerNodeDefinition[] => {\n const gotoUrlNode: AgentTestRunnerNodeDefinition<\n z.output<typeof gotoUrlInputSchema>,\n GotoUrlNodeResult\n > = {\n name: 'gotoUrl',\n title: 'Open a Web URL',\n description:\n 'Navigate the current browser Page to an absolute HTTP(S) URL or a path relative to testRunner.baseURL or the current page URL.',\n stringInputKey: 'url',\n inputSchema: gotoUrlInputSchema,\n async execute(agent, input, executionContext) {\n const runtimeAgent = adapter.requireAgent(agent);\n const driver = adapter.createDriver(runtimeAgent);\n throwIfAborted(executionContext.signal, 'gotoUrl');\n const url = resolveNavigationUrl(\n input.url,\n driver.currentUrl(),\n adapter.getOptions(runtimeAgent)?.baseURL ?? driver.baseUrl(),\n );\n const response = await driver.goto(url, {\n waitUntil: input.waitUntil,\n timeoutMs: input.timeoutMs,\n });\n throwIfAborted(executionContext.signal, 'gotoUrl');\n const result = {\n url: driver.currentUrl(),\n status: response.status,\n title: await driver.title(),\n };\n return { summary: `Navigated to ${result.url}`, data: result };\n },\n };\n\n const setCookiesNode: AgentTestRunnerNodeDefinition<\n z.output<typeof setCookiesInputSchema>,\n SetCookiesNodeResult\n > = {\n name: 'setCookies',\n title: 'Set browser cookies',\n description:\n 'Load cookies from an environment variable, configured profile, or browser storage-state file without persisting cookie values in workflow input or output.',\n stringInputKey: false,\n inputSchema: setCookiesInputSchema,\n async execute(agent, input, executionContext) {\n const runtimeAgent = adapter.requireAgent(agent);\n const driver = adapter.createDriver(runtimeAgent);\n throwIfAborted(executionContext.signal, 'setCookies');\n const { cookies, result } = await resolveCookieInput(\n input,\n { ...executionContext, input, context: runtimeAgent },\n adapter.getOptions(runtimeAgent) ?? {},\n );\n throwIfAborted(executionContext.signal, 'setCookies');\n try {\n await driver.setCookies(cookies);\n } catch {\n throw new Error(\n `Failed to set ${cookies.length} browser cookie(s); the browser error was redacted because it may contain cookie values.`,\n );\n }\n return {\n summary: `Set ${result.count} browser cookie(s) from ${result.source} ${result.sourceName}`,\n data: result,\n };\n },\n };\n\n const clearCookiesNode: AgentTestRunnerNodeDefinition<\n z.output<typeof clearCookiesInputSchema>,\n { filters: WebCookieFilters }\n > = {\n name: 'clearCookies',\n title: 'Clear browser cookies',\n description:\n 'Clear all cookies from the current browser context, or only cookies matching name, domain, or path.',\n stringInputKey: false,\n inputSchema: clearCookiesInputSchema,\n async execute(agent, input, executionContext) {\n const runtimeAgent = adapter.requireAgent(agent);\n const driver = adapter.createDriver(runtimeAgent);\n throwIfAborted(executionContext.signal, 'clearCookies');\n const filters: WebCookieFilters = { ...input };\n await driver.clearCookies(filters);\n const filterNames = Object.keys(filters);\n return {\n summary:\n filterNames.length === 0\n ? 'Cleared all browser cookies'\n : `Cleared browser cookies matching ${filterNames.join(', ')}`,\n data: { filters },\n };\n },\n };\n\n const setViewportSizeNode: AgentTestRunnerNodeDefinition<\n z.output<typeof setViewportSizeInputSchema>,\n WebViewportSize\n > = {\n name: 'setViewportSize',\n title: 'Set the browser viewport size',\n description:\n 'Set the current browser Page viewport size in CSS pixels and return the effective size.',\n stringInputKey: false,\n inputSchema: setViewportSizeInputSchema,\n async execute(agent, input, executionContext) {\n const runtimeAgent = adapter.requireAgent(agent);\n const driver = adapter.createDriver(runtimeAgent);\n throwIfAborted(executionContext.signal, 'setViewportSize');\n const viewport = await driver.setViewportSize(input);\n return {\n summary: `Set viewport to ${viewport.width}x${viewport.height}`,\n data: viewport,\n };\n },\n };\n\n return [gotoUrlNode, setCookiesNode, clearCookiesNode, setViewportSizeNode];\n};\n\nexport { setCookiesInputSchema };\nexport type { SetCookiesNodeInput, SetCookiesNodeResult } from './cookies';\n"],"names":["DEFAULT_NAVIGATION_TIMEOUT_MS","DEFAULT_WAIT_UNTIL","gotoUrlInputSchema","z","clearCookiesInputSchema","setViewportSizeInputSchema","resolveNavigationUrl","target","currentUrl","configuredBase","trimmed","TypeError","resolveWebUrl","undefined","JSON","createWebTestRunnerNodeDefinitions","adapter","gotoUrlNode","agent","input","executionContext","runtimeAgent","driver","throwIfAborted","url","response","result","setCookiesNode","setCookiesInputSchema","cookies","resolveCookieInput","Error","clearCookiesNode","filters","filterNames","Object","setViewportSizeNode","viewport"],"mappings":"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"}
|