@midscene/web 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,7 @@ class BridgeClient {
23
23
  ]
24
24
  } : {},
25
25
  query: {
26
- version: "1.14.0"
26
+ version: "1.15.0"
27
27
  }
28
28
  });
29
29
  const timeout = setTimeout(()=>{
@@ -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.14.0, browser-side version v${clientVersion}`);
104
+ logMsg(`Bridge connected, cli-side version v1.15.0, 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.14.0"
128
+ version: "1.15.0"
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.14.0`, 'log');
103
+ this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.15.0`, 'log');
104
104
  }
105
105
  async connect() {
106
106
  return await this.setupBridgeClient();
package/dist/es/cli.mjs CHANGED
@@ -18,7 +18,7 @@ Promise.resolve().then(()=>{
18
18
  return runToolsCLI(tools, 'midscene-web', {
19
19
  stripPrefix: 'web_',
20
20
  argv: parsedOptions.argv,
21
- version: "1.14.0",
21
+ version: "1.15.0",
22
22
  extraCommands: createReportCliCommands()
23
23
  });
24
24
  }).catch((e)=>{
@@ -1 +1 @@
1
- {"version":3,"file":"puppeteer/agent-launcher.mjs","sources":["../../../src/puppeteer/agent-launcher.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\n\nimport { resolveBrowserAgentRuntimeOptions } from '@/common/browser-agent';\nimport {\n defaultViewportHeight,\n defaultViewportWidth,\n resolveWebViewportSize,\n} from '@/common/viewport';\nimport { PuppeteerAgent, PuppeteerBrowserAgent } from '@/puppeteer/index';\nimport { PuppeteerPageOwnership } from '@/puppeteer/page-ownership';\nimport { createScopedPuppeteerBrowserAgent } from '@/puppeteer/scoped-browser-agent';\nimport type { AgentOpt, Cache, MidsceneYamlScriptWebEnv } from '@midscene/core';\nimport { DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT } from '@midscene/shared/constants';\nimport puppeteer, {\n type Browser,\n type BrowserContext,\n type DownloadBehavior,\n type Page,\n} from 'puppeteer';\n\nexport { defaultViewportWidth, defaultViewportHeight } from '@/common/viewport';\n\nexport const defaultUA =\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36';\n// Setting deviceScaleFactor value to `0` means reset this value to the system default in Puppeteer.\nexport const defaultViewportScale = 0;\nexport const defaultWaitForNetworkIdleTimeout =\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT;\n\nexport function resolveAiActionContext(\n target: MidsceneYamlScriptWebEnv,\n preference?: Partial<Pick<AgentOpt, 'aiActionContext' | 'aiActContext'>>,\n): AgentOpt['aiActionContext'] | undefined {\n // Prefer agent-level preference if provided; otherwise fall back to target-level context.\n // Priority: preference.aiActContext > preference.aiActionContext (deprecated) > target.aiActionContext\n const data =\n preference?.aiActContext ??\n preference?.aiActionContext ??\n target.aiActionContext;\n return data;\n}\n\n/**\n * Chrome arguments that may reduce browser security.\n * These should only be used in controlled testing environments.\n *\n * Security implications:\n * - `--no-sandbox`: Disables Chrome's sandbox security model\n * - `--disable-setuid-sandbox`: Disables setuid sandbox on Linux\n * - `--disable-web-security`: Allows cross-origin requests without CORS\n * - `--ignore-certificate-errors`: Ignores SSL/TLS certificate errors\n * - `--disable-features=IsolateOrigins`: Disables origin isolation\n * - `--disable-site-isolation-trials`: Disables site isolation\n * - `--allow-running-insecure-content`: Allows mixed HTTP/HTTPS content\n */\nconst DANGEROUS_ARGS = [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-web-security',\n '--ignore-certificate-errors',\n '--disable-features=IsolateOrigins',\n '--disable-site-isolation-trials',\n '--allow-running-insecure-content',\n] as const;\n\n/**\n * Validates Chrome launch arguments for security concerns.\n * Emits a warning if dangerous arguments are detected.\n *\n * This function filters out arguments that are already present in baseArgs\n * to avoid warning about platform-specific defaults (e.g., --no-sandbox on non-Windows).\n *\n * @param args - Chrome launch arguments to validate\n * @param baseArgs - Base Chrome arguments already configured\n *\n * @example\n * ```typescript\n * // Will show warning for --disable-web-security\n * validateChromeArgs(['--disable-web-security', '--headless'], ['--no-sandbox']);\n *\n * // Will NOT show warning for --no-sandbox (already in baseArgs)\n * validateChromeArgs(['--no-sandbox'], ['--no-sandbox', '--headless']);\n * ```\n */\nfunction validateChromeArgs(args: string[], baseArgs: string[]): void {\n // Filter out arguments that are already in baseArgs\n const newArgs = args.filter(\n (arg) =>\n !baseArgs.some((baseArg) => {\n // Check if arg starts with the same flag as baseArg (before '=' if present)\n const argFlag = arg.split('=')[0];\n const baseFlag = baseArg.split('=')[0];\n return argFlag === baseFlag;\n }),\n );\n\n const dangerousArgs = newArgs.filter((arg) =>\n DANGEROUS_ARGS.some((dangerous) => arg.startsWith(dangerous)),\n );\n\n if (dangerousArgs.length > 0) {\n console.warn(\n `Warning: Dangerous Chrome arguments detected: ${dangerousArgs.join(', ')}.\\nThese arguments may reduce browser security. Use only in controlled testing environments.`,\n );\n }\n}\n\ninterface FreeFn {\n name: string;\n fn: () => void | Promise<void>;\n}\n\nconst launcherDebug = getDebug('puppeteer:launcher');\nconst launcherWarning = getDebug('puppeteer:launcher', { console: true });\n\nasync function cleanupFailedLaunch(freeFn: FreeFn[]): Promise<void> {\n for (const cleanup of [...freeFn].reverse()) {\n try {\n await cleanup.fn();\n } catch (error) {\n launcherWarning(\n `failed to run ${cleanup.name} after Puppeteer page initialization failed`,\n error,\n );\n }\n }\n}\n\nexport function buildDownloadBehavior(\n downloadPath: string | undefined,\n): DownloadBehavior | undefined {\n if (!downloadPath) {\n return undefined;\n }\n\n return {\n policy: 'allow',\n downloadPath: path.resolve(downloadPath),\n };\n}\n\nexport interface BuildChromeArgsOptions {\n userAgent?: string;\n windowSize?: { width: number; height: number };\n chromeArgs?: string[];\n}\n\n/**\n * Builds Chrome launch arguments with sensible defaults.\n *\n * Platform-specific behavior:\n * - On non-Windows systems, automatically adds --no-sandbox and --disable-setuid-sandbox\n * for compatibility with containerized/CI environments\n *\n * @param options - Configuration options for Chrome arguments\n * @returns Array of Chrome launch arguments\n *\n * @example\n * ```typescript\n * // Basic usage\n * const args = buildChromeArgs();\n *\n * // With custom arguments\n * const args = buildChromeArgs({\n * chromeArgs: ['--disable-gpu', '--disable-dev-shm-usage'],\n * userAgent: 'CustomUA/1.0',\n * windowSize: { width: 1920, height: 1080 },\n * });\n * ```\n */\nexport function buildChromeArgs(options?: BuildChromeArgsOptions): string[] {\n const isWindows = process.platform === 'win32';\n\n const sandboxArgs = isWindows\n ? []\n : ['--no-sandbox', '--disable-setuid-sandbox'];\n const featureArgs = [\n '--disable-features=HttpsFirstBalancedModeAutoEnable',\n '--disable-features=PasswordLeakDetection',\n '--disable-save-password-bubble',\n ];\n const userAgentArg = options?.userAgent\n ? [`--user-agent=\"${options.userAgent}\"`]\n : [];\n const windowSizeArg = options?.windowSize\n ? [`--window-size=${options.windowSize.width},${options.windowSize.height}`]\n : [];\n\n const baseArgs = [\n ...sandboxArgs,\n ...featureArgs,\n ...userAgentArg,\n ...windowSizeArg,\n ];\n\n if (options?.chromeArgs?.length) {\n validateChromeArgs(options.chromeArgs, baseArgs);\n return [...baseArgs, ...options.chromeArgs];\n }\n\n return baseArgs;\n}\n\nasync function preparePuppeteerPage(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n ignoreDefaultArgs?: boolean | string[];\n },\n browser?: Browser,\n existingPage?: Page,\n browserContext?: BrowserContext,\n) {\n assert(target.url, 'url is required');\n const freeFn: FreeFn[] = [];\n\n // prepare the environment\n const ua = target.userAgent || defaultUA;\n const { width, height } = resolveWebViewportSize(target);\n let dpr = defaultViewportScale;\n if (\n target.deviceScaleFactor !== undefined &&\n target.deviceScaleFactor !== null\n ) {\n assert(\n typeof target.deviceScaleFactor === 'number',\n 'deviceScaleFactor must be a number',\n );\n dpr = target.deviceScaleFactor;\n assert(dpr > 0, `deviceScaleFactor must be > 0, but got ${dpr}`);\n }\n const viewportConfig = {\n width,\n height,\n deviceScaleFactor: dpr,\n };\n\n const headed = preference?.headed || preference?.keepWindow;\n const defaultViewportConfig = headed ? null : viewportConfig;\n\n // launch the browser\n if (headed && process.env.CI === '1') {\n console.warn(\n 'you are probably running headed mode in CI, this will usually fail.',\n );\n }\n\n // Build Chrome arguments using the shared helper\n // Only pass windowSize in headed mode; in headless mode, defaultViewport takes precedence\n // Add 100px to height to account for browser UI (address bar, tabs, etc.)\n const browserUIHeight = 100;\n const args = buildChromeArgs({\n userAgent: ua,\n windowSize: headed\n ? { width, height: height + browserUIHeight }\n : undefined,\n chromeArgs: target.chromeArgs,\n });\n const downloadBehavior = buildDownloadBehavior(target.downloadPath);\n\n launcherDebug(\n 'launching browser with viewport, headed',\n headed,\n 'viewport',\n viewportConfig,\n 'args',\n args,\n 'preference',\n preference,\n );\n // Callers may provide a page when they need to own its lifecycle. Otherwise\n // create a new page in the supplied browser (or a newly launched browser).\n let page: Page;\n let browserInstance = browser;\n let ownsBrowser = false;\n\n try {\n if (existingPage) {\n page = existingPage;\n launcherDebug('using caller-provided page');\n\n // Get the browser instance from the existing page\n if (!browserInstance) {\n browserInstance = page.browser();\n }\n } else {\n // Create a new browser and page\n if (!browserInstance) {\n browserInstance = await puppeteer.launch({\n headless: !preference?.headed,\n defaultViewport: defaultViewportConfig,\n downloadBehavior,\n args,\n acceptInsecureCerts: target.acceptInsecureCerts,\n ignoreDefaultArgs: preference?.ignoreDefaultArgs,\n });\n ownsBrowser = true;\n freeFn.push({\n name: 'puppeteer_browser',\n fn: async () => {\n if (preference?.keepWindow) {\n return;\n }\n if (process.platform === 'win32') {\n await new Promise((resolve) => setTimeout(resolve, 800));\n }\n await browserInstance?.close();\n },\n });\n }\n page = browserContext\n ? await browserContext.newPage()\n : await browserInstance.newPage();\n if (!ownsBrowser) {\n const createdPage = page;\n freeFn.push({\n name: 'puppeteer_page',\n fn: async () => {\n if (!preference?.keepWindow && !createdPage.isClosed()) {\n try {\n await createdPage.close();\n } catch (error) {\n throw new Error('Failed to close a YAML execution page', {\n cause: error,\n });\n }\n }\n },\n });\n }\n }\n\n if (target.cookie) {\n const cookieFileContent = readFileSync(target.cookie, 'utf-8');\n await page.browserContext().setCookie(...JSON.parse(cookieFileContent));\n }\n\n if (ua) {\n await page.setUserAgent(ua);\n }\n\n if (target.extraHTTPHeaders) {\n // YAML may parse unquoted values into booleans/numbers (e.g. `yes` -> true),\n // but Puppeteer requires string header values, so normalize them here.\n const normalizedHeaders = Object.fromEntries(\n Object.entries(target.extraHTTPHeaders).map(([key, value]) => [\n key,\n String(value),\n ]),\n );\n await page.setExtraHTTPHeaders(normalizedHeaders);\n }\n\n if (viewportConfig) {\n await page.setViewport(viewportConfig);\n }\n\n return { page, freeFn };\n } catch (error) {\n await cleanupFailedLaunch(freeFn);\n throw error;\n }\n}\n\nasync function navigatePuppeteerPage(\n page: Page,\n target: MidsceneYamlScriptWebEnv,\n): Promise<void> {\n const waitForNetworkIdleTimeout =\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : defaultWaitForNetworkIdleTimeout;\n\n launcherDebug('goto', target.url);\n await page.goto(target.url);\n\n if (waitForNetworkIdleTimeout <= 0) {\n return;\n }\n\n launcherDebug('waitForNetworkIdle', waitForNetworkIdleTimeout);\n try {\n await page.waitForNetworkIdle({\n timeout: waitForNetworkIdleTimeout,\n });\n } catch (error) {\n if (target.waitForNetworkIdle?.continueOnNetworkIdleError === false) {\n throw new Error(`failed to wait for network idle: ${error}`, {\n cause: error,\n });\n }\n launcherWarning(\n `failed to wait for network idle after ${waitForNetworkIdleTimeout}ms, but the script will continue.`,\n error,\n );\n }\n}\n\nexport async function launchPuppeteerPage(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n ignoreDefaultArgs?: boolean | string[];\n },\n browser?: Browser,\n existingPage?: Page,\n browserContext?: BrowserContext,\n) {\n const preparedPage = await preparePuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n browserContext,\n );\n try {\n await navigatePuppeteerPage(preparedPage.page, target);\n return preparedPage;\n } catch (error) {\n await cleanupFailedLaunch(preparedPage.freeFn);\n throw error;\n }\n}\n\nexport async function puppeteerAgentForTarget(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n } & Partial<\n Pick<\n AgentOpt,\n | 'groupName'\n | 'groupDescription'\n | 'generateReport'\n | 'persistExecutionDump'\n | 'autoPrintReportMsg'\n | 'reportFileName'\n | 'replanningCycleLimit'\n | 'cache'\n | 'aiActionContext'\n >\n >,\n browser?: Browser,\n existingPage?: Page,\n browserContext?: BrowserContext,\n) {\n const mode = target.mode ?? 'page';\n\n if (mode !== 'page' && mode !== 'browser') {\n throw new Error(\n `[midscene] web target mode must be either \"page\" or \"browser\", but got \"${mode}\".`,\n );\n }\n\n const runtimeOptions = resolveBrowserAgentRuntimeOptions({\n agentName: 'YAML web target',\n pageScope: mode,\n forceSameTabNavigation: target.forceSameTabNavigation,\n autoFollowNewPage: target.autoFollowNewPage,\n });\n\n const { page, freeFn } = await preparePuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n browserContext,\n );\n const aiActContext = resolveAiActionContext(target, preference);\n\n const { aiActionContext, ...preferenceToUse } = preference ?? {};\n\n const commonAgentOpts = {\n ...preferenceToUse,\n aiActContext,\n waitForNetworkIdleTimeout:\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : undefined,\n };\n\n // Install page ownership and BrowserAgent listeners before the first\n // navigation. Initial document loading may synchronously open a popup; if\n // listeners are installed after goto(), that page escapes the YAML scope.\n let agent: PuppeteerAgent | PuppeteerBrowserAgent | undefined;\n let pageOwnership: PuppeteerPageOwnership | undefined;\n try {\n if (mode === 'browser' && browser && !existingPage) {\n pageOwnership = new PuppeteerPageOwnership(page);\n }\n\n if (mode === 'browser') {\n const browserAgentOptions = {\n ...commonAgentOpts,\n autoFollowNewPage: runtimeOptions.autoFollowNewPage,\n };\n agent = pageOwnership\n ? createScopedPuppeteerBrowserAgent(\n page.browser(),\n page,\n browserAgentOptions,\n pageOwnership,\n )\n : await PuppeteerBrowserAgent.create(page.browser(), {\n ...browserAgentOptions,\n initialPage: page,\n });\n } else {\n agent = new PuppeteerAgent(page, {\n ...commonAgentOpts,\n forceSameTabNavigation: runtimeOptions.forceSameTabNavigation,\n });\n }\n\n await navigatePuppeteerPage(page, target);\n } catch (error) {\n const failedLaunchCleanup = pageOwnership\n ? [\n ...freeFn.filter((cleanup) => cleanup.name !== 'puppeteer_page'),\n {\n name: 'puppeteer_page_scope',\n fn: () =>\n preference?.keepWindow\n ? pageOwnership?.release()\n : pageOwnership?.close(),\n },\n ]\n : [...freeFn];\n if (agent) {\n failedLaunchCleanup.push({\n name: 'midscene_puppeteer_agent',\n fn: () => agent?.destroy(),\n });\n }\n await cleanupFailedLaunch(failedLaunchCleanup);\n throw error;\n }\n\n assert(agent, 'failed to initialize the Puppeteer agent');\n const launchedAgent = agent;\n\n const agentCleanup: FreeFn = {\n name: 'midscene_puppeteer_agent',\n fn: () => launchedAgent.destroy(),\n };\n const pageOwnershipCleanup: FreeFn | undefined = pageOwnership\n ? {\n name: 'puppeteer_page_scope',\n fn: () =>\n preference?.keepWindow\n ? pageOwnership.release()\n : pageOwnership.close(),\n }\n : undefined;\n const launcherCleanup = pageOwnership\n ? freeFn.filter((cleanup) => cleanup.name !== 'puppeteer_page')\n : freeFn;\n\n return {\n agent: launchedAgent,\n freeFn: [\n agentCleanup,\n ...(pageOwnershipCleanup ? [pageOwnershipCleanup] : []),\n ...launcherCleanup,\n ],\n };\n}\n"],"names":["defaultUA","defaultViewportScale","defaultWaitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","resolveAiActionContext","target","preference","data","DANGEROUS_ARGS","validateChromeArgs","args","baseArgs","newArgs","arg","baseArg","argFlag","baseFlag","dangerousArgs","dangerous","console","launcherDebug","getDebug","launcherWarning","cleanupFailedLaunch","freeFn","cleanup","error","buildDownloadBehavior","downloadPath","path","buildChromeArgs","options","isWindows","process","sandboxArgs","featureArgs","userAgentArg","windowSizeArg","preparePuppeteerPage","browser","existingPage","browserContext","assert","ua","width","height","resolveWebViewportSize","dpr","undefined","viewportConfig","headed","defaultViewportConfig","browserUIHeight","downloadBehavior","page","browserInstance","ownsBrowser","puppeteer","Promise","resolve","setTimeout","createdPage","Error","cookieFileContent","readFileSync","JSON","normalizedHeaders","Object","key","value","String","navigatePuppeteerPage","waitForNetworkIdleTimeout","launchPuppeteerPage","preparedPage","puppeteerAgentForTarget","mode","runtimeOptions","resolveBrowserAgentRuntimeOptions","aiActContext","aiActionContext","preferenceToUse","commonAgentOpts","agent","pageOwnership","PuppeteerPageOwnership","browserAgentOptions","createScopedPuppeteerBrowserAgent","PuppeteerBrowserAgent","PuppeteerAgent","failedLaunchCleanup","launchedAgent","agentCleanup","pageOwnershipCleanup","launcherCleanup"],"mappings":"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"}
1
+ {"version":3,"file":"puppeteer/agent-launcher.mjs","sources":["../../../src/puppeteer/agent-launcher.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\n\nimport { resolveBrowserAgentRuntimeOptions } from '@/common/browser-agent';\nimport {\n defaultViewportHeight,\n defaultViewportWidth,\n resolveWebViewportSize,\n} from '@/common/viewport';\nimport { PuppeteerAgent, PuppeteerBrowserAgent } from '@/puppeteer/index';\nimport { PuppeteerPageOwnership } from '@/puppeteer/page-ownership';\nimport { createScopedPuppeteerBrowserAgent } from '@/puppeteer/scoped-browser-agent';\nimport type { AgentOpt, Cache, MidsceneYamlScriptWebEnv } from '@midscene/core';\nimport { DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT } from '@midscene/shared/constants';\nimport puppeteer, {\n type Browser,\n type BrowserContext,\n type DownloadBehavior,\n type Page,\n} from 'puppeteer';\n\nexport { defaultViewportWidth, defaultViewportHeight } from '@/common/viewport';\n\nexport const defaultUA =\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36';\n// Setting deviceScaleFactor value to `0` means reset this value to the system default in Puppeteer.\nexport const defaultViewportScale = 0;\nexport const defaultWaitForNetworkIdleTimeout =\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT;\n\nexport function resolveAiActionContext(\n target: MidsceneYamlScriptWebEnv,\n preference?: Partial<Pick<AgentOpt, 'aiActionContext' | 'aiActContext'>>,\n): AgentOpt['aiActionContext'] | undefined {\n // Prefer agent-level preference if provided; otherwise fall back to target-level context.\n // Priority: preference.aiActContext > preference.aiActionContext (deprecated) > target.aiActionContext\n const data =\n preference?.aiActContext ??\n preference?.aiActionContext ??\n target.aiActionContext;\n return data;\n}\n\n/**\n * Chrome arguments that may reduce browser security.\n * These should only be used in controlled testing environments.\n *\n * Security implications:\n * - `--no-sandbox`: Disables Chrome's sandbox security model\n * - `--disable-setuid-sandbox`: Disables setuid sandbox on Linux\n * - `--disable-web-security`: Allows cross-origin requests without CORS\n * - `--ignore-certificate-errors`: Ignores SSL/TLS certificate errors\n * - `--disable-features=IsolateOrigins`: Disables origin isolation\n * - `--disable-site-isolation-trials`: Disables site isolation\n * - `--allow-running-insecure-content`: Allows mixed HTTP/HTTPS content\n */\nconst DANGEROUS_ARGS = [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-web-security',\n '--ignore-certificate-errors',\n '--disable-features=IsolateOrigins',\n '--disable-site-isolation-trials',\n '--allow-running-insecure-content',\n] as const;\n\n/**\n * Validates Chrome launch arguments for security concerns.\n * Emits a warning if dangerous arguments are detected.\n *\n * This function filters out arguments that are already present in baseArgs\n * to avoid warning about platform-specific defaults (e.g., --no-sandbox on non-Windows).\n *\n * @param args - Chrome launch arguments to validate\n * @param baseArgs - Base Chrome arguments already configured\n *\n * @example\n * ```typescript\n * // Will show warning for --disable-web-security\n * validateChromeArgs(['--disable-web-security', '--headless'], ['--no-sandbox']);\n *\n * // Will NOT show warning for --no-sandbox (already in baseArgs)\n * validateChromeArgs(['--no-sandbox'], ['--no-sandbox', '--headless']);\n * ```\n */\nfunction validateChromeArgs(args: string[], baseArgs: string[]): void {\n // Filter out arguments that are already in baseArgs\n const newArgs = args.filter(\n (arg) =>\n !baseArgs.some((baseArg) => {\n // Check if arg starts with the same flag as baseArg (before '=' if present)\n const argFlag = arg.split('=')[0];\n const baseFlag = baseArg.split('=')[0];\n return argFlag === baseFlag;\n }),\n );\n\n const dangerousArgs = newArgs.filter((arg) =>\n DANGEROUS_ARGS.some((dangerous) => arg.startsWith(dangerous)),\n );\n\n if (dangerousArgs.length > 0) {\n console.warn(\n `Warning: Dangerous Chrome arguments detected: ${dangerousArgs.join(', ')}.\\nThese arguments may reduce browser security. Use only in controlled testing environments.`,\n );\n }\n}\n\ninterface FreeFn {\n name: string;\n fn: () => void | Promise<void>;\n}\n\nconst launcherDebug = getDebug('puppeteer:launcher');\nconst launcherWarning = getDebug('puppeteer:launcher', { console: true });\n\nasync function cleanupFailedLaunch(freeFn: FreeFn[]): Promise<void> {\n for (const cleanup of [...freeFn].reverse()) {\n try {\n await cleanup.fn();\n } catch (error) {\n launcherWarning(\n `failed to run ${cleanup.name} after Puppeteer page initialization failed`,\n error,\n );\n }\n }\n}\n\nexport function buildDownloadBehavior(\n downloadPath: string | undefined,\n): DownloadBehavior | undefined {\n if (!downloadPath) {\n return undefined;\n }\n\n return {\n policy: 'allow',\n downloadPath: path.resolve(downloadPath),\n };\n}\n\nexport interface BuildChromeArgsOptions {\n userAgent?: string;\n windowSize?: { width: number; height: number };\n chromeArgs?: string[];\n}\n\n/**\n * Builds Chrome launch arguments with sensible defaults.\n *\n * Platform-specific behavior:\n * - On non-Windows systems, automatically adds --no-sandbox and --disable-setuid-sandbox\n * for compatibility with containerized/CI environments\n *\n * @param options - Configuration options for Chrome arguments\n * @returns Array of Chrome launch arguments\n *\n * @example\n * ```typescript\n * // Basic usage\n * const args = buildChromeArgs();\n *\n * // With custom arguments\n * const args = buildChromeArgs({\n * chromeArgs: ['--disable-gpu', '--disable-dev-shm-usage'],\n * userAgent: 'CustomUA/1.0',\n * windowSize: { width: 1920, height: 1080 },\n * });\n * ```\n */\nexport function buildChromeArgs(options?: BuildChromeArgsOptions): string[] {\n const isWindows = process.platform === 'win32';\n\n const sandboxArgs = isWindows\n ? []\n : ['--no-sandbox', '--disable-setuid-sandbox'];\n const featureArgs = [\n '--disable-features=HttpsFirstBalancedModeAutoEnable',\n '--disable-features=PasswordLeakDetection',\n '--disable-save-password-bubble',\n ];\n const userAgentArg = options?.userAgent\n ? [`--user-agent=\"${options.userAgent}\"`]\n : [];\n const windowSizeArg = options?.windowSize\n ? [`--window-size=${options.windowSize.width},${options.windowSize.height}`]\n : [];\n\n const baseArgs = [\n ...sandboxArgs,\n ...featureArgs,\n ...userAgentArg,\n ...windowSizeArg,\n ];\n\n if (options?.chromeArgs?.length) {\n validateChromeArgs(options.chromeArgs, baseArgs);\n return [...baseArgs, ...options.chromeArgs];\n }\n\n return baseArgs;\n}\n\nasync function preparePuppeteerPage(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n ignoreDefaultArgs?: boolean | string[];\n },\n browser?: Browser,\n existingPage?: Page,\n browserContext?: BrowserContext,\n) {\n assert(target.url, 'url is required');\n const freeFn: FreeFn[] = [];\n\n // prepare the environment\n const ua = target.userAgent || defaultUA;\n const { width, height } = resolveWebViewportSize(target);\n let dpr = defaultViewportScale;\n if (\n target.deviceScaleFactor !== undefined &&\n target.deviceScaleFactor !== null\n ) {\n assert(\n typeof target.deviceScaleFactor === 'number',\n 'deviceScaleFactor must be a number',\n );\n dpr = target.deviceScaleFactor;\n assert(dpr > 0, `deviceScaleFactor must be > 0, but got ${dpr}`);\n }\n const viewportConfig = {\n width,\n height,\n deviceScaleFactor: dpr,\n };\n\n const headed = preference?.headed || preference?.keepWindow;\n const defaultViewportConfig = headed ? null : viewportConfig;\n\n // launch the browser\n if (headed && process.env.CI === '1') {\n console.warn(\n 'you are probably running headed mode in CI, this will usually fail.',\n );\n }\n\n // Build Chrome arguments using the shared helper\n // Only pass windowSize in headed mode; in headless mode, defaultViewport takes precedence\n // Add 100px to height to account for browser UI (address bar, tabs, etc.)\n const browserUIHeight = 100;\n const args = buildChromeArgs({\n userAgent: ua,\n windowSize: headed\n ? { width, height: height + browserUIHeight }\n : undefined,\n chromeArgs: target.chromeArgs,\n });\n const downloadBehavior = buildDownloadBehavior(target.downloadPath);\n\n launcherDebug(\n 'launching browser with viewport, headed',\n headed,\n 'viewport',\n viewportConfig,\n 'args',\n args,\n 'preference',\n preference,\n );\n // Callers may provide a page when they need to own its lifecycle. Otherwise\n // create a new page in the supplied browser (or a newly launched browser).\n let page: Page;\n let browserInstance = browser;\n let ownsBrowser = false;\n\n try {\n if (existingPage) {\n page = existingPage;\n launcherDebug('using caller-provided page');\n\n // Get the browser instance from the existing page\n if (!browserInstance) {\n browserInstance = page.browser();\n }\n } else {\n // Create a new browser and page\n if (!browserInstance) {\n browserInstance = await puppeteer.launch({\n headless: !preference?.headed,\n defaultViewport: defaultViewportConfig,\n downloadBehavior,\n args,\n acceptInsecureCerts: target.acceptInsecureCerts,\n ignoreDefaultArgs: preference?.ignoreDefaultArgs,\n });\n ownsBrowser = true;\n freeFn.push({\n name: 'puppeteer_browser',\n fn: async () => {\n if (preference?.keepWindow) {\n return;\n }\n if (process.platform === 'win32') {\n await new Promise((resolve) => setTimeout(resolve, 800));\n }\n await browserInstance?.close();\n },\n });\n }\n page = browserContext\n ? await browserContext.newPage()\n : await browserInstance.newPage();\n if (!ownsBrowser) {\n const createdPage = page;\n freeFn.push({\n name: 'puppeteer_page',\n fn: async () => {\n if (!preference?.keepWindow && !createdPage.isClosed()) {\n try {\n await createdPage.close();\n } catch (error) {\n throw new Error('Failed to close a YAML execution page', {\n cause: error,\n });\n }\n }\n },\n });\n }\n }\n\n if (target.cookie) {\n const cookieFileContent = readFileSync(target.cookie, 'utf-8');\n await page.browserContext().setCookie(...JSON.parse(cookieFileContent));\n }\n\n if (ua) {\n await page.setUserAgent(ua);\n }\n\n if (target.extraHTTPHeaders) {\n // YAML may parse unquoted values into booleans/numbers (e.g. `yes` -> true),\n // but Puppeteer requires string header values, so normalize them here.\n const normalizedHeaders = Object.fromEntries(\n Object.entries(target.extraHTTPHeaders).map(([key, value]) => [\n key,\n String(value),\n ]),\n );\n await page.setExtraHTTPHeaders(normalizedHeaders);\n }\n\n if (viewportConfig) {\n await page.setViewport(viewportConfig);\n }\n\n return { page, freeFn };\n } catch (error) {\n await cleanupFailedLaunch(freeFn);\n throw error;\n }\n}\n\nasync function navigatePuppeteerPage(\n page: Page,\n target: MidsceneYamlScriptWebEnv,\n): Promise<void> {\n const waitForNetworkIdleTimeout =\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : defaultWaitForNetworkIdleTimeout;\n\n launcherDebug('goto', target.url);\n await page.goto(target.url);\n\n if (waitForNetworkIdleTimeout <= 0) {\n return;\n }\n\n launcherDebug('waitForNetworkIdle', waitForNetworkIdleTimeout);\n try {\n await page.waitForNetworkIdle({\n timeout: waitForNetworkIdleTimeout,\n });\n } catch (error) {\n if (target.waitForNetworkIdle?.continueOnNetworkIdleError === false) {\n throw new Error(`failed to wait for network idle: ${error}`, {\n cause: error,\n });\n }\n launcherWarning(\n `failed to wait for network idle after ${waitForNetworkIdleTimeout}ms, but the script will continue.`,\n error,\n );\n }\n}\n\nexport async function launchPuppeteerPage(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n ignoreDefaultArgs?: boolean | string[];\n },\n browser?: Browser,\n existingPage?: Page,\n browserContext?: BrowserContext,\n) {\n const preparedPage = await preparePuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n browserContext,\n );\n try {\n await navigatePuppeteerPage(preparedPage.page, target);\n return preparedPage;\n } catch (error) {\n await cleanupFailedLaunch(preparedPage.freeFn);\n throw error;\n }\n}\n\nexport async function puppeteerAgentForTarget(\n target: MidsceneYamlScriptWebEnv,\n preference?: {\n headed?: boolean;\n keepWindow?: boolean;\n } & Partial<\n Pick<\n AgentOpt,\n | 'groupName'\n | 'groupDescription'\n | 'generateReport'\n | 'persistExecutionDump'\n | 'autoPrintReportMsg'\n | 'reportFileName'\n | 'replanningCycleLimit'\n | 'cache'\n | 'aiActionContext'\n | 'waitForActionReady'\n >\n >,\n browser?: Browser,\n existingPage?: Page,\n browserContext?: BrowserContext,\n) {\n const mode = target.mode ?? 'page';\n\n if (mode !== 'page' && mode !== 'browser') {\n throw new Error(\n `[midscene] web target mode must be either \"page\" or \"browser\", but got \"${mode}\".`,\n );\n }\n\n const runtimeOptions = resolveBrowserAgentRuntimeOptions({\n agentName: 'YAML web target',\n pageScope: mode,\n forceSameTabNavigation: target.forceSameTabNavigation,\n autoFollowNewPage: target.autoFollowNewPage,\n });\n\n const { page, freeFn } = await preparePuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n browserContext,\n );\n const aiActContext = resolveAiActionContext(target, preference);\n\n const { aiActionContext, ...preferenceToUse } = preference ?? {};\n\n const commonAgentOpts = {\n ...preferenceToUse,\n aiActContext,\n waitForNetworkIdleTimeout:\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : undefined,\n };\n\n // Install page ownership and BrowserAgent listeners before the first\n // navigation. Initial document loading may synchronously open a popup; if\n // listeners are installed after goto(), that page escapes the YAML scope.\n let agent: PuppeteerAgent | PuppeteerBrowserAgent | undefined;\n let pageOwnership: PuppeteerPageOwnership | undefined;\n try {\n if (mode === 'browser' && browser && !existingPage) {\n pageOwnership = new PuppeteerPageOwnership(page);\n }\n\n if (mode === 'browser') {\n const browserAgentOptions = {\n ...commonAgentOpts,\n autoFollowNewPage: runtimeOptions.autoFollowNewPage,\n };\n agent = pageOwnership\n ? createScopedPuppeteerBrowserAgent(\n page.browser(),\n page,\n browserAgentOptions,\n pageOwnership,\n )\n : await PuppeteerBrowserAgent.create(page.browser(), {\n ...browserAgentOptions,\n initialPage: page,\n });\n } else {\n agent = new PuppeteerAgent(page, {\n ...commonAgentOpts,\n forceSameTabNavigation: runtimeOptions.forceSameTabNavigation,\n });\n }\n\n await navigatePuppeteerPage(page, target);\n } catch (error) {\n const failedLaunchCleanup = pageOwnership\n ? [\n ...freeFn.filter((cleanup) => cleanup.name !== 'puppeteer_page'),\n {\n name: 'puppeteer_page_scope',\n fn: () =>\n preference?.keepWindow\n ? pageOwnership?.release()\n : pageOwnership?.close(),\n },\n ]\n : [...freeFn];\n if (agent) {\n failedLaunchCleanup.push({\n name: 'midscene_puppeteer_agent',\n fn: () => agent?.destroy(),\n });\n }\n await cleanupFailedLaunch(failedLaunchCleanup);\n throw error;\n }\n\n assert(agent, 'failed to initialize the Puppeteer agent');\n const launchedAgent = agent;\n\n const agentCleanup: FreeFn = {\n name: 'midscene_puppeteer_agent',\n fn: () => launchedAgent.destroy(),\n };\n const pageOwnershipCleanup: FreeFn | undefined = pageOwnership\n ? {\n name: 'puppeteer_page_scope',\n fn: () =>\n preference?.keepWindow\n ? pageOwnership.release()\n : pageOwnership.close(),\n }\n : undefined;\n const launcherCleanup = pageOwnership\n ? freeFn.filter((cleanup) => cleanup.name !== 'puppeteer_page')\n : freeFn;\n\n return {\n agent: launchedAgent,\n freeFn: [\n agentCleanup,\n ...(pageOwnershipCleanup ? [pageOwnershipCleanup] : []),\n ...launcherCleanup,\n ],\n };\n}\n"],"names":["defaultUA","defaultViewportScale","defaultWaitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","resolveAiActionContext","target","preference","data","DANGEROUS_ARGS","validateChromeArgs","args","baseArgs","newArgs","arg","baseArg","argFlag","baseFlag","dangerousArgs","dangerous","console","launcherDebug","getDebug","launcherWarning","cleanupFailedLaunch","freeFn","cleanup","error","buildDownloadBehavior","downloadPath","path","buildChromeArgs","options","isWindows","process","sandboxArgs","featureArgs","userAgentArg","windowSizeArg","preparePuppeteerPage","browser","existingPage","browserContext","assert","ua","width","height","resolveWebViewportSize","dpr","undefined","viewportConfig","headed","defaultViewportConfig","browserUIHeight","downloadBehavior","page","browserInstance","ownsBrowser","puppeteer","Promise","resolve","setTimeout","createdPage","Error","cookieFileContent","readFileSync","JSON","normalizedHeaders","Object","key","value","String","navigatePuppeteerPage","waitForNetworkIdleTimeout","launchPuppeteerPage","preparedPage","puppeteerAgentForTarget","mode","runtimeOptions","resolveBrowserAgentRuntimeOptions","aiActContext","aiActionContext","preferenceToUse","commonAgentOpts","agent","pageOwnership","PuppeteerPageOwnership","browserAgentOptions","createScopedPuppeteerBrowserAgent","PuppeteerBrowserAgent","PuppeteerAgent","failedLaunchCleanup","launchedAgent","agentCleanup","pageOwnershipCleanup","launcherCleanup"],"mappings":"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"}
@@ -759,11 +759,13 @@ class Page {
759
759
  async beforeInvokeAction(name, param) {
760
760
  if (this.onBeforeInvokeAction) await this.onBeforeInvokeAction(name, param);
761
761
  }
762
- async afterInvokeAction(name, param) {
762
+ async defaultActionWait(name, _param) {
763
763
  await Promise.all([
764
764
  this.waitForNavigation('afterInvokeAction', name),
765
765
  this.waitForNetworkIdle('afterInvokeAction', name)
766
766
  ]);
767
+ }
768
+ async afterInvokeAction(name, param) {
767
769
  if (this.onAfterInvokeAction) await this.onAfterInvokeAction(name, param);
768
770
  }
769
771
  async destroy() {}