@midscene/web 1.9.7 → 1.9.8-beta-20260618091332.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/bridge-mode/io-client.mjs +1 -1
- package/dist/es/bridge-mode/io-server.mjs +2 -2
- package/dist/es/bridge-mode/io-server.mjs.map +1 -1
- package/dist/es/bridge-mode/page-browser-side.mjs +1 -1
- package/dist/es/bridge-mode/page-browser-side.mjs.map +1 -1
- package/dist/es/cli.mjs +1 -1
- package/dist/es/mcp-agent-init-args.mjs +19 -0
- package/dist/es/mcp-agent-init-args.mjs.map +1 -0
- package/dist/es/mcp-server.mjs +1 -1
- package/dist/es/mcp-tools-cdp.mjs +25 -9
- package/dist/es/mcp-tools-cdp.mjs.map +1 -1
- package/dist/es/mcp-tools-puppeteer.mjs +26 -9
- package/dist/es/mcp-tools-puppeteer.mjs.map +1 -1
- package/dist/es/mcp-tools.mjs +47 -11
- package/dist/es/mcp-tools.mjs.map +1 -1
- package/dist/es/puppeteer/agent-launcher.mjs +11 -1
- package/dist/es/puppeteer/agent-launcher.mjs.map +1 -1
- package/dist/es/puppeteer/base-page.mjs +31 -18
- package/dist/es/puppeteer/base-page.mjs.map +1 -1
- package/dist/lib/bridge-mode/io-client.js +1 -1
- package/dist/lib/bridge-mode/io-server.js +2 -2
- package/dist/lib/bridge-mode/io-server.js.map +1 -1
- package/dist/lib/bridge-mode/page-browser-side.js +1 -1
- package/dist/lib/bridge-mode/page-browser-side.js.map +1 -1
- package/dist/lib/cli.js +1 -1
- package/dist/lib/mcp-agent-init-args.js +56 -0
- package/dist/lib/mcp-agent-init-args.js.map +1 -0
- package/dist/lib/mcp-server.js +1 -1
- package/dist/lib/mcp-tools-cdp.js +24 -8
- package/dist/lib/mcp-tools-cdp.js.map +1 -1
- package/dist/lib/mcp-tools-puppeteer.js +25 -8
- package/dist/lib/mcp-tools-puppeteer.js.map +1 -1
- package/dist/lib/mcp-tools.js +46 -10
- package/dist/lib/mcp-tools.js.map +1 -1
- package/dist/lib/puppeteer/agent-launcher.js +14 -0
- package/dist/lib/puppeteer/agent-launcher.js.map +1 -1
- package/dist/lib/puppeteer/base-page.js +31 -18
- package/dist/lib/puppeteer/base-page.js.map +1 -1
- package/dist/types/mcp-agent-init-args.d.ts +13 -0
- package/dist/types/mcp-tools-cdp.d.ts +6 -3
- package/dist/types/mcp-tools-puppeteer.d.ts +6 -3
- package/dist/types/mcp-tools.d.ts +6 -3
- package/dist/types/puppeteer/agent-launcher.d.ts +2 -1
- package/dist/types/puppeteer/base-page.d.ts +2 -0
- package/package.json +4 -4
package/dist/es/mcp-tools.mjs
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import { ScreenshotItem
|
|
1
|
+
import { ScreenshotItem } from "@midscene/core";
|
|
2
|
+
import { extractAgentBehaviorInitArgs, getAgentInitArgsSignature, shouldRebuildAgentForInitArgs } from "@midscene/shared/mcp/agent-behavior-init-args";
|
|
2
3
|
import { BaseMidsceneTools } from "@midscene/shared/mcp/base-tools";
|
|
3
4
|
import { AgentOverChromeBridge } from "./bridge-mode/index.mjs";
|
|
4
5
|
import { defaultStaticPageViewportSize } from "./common/viewport.mjs";
|
|
6
|
+
import { adaptWebAgentInitArgs, webAgentInitArgShape } from "./mcp-agent-init-args.mjs";
|
|
5
7
|
import { StaticPage } from "./static/index.mjs";
|
|
8
|
+
function _define_property(obj, key, value) {
|
|
9
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
10
|
+
value: value,
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true
|
|
14
|
+
});
|
|
15
|
+
else obj[key] = value;
|
|
16
|
+
return obj;
|
|
17
|
+
}
|
|
6
18
|
class WebMidsceneTools extends BaseMidsceneTools {
|
|
7
19
|
getCliReportSessionName() {
|
|
8
20
|
return 'midscene-web';
|
|
@@ -14,8 +26,10 @@ class WebMidsceneTools extends BaseMidsceneTools {
|
|
|
14
26
|
shrunkShotToLogicalRatio: 1
|
|
15
27
|
});
|
|
16
28
|
}
|
|
17
|
-
async ensureAgent(
|
|
18
|
-
|
|
29
|
+
async ensureAgent(initArgs) {
|
|
30
|
+
const nextSignature = getAgentInitArgsSignature(initArgs);
|
|
31
|
+
const shouldOpenUrl = 'string' == typeof initArgs?.url;
|
|
32
|
+
if (this.agent && (shouldOpenUrl || shouldRebuildAgentForInitArgs(this.lastInitArgsSignature, nextSignature))) {
|
|
19
33
|
try {
|
|
20
34
|
await this.agent?.destroy?.();
|
|
21
35
|
} catch (error) {
|
|
@@ -24,13 +38,16 @@ class WebMidsceneTools extends BaseMidsceneTools {
|
|
|
24
38
|
this.agent = void 0;
|
|
25
39
|
}
|
|
26
40
|
if (this.agent) return this.agent;
|
|
27
|
-
this.agent = await this.initBridgeModeAgent(
|
|
41
|
+
this.agent = await this.initBridgeModeAgent(initArgs);
|
|
42
|
+
this.lastInitArgsSignature = nextSignature;
|
|
28
43
|
return this.agent;
|
|
29
44
|
}
|
|
30
|
-
async initBridgeModeAgent(
|
|
45
|
+
async initBridgeModeAgent(initArgs) {
|
|
46
|
+
const url = initArgs?.url;
|
|
31
47
|
const reportOptions = this.readCliReportAgentOptions();
|
|
32
48
|
const agent = new AgentOverChromeBridge({
|
|
33
49
|
closeConflictServer: true,
|
|
50
|
+
...extractAgentBehaviorInitArgs(initArgs) ?? {},
|
|
34
51
|
...reportOptions ?? {}
|
|
35
52
|
});
|
|
36
53
|
if (url) await agent.connectNewTabWithUrl(url);
|
|
@@ -42,20 +59,21 @@ class WebMidsceneTools extends BaseMidsceneTools {
|
|
|
42
59
|
{
|
|
43
60
|
name: 'web_connect',
|
|
44
61
|
description: 'Connect to web page. If URL provided, opens new tab; otherwise connects to current tab.',
|
|
45
|
-
schema:
|
|
46
|
-
|
|
47
|
-
},
|
|
62
|
+
schema: this.getAgentInitArgSchema(),
|
|
63
|
+
cli: this.getAgentInitArgCliMetadata(),
|
|
48
64
|
handler: async (args)=>{
|
|
49
|
-
const
|
|
65
|
+
const initArgs = this.extractAgentInitParam(args);
|
|
66
|
+
const url = initArgs?.url;
|
|
50
67
|
if (this.agent) {
|
|
51
68
|
try {
|
|
52
69
|
await this.agent.destroy?.();
|
|
53
70
|
} catch {}
|
|
54
71
|
this.agent = void 0;
|
|
72
|
+
this.lastInitArgsSignature = void 0;
|
|
55
73
|
}
|
|
56
74
|
const reportSession = this.createNewCliReportSession(url ?? 'current-tab');
|
|
57
75
|
this.commitCliReportSession(reportSession);
|
|
58
|
-
this.agent = await this.
|
|
76
|
+
this.agent = await this.ensureAgent(initArgs);
|
|
59
77
|
const screenshot = await this.agent.page?.screenshotBase64();
|
|
60
78
|
const label = url ?? 'current tab';
|
|
61
79
|
return {
|
|
@@ -73,10 +91,28 @@ class WebMidsceneTools extends BaseMidsceneTools {
|
|
|
73
91
|
name: 'web_disconnect',
|
|
74
92
|
description: 'Disconnect from current web page and release browser resources',
|
|
75
93
|
schema: {},
|
|
76
|
-
handler:
|
|
94
|
+
handler: async ()=>{
|
|
95
|
+
if (!this.agent) return this.buildTextResult('No active connection to disconnect');
|
|
96
|
+
try {
|
|
97
|
+
await this.agent.destroy?.();
|
|
98
|
+
} catch {}
|
|
99
|
+
this.agent = void 0;
|
|
100
|
+
this.lastInitArgsSignature = void 0;
|
|
101
|
+
return this.buildTextResult('Disconnected from web page');
|
|
102
|
+
}
|
|
77
103
|
}
|
|
78
104
|
];
|
|
79
105
|
}
|
|
106
|
+
constructor(...args){
|
|
107
|
+
super(...args), _define_property(this, "lastInitArgsSignature", void 0), _define_property(this, "initArgSpec", {
|
|
108
|
+
namespace: 'web',
|
|
109
|
+
shape: webAgentInitArgShape,
|
|
110
|
+
cli: {
|
|
111
|
+
preferBareKeys: true
|
|
112
|
+
},
|
|
113
|
+
adapt: adaptWebAgentInitArgs
|
|
114
|
+
});
|
|
115
|
+
}
|
|
80
116
|
}
|
|
81
117
|
export { WebMidsceneTools };
|
|
82
118
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools.mjs","sources":["../../src/mcp-tools.ts"],"sourcesContent":["import { ScreenshotItem
|
|
1
|
+
{"version":3,"file":"mcp-tools.mjs","sources":["../../src/mcp-tools.ts"],"sourcesContent":["import { ScreenshotItem } from '@midscene/core';\nimport {\n extractAgentBehaviorInitArgs,\n getAgentInitArgsSignature,\n shouldRebuildAgentForInitArgs,\n} from '@midscene/shared/mcp/agent-behavior-init-args';\nimport {\n BaseMidsceneTools,\n type InitArgSpec,\n} from '@midscene/shared/mcp/base-tools';\nimport type { ToolDefinition } from '@midscene/shared/mcp/types';\nimport { AgentOverChromeBridge } from './bridge-mode';\nimport { defaultStaticPageViewportSize } from './common/viewport';\nimport {\n type WebAgentInitArgs,\n adaptWebAgentInitArgs,\n webAgentInitArgShape,\n} from './mcp-agent-init-args';\nimport { StaticPage } from './static';\n\n/**\n * Tools manager for Web bridge-mode MCP\n */\nexport class WebMidsceneTools extends BaseMidsceneTools<\n AgentOverChromeBridge,\n WebAgentInitArgs\n> {\n private lastInitArgsSignature?: string;\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 // Use require to avoid type incompatibility with DeviceAction vs ActionSpaceItem\n // StaticPage.actionSpace() returns DeviceAction[] which is compatible at runtime\n // Use screenshotBase64 field to avoid async ScreenshotItem.create()\n return new StaticPage({\n screenshot: ScreenshotItem.create('', Date.now()),\n shotSize: defaultStaticPageViewportSize,\n shrunkShotToLogicalRatio: 1,\n });\n }\n\n protected async ensureAgent(\n initArgs?: WebAgentInitArgs,\n ): Promise<AgentOverChromeBridge> {\n const nextSignature = getAgentInitArgsSignature(initArgs);\n const shouldOpenUrl = typeof initArgs?.url === '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 (error) {\n console.debug('Failed to destroy agent during re-init:', error);\n }\n this.agent = undefined;\n }\n\n if (this.agent) return this.agent;\n\n // Connect to current tab when no URL provided (handles CLI stateless calls)\n this.agent = await this.initBridgeModeAgent(initArgs);\n this.lastInitArgsSignature = nextSignature;\n\n return this.agent;\n }\n\n private async initBridgeModeAgent(\n initArgs?: WebAgentInitArgs,\n ): Promise<AgentOverChromeBridge> {\n const url = initArgs?.url;\n const reportOptions = this.readCliReportAgentOptions();\n const agent = new AgentOverChromeBridge({\n closeConflictServer: true,\n ...(extractAgentBehaviorInitArgs(initArgs) ?? {}),\n ...(reportOptions ?? {}),\n });\n\n if (!url) {\n await agent.connectCurrentTab();\n } else {\n await agent.connectNewTabWithUrl(url);\n }\n\n return agent;\n }\n\n protected preparePlatformTools(): ToolDefinition[] {\n return [\n {\n name: 'web_connect',\n description:\n 'Connect to web page. If URL provided, opens new tab; otherwise connects to current tab.',\n schema: 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 bridge session.\n if (this.agent) {\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n this.lastInitArgsSignature = undefined;\n }\n const reportSession = this.createNewCliReportSession(\n url ?? 'current-tab',\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 tab';\n\n return {\n content: [\n { type: 'text', text: `Connected to: ${label}` },\n ...(screenshot ? this.buildScreenshotContent(screenshot) : []),\n ],\n };\n },\n },\n {\n name: 'web_disconnect',\n description:\n 'Disconnect from current web page and release browser resources',\n schema: {},\n handler: async () => {\n if (!this.agent) {\n return this.buildTextResult('No active connection to disconnect');\n }\n\n try {\n await this.agent.destroy?.();\n } catch {}\n this.agent = undefined;\n this.lastInitArgsSignature = undefined;\n\n return this.buildTextResult('Disconnected from web page');\n },\n },\n ];\n }\n}\n"],"names":["WebMidsceneTools","BaseMidsceneTools","StaticPage","ScreenshotItem","Date","defaultStaticPageViewportSize","initArgs","nextSignature","getAgentInitArgsSignature","shouldOpenUrl","shouldRebuildAgentForInitArgs","error","console","undefined","url","reportOptions","agent","AgentOverChromeBridge","extractAgentBehaviorInitArgs","args","reportSession","screenshot","label","webAgentInitArgShape","adaptWebAgentInitArgs"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import node_path from "node:path";
|
|
2
3
|
import { getDebug } from "@midscene/shared/logger";
|
|
3
4
|
import { assert } from "@midscene/shared/utils";
|
|
4
5
|
import { defaultViewportHeight, defaultViewportWidth, resolveWebViewportSize } from "../common/viewport.mjs";
|
|
@@ -31,6 +32,13 @@ function validateChromeArgs(args, baseArgs) {
|
|
|
31
32
|
if (dangerousArgs.length > 0) console.warn(`Warning: Dangerous Chrome arguments detected: ${dangerousArgs.join(', ')}.\nThese arguments may reduce browser security. Use only in controlled testing environments.`);
|
|
32
33
|
}
|
|
33
34
|
const launcherDebug = getDebug('puppeteer:launcher');
|
|
35
|
+
function buildDownloadBehavior(downloadPath) {
|
|
36
|
+
if (!downloadPath) return;
|
|
37
|
+
return {
|
|
38
|
+
policy: 'allow',
|
|
39
|
+
downloadPath: node_path.resolve(downloadPath)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
34
42
|
function buildChromeArgs(options) {
|
|
35
43
|
const isWindows = 'win32' === process.platform;
|
|
36
44
|
const sandboxArgs = isWindows ? [] : [
|
|
@@ -91,6 +99,7 @@ async function launchPuppeteerPage(target, preference, browser, existingPage) {
|
|
|
91
99
|
} : void 0,
|
|
92
100
|
chromeArgs: target.chromeArgs
|
|
93
101
|
});
|
|
102
|
+
const downloadBehavior = buildDownloadBehavior(target.downloadPath);
|
|
94
103
|
launcherDebug('launching browser with viewport, headed', headed, 'viewport', viewportConfig, 'args', args, 'preference', preference);
|
|
95
104
|
let page;
|
|
96
105
|
let browserInstance = browser;
|
|
@@ -103,6 +112,7 @@ async function launchPuppeteerPage(target, preference, browser, existingPage) {
|
|
|
103
112
|
browserInstance = await puppeteer.launch({
|
|
104
113
|
headless: !preference?.headed,
|
|
105
114
|
defaultViewport: defaultViewportConfig,
|
|
115
|
+
downloadBehavior,
|
|
106
116
|
args,
|
|
107
117
|
acceptInsecureCerts: target.acceptInsecureCerts,
|
|
108
118
|
ignoreDefaultArgs: preference?.ignoreDefaultArgs
|
|
@@ -176,6 +186,6 @@ async function puppeteerAgentForTarget(target, preference, browser, existingPage
|
|
|
176
186
|
freeFn
|
|
177
187
|
};
|
|
178
188
|
}
|
|
179
|
-
export { buildChromeArgs, defaultUA, defaultViewportHeight, defaultViewportScale, defaultViewportWidth, defaultWaitForNetworkIdleTimeout, launchPuppeteerPage, puppeteerAgentForTarget, resolveAiActionContext };
|
|
189
|
+
export { buildChromeArgs, buildDownloadBehavior, defaultUA, defaultViewportHeight, defaultViewportScale, defaultViewportWidth, defaultWaitForNetworkIdleTimeout, launchPuppeteerPage, puppeteerAgentForTarget, resolveAiActionContext };
|
|
180
190
|
|
|
181
191
|
//# sourceMappingURL=agent-launcher.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"puppeteer/agent-launcher.mjs","sources":["../../../src/puppeteer/agent-launcher.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\n\nimport {\n defaultViewportHeight,\n defaultViewportWidth,\n resolveWebViewportSize,\n} from '@/common/viewport';\nimport { PuppeteerAgent } from '@/puppeteer/index';\nimport type { AgentOpt, Cache, MidsceneYamlScriptWebEnv } from '@midscene/core';\nimport { DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT } from '@midscene/shared/constants';\nimport puppeteer, { type Browser, type Page } 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;\n}\n\nconst launcherDebug = getDebug('puppeteer:launcher');\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\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) {\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\n launcherDebug(\n 'launching browser with viewport, headed',\n headed,\n 'viewport',\n viewportConfig,\n 'args',\n args,\n 'preference',\n preference,\n );\n // If an existing page is provided, reuse it instead of creating a new one\n // This allows sharing localStorage and sessionStorage between YAML files\n let page: Page;\n let browserInstance = browser;\n\n if (existingPage) {\n // Reuse the existing page - this preserves localStorage and sessionStorage\n page = existingPage;\n launcherDebug('reusing existing page for shared browser context');\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 args,\n acceptInsecureCerts: target.acceptInsecureCerts,\n ignoreDefaultArgs: preference?.ignoreDefaultArgs,\n });\n freeFn.push({\n name: 'puppeteer_browser',\n fn: () => {\n if (!preference?.keepWindow) {\n if (process.platform === 'win32') {\n setTimeout(() => {\n browserInstance?.close();\n }, 800);\n } else {\n browserInstance?.close();\n }\n }\n },\n });\n }\n page = await browserInstance.newPage();\n }\n\n if (target.cookie) {\n const cookieFileContent = readFileSync(target.cookie, 'utf-8');\n await browserInstance.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 const waitForNetworkIdleTimeout =\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : defaultWaitForNetworkIdleTimeout;\n\n try {\n launcherDebug('goto', target.url);\n await page.goto(target.url);\n if (waitForNetworkIdleTimeout > 0) {\n launcherDebug('waitForNetworkIdle', waitForNetworkIdleTimeout);\n await page.waitForNetworkIdle({\n timeout: waitForNetworkIdleTimeout,\n });\n }\n } catch (e) {\n if (\n typeof target.waitForNetworkIdle?.continueOnNetworkIdleError ===\n 'boolean' &&\n !target.waitForNetworkIdle?.continueOnNetworkIdleError\n ) {\n const newError = new Error(`failed to wait for network idle: ${e}`, {\n cause: e,\n });\n throw newError;\n }\n const newMessage = `failed to wait for network idle after ${waitForNetworkIdleTimeout}ms, but the script will continue.`;\n console.warn(newMessage);\n }\n\n return { page, freeFn };\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) {\n const { page, freeFn } = await launchPuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n );\n const aiActContext = resolveAiActionContext(target, preference);\n\n const { aiActionContext, ...preferenceToUse } = preference ?? {};\n\n // prepare Midscene agent\n const agent = new PuppeteerAgent(page, {\n ...preferenceToUse,\n aiActContext,\n waitForNetworkIdleTimeout:\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : undefined,\n forceSameTabNavigation:\n typeof target.forceSameTabNavigation !== 'undefined'\n ? target.forceSameTabNavigation\n : true, // true for default in yaml script\n });\n\n freeFn.push({\n name: 'midscene_puppeteer_agent',\n fn: () => agent.destroy(),\n });\n\n return { agent, freeFn };\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","buildChromeArgs","options","isWindows","process","sandboxArgs","featureArgs","userAgentArg","windowSizeArg","launchPuppeteerPage","browser","existingPage","assert","freeFn","ua","width","height","resolveWebViewportSize","dpr","undefined","viewportConfig","headed","defaultViewportConfig","browserUIHeight","page","browserInstance","puppeteer","setTimeout","cookieFileContent","readFileSync","JSON","normalizedHeaders","Object","key","value","String","waitForNetworkIdleTimeout","e","newError","Error","newMessage","puppeteerAgentForTarget","aiActContext","aiActionContext","preferenceToUse","agent","PuppeteerAgent"],"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 {\n defaultViewportHeight,\n defaultViewportWidth,\n resolveWebViewportSize,\n} from '@/common/viewport';\nimport { PuppeteerAgent } from '@/puppeteer/index';\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 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;\n}\n\nconst launcherDebug = getDebug('puppeteer:launcher');\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\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) {\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 // If an existing page is provided, reuse it instead of creating a new one\n // This allows sharing localStorage and sessionStorage between YAML files\n let page: Page;\n let browserInstance = browser;\n\n if (existingPage) {\n // Reuse the existing page - this preserves localStorage and sessionStorage\n page = existingPage;\n launcherDebug('reusing existing page for shared browser context');\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 freeFn.push({\n name: 'puppeteer_browser',\n fn: () => {\n if (!preference?.keepWindow) {\n if (process.platform === 'win32') {\n setTimeout(() => {\n browserInstance?.close();\n }, 800);\n } else {\n browserInstance?.close();\n }\n }\n },\n });\n }\n page = await browserInstance.newPage();\n }\n\n if (target.cookie) {\n const cookieFileContent = readFileSync(target.cookie, 'utf-8');\n await browserInstance.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 const waitForNetworkIdleTimeout =\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : defaultWaitForNetworkIdleTimeout;\n\n try {\n launcherDebug('goto', target.url);\n await page.goto(target.url);\n if (waitForNetworkIdleTimeout > 0) {\n launcherDebug('waitForNetworkIdle', waitForNetworkIdleTimeout);\n await page.waitForNetworkIdle({\n timeout: waitForNetworkIdleTimeout,\n });\n }\n } catch (e) {\n if (\n typeof target.waitForNetworkIdle?.continueOnNetworkIdleError ===\n 'boolean' &&\n !target.waitForNetworkIdle?.continueOnNetworkIdleError\n ) {\n const newError = new Error(`failed to wait for network idle: ${e}`, {\n cause: e,\n });\n throw newError;\n }\n const newMessage = `failed to wait for network idle after ${waitForNetworkIdleTimeout}ms, but the script will continue.`;\n console.warn(newMessage);\n }\n\n return { page, freeFn };\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) {\n const { page, freeFn } = await launchPuppeteerPage(\n target,\n preference,\n browser,\n existingPage,\n );\n const aiActContext = resolveAiActionContext(target, preference);\n\n const { aiActionContext, ...preferenceToUse } = preference ?? {};\n\n // prepare Midscene agent\n const agent = new PuppeteerAgent(page, {\n ...preferenceToUse,\n aiActContext,\n waitForNetworkIdleTimeout:\n typeof target.waitForNetworkIdle?.timeout === 'number'\n ? target.waitForNetworkIdle.timeout\n : undefined,\n forceSameTabNavigation:\n typeof target.forceSameTabNavigation !== 'undefined'\n ? target.forceSameTabNavigation\n : true, // true for default in yaml script\n });\n\n freeFn.push({\n name: 'midscene_puppeteer_agent',\n fn: () => agent.destroy(),\n });\n\n return { agent, freeFn };\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","buildDownloadBehavior","downloadPath","path","buildChromeArgs","options","isWindows","process","sandboxArgs","featureArgs","userAgentArg","windowSizeArg","launchPuppeteerPage","browser","existingPage","assert","freeFn","ua","width","height","resolveWebViewportSize","dpr","undefined","viewportConfig","headed","defaultViewportConfig","browserUIHeight","downloadBehavior","page","browserInstance","puppeteer","setTimeout","cookieFileContent","readFileSync","JSON","normalizedHeaders","Object","key","value","String","waitForNetworkIdleTimeout","e","newError","Error","newMessage","puppeteerAgentForTarget","aiActContext","aiActionContext","preferenceToUse","agent","PuppeteerAgent"],"mappings":"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"}
|
|
@@ -490,6 +490,30 @@ class Page {
|
|
|
490
490
|
}
|
|
491
491
|
};
|
|
492
492
|
}
|
|
493
|
+
async createInputCdpSession() {
|
|
494
|
+
if ('puppeteer' === this.interfaceType) {
|
|
495
|
+
const page = this.underlyingPage;
|
|
496
|
+
return await ('function' == typeof page.createCDPSession ? page.createCDPSession() : page.target().createCDPSession());
|
|
497
|
+
}
|
|
498
|
+
const page = this.underlyingPage;
|
|
499
|
+
return await page.context().newCDPSession(page);
|
|
500
|
+
}
|
|
501
|
+
async selectAllByCdp() {
|
|
502
|
+
const client = await this.createInputCdpSession();
|
|
503
|
+
try {
|
|
504
|
+
await client.send('Input.dispatchKeyEvent', {
|
|
505
|
+
type: 'rawKeyDown',
|
|
506
|
+
commands: [
|
|
507
|
+
'selectAll'
|
|
508
|
+
]
|
|
509
|
+
});
|
|
510
|
+
await client.send('Input.dispatchKeyEvent', {
|
|
511
|
+
type: 'keyUp'
|
|
512
|
+
});
|
|
513
|
+
} finally{
|
|
514
|
+
await client.detach().catch(()=>void 0);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
493
517
|
async clearInput(element) {
|
|
494
518
|
const backspace = async ()=>{
|
|
495
519
|
await sleep(100);
|
|
@@ -499,26 +523,15 @@ class Page {
|
|
|
499
523
|
}
|
|
500
524
|
]);
|
|
501
525
|
};
|
|
502
|
-
const isMac = 'darwin' === process.platform;
|
|
503
526
|
debugPage('clearInput begin');
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
count: 3
|
|
508
|
-
});
|
|
509
|
-
await backspace();
|
|
510
|
-
}
|
|
511
|
-
element && await this.mouse.click(element.center[0], element.center[1]);
|
|
512
|
-
await this.underlyingPage.keyboard.down('Meta');
|
|
513
|
-
await this.underlyingPage.keyboard.press('a');
|
|
514
|
-
await this.underlyingPage.keyboard.up('Meta');
|
|
515
|
-
await backspace();
|
|
516
|
-
} else {
|
|
517
|
-
element && await this.mouse.click(element.center[0], element.center[1]);
|
|
518
|
-
await this.underlyingPage.keyboard.down('Control');
|
|
519
|
-
await this.underlyingPage.keyboard.press('a');
|
|
520
|
-
await this.underlyingPage.keyboard.up('Control');
|
|
527
|
+
element && await this.mouse.click(element.center[0], element.center[1]);
|
|
528
|
+
try {
|
|
529
|
+
await this.selectAllByCdp();
|
|
521
530
|
await backspace();
|
|
531
|
+
debugPage('clearInput end');
|
|
532
|
+
return;
|
|
533
|
+
} catch (error) {
|
|
534
|
+
debugPage('clearInput cdp selectAll failed, fallback to shortcut', error);
|
|
522
535
|
}
|
|
523
536
|
debugPage('clearInput end');
|
|
524
537
|
}
|