@midscene/test 1.12.10-beta-20260920031616.0 → 1.13.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.
@@ -0,0 +1,317 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import node_path, { basename, extname, join } from "node:path";
3
+ import { ScriptPlayer, parseYamlScript, resolveWebTarget } from "@midscene/core/yaml";
4
+ import node_assert from "node:assert";
5
+ import { createAgent, getReportFileName } from "@midscene/core/agent";
6
+ import { getLegacyYamlPlayerState } from "@midscene/core/internal/yaml-runtime";
7
+ import { processCacheConfig } from "@midscene/core/utils";
8
+ import { getDebug } from "@midscene/shared/logger";
9
+ import dotenv from "dotenv";
10
+ const debug = getDebug('create-yaml-player');
11
+ const cleanupWarning = getDebug('create-yaml-player', {
12
+ console: true
13
+ });
14
+ async function cleanupFailedPlayerSetup(freeFn) {
15
+ const errors = [];
16
+ for (const cleanup of freeFn)try {
17
+ await cleanup.fn();
18
+ } catch (error) {
19
+ errors.push(error);
20
+ }
21
+ return errors;
22
+ }
23
+ const launchServer = async (dir)=>{
24
+ const { createServer } = await import("http-server");
25
+ return new Promise((resolve, reject)=>{
26
+ const server = createServer({
27
+ root: dir
28
+ });
29
+ server.server.once('error', reject);
30
+ server.listen(0, '127.0.0.1', ()=>{
31
+ server.server.removeListener('error', reject);
32
+ resolve(server);
33
+ });
34
+ });
35
+ };
36
+ function resolveReportFileName(yamlReportFileName, cliTestId, yamlTestId, fileName) {
37
+ if (void 0 !== yamlReportFileName) return yamlReportFileName;
38
+ const baseName = cliTestId ?? yamlTestId ?? fileName;
39
+ return getReportFileName(baseName);
40
+ }
41
+ function buildAgentOptions(yamlAgent, reportFileName, fileName) {
42
+ return {
43
+ ...yamlAgent || {},
44
+ cache: processCacheConfig(yamlAgent?.cache, fileName),
45
+ reportFileName
46
+ };
47
+ }
48
+ async function createYamlPlayer(file, script, options) {
49
+ const yamlScript = script || parseYamlScript(readFileSync(file, 'utf-8'), file);
50
+ const clonedYamlScript = structuredClone(yamlScript);
51
+ const fileName = basename(file, extname(file));
52
+ const preference = {
53
+ headed: options?.headed,
54
+ keepWindow: options?.keepWindow,
55
+ reportFileName: resolveReportFileName(clonedYamlScript.agent?.reportFileName, options?.testId, clonedYamlScript.agent?.testId, fileName)
56
+ };
57
+ const player = new ScriptPlayer(clonedYamlScript, ()=>createYamlAgent(file, clonedYamlScript, options, preference.reportFileName), void 0, file);
58
+ getLegacyYamlPlayerState(player).fallbackReportFileName = preference.reportFileName;
59
+ return player;
60
+ }
61
+ async function createYamlAgent(file, script, options, reportFileName) {
62
+ if (options?.agent) return {
63
+ agent: options.agent,
64
+ freeFn: []
65
+ };
66
+ const webOptions = options;
67
+ const clonedYamlScript = structuredClone(script);
68
+ const fileName = basename(file, extname(file));
69
+ const preference = {
70
+ headed: options?.headed,
71
+ keepWindow: options?.keepWindow,
72
+ reportFileName: reportFileName ?? resolveReportFileName(clonedYamlScript.agent?.reportFileName, options?.testId, clonedYamlScript.agent?.testId, fileName)
73
+ };
74
+ const freeFn = [];
75
+ try {
76
+ const resolvedWebTarget = resolveWebTarget(clonedYamlScript);
77
+ const webTarget = resolvedWebTarget?.target;
78
+ const targetCount = [
79
+ void 0 !== resolvedWebTarget,
80
+ void 0 !== clonedYamlScript.android,
81
+ void 0 !== clonedYamlScript.ios,
82
+ void 0 !== clonedYamlScript.harmony,
83
+ void 0 !== clonedYamlScript.computer,
84
+ void 0 !== clonedYamlScript.interface
85
+ ].filter(Boolean).length;
86
+ if (targetCount > 1) {
87
+ const specifiedTargets = [
88
+ resolvedWebTarget?.source ?? null,
89
+ void 0 !== clonedYamlScript.android ? 'android' : null,
90
+ void 0 !== clonedYamlScript.ios ? 'ios' : null,
91
+ void 0 !== clonedYamlScript.harmony ? 'harmony' : null,
92
+ void 0 !== clonedYamlScript.computer ? 'computer' : null,
93
+ void 0 !== clonedYamlScript.interface ? 'interface' : null
94
+ ].filter(Boolean);
95
+ throw new Error(`Only one target type can be specified, but found multiple: ${specifiedTargets.join(', ')}. Please specify only one of: page, browser, web, android, ios, harmony, computer, or interface.`);
96
+ }
97
+ if (void 0 !== webTarget) {
98
+ const { buildDownloadBehavior, puppeteerAgentForTarget } = await import("@midscene/web/puppeteer-agent-launcher");
99
+ if (resolvedWebTarget?.source === 'target') cleanupWarning("target is deprecated, please use page or browser instead. See https://midscenejs.com/automate-with-scripts-in-yaml for more information. Sorry for the inconvenience.");
100
+ let localServer;
101
+ let urlToVisit;
102
+ if (webTarget.serve) {
103
+ node_assert('string' == typeof webTarget.url, 'url is required in serve mode');
104
+ localServer = await launchServer(webTarget.serve);
105
+ const serverAddress = localServer.server.address();
106
+ freeFn.push({
107
+ name: 'local_server',
108
+ fn: async ()=>{
109
+ if (!localServer) return;
110
+ await new Promise((resolve, reject)=>{
111
+ localServer?.server.close((error)=>{
112
+ if (error) reject(error);
113
+ else resolve();
114
+ });
115
+ });
116
+ }
117
+ });
118
+ node_assert(serverAddress && 'string' != typeof serverAddress, 'Local server did not bind a TCP address');
119
+ urlToVisit = webTarget.url.startsWith('/') ? `http://${serverAddress?.address}:${serverAddress?.port}${webTarget.url}` : `http://${serverAddress?.address}:${serverAddress?.port}/${webTarget.url}`;
120
+ webTarget.url = urlToVisit;
121
+ }
122
+ if (webTarget.cdpEndpoint && webTarget.bridgeMode) throw new Error('cdpEndpoint and bridgeMode are mutually exclusive. Please specify only one.');
123
+ if ('browser' === webTarget.mode && webTarget.bridgeMode) throw new Error('[midscene] browser mode does not support bridgeMode. Use page: or web.mode: page for bridge mode.');
124
+ if (webTarget.cdpEndpoint) {
125
+ const { default: puppeteer } = await import("puppeteer");
126
+ const ownsCdpBrowser = !webOptions?.browser;
127
+ const cdpBrowser = webOptions?.browser ?? await puppeteer.connect({
128
+ browserWSEndpoint: webTarget.cdpEndpoint,
129
+ defaultViewport: null,
130
+ downloadBehavior: buildDownloadBehavior(webTarget.downloadPath)
131
+ });
132
+ if (webTarget.chromeArgs) cleanupWarning('chromeArgs are not supported in CDP mode (browser is already running). They will be ignored.');
133
+ let launchedAgent;
134
+ try {
135
+ launchedAgent = await puppeteerAgentForTarget(webTarget, {
136
+ ...preference,
137
+ ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
138
+ }, cdpBrowser, webOptions?.page, webOptions?.browserContext);
139
+ } catch (error) {
140
+ if (ownsCdpBrowser) try {
141
+ cdpBrowser.disconnect();
142
+ } catch (cleanupError) {
143
+ cleanupWarning('failed to disconnect the CDP browser after agent setup failed', cleanupError);
144
+ }
145
+ throw error;
146
+ }
147
+ const { agent, freeFn: newFreeFn } = launchedAgent;
148
+ const cleanFreeFn = newFreeFn.filter((f)=>'puppeteer_browser' !== f.name);
149
+ freeFn.push(...cleanFreeFn);
150
+ if (ownsCdpBrowser) freeFn.push({
151
+ name: 'cdp_browser_disconnect',
152
+ fn: ()=>cdpBrowser.disconnect()
153
+ });
154
+ return {
155
+ agent,
156
+ freeFn
157
+ };
158
+ }
159
+ if (!webTarget.bridgeMode) {
160
+ const { agent, freeFn: newFreeFn } = await puppeteerAgentForTarget(webTarget, {
161
+ ...preference,
162
+ ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
163
+ }, webOptions?.browser, webOptions?.page, webOptions?.browserContext);
164
+ freeFn.push(...newFreeFn);
165
+ return {
166
+ agent,
167
+ freeFn
168
+ };
169
+ }
170
+ node_assert('newTabWithUrl' === webTarget.bridgeMode || 'currentTab' === webTarget.bridgeMode, `bridgeMode config value must be either "newTabWithUrl" or "currentTab", but got ${webTarget.bridgeMode}`);
171
+ const bridgeUnsupportedKeys = [
172
+ 'userAgent',
173
+ 'viewportWidth',
174
+ 'viewportHeight',
175
+ 'deviceScaleFactor',
176
+ 'waitForNetworkIdle',
177
+ 'cookie',
178
+ 'extraHTTPHeaders',
179
+ 'downloadPath',
180
+ 'chromeArgs'
181
+ ];
182
+ const ignoredKeys = bridgeUnsupportedKeys.filter((key)=>null != webTarget[key]);
183
+ if (ignoredKeys.length > 0) cleanupWarning(`puppeteer options (${ignoredKeys.join(', ')}) are not supported in bridge mode. They will be ignored.`);
184
+ const { AgentOverChromeBridge } = await import("@midscene/web/bridge-mode");
185
+ const agent = new AgentOverChromeBridge({
186
+ closeNewTabsAfterDisconnect: webTarget.closeNewTabsAfterDisconnect,
187
+ closeConflictServer: true,
188
+ ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
189
+ });
190
+ freeFn.push({
191
+ name: 'destroy_agent_over_chrome_bridge',
192
+ fn: ()=>agent.destroy()
193
+ });
194
+ if ('newTabWithUrl' === webTarget.bridgeMode) await agent.connectNewTabWithUrl(webTarget.url);
195
+ else {
196
+ if (webTarget.url) cleanupWarning('url will be ignored in bridge mode with "currentTab"');
197
+ await agent.connectCurrentTab();
198
+ }
199
+ return {
200
+ agent,
201
+ freeFn
202
+ };
203
+ }
204
+ if (void 0 !== clonedYamlScript.android) {
205
+ const androidTarget = clonedYamlScript.android;
206
+ const { agentFromAdbDevice } = await import("@midscene/android");
207
+ const agent = await agentFromAdbDevice(androidTarget?.deviceId, {
208
+ ...androidTarget,
209
+ ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
210
+ });
211
+ freeFn.push({
212
+ name: 'destroy_android_agent',
213
+ fn: ()=>agent.destroy()
214
+ });
215
+ if (androidTarget?.launch) await agent.launch(androidTarget.launch);
216
+ return {
217
+ agent,
218
+ freeFn
219
+ };
220
+ }
221
+ if (void 0 !== clonedYamlScript.ios) {
222
+ const iosTarget = clonedYamlScript.ios;
223
+ const { agentFromWebDriverAgent } = await import("@midscene/ios");
224
+ const agent = await agentFromWebDriverAgent({
225
+ ...iosTarget,
226
+ ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
227
+ });
228
+ freeFn.push({
229
+ name: 'destroy_ios_agent',
230
+ fn: ()=>agent.destroy()
231
+ });
232
+ if (iosTarget?.launch) await agent.launch(iosTarget.launch);
233
+ return {
234
+ agent,
235
+ freeFn
236
+ };
237
+ }
238
+ if (void 0 !== clonedYamlScript.harmony) {
239
+ const harmonyTarget = clonedYamlScript.harmony;
240
+ const { agentFromHdcDevice } = await import("@midscene/harmony");
241
+ const agent = await agentFromHdcDevice(harmonyTarget?.deviceId, {
242
+ ...harmonyTarget,
243
+ ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
244
+ });
245
+ freeFn.push({
246
+ name: 'destroy_harmony_agent',
247
+ fn: ()=>agent.destroy()
248
+ });
249
+ if (harmonyTarget?.launch) await agent.launch(harmonyTarget.launch);
250
+ return {
251
+ agent,
252
+ freeFn
253
+ };
254
+ }
255
+ if (void 0 !== clonedYamlScript.computer) {
256
+ const computerTarget = clonedYamlScript.computer;
257
+ const { agentForComputer } = await import("@midscene/computer");
258
+ const agent = await agentForComputer({
259
+ ...computerTarget,
260
+ ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
261
+ });
262
+ freeFn.push({
263
+ name: 'destroy_computer_agent',
264
+ fn: ()=>agent.destroy()
265
+ });
266
+ return {
267
+ agent,
268
+ freeFn
269
+ };
270
+ }
271
+ if (void 0 !== clonedYamlScript.interface) {
272
+ const interfaceTarget = clonedYamlScript.interface;
273
+ const moduleSpecifier = interfaceTarget.module;
274
+ let finalModuleSpecifier;
275
+ if (moduleSpecifier.startsWith('./') || moduleSpecifier.startsWith('../') || node_path.isAbsolute(moduleSpecifier)) {
276
+ const resolvedPath = node_path.resolve(process.cwd(), moduleSpecifier);
277
+ finalModuleSpecifier = resolvedPath;
278
+ } else finalModuleSpecifier = moduleSpecifier;
279
+ debug('importing module config', interfaceTarget.module, 'with export config', interfaceTarget.export, 'final module specifier', finalModuleSpecifier);
280
+ const importedModule = await import(finalModuleSpecifier);
281
+ const DeviceClass = interfaceTarget.export ? importedModule[interfaceTarget.export] : importedModule.default || importedModule;
282
+ debug('DeviceClass', DeviceClass, 'with param', interfaceTarget.param);
283
+ const device = new DeviceClass(interfaceTarget.param || {});
284
+ debug('creating agent from device', device);
285
+ const agent = createAgent(device, buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName));
286
+ freeFn.push({
287
+ name: 'destroy_general_interface_agent',
288
+ fn: ()=>agent.destroy()
289
+ });
290
+ return {
291
+ agent,
292
+ freeFn
293
+ };
294
+ }
295
+ throw new Error('No valid interface configuration found in the yaml script, should be either "web", "android", "ios", "harmony", "computer", or "interface"');
296
+ } catch (error) {
297
+ const cleanupErrors = await cleanupFailedPlayerSetup(freeFn);
298
+ if (cleanupErrors.length) throw new AggregateError([
299
+ error,
300
+ ...cleanupErrors
301
+ ], 'YAML setup and cleanup failed');
302
+ throw error;
303
+ }
304
+ }
305
+ function loadDotenvConfig(options = {}) {
306
+ const dotEnvConfigFile = join(options.cwd ?? process.cwd(), '.env');
307
+ if (!existsSync(dotEnvConfigFile)) return;
308
+ options.log?.(` Env file: ${dotEnvConfigFile}`);
309
+ dotenv.config({
310
+ path: dotEnvConfigFile,
311
+ debug: options.dotenvDebug,
312
+ override: options.dotenvOverride
313
+ });
314
+ }
315
+ export { createYamlAgent, createYamlPlayer, launchServer, loadDotenvConfig };
316
+
317
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime/index.mjs","sources":["../../../src/runtime/create-yaml-player.ts","../../../src/runtime/dotenv-loader.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport type { Server } from 'node:http';\nimport path, { basename, extname, join } from 'node:path';\nimport {\n ScriptPlayer,\n parseYamlScript,\n resolveWebTarget,\n} from '@midscene/core/yaml';\n\nimport assert from 'node:assert';\nimport type {\n AgentOpt,\n FreeFn,\n MidsceneYamlScript,\n MidsceneYamlScriptAgentOpt,\n MidsceneYamlScriptEnv,\n MidsceneYamlScriptWebEnv,\n} from '@midscene/core';\nimport { createAgent, getReportFileName } from '@midscene/core/agent';\nimport type { Agent } from '@midscene/core/agent';\nimport type { AbstractInterface } from '@midscene/core/device';\nimport { getLegacyYamlPlayerState } from '@midscene/core/internal/yaml-runtime';\nimport { processCacheConfig } from '@midscene/core/utils';\nimport { getDebug } from '@midscene/shared/logger';\nimport type { Browser, BrowserContext, Page } from 'puppeteer';\n\nexport interface SingleYamlExecutionResult {\n success: boolean;\n file: string;\n player: ScriptPlayer<MidsceneYamlScriptEnv>;\n}\n\nexport interface CreateYamlPlayerOptions {\n /** Borrow an existing Agent; its owner remains responsible for teardown. */\n agent?: Agent;\n headed?: boolean;\n keepWindow?: boolean;\n /** Puppeteer handles. Platform-specific types are optional for other hosts. */\n browser?: object;\n page?: object;\n browserContext?: object;\n testId?: string;\n}\n\nconst debug = getDebug('create-yaml-player');\nconst cleanupWarning = getDebug('create-yaml-player', { console: true });\n\nasync function cleanupFailedPlayerSetup(freeFn: FreeFn[]): Promise<unknown[]> {\n const errors: unknown[] = [];\n for (const cleanup of freeFn) {\n try {\n await cleanup.fn();\n } catch (error) {\n errors.push(error);\n }\n }\n return errors;\n}\n\nexport interface YamlStaticServer {\n server: Server;\n listen(port: number, host: string, callback: () => void): void;\n}\n\nexport const launchServer = async (dir: string): Promise<YamlStaticServer> => {\n const { createServer } = await import('http-server');\n // https://github.com/http-party/http-server/blob/master/bin/http-server\n return new Promise((resolve, reject) => {\n const server = createServer({\n root: dir,\n });\n server.server.once('error', reject);\n server.listen(0, '127.0.0.1', () => {\n server.server.removeListener('error', reject);\n resolve(server);\n });\n });\n};\n\n/**\n * Resolves reportFileName with proper priority handling.\n * Priority: YAML reportFileName > CLI testId (legacy) > YAML testId (legacy) > fileName\n * Explicit YAML reportFileName is treated as the exact output name. Generated\n * legacy fallbacks include a unique suffix to avoid overwriting.\n */\nfunction resolveReportFileName(\n yamlReportFileName: string | undefined,\n cliTestId: string | undefined,\n yamlTestId: string | undefined,\n fileName: string,\n): string {\n if (yamlReportFileName !== undefined) {\n return yamlReportFileName;\n }\n const baseName = cliTestId ?? yamlTestId ?? fileName;\n return getReportFileName(baseName);\n}\n\n/**\n * Builds agent options by merging YAML agent config with processed cache and report name.\n * Handles the spread of agent options and ensures proper cache configuration.\n */\nfunction buildAgentOptions(\n yamlAgent: MidsceneYamlScriptAgentOpt | undefined,\n reportFileName: string,\n fileName: string,\n): Partial<AgentOpt> {\n return {\n ...(yamlAgent || {}),\n cache: processCacheConfig(yamlAgent?.cache, fileName),\n reportFileName,\n };\n}\n\nexport async function createYamlPlayer(\n file: string,\n script?: MidsceneYamlScript,\n options?: CreateYamlPlayerOptions,\n): Promise<ScriptPlayer<MidsceneYamlScriptEnv>> {\n const yamlScript =\n script || parseYamlScript(readFileSync(file, 'utf-8'), file);\n\n // Deep clone the script to avoid mutation issues when the same file is executed multiple times\n // This ensures each ScriptPlayer instance has its own independent copy of the YAML data\n const clonedYamlScript = structuredClone(yamlScript);\n\n const fileName = basename(file, extname(file));\n const preference = {\n headed: options?.headed,\n keepWindow: options?.keepWindow,\n reportFileName: resolveReportFileName(\n clonedYamlScript.agent?.reportFileName,\n options?.testId,\n clonedYamlScript.agent?.testId,\n fileName,\n ),\n };\n\n const player = new ScriptPlayer(\n clonedYamlScript,\n () =>\n createYamlAgent(\n file,\n clonedYamlScript,\n options,\n preference.reportFileName,\n ),\n undefined,\n file,\n );\n\n getLegacyYamlPlayerState(player).fallbackReportFileName =\n preference.reportFileName;\n return player;\n}\n\nexport type YamlAgentConfig = Omit<MidsceneYamlScript, 'tasks'>;\n\n/** Launch any YAML-supported platform for a native Test setup or another host. */\nexport async function createYamlAgent(\n file: string,\n script: YamlAgentConfig,\n options?: CreateYamlPlayerOptions,\n reportFileName?: string,\n): Promise<{ agent: Agent; freeFn: FreeFn[] }> {\n if (options?.agent) return { agent: options.agent, freeFn: [] };\n // Keep optional driver types inside the selected platform boundary.\n const webOptions = options as\n | (Omit<CreateYamlPlayerOptions, 'browser' | 'page' | 'browserContext'> & {\n browser?: Browser;\n page?: Page;\n browserContext?: BrowserContext;\n })\n | undefined;\n const clonedYamlScript = structuredClone(script);\n const fileName = basename(file, extname(file));\n const preference = {\n headed: options?.headed,\n keepWindow: options?.keepWindow,\n reportFileName:\n reportFileName ??\n resolveReportFileName(\n clonedYamlScript.agent?.reportFileName,\n options?.testId,\n clonedYamlScript.agent?.testId,\n fileName,\n ),\n };\n const freeFn: FreeFn[] = [];\n try {\n const resolvedWebTarget = resolveWebTarget(clonedYamlScript);\n const webTarget = resolvedWebTarget?.target as\n | MidsceneYamlScriptWebEnv\n | undefined;\n\n // Validate that only one target type is specified\n const targetCount = [\n typeof resolvedWebTarget !== 'undefined',\n typeof clonedYamlScript.android !== 'undefined',\n typeof clonedYamlScript.ios !== 'undefined',\n typeof clonedYamlScript.harmony !== 'undefined',\n typeof clonedYamlScript.computer !== 'undefined',\n typeof clonedYamlScript.interface !== 'undefined',\n ].filter(Boolean).length;\n\n if (targetCount > 1) {\n const specifiedTargets = [\n resolvedWebTarget?.source ?? null,\n typeof clonedYamlScript.android !== 'undefined' ? 'android' : null,\n typeof clonedYamlScript.ios !== 'undefined' ? 'ios' : null,\n typeof clonedYamlScript.harmony !== 'undefined' ? 'harmony' : null,\n typeof clonedYamlScript.computer !== 'undefined' ? 'computer' : null,\n typeof clonedYamlScript.interface !== 'undefined' ? 'interface' : null,\n ].filter(Boolean);\n\n throw new Error(\n `Only one target type can be specified, but found multiple: ${specifiedTargets.join(', ')}. Please specify only one of: page, browser, web, android, ios, harmony, computer, or interface.`,\n );\n }\n\n // handle new web config\n if (typeof webTarget !== 'undefined') {\n const { buildDownloadBehavior, puppeteerAgentForTarget } = await import(\n '@midscene/web/puppeteer-agent-launcher'\n );\n if (resolvedWebTarget?.source === 'target') {\n cleanupWarning(\n 'target is deprecated, please use page or browser instead. See https://midscenejs.com/automate-with-scripts-in-yaml for more information. Sorry for the inconvenience.',\n );\n }\n\n // launch local server if needed\n let localServer: Awaited<ReturnType<typeof launchServer>> | undefined;\n let urlToVisit: string | undefined;\n if (webTarget.serve) {\n assert(\n typeof webTarget.url === 'string',\n 'url is required in serve mode',\n );\n localServer = await launchServer(webTarget.serve);\n const serverAddress = localServer.server.address();\n freeFn.push({\n name: 'local_server',\n fn: async () => {\n if (!localServer) {\n return;\n }\n await new Promise<void>((resolve, reject) => {\n localServer?.server.close((error?: Error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n },\n });\n assert(\n serverAddress && typeof serverAddress !== 'string',\n 'Local server did not bind a TCP address',\n );\n if (webTarget.url.startsWith('/')) {\n urlToVisit = `http://${serverAddress?.address}:${serverAddress?.port}${webTarget.url}`;\n } else {\n urlToVisit = `http://${serverAddress?.address}:${serverAddress?.port}/${webTarget.url}`;\n }\n webTarget.url = urlToVisit;\n }\n\n // Validate: cdpEndpoint and bridgeMode are mutually exclusive\n if (webTarget.cdpEndpoint && webTarget.bridgeMode) {\n throw new Error(\n 'cdpEndpoint and bridgeMode are mutually exclusive. Please specify only one.',\n );\n }\n\n if (webTarget.mode === 'browser' && webTarget.bridgeMode) {\n throw new Error(\n '[midscene] browser mode does not support bridgeMode. Use page: or web.mode: page for bridge mode.',\n );\n }\n\n // CDP mode: connect to an existing browser via Chrome DevTools Protocol\n if (webTarget.cdpEndpoint) {\n const { default: puppeteer } = await import('puppeteer');\n // Use the shared browser from batch-runner if available (shareBrowserContext),\n // otherwise connect via CDP endpoint\n const ownsCdpBrowser = !webOptions?.browser;\n const cdpBrowser =\n webOptions?.browser ??\n (await puppeteer.connect({\n browserWSEndpoint: webTarget.cdpEndpoint,\n defaultViewport: null,\n downloadBehavior: buildDownloadBehavior(webTarget.downloadPath),\n }));\n // Warn about options that don't apply to an already-running browser\n if (webTarget.chromeArgs) {\n cleanupWarning(\n 'chromeArgs are not supported in CDP mode (browser is already running). They will be ignored.',\n );\n }\n\n // Reuse puppeteerAgentForTarget which handles page setup (userAgent, viewport,\n // cookie, waitForNetworkIdle, etc.) — pass the CDP browser as the browser param\n let launchedAgent: Awaited<ReturnType<typeof puppeteerAgentForTarget>>;\n try {\n launchedAgent = await puppeteerAgentForTarget(\n webTarget,\n {\n ...preference,\n ...buildAgentOptions(\n clonedYamlScript.agent,\n preference.reportFileName,\n fileName,\n ),\n },\n cdpBrowser as Browser,\n webOptions?.page,\n webOptions?.browserContext,\n );\n } catch (error) {\n if (ownsCdpBrowser) {\n try {\n cdpBrowser.disconnect();\n } catch (cleanupError) {\n cleanupWarning(\n 'failed to disconnect the CDP browser after agent setup failed',\n cleanupError,\n );\n }\n }\n throw error;\n }\n const { agent, freeFn: newFreeFn } = launchedAgent;\n\n // Replace the default browser close with disconnect for CDP\n const cleanFreeFn = newFreeFn.filter(\n (f) => f.name !== 'puppeteer_browser',\n );\n freeFn.push(...cleanFreeFn);\n if (ownsCdpBrowser) {\n freeFn.push({\n name: 'cdp_browser_disconnect',\n fn: () => cdpBrowser.disconnect(),\n });\n }\n\n return { agent, freeFn };\n }\n\n if (!webTarget.bridgeMode) {\n // use puppeteer\n const { agent, freeFn: newFreeFn } = await puppeteerAgentForTarget(\n webTarget,\n {\n ...preference,\n ...buildAgentOptions(\n clonedYamlScript.agent,\n preference.reportFileName,\n fileName,\n ),\n },\n webOptions?.browser,\n webOptions?.page,\n webOptions?.browserContext,\n );\n freeFn.push(...newFreeFn);\n\n return { agent, freeFn };\n }\n assert(\n webTarget.bridgeMode === 'newTabWithUrl' ||\n webTarget.bridgeMode === 'currentTab',\n `bridgeMode config value must be either \"newTabWithUrl\" or \"currentTab\", but got ${webTarget.bridgeMode}`,\n );\n\n const bridgeUnsupportedKeys: (keyof MidsceneYamlScriptWebEnv)[] = [\n 'userAgent',\n 'viewportWidth',\n 'viewportHeight',\n 'deviceScaleFactor',\n 'waitForNetworkIdle',\n 'cookie',\n 'extraHTTPHeaders',\n 'downloadPath',\n 'chromeArgs',\n ];\n const ignoredKeys = bridgeUnsupportedKeys.filter(\n (key) => webTarget[key] != null,\n );\n if (ignoredKeys.length > 0) {\n cleanupWarning(\n `puppeteer options (${ignoredKeys.join(', ')}) are not supported in bridge mode. They will be ignored.`,\n );\n }\n\n const { AgentOverChromeBridge } = await import(\n '@midscene/web/bridge-mode'\n );\n const agent = new AgentOverChromeBridge({\n closeNewTabsAfterDisconnect: webTarget.closeNewTabsAfterDisconnect,\n closeConflictServer: true,\n ...buildAgentOptions(\n clonedYamlScript.agent,\n preference.reportFileName,\n fileName,\n ),\n });\n\n freeFn.push({\n name: 'destroy_agent_over_chrome_bridge',\n fn: () => agent.destroy(),\n });\n\n if (webTarget.bridgeMode === 'newTabWithUrl') {\n await agent.connectNewTabWithUrl(webTarget.url);\n } else {\n if (webTarget.url) {\n cleanupWarning(\n 'url will be ignored in bridge mode with \"currentTab\"',\n );\n }\n await agent.connectCurrentTab();\n }\n return {\n agent,\n freeFn,\n };\n }\n\n // handle android\n if (typeof clonedYamlScript.android !== 'undefined') {\n const androidTarget = clonedYamlScript.android;\n const { agentFromAdbDevice } = await import('@midscene/android');\n const agent = await agentFromAdbDevice(androidTarget?.deviceId, {\n ...androidTarget, // Pass all Android config options\n ...buildAgentOptions(\n clonedYamlScript.agent,\n preference.reportFileName,\n fileName,\n ),\n });\n\n freeFn.push({\n name: 'destroy_android_agent',\n fn: () => agent.destroy(),\n });\n\n if (androidTarget?.launch) {\n await agent.launch(androidTarget.launch);\n }\n\n return { agent, freeFn };\n }\n\n // handle iOS\n if (typeof clonedYamlScript.ios !== 'undefined') {\n const iosTarget = clonedYamlScript.ios;\n const { agentFromWebDriverAgent } = await import('@midscene/ios');\n const agent = await agentFromWebDriverAgent({\n ...iosTarget, // Pass all iOS config options\n ...buildAgentOptions(\n clonedYamlScript.agent,\n preference.reportFileName,\n fileName,\n ),\n });\n\n freeFn.push({\n name: 'destroy_ios_agent',\n fn: () => agent.destroy(),\n });\n\n if (iosTarget?.launch) {\n await agent.launch(iosTarget.launch);\n }\n\n return { agent, freeFn };\n }\n\n // handle harmony\n if (typeof clonedYamlScript.harmony !== 'undefined') {\n const harmonyTarget = clonedYamlScript.harmony;\n const { agentFromHdcDevice } = await import('@midscene/harmony');\n const agent = await agentFromHdcDevice(harmonyTarget?.deviceId, {\n ...harmonyTarget, // Pass all HarmonyOS config options\n ...buildAgentOptions(\n clonedYamlScript.agent,\n preference.reportFileName,\n fileName,\n ),\n });\n\n freeFn.push({\n name: 'destroy_harmony_agent',\n fn: () => agent.destroy(),\n });\n\n if (harmonyTarget?.launch) {\n await agent.launch(harmonyTarget.launch);\n }\n\n return { agent, freeFn };\n }\n\n // handle computer\n if (typeof clonedYamlScript.computer !== 'undefined') {\n const computerTarget = clonedYamlScript.computer;\n const { agentForComputer } = await import('@midscene/computer');\n const agent = await agentForComputer({\n ...computerTarget,\n ...buildAgentOptions(\n clonedYamlScript.agent,\n preference.reportFileName,\n fileName,\n ),\n });\n\n freeFn.push({\n name: 'destroy_computer_agent',\n fn: () => agent.destroy(),\n });\n\n return { agent, freeFn };\n }\n\n // handle general interface\n if (typeof clonedYamlScript.interface !== 'undefined') {\n const interfaceTarget = clonedYamlScript.interface;\n\n const moduleSpecifier = interfaceTarget.module;\n let finalModuleSpecifier: string;\n if (\n moduleSpecifier.startsWith('./') ||\n moduleSpecifier.startsWith('../') ||\n path.isAbsolute(moduleSpecifier)\n ) {\n const resolvedPath = path.resolve(process.cwd(), moduleSpecifier);\n finalModuleSpecifier = resolvedPath;\n } else {\n finalModuleSpecifier = moduleSpecifier;\n }\n\n // import the module dynamically\n debug(\n 'importing module config',\n interfaceTarget.module,\n 'with export config',\n interfaceTarget.export,\n 'final module specifier',\n finalModuleSpecifier,\n );\n\n const importedModule = await import(finalModuleSpecifier);\n\n // get the specific export or use default export\n const DeviceClass = interfaceTarget.export\n ? importedModule[interfaceTarget.export]\n : importedModule.default || importedModule;\n\n debug('DeviceClass', DeviceClass, 'with param', interfaceTarget.param);\n\n // create device instance with parameters\n const device: AbstractInterface = new DeviceClass(\n interfaceTarget.param || {},\n );\n\n // create agent from device\n debug('creating agent from device', device);\n const agent = createAgent(\n device,\n buildAgentOptions(\n clonedYamlScript.agent,\n preference.reportFileName,\n fileName,\n ),\n );\n\n freeFn.push({\n name: 'destroy_general_interface_agent',\n fn: () => agent.destroy(),\n });\n\n return { agent, freeFn };\n }\n\n throw new Error(\n 'No valid interface configuration found in the yaml script, should be either \"web\", \"android\", \"ios\", \"harmony\", \"computer\", or \"interface\"',\n );\n } catch (error) {\n const cleanupErrors = await cleanupFailedPlayerSetup(freeFn);\n if (cleanupErrors.length)\n throw new AggregateError(\n [error, ...cleanupErrors],\n 'YAML setup and cleanup failed',\n );\n throw error;\n }\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport dotenv from 'dotenv';\n\nexport interface DotenvLoadOptions {\n dotenvOverride?: boolean;\n dotenvDebug?: boolean;\n cwd?: string;\n log?: (message: string) => void;\n}\n\nexport function loadDotenvConfig(options: DotenvLoadOptions = {}) {\n const dotEnvConfigFile = join(options.cwd ?? process.cwd(), '.env');\n if (!existsSync(dotEnvConfigFile)) {\n return;\n }\n\n options.log?.(` Env file: ${dotEnvConfigFile}`);\n dotenv.config({\n path: dotEnvConfigFile,\n debug: options.dotenvDebug,\n override: options.dotenvOverride,\n });\n}\n"],"names":["debug","getDebug","cleanupWarning","cleanupFailedPlayerSetup","freeFn","errors","cleanup","error","launchServer","dir","createServer","Promise","resolve","reject","server","resolveReportFileName","yamlReportFileName","cliTestId","yamlTestId","fileName","undefined","baseName","getReportFileName","buildAgentOptions","yamlAgent","reportFileName","processCacheConfig","createYamlPlayer","file","script","options","yamlScript","parseYamlScript","readFileSync","clonedYamlScript","structuredClone","basename","extname","preference","player","ScriptPlayer","createYamlAgent","getLegacyYamlPlayerState","webOptions","resolvedWebTarget","resolveWebTarget","webTarget","targetCount","Boolean","specifiedTargets","Error","buildDownloadBehavior","puppeteerAgentForTarget","localServer","urlToVisit","assert","serverAddress","puppeteer","ownsCdpBrowser","cdpBrowser","launchedAgent","cleanupError","agent","newFreeFn","cleanFreeFn","f","bridgeUnsupportedKeys","ignoredKeys","key","AgentOverChromeBridge","androidTarget","agentFromAdbDevice","iosTarget","agentFromWebDriverAgent","harmonyTarget","agentFromHdcDevice","computerTarget","agentForComputer","interfaceTarget","moduleSpecifier","finalModuleSpecifier","path","resolvedPath","process","importedModule","DeviceClass","device","createAgent","cleanupErrors","AggregateError","loadDotenvConfig","dotEnvConfigFile","join","existsSync","dotenv"],"mappings":"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"}