@midscene/cli 1.8.7 → 1.8.8-beta-20260601092817.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/lib/index.js CHANGED
@@ -3007,1152 +3007,375 @@ var __webpack_modules__ = {
3007
3007
  };
3008
3008
  module.exports = (string, columns, options)=>String(string).normalize().replace(/\r\n/g, '\n').split('\n').map((line)=>exec(line, columns, options)).join('\n');
3009
3009
  },
3010
- "./src/index.ts" (__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
3010
+ "./src/framework/index.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
3011
3011
  "use strict";
3012
- const external_node_fs_namespaceObject = require("node:fs");
3013
- var external_node_fs_namespaceObject_0 = /*#__PURE__*/ __webpack_require__.t(external_node_fs_namespaceObject, 2);
3014
- const external_node_path_namespaceObject = require("node:path");
3015
- var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
3016
- const core_namespaceObject = require("@midscene/core");
3017
- const cli_namespaceObject = require("@midscene/shared/cli");
3018
- var main = __webpack_require__("../../node_modules/.pnpm/dotenv@16.4.5/node_modules/dotenv/lib/main.js");
3019
- var main_default = /*#__PURE__*/ __webpack_require__.n(main);
3020
- var package_namespaceObject = {
3021
- rE: "1.8.7"
3022
- };
3023
- const yaml_namespaceObject = require("@midscene/core/yaml");
3012
+ __webpack_require__.d(__webpack_exports__, {
3013
+ runFrameworkTestConfig: ()=>runFrameworkTestConfig
3014
+ });
3015
+ var external_node_fs_ = __webpack_require__("node:fs");
3016
+ var external_node_path_ = __webpack_require__("node:path");
3024
3017
  const common_namespaceObject = require("@midscene/shared/common");
3025
- const puppeteer_agent_launcher_namespaceObject = require("@midscene/web/puppeteer-agent-launcher");
3026
- const external_lodash_merge_namespaceObject = require("lodash.merge");
3027
- var external_lodash_merge_default = /*#__PURE__*/ __webpack_require__.n(external_lodash_merge_namespaceObject);
3028
- class Node {
3029
- value;
3030
- next;
3031
- constructor(value){
3032
- this.value = value;
3033
- }
3034
- }
3035
- class Queue {
3036
- #head;
3037
- #tail;
3038
- #size;
3039
- constructor(){
3040
- this.clear();
3041
- }
3042
- enqueue(value) {
3043
- const node = new Node(value);
3044
- if (this.#head) {
3045
- this.#tail.next = node;
3046
- this.#tail = node;
3047
- } else {
3048
- this.#head = node;
3049
- this.#tail = node;
3050
- }
3051
- this.#size++;
3052
- }
3053
- dequeue() {
3054
- const current = this.#head;
3055
- if (!current) return;
3056
- this.#head = this.#head.next;
3057
- this.#size--;
3058
- return current.value;
3059
- }
3060
- peek() {
3061
- if (!this.#head) return;
3062
- return this.#head.value;
3063
- }
3064
- clear() {
3065
- this.#head = void 0;
3066
- this.#tail = void 0;
3067
- this.#size = 0;
3068
- }
3069
- get size() {
3070
- return this.#size;
3071
- }
3072
- *[Symbol.iterator]() {
3073
- let current = this.#head;
3074
- while(current){
3075
- yield current.value;
3076
- current = current.next;
3077
- }
3078
- }
3079
- *drain() {
3080
- while(this.#head)yield this.dequeue();
3081
- }
3082
- }
3083
- function pLimit(concurrency) {
3084
- validateConcurrency(concurrency);
3085
- const queue = new Queue();
3086
- let activeCount = 0;
3087
- const resumeNext = ()=>{
3088
- if (activeCount < concurrency && queue.size > 0) {
3089
- queue.dequeue()();
3090
- activeCount++;
3091
- }
3092
- };
3093
- const next = ()=>{
3094
- activeCount--;
3095
- resumeNext();
3096
- };
3097
- const run = async (function_, resolve, arguments_)=>{
3098
- const result = (async ()=>function_(...arguments_))();
3099
- resolve(result);
3100
- try {
3101
- await result;
3102
- } catch {}
3103
- next();
3104
- };
3105
- const enqueue = (function_, resolve, arguments_)=>{
3106
- new Promise((internalResolve)=>{
3107
- queue.enqueue(internalResolve);
3108
- }).then(run.bind(void 0, function_, resolve, arguments_));
3109
- (async ()=>{
3110
- await Promise.resolve();
3111
- if (activeCount < concurrency) resumeNext();
3112
- })();
3113
- };
3114
- const generator = (function_, ...arguments_)=>new Promise((resolve)=>{
3115
- enqueue(function_, resolve, arguments_);
3116
- });
3117
- Object.defineProperties(generator, {
3118
- activeCount: {
3119
- get: ()=>activeCount
3120
- },
3121
- pendingCount: {
3122
- get: ()=>queue.size
3123
- },
3124
- clearQueue: {
3125
- value () {
3126
- queue.clear();
3127
- }
3128
- },
3129
- concurrency: {
3130
- get: ()=>concurrency,
3131
- set (newConcurrency) {
3132
- validateConcurrency(newConcurrency);
3133
- concurrency = newConcurrency;
3134
- queueMicrotask(()=>{
3135
- while(activeCount < concurrency && queue.size > 0)resumeNext();
3136
- });
3137
- }
3138
- }
3139
- });
3140
- return generator;
3141
- }
3142
- function validateConcurrency(concurrency) {
3143
- if (!((Number.isInteger(concurrency) || concurrency === 1 / 0) && concurrency > 0)) throw new TypeError('Expected `concurrency` to be a number from 1 and up');
3144
- }
3145
- const external_puppeteer_namespaceObject = require("puppeteer");
3146
- var external_puppeteer_default = /*#__PURE__*/ __webpack_require__.n(external_puppeteer_namespaceObject);
3147
- const external_http_server_namespaceObject = require("http-server");
3148
- const external_node_assert_namespaceObject = require("node:assert");
3149
- var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
3150
- const agent_namespaceObject = require("@midscene/core/agent");
3151
- const utils_namespaceObject = require("@midscene/core/utils");
3152
- const logger_namespaceObject = require("@midscene/shared/logger");
3153
- const bridge_mode_namespaceObject = require("@midscene/web/bridge-mode");
3154
- const debug = (0, logger_namespaceObject.getDebug)('create-yaml-player');
3155
- const launchServer = async (dir)=>new Promise((resolve)=>{
3156
- const server = (0, external_http_server_namespaceObject.createServer)({
3157
- root: dir
3158
- });
3159
- server.listen(0, '127.0.0.1', ()=>{
3160
- resolve(server);
3161
- });
3162
- });
3163
- function resolveReportFileName(yamlReportFileName, cliTestId, yamlTestId, fileName) {
3164
- const baseName = yamlReportFileName ?? cliTestId ?? yamlTestId ?? fileName;
3165
- return (0, agent_namespaceObject.getReportFileName)(baseName);
3166
- }
3167
- function buildAgentOptions(yamlAgent, reportFileName, fileName) {
3018
+ const notExecutedError = 'Not executed (previous task failed)';
3019
+ function execution_summary_createNotExecutedYamlResult(file) {
3168
3020
  return {
3169
- ...yamlAgent || {},
3170
- cache: (0, utils_namespaceObject.processCacheConfig)(yamlAgent?.cache, fileName),
3171
- reportFileName
3021
+ file,
3022
+ success: false,
3023
+ executed: false,
3024
+ output: void 0,
3025
+ report: void 0,
3026
+ duration: 0,
3027
+ resultType: 'notExecuted',
3028
+ error: notExecutedError
3172
3029
  };
3173
3030
  }
3174
- async function createYamlPlayer(file, script, options) {
3175
- const yamlScript = script || (0, yaml_namespaceObject.parseYamlScript)((0, external_node_fs_namespaceObject.readFileSync)(file, 'utf-8'), file);
3176
- const clonedYamlScript = structuredClone(yamlScript);
3177
- const fileName = (0, external_node_path_namespaceObject.basename)(file, (0, external_node_path_namespaceObject.extname)(file));
3178
- const preference = {
3179
- headed: options?.headed,
3180
- keepWindow: options?.keepWindow,
3181
- reportFileName: resolveReportFileName(clonedYamlScript.agent?.reportFileName, options?.testId, clonedYamlScript.agent?.testId, fileName)
3182
- };
3183
- const player = new yaml_namespaceObject.ScriptPlayer(clonedYamlScript, async ()=>{
3184
- const freeFn = [];
3185
- const webTarget = clonedYamlScript.web || clonedYamlScript.target;
3186
- const targetCount = [
3187
- void 0 !== webTarget,
3188
- void 0 !== clonedYamlScript.android,
3189
- void 0 !== clonedYamlScript.ios,
3190
- void 0 !== clonedYamlScript.computer,
3191
- void 0 !== clonedYamlScript.interface
3192
- ].filter(Boolean).length;
3193
- if (targetCount > 1) {
3194
- const specifiedTargets = [
3195
- void 0 !== webTarget ? 'web' : null,
3196
- void 0 !== clonedYamlScript.android ? 'android' : null,
3197
- void 0 !== clonedYamlScript.ios ? 'ios' : null,
3198
- void 0 !== clonedYamlScript.computer ? 'computer' : null,
3199
- void 0 !== clonedYamlScript.interface ? 'interface' : null
3200
- ].filter(Boolean);
3201
- throw new Error(`Only one target type can be specified, but found multiple: ${specifiedTargets.join(', ')}. Please specify only one of: web, android, ios, computer, or interface.`);
3202
- }
3203
- if (void 0 !== webTarget) {
3204
- if (void 0 !== clonedYamlScript.target) console.warn("target is deprecated, please use web instead. See https://midscenejs.com/automate-with-scripts-in-yaml for more information. Sorry for the inconvenience.");
3205
- let localServer;
3206
- let urlToVisit;
3207
- if (webTarget.serve) {
3208
- external_node_assert_default()('string' == typeof webTarget.url, 'url is required in serve mode');
3209
- localServer = await launchServer(webTarget.serve);
3210
- const serverAddress = localServer.server.address();
3211
- freeFn.push({
3212
- name: 'local_server',
3213
- fn: ()=>localServer?.server.close()
3214
- });
3215
- urlToVisit = webTarget.url.startsWith('/') ? `http://${serverAddress?.address}:${serverAddress?.port}${webTarget.url}` : `http://${serverAddress?.address}:${serverAddress?.port}/${webTarget.url}`;
3216
- webTarget.url = urlToVisit;
3217
- }
3218
- if (webTarget.cdpEndpoint && webTarget.bridgeMode) throw new Error('cdpEndpoint and bridgeMode are mutually exclusive. Please specify only one.');
3219
- if (webTarget.cdpEndpoint) {
3220
- const cdpBrowser = options?.browser ?? await external_puppeteer_default().connect({
3221
- browserWSEndpoint: webTarget.cdpEndpoint,
3222
- defaultViewport: null
3223
- });
3224
- if (webTarget.chromeArgs) console.warn('chromeArgs are not supported in CDP mode (browser is already running). They will be ignored.');
3225
- const { agent, freeFn: newFreeFn } = await (0, puppeteer_agent_launcher_namespaceObject.puppeteerAgentForTarget)(webTarget, {
3226
- ...preference,
3227
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3228
- }, cdpBrowser, options?.page);
3229
- const cleanFreeFn = newFreeFn.filter((f)=>'puppeteer_browser' !== f.name);
3230
- if (!options?.browser) cleanFreeFn.push({
3231
- name: 'cdp_browser_disconnect',
3232
- fn: ()=>cdpBrowser.disconnect()
3233
- });
3234
- freeFn.push(...cleanFreeFn);
3235
- return {
3236
- agent,
3237
- freeFn
3238
- };
3239
- }
3240
- if (!webTarget.bridgeMode) {
3241
- const { agent, freeFn: newFreeFn } = await (0, puppeteer_agent_launcher_namespaceObject.puppeteerAgentForTarget)(webTarget, {
3242
- ...preference,
3243
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3244
- }, options?.browser, options?.page);
3245
- freeFn.push(...newFreeFn);
3246
- return {
3247
- agent,
3248
- freeFn
3249
- };
3250
- }
3251
- external_node_assert_default()('newTabWithUrl' === webTarget.bridgeMode || 'currentTab' === webTarget.bridgeMode, `bridgeMode config value must be either "newTabWithUrl" or "currentTab", but got ${webTarget.bridgeMode}`);
3252
- if (webTarget.userAgent || null != webTarget.viewportWidth || null != webTarget.viewportHeight || null != webTarget.deviceScaleFactor || webTarget.waitForNetworkIdle || webTarget.cookie || webTarget.chromeArgs) console.warn('puppeteer options (userAgent, viewportWidth, viewportHeight, deviceScaleFactor, waitForNetworkIdle, cookie, chromeArgs) are not supported in bridge mode. They will be ignored.');
3253
- const agent = new bridge_mode_namespaceObject.AgentOverChromeBridge({
3254
- closeNewTabsAfterDisconnect: webTarget.closeNewTabsAfterDisconnect,
3255
- closeConflictServer: true,
3256
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3257
- });
3258
- if ('newTabWithUrl' === webTarget.bridgeMode) await agent.connectNewTabWithUrl(webTarget.url);
3259
- else {
3260
- if (webTarget.url) console.warn('url will be ignored in bridge mode with "currentTab"');
3261
- await agent.connectCurrentTab();
3262
- }
3263
- freeFn.push({
3264
- name: 'destroy_agent_over_chrome_bridge',
3265
- fn: ()=>agent.destroy()
3266
- });
3267
- return {
3268
- agent,
3269
- freeFn
3270
- };
3271
- }
3272
- if (void 0 !== clonedYamlScript.android) {
3273
- const androidTarget = clonedYamlScript.android;
3274
- const { agentFromAdbDevice } = await import("@midscene/android");
3275
- const agent = await agentFromAdbDevice(androidTarget?.deviceId, {
3276
- ...androidTarget,
3277
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3278
- });
3279
- if (androidTarget?.launch) await agent.launch(androidTarget.launch);
3280
- freeFn.push({
3281
- name: 'destroy_android_agent',
3282
- fn: ()=>agent.destroy()
3283
- });
3284
- return {
3285
- agent,
3286
- freeFn
3287
- };
3288
- }
3289
- if (void 0 !== clonedYamlScript.ios) {
3290
- const iosTarget = clonedYamlScript.ios;
3291
- const { agentFromWebDriverAgent } = await import("@midscene/ios");
3292
- const agent = await agentFromWebDriverAgent({
3293
- ...iosTarget,
3294
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3295
- });
3296
- if (iosTarget?.launch) await agent.launch(iosTarget.launch);
3297
- freeFn.push({
3298
- name: 'destroy_ios_agent',
3299
- fn: ()=>agent.destroy()
3300
- });
3301
- return {
3302
- agent,
3303
- freeFn
3304
- };
3305
- }
3306
- if (void 0 !== clonedYamlScript.computer) {
3307
- const computerTarget = clonedYamlScript.computer;
3308
- const { agentForComputer } = await import("@midscene/computer");
3309
- const agent = await agentForComputer({
3310
- ...computerTarget,
3311
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3312
- });
3313
- freeFn.push({
3314
- name: 'destroy_computer_agent',
3315
- fn: ()=>agent.destroy()
3316
- });
3317
- return {
3318
- agent,
3319
- freeFn
3320
- };
3321
- }
3322
- if (void 0 !== clonedYamlScript.interface) {
3323
- const interfaceTarget = clonedYamlScript.interface;
3324
- const moduleSpecifier = interfaceTarget.module;
3325
- let finalModuleSpecifier;
3326
- if (moduleSpecifier.startsWith('./') || moduleSpecifier.startsWith('../') || external_node_path_default().isAbsolute(moduleSpecifier)) {
3327
- const resolvedPath = (0, external_node_path_namespaceObject.join)(process.cwd(), moduleSpecifier);
3328
- finalModuleSpecifier = resolvedPath;
3329
- } else finalModuleSpecifier = moduleSpecifier;
3330
- debug('importing module config', interfaceTarget.module, 'with export config', interfaceTarget.export, 'final module specifier', finalModuleSpecifier);
3331
- const importedModule = await import(finalModuleSpecifier);
3332
- const DeviceClass = interfaceTarget.export ? importedModule[interfaceTarget.export] : importedModule.default || importedModule;
3333
- debug('DeviceClass', DeviceClass, 'with param', interfaceTarget.param);
3334
- const device = new DeviceClass(interfaceTarget.param || {});
3335
- debug('creating agent from device', device);
3336
- const agent = (0, agent_namespaceObject.createAgent)(device, buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName));
3337
- freeFn.push({
3338
- name: 'destroy_general_interface_agent',
3339
- fn: ()=>{
3340
- agent.destroy();
3341
- }
3342
- });
3343
- return {
3344
- agent,
3345
- freeFn
3346
- };
3347
- }
3348
- throw new Error('No valid interface configuration found in the yaml script, should be either "web", "android", "ios", "computer", or "interface"');
3349
- }, void 0, file);
3350
- return player;
3351
- }
3352
- var chalk_source = __webpack_require__("../../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/source/index.js");
3353
- var source_default = /*#__PURE__*/ __webpack_require__.n(chalk_source);
3354
- const isTTY = process.env.MIDSCENE_CLI_LOG_ON_NON_TTY ? false : process.stdout.isTTY;
3355
- const printer_indent = ' ';
3356
- const spinnerInterval = 80;
3357
- const spinnerFrames = [
3358
- '◰',
3359
- '◳',
3360
- '◲',
3361
- '◱'
3362
- ];
3363
- const currentSpinningFrame = ()=>spinnerFrames[Math.floor(Date.now() / spinnerInterval) % spinnerFrames.length];
3364
- function indicatorForStatus(status) {
3365
- if ('init' === status) return source_default().gray('◌');
3366
- if ('running' === status) return source_default().yellowBright(currentSpinningFrame());
3367
- if ('done' === status) return source_default().green('✔︎');
3368
- if ('error' === status) return source_default().red('✘');
3369
- }
3370
- const contextInfo = (context)=>{
3371
- const filePath = context.file;
3372
- const filePathToShow = (0, external_node_path_namespaceObject.relative)(process.cwd(), filePath);
3373
- const fileNameToPrint = `${source_default().gray(`${filePathToShow}`)}`;
3374
- const fileStatusText = indicatorForStatus(context.player.status);
3375
- const contextActionText = void 0 === context.player.currentTaskIndex && 'running' === context.player.status ? source_default().gray('(navigating)') : '';
3376
- const errorText = context.player.errorInSetup ? `\n${printer_indent}${source_default().red('error:')} ${context.player.errorInSetup?.message}\n${printer_indent}${printer_indent}${context.player.errorInSetup?.stack}` : '';
3377
- const outputFile = context.player.output;
3378
- const outputText = outputFile && Object.keys(context.player.result || {}).length > 0 ? `\n${printer_indent}${source_default().gray(`output: ${outputFile}`)}` : '';
3379
- const reportFile = context.player.reportFile;
3380
- const reportText = reportFile ? `\n${printer_indent}${source_default().gray(`report: ${reportFile}`)}` : '';
3381
- const agentStatusTip = context.player.agentStatusTip;
3382
- const agentStatusText = agentStatusTip ? `\n${printer_indent}${source_default().gray(`agent status: ${agentStatusTip}`)}` : '';
3383
- const mergedText = `${fileStatusText} ${fileNameToPrint} ${contextActionText}${outputText}${reportText}${errorText}${agentStatusText}`.trim();
3031
+ function getExecutionSummary(results) {
3384
3032
  return {
3385
- fileNameToPrint,
3386
- fileStatusText,
3387
- contextActionText,
3388
- outputText,
3389
- reportText,
3390
- mergedText
3033
+ total: results.length,
3034
+ successful: getResultsByType(results, 'success').length,
3035
+ failed: getResultsByType(results, 'failed').length,
3036
+ partialFailed: getResultsByType(results, 'partialFailed').length,
3037
+ notExecuted: getResultsByType(results, 'notExecuted').length,
3038
+ totalDuration: results.reduce((sum, r)=>sum + (r.duration || 0), 0)
3391
3039
  };
3392
- };
3393
- const singleTaskInfo = (task)=>{
3394
- let stepText = '';
3395
- if ('init' === task.status) stepText = '';
3396
- else if ('running' === task.status || 'error' === task.status) if (void 0 === task.currentStep) stepText = source_default().gray('(navigating)');
3397
- else if ('number' == typeof task.currentStep) {
3398
- const actionText = '';
3399
- stepText = source_default().gray(`(task ${task.currentStep + 1}/${task.totalSteps}${actionText})`.trim());
3400
- } else stepText = source_default().gray('(unknown task)');
3401
- const errorText = 'error' === task.status ? `\n${printer_indent}${source_default().gray('error:')}\n${printer_indent}${printer_indent}${task.error?.message}` : '';
3402
- const statusText = indicatorForStatus(task.status);
3403
- const mergedLine = `${statusText} ${task.name} ${stepText}${errorText}`;
3404
- return {
3405
- nameText: task.name,
3406
- stepText,
3407
- errorText,
3408
- itemStatusText: statusText,
3409
- mergedLine
3410
- };
3411
- };
3412
- function paddingLines(lines) {
3413
- return lines.map((line)=>`${printer_indent}${line}`);
3414
- }
3415
- const contextTaskListSummary = (taskStatusArray, context)=>{
3416
- const prefixLines = [];
3417
- const currentLine = [];
3418
- const suffixText = [];
3419
- const { mergedText: fileInfo } = contextInfo(context);
3420
- if (!context.player.errorInSetup) for (const task of taskStatusArray){
3421
- const { mergedLine } = singleTaskInfo(task);
3422
- if ('init' === context.player.status) suffixText.push(mergedLine);
3423
- else if ('running' === context.player.status) currentLine.push(mergedLine);
3424
- else if ('done' === context.player.status) prefixLines.push(mergedLine);
3425
- else if ('error' === context.player.status) prefixLines.push(mergedLine);
3426
- }
3427
- const lines = [
3428
- fileInfo
3429
- ];
3430
- if (prefixLines.length > 0) lines.push(...paddingLines(prefixLines));
3431
- if (currentLine.length > 0) lines.push(...paddingLines(currentLine));
3432
- if (suffixText.length > 0) lines.push(...paddingLines(suffixText));
3433
- return lines.join('\n');
3434
- };
3435
- const external_node_util_namespaceObject = require("node:util");
3436
- const external_node_process_namespaceObject = require("node:process");
3437
- const copyProperty = (to, from, property, ignoreNonConfigurable)=>{
3438
- if ('length' === property || 'prototype' === property) return;
3439
- if ('arguments' === property || 'caller' === property) return;
3440
- const toDescriptor = Object.getOwnPropertyDescriptor(to, property);
3441
- const fromDescriptor = Object.getOwnPropertyDescriptor(from, property);
3442
- if (!canCopyProperty(toDescriptor, fromDescriptor) && ignoreNonConfigurable) return;
3443
- Object.defineProperty(to, property, fromDescriptor);
3444
- };
3445
- const canCopyProperty = function(toDescriptor, fromDescriptor) {
3446
- return void 0 === toDescriptor || toDescriptor.configurable || toDescriptor.writable === fromDescriptor.writable && toDescriptor.enumerable === fromDescriptor.enumerable && toDescriptor.configurable === fromDescriptor.configurable && (toDescriptor.writable || toDescriptor.value === fromDescriptor.value);
3447
- };
3448
- const changePrototype = (to, from)=>{
3449
- const fromPrototype = Object.getPrototypeOf(from);
3450
- if (fromPrototype === Object.getPrototypeOf(to)) return;
3451
- Object.setPrototypeOf(to, fromPrototype);
3452
- };
3453
- const wrappedToString = (withName, fromBody)=>`/* Wrapped ${withName}*/\n${fromBody}`;
3454
- const toStringDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, 'toString');
3455
- const toStringName = Object.getOwnPropertyDescriptor(Function.prototype.toString, 'name');
3456
- const changeToString = (to, from, name)=>{
3457
- const withName = '' === name ? '' : `with ${name.trim()}() `;
3458
- const newToString = wrappedToString.bind(null, withName, from.toString());
3459
- Object.defineProperty(newToString, 'name', toStringName);
3460
- const { writable, enumerable, configurable } = toStringDescriptor;
3461
- Object.defineProperty(to, 'toString', {
3462
- value: newToString,
3463
- writable,
3464
- enumerable,
3465
- configurable
3040
+ }
3041
+ function getResultsByType(results, resultType) {
3042
+ return results.filter((result)=>result.resultType === resultType);
3043
+ }
3044
+ function getSummaryAbsolutePath(summary) {
3045
+ return (0, external_node_path_.resolve)((0, common_namespaceObject.getMidsceneRunSubDir)('output'), summary);
3046
+ }
3047
+ function execution_summary_writeExecutionSummaryFile(summary, results) {
3048
+ const indexPath = getSummaryAbsolutePath(summary);
3049
+ const outputDir = (0, external_node_path_.dirname)(indexPath);
3050
+ (0, external_node_fs_.mkdirSync)(outputDir, {
3051
+ recursive: true
3466
3052
  });
3467
- };
3468
- function mimicFunction(to, from, { ignoreNonConfigurable = false } = {}) {
3469
- const { name } = to;
3470
- for (const property of Reflect.ownKeys(from))copyProperty(to, from, property, ignoreNonConfigurable);
3471
- changePrototype(to, from);
3472
- changeToString(to, from, name);
3473
- return to;
3474
- }
3475
- const calledFunctions = new WeakMap();
3476
- const onetime_onetime = (function_, options = {})=>{
3477
- if ('function' != typeof function_) throw new TypeError('Expected a function');
3478
- let returnValue;
3479
- let callCount = 0;
3480
- const functionName = function_.displayName || function_.name || '<anonymous>';
3481
- const onetime = function(...arguments_) {
3482
- calledFunctions.set(onetime, ++callCount);
3483
- if (1 === callCount) {
3484
- returnValue = function_.apply(this, arguments_);
3485
- function_ = void 0;
3486
- } else if (true === options.throw) throw new Error(`Function \`${functionName}\` can only be called once`);
3487
- return returnValue;
3488
- };
3489
- mimicFunction(onetime, function_);
3490
- calledFunctions.set(onetime, callCount);
3491
- return onetime;
3492
- };
3493
- onetime_onetime.callCount = (function_)=>{
3494
- if (!calledFunctions.has(function_)) throw new Error(`The given function \`${function_.name}\` is not wrapped by the \`onetime\` package`);
3495
- return calledFunctions.get(function_);
3496
- };
3497
- const node_modules_onetime = onetime_onetime;
3498
- const signals = [];
3499
- signals.push('SIGHUP', 'SIGINT', 'SIGTERM');
3500
- if ('win32' !== process.platform) signals.push('SIGALRM', 'SIGABRT', 'SIGVTALRM', 'SIGXCPU', 'SIGXFSZ', 'SIGUSR2', 'SIGTRAP', 'SIGSYS', 'SIGQUIT', 'SIGIOT');
3501
- if ('linux' === process.platform) signals.push('SIGIO', 'SIGPOLL', 'SIGPWR', 'SIGSTKFLT');
3502
- const processOk = (process1)=>!!process1 && 'object' == typeof process1 && 'function' == typeof process1.removeListener && 'function' == typeof process1.emit && 'function' == typeof process1.reallyExit && 'function' == typeof process1.listeners && 'function' == typeof process1.kill && 'number' == typeof process1.pid && 'function' == typeof process1.on;
3503
- const kExitEmitter = Symbol.for('signal-exit emitter');
3504
- const global = globalThis;
3505
- const ObjectDefineProperty = Object.defineProperty.bind(Object);
3506
- class Emitter {
3507
- emitted = {
3508
- afterExit: false,
3509
- exit: false
3510
- };
3511
- listeners = {
3512
- afterExit: [],
3513
- exit: []
3053
+ const executionSummary = getExecutionSummary(results);
3054
+ const indexData = {
3055
+ summary: {
3056
+ ...executionSummary,
3057
+ generatedAt: new Date().toLocaleString()
3058
+ },
3059
+ results: results.map((result)=>({
3060
+ script: (0, external_node_path_.relative)(outputDir, result.file),
3061
+ success: result.success,
3062
+ resultType: result.resultType,
3063
+ output: result.output ? (()=>{
3064
+ const relativePath = (0, external_node_path_.relative)(outputDir, result.output);
3065
+ return relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
3066
+ })() : void 0,
3067
+ report: result.report ? (0, external_node_path_.relative)(outputDir, result.report) : void 0,
3068
+ error: result.error,
3069
+ duration: result.duration
3070
+ }))
3514
3071
  };
3515
- count = 0;
3516
- id = Math.random();
3517
- constructor(){
3518
- if (global[kExitEmitter]) return global[kExitEmitter];
3519
- ObjectDefineProperty(global, kExitEmitter, {
3520
- value: this,
3521
- writable: false,
3522
- enumerable: false,
3523
- configurable: false
3072
+ (0, external_node_fs_.writeFileSync)(indexPath, JSON.stringify(indexData, null, 2));
3073
+ return indexPath;
3074
+ }
3075
+ function execution_summary_printExecutionPlan(config) {
3076
+ console.log(' Scripts:');
3077
+ for (const file of config.files)console.log(` - ${file}`);
3078
+ console.log('📋 Execution plan');
3079
+ console.log(` Concurrency: ${config.concurrent}`);
3080
+ console.log(` Keep window: ${config.keepWindow}`);
3081
+ console.log(` Headed: ${config.headed}`);
3082
+ console.log(` Continue on error: ${config.continueOnError}`);
3083
+ console.log(` Share browser context: ${config.shareBrowserContext ?? false}`);
3084
+ console.log(` Summary output: ${config.summary}`);
3085
+ }
3086
+ function execution_summary_printExecutionFinished() {
3087
+ console.log('Execution finished:');
3088
+ }
3089
+ function printExecutionSummary(results, summaryPath) {
3090
+ const summary = getExecutionSummary(results);
3091
+ const successfulFiles = getResultsByType(results, 'success');
3092
+ const failedFiles = getResultsByType(results, 'failed');
3093
+ const partialFailedFiles = getResultsByType(results, 'partialFailed');
3094
+ const notExecutedFiles = getResultsByType(results, 'notExecuted');
3095
+ const success = 0 === summary.failed && 0 === summary.partialFailed && 0 === summary.notExecuted;
3096
+ console.log('\n📊 Execution Summary:');
3097
+ console.log(` Total files: ${summary.total}`);
3098
+ console.log(` Successful: ${summary.successful}`);
3099
+ console.log(` Failed: ${summary.failed}`);
3100
+ console.log(` Partial failed: ${summary.partialFailed}`);
3101
+ console.log(` Not executed: ${summary.notExecuted}`);
3102
+ console.log(` Duration: ${(summary.totalDuration / 1000).toFixed(2)}s`);
3103
+ console.log(` Summary: ${summaryPath}`);
3104
+ if (successfulFiles.length > 0) {
3105
+ console.log('\n✅ Successful files:');
3106
+ successfulFiles.forEach((result)=>{
3107
+ console.log(` ${result.file}`);
3524
3108
  });
3525
3109
  }
3526
- on(ev, fn) {
3527
- this.listeners[ev].push(fn);
3528
- }
3529
- removeListener(ev, fn) {
3530
- const list = this.listeners[ev];
3531
- const i = list.indexOf(fn);
3532
- if (-1 === i) return;
3533
- if (0 === i && 1 === list.length) list.length = 0;
3534
- else list.splice(i, 1);
3535
- }
3536
- emit(ev, code, signal) {
3537
- if (this.emitted[ev]) return false;
3538
- this.emitted[ev] = true;
3539
- let ret = false;
3540
- for (const fn of this.listeners[ev])ret = true === fn(code, signal) || ret;
3541
- if ('exit' === ev) ret = this.emit('afterExit', code, signal) || ret;
3542
- return ret;
3110
+ if (failedFiles.length > 0) {
3111
+ console.log('\n❌ Failed files');
3112
+ failedFiles.forEach((result)=>{
3113
+ console.log(` ${result.file}`);
3114
+ if (result.error) console.log(` Error: ${result.error}`);
3115
+ });
3543
3116
  }
3544
- }
3545
- class SignalExitBase {
3546
- }
3547
- const signalExitWrap = (handler)=>({
3548
- onExit (cb, opts) {
3549
- return handler.onExit(cb, opts);
3550
- },
3551
- load () {
3552
- return handler.load();
3553
- },
3554
- unload () {
3555
- return handler.unload();
3556
- }
3557
- });
3558
- class SignalExitFallback extends SignalExitBase {
3559
- onExit() {
3560
- return ()=>{};
3561
- }
3562
- load() {}
3563
- unload() {}
3564
- }
3565
- class SignalExit extends SignalExitBase {
3566
- #hupSig = 'win32' === mjs_process.platform ? 'SIGINT' : 'SIGHUP';
3567
- #emitter = new Emitter();
3568
- #process;
3569
- #originalProcessEmit;
3570
- #originalProcessReallyExit;
3571
- #sigListeners = {};
3572
- #loaded = false;
3573
- constructor(process1){
3574
- super();
3575
- this.#process = process1;
3576
- this.#sigListeners = {};
3577
- for (const sig of signals)this.#sigListeners[sig] = ()=>{
3578
- const listeners = this.#process.listeners(sig);
3579
- let { count } = this.#emitter;
3580
- const p = process1;
3581
- if ('object' == typeof p.__signal_exit_emitter__ && 'number' == typeof p.__signal_exit_emitter__.count) count += p.__signal_exit_emitter__.count;
3582
- if (listeners.length === count) {
3583
- this.unload();
3584
- const ret = this.#emitter.emit('exit', null, sig);
3585
- const s = 'SIGHUP' === sig ? this.#hupSig : sig;
3586
- if (!ret) process1.kill(process1.pid, s);
3587
- }
3588
- };
3589
- this.#originalProcessReallyExit = process1.reallyExit;
3590
- this.#originalProcessEmit = process1.emit;
3591
- }
3592
- onExit(cb, opts) {
3593
- if (!processOk(this.#process)) return ()=>{};
3594
- if (false === this.#loaded) this.load();
3595
- const ev = opts?.alwaysLast ? 'afterExit' : 'exit';
3596
- this.#emitter.on(ev, cb);
3597
- return ()=>{
3598
- this.#emitter.removeListener(ev, cb);
3599
- if (0 === this.#emitter.listeners['exit'].length && 0 === this.#emitter.listeners['afterExit'].length) this.unload();
3600
- };
3117
+ if (partialFailedFiles.length > 0) {
3118
+ console.log('\n⚠️ Partial failed files (some tasks failed with continueOnError)');
3119
+ partialFailedFiles.forEach((result)=>{
3120
+ console.log(` ${result.file}`);
3121
+ if (result.error) console.log(` Error: ${result.error}`);
3122
+ });
3601
3123
  }
3602
- load() {
3603
- if (this.#loaded) return;
3604
- this.#loaded = true;
3605
- this.#emitter.count += 1;
3606
- for (const sig of signals)try {
3607
- const fn = this.#sigListeners[sig];
3608
- if (fn) this.#process.on(sig, fn);
3609
- } catch (_) {}
3610
- this.#process.emit = (ev, ...a)=>this.#processEmit(ev, ...a);
3611
- this.#process.reallyExit = (code)=>this.#processReallyExit(code);
3612
- }
3613
- unload() {
3614
- if (!this.#loaded) return;
3615
- this.#loaded = false;
3616
- signals.forEach((sig)=>{
3617
- const listener = this.#sigListeners[sig];
3618
- if (!listener) throw new Error('Listener not defined for signal: ' + sig);
3619
- try {
3620
- this.#process.removeListener(sig, listener);
3621
- } catch (_) {}
3124
+ if (notExecutedFiles.length > 0) {
3125
+ console.log('\n⏸️ Not executed files');
3126
+ notExecutedFiles.forEach((result)=>{
3127
+ console.log(` ${result.file}`);
3622
3128
  });
3623
- this.#process.emit = this.#originalProcessEmit;
3624
- this.#process.reallyExit = this.#originalProcessReallyExit;
3625
- this.#emitter.count -= 1;
3626
- }
3627
- #processReallyExit(code) {
3628
- if (!processOk(this.#process)) return 0;
3629
- this.#process.exitCode = code || 0;
3630
- this.#emitter.emit('exit', this.#process.exitCode, null);
3631
- return this.#originalProcessReallyExit.call(this.#process, this.#process.exitCode);
3632
- }
3633
- #processEmit(ev, ...args) {
3634
- const og = this.#originalProcessEmit;
3635
- if (!('exit' === ev && processOk(this.#process))) return og.call(this.#process, ev, ...args);
3636
- {
3637
- if ('number' == typeof args[0]) this.#process.exitCode = args[0];
3638
- const ret = og.call(this.#process, ev, ...args);
3639
- this.#emitter.emit('exit', this.#process.exitCode, null);
3640
- return ret;
3641
- }
3642
3129
  }
3643
- }
3644
- const mjs_process = globalThis.process;
3645
- const { onExit, load, unload } = signalExitWrap(processOk(mjs_process) ? new SignalExit(mjs_process) : new SignalExitFallback());
3646
- const terminal = external_node_process_namespaceObject.stderr.isTTY ? external_node_process_namespaceObject.stderr : external_node_process_namespaceObject.stdout.isTTY ? external_node_process_namespaceObject.stdout : void 0;
3647
- const restoreCursor = terminal ? node_modules_onetime(()=>{
3648
- onExit(()=>{
3649
- terminal.write('\u001B[?25h');
3650
- }, {
3651
- alwaysLast: true
3652
- });
3653
- }) : ()=>{};
3654
- const restore_cursor = restoreCursor;
3655
- function _define_property(obj, key, value) {
3656
- if (key in obj) Object.defineProperty(obj, key, {
3657
- value: value,
3658
- enumerable: true,
3659
- configurable: true,
3660
- writable: true
3130
+ if (success) console.log('\n🎉 All files executed successfully!');
3131
+ else console.log('\n⚠️ Some files failed or were not executed.');
3132
+ return success;
3133
+ }
3134
+ const DEFAULT_YAML_TEST_TIMEOUT = 0;
3135
+ const toPosixPath = (value)=>value.split(external_node_path_.sep).join('/');
3136
+ const toImportLiteral = (value)=>JSON.stringify(toPosixPath(value));
3137
+ const toVirtualModuleId = (fileStem)=>`virtual:midscene-yaml/${fileStem}.test.ts`;
3138
+ const safeFileStem = (file, index)=>{
3139
+ const base = (0, external_node_path_.basename)(file, (0, external_node_path_.extname)(file)).replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
3140
+ return `${String(index + 1).padStart(3, '0')}-${base || 'case'}`;
3141
+ };
3142
+ const resolveTestName = (projectDir, yamlFile)=>{
3143
+ const relativePath = (0, external_node_path_.relative)(projectDir, yamlFile);
3144
+ return toPosixPath(relativePath.startsWith('..') ? yamlFile : relativePath);
3145
+ };
3146
+ const createGeneratedTestContent = (options)=>{
3147
+ const testOptions = {
3148
+ testName: options.testName,
3149
+ yamlFile: options.yamlFile,
3150
+ resultFile: options.resultFile,
3151
+ ...options.caseOptions ? {
3152
+ caseOptions: options.caseOptions
3153
+ } : {},
3154
+ ...options.webRuntimeOptions ? {
3155
+ webRuntimeOptions: options.webRuntimeOptions
3156
+ } : {}
3157
+ };
3158
+ return `import { defineYamlCaseTest } from ${toImportLiteral(options.frameworkImport)};
3159
+
3160
+ defineYamlCaseTest(${JSON.stringify(testOptions, null, 2)});
3161
+ `;
3162
+ };
3163
+ const createGeneratedBatchTestContent = (options)=>{
3164
+ const testOptions = {
3165
+ testName: options.testName,
3166
+ config: options.config,
3167
+ resultFiles: options.resultFiles
3168
+ };
3169
+ return `import { defineYamlBatchTest } from ${toImportLiteral(options.frameworkImport)};
3170
+
3171
+ defineYamlBatchTest(${JSON.stringify(testOptions, null, 2)});
3172
+ `;
3173
+ };
3174
+ const resolveDefaultFrameworkImport = ()=>{
3175
+ const entry = process.argv[1] ? (0, external_node_path_.resolve)(process.argv[1]) : '';
3176
+ const candidates = [
3177
+ entry ? (0, external_node_path_.join)((0, external_node_path_.dirname)(entry), 'framework', 'index.js') : '',
3178
+ entry ? (0, external_node_path_.join)((0, external_node_path_.dirname)(entry), '..', 'dist', 'lib', 'framework', 'index.js') : ''
3179
+ ].filter(Boolean);
3180
+ const matched = candidates.find((candidate)=>(0, external_node_fs_.existsSync)(candidate));
3181
+ return matched || '@midscene/cli/dist/lib/framework/index.js';
3182
+ };
3183
+ function createRstestYamlProject(options) {
3184
+ const projectDir = (0, external_node_path_.resolve)(options.projectDir || process.cwd());
3185
+ const outputDir = options.outputDir || (0, external_node_path_.join)((0, common_namespaceObject.getMidsceneRunSubDir)('tmp'), `rstest-yaml-${Date.now()}`);
3186
+ const resultDir = options.resultDir || (0, external_node_path_.join)(outputDir, 'results');
3187
+ const frameworkImport = options.frameworkImport || resolveDefaultFrameworkImport();
3188
+ const testTimeout = options.testTimeout ?? DEFAULT_YAML_TEST_TIMEOUT;
3189
+ (0, external_node_fs_.rmSync)(outputDir, {
3190
+ recursive: true,
3191
+ force: true
3661
3192
  });
3662
- else obj[key] = value;
3663
- return obj;
3664
- }
3665
- const DEFAULT_RENDER_INTERVAL = 160;
3666
- const ESC = '\x1B[';
3667
- const CLEAR_LINE = `${ESC}K`;
3668
- const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`;
3669
- const HIDE_CURSOR = `${ESC}?25l`;
3670
- const SHOW_CURSOR = `${ESC}?25h`;
3671
- const SYNC_START = `${ESC}?2026h`;
3672
- const SYNC_END = `${ESC}?2026l`;
3673
- class TTYWindowRenderer {
3674
- start() {
3675
- this.finished = false;
3676
- this.renderInterval = setInterval(()=>this.flushBuffer(), this.options.interval);
3677
- }
3678
- stop() {
3679
- this.flushBuffer();
3680
- this.write(SHOW_CURSOR, 'output');
3681
- this.cleanups.splice(0).map((fn)=>fn());
3682
- clearInterval(this.renderInterval);
3683
- }
3684
- finish() {
3685
- this.finished = true;
3686
- this.flushBuffer();
3687
- clearInterval(this.renderInterval);
3688
- }
3689
- flushBuffer() {
3690
- if (0 === this.buffer.length) return this.render();
3691
- let current;
3692
- for (const next of this.buffer.splice(0)){
3693
- if (!current) {
3694
- current = next;
3695
- continue;
3696
- }
3697
- if (current.type !== next.type) {
3698
- this.render(current.message, current.type);
3699
- current = next;
3700
- continue;
3701
- }
3702
- current.message += next.message;
3703
- }
3704
- if (current) this.render(current?.message, current?.type);
3705
- }
3706
- render(message, type = 'output') {
3707
- if (this.finished) {
3708
- this.clearWindow();
3709
- return this.write(message || '', type);
3710
- }
3711
- const windowContent = this.options.getWindow();
3712
- const rowCount = getRenderedRowCount(windowContent, this.options.outputStream);
3713
- let padding = this.windowHeight - rowCount;
3714
- if (padding > 0 && message) padding -= getRenderedRowCount([
3715
- message
3716
- ], this.options.outputStream);
3717
- this.write(SYNC_START);
3718
- this.clearWindow();
3719
- if (message) this.write(message, type);
3720
- if (padding > 0) this.write('\n'.repeat(padding));
3721
- this.write(windowContent.join('\n'));
3722
- this.write(SYNC_END);
3723
- this.windowHeight = rowCount + Math.max(0, padding);
3724
- }
3725
- clearWindow() {
3726
- if (0 === this.windowHeight) return;
3727
- this.write(CLEAR_LINE);
3728
- for(let i = 1; i < this.windowHeight; i++)this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`);
3729
- this.windowHeight = 0;
3730
- }
3731
- interceptStream(stream, type) {
3732
- const original = stream.write;
3733
- stream.write = (chunk, _, callback)=>{
3734
- if (chunk) if (this.finished) this.write(chunk.toString(), type);
3735
- else this.buffer.push({
3736
- type,
3737
- message: chunk.toString()
3738
- });
3739
- callback?.();
3740
- };
3741
- return function() {
3742
- stream.write = original;
3743
- };
3744
- }
3745
- write(message, type = 'output') {
3746
- this.streams[type](message);
3747
- }
3748
- constructor(options){
3749
- _define_property(this, "options", void 0);
3750
- _define_property(this, "streams", void 0);
3751
- _define_property(this, "buffer", []);
3752
- _define_property(this, "renderInterval", void 0);
3753
- _define_property(this, "windowHeight", 0);
3754
- _define_property(this, "finished", false);
3755
- _define_property(this, "cleanups", []);
3756
- this.options = {
3757
- interval: DEFAULT_RENDER_INTERVAL,
3758
- ...options
3759
- };
3760
- this.streams = {
3761
- output: options.outputStream.write.bind(options.outputStream),
3762
- error: options.errorStream.write.bind(options.errorStream)
3763
- };
3764
- this.cleanups.push(this.interceptStream(process.stdout, 'output'), this.interceptStream(process.stderr, 'error'));
3765
- restore_cursor();
3766
- this.write(HIDE_CURSOR, 'output');
3767
- this.start();
3768
- }
3769
- }
3770
- function getRenderedRowCount(contents, stream) {
3771
- let count = 0;
3772
- const columns = 'columns' in stream ? stream.columns : 80;
3773
- for (const content of contents){
3774
- const rows = content.split('\n');
3775
- for (const row of rows){
3776
- const text = (0, external_node_util_namespaceObject.stripVTControlCharacters)(row);
3777
- count += Math.max(1, Math.ceil(text.length / columns));
3778
- }
3779
- }
3780
- return count;
3781
- }
3782
- function batch_runner_define_property(obj, key, value) {
3783
- if (key in obj) Object.defineProperty(obj, key, {
3784
- value: value,
3785
- enumerable: true,
3786
- configurable: true,
3787
- writable: true
3193
+ (0, external_node_fs_.mkdirSync)(resultDir, {
3194
+ recursive: true
3788
3195
  });
3789
- else obj[key] = value;
3790
- return obj;
3791
- }
3792
- class BatchRunner {
3793
- async run() {
3794
- const { keepWindow, headed } = this.config;
3795
- this.printExecutionPlan();
3796
- const fileContextList = [];
3797
- let browser = null;
3798
- let sharedPage = null;
3799
- try {
3800
- for (const file of this.config.files){
3801
- const fileConfig = await this.loadFileConfig(file);
3802
- const context = await this.createFileContext(file, fileConfig, {
3803
- headed,
3804
- keepWindow
3805
- });
3806
- fileContextList.push(context);
3807
- }
3808
- const needsBrowser = fileContextList.some((ctx)=>Object.keys(ctx.executionConfig.web || ctx.executionConfig.target || {}).length > 0);
3809
- if (needsBrowser && this.config.shareBrowserContext) {
3810
- const globalWebConfig = this.config.globalConfig?.web;
3811
- if (globalWebConfig?.cdpEndpoint) browser = await external_puppeteer_default().connect({
3812
- browserWSEndpoint: globalWebConfig.cdpEndpoint,
3813
- defaultViewport: null
3814
- });
3815
- else {
3816
- const width = globalWebConfig?.viewportWidth ?? puppeteer_agent_launcher_namespaceObject.defaultViewportWidth;
3817
- const height = globalWebConfig?.viewportHeight ?? puppeteer_agent_launcher_namespaceObject.defaultViewportHeight;
3818
- const args = (0, puppeteer_agent_launcher_namespaceObject.buildChromeArgs)({
3819
- userAgent: globalWebConfig?.userAgent,
3820
- windowSize: headed ? {
3821
- width,
3822
- height
3823
- } : void 0,
3824
- chromeArgs: globalWebConfig?.chromeArgs
3825
- });
3826
- browser = await external_puppeteer_default().launch({
3827
- headless: !headed,
3828
- defaultViewport: headed ? null : {
3829
- width,
3830
- height
3831
- },
3832
- args,
3833
- acceptInsecureCerts: globalWebConfig?.acceptInsecureCerts
3834
- });
3835
- }
3836
- sharedPage = await browser.newPage();
3837
- for (const context of fileContextList){
3838
- context.options.browser = browser;
3839
- context.options.page = sharedPage;
3840
- }
3841
- }
3842
- const { executedResults, notExecutedContexts } = await this.executeFiles(fileContextList);
3843
- this.results = await this.processResults(executedResults, notExecutedContexts);
3844
- } finally{
3845
- if (browser && !this.config.keepWindow) {
3846
- const isCdp = !!this.config.globalConfig?.web?.cdpEndpoint;
3847
- if (isCdp) browser.disconnect();
3848
- else await browser.close();
3849
- }
3850
- await this.generateOutputIndex();
3851
- }
3852
- return this.results;
3853
- }
3854
- async createFileContext(file, fileConfig, options) {
3855
- const { globalConfig } = this.config;
3856
- const clonedFileConfig = JSON.parse(JSON.stringify(fileConfig));
3857
- if (clonedFileConfig.target) {
3858
- clonedFileConfig.web = {
3859
- ...clonedFileConfig.target,
3860
- ...clonedFileConfig.web
3861
- };
3862
- delete clonedFileConfig.target;
3863
- }
3864
- if (globalConfig?.target) {
3865
- globalConfig.web = {
3866
- ...globalConfig.target,
3867
- ...globalConfig.web
3868
- };
3869
- delete globalConfig.target;
3870
- }
3871
- const executionConfig = external_lodash_merge_default()(clonedFileConfig, globalConfig);
3196
+ const virtualModules = {};
3197
+ const cases = options.files.map((file, index)=>{
3198
+ const yamlFile = (0, external_node_path_.resolve)(file);
3199
+ const testName = resolveTestName(projectDir, yamlFile);
3200
+ const fileStem = safeFileStem(yamlFile, index);
3201
+ const resultFile = (0, external_node_path_.join)(resultDir, `${fileStem}.json`);
3202
+ const testModule = toVirtualModuleId(fileStem);
3203
+ virtualModules[testModule] = createGeneratedTestContent({
3204
+ frameworkImport,
3205
+ yamlFile,
3206
+ resultFile,
3207
+ testName,
3208
+ caseOptions: options.caseOptions?.[yamlFile],
3209
+ webRuntimeOptions: options.webRuntimeOptions?.[yamlFile]
3210
+ });
3872
3211
  return {
3873
- file,
3874
- executionConfig,
3875
- options
3212
+ yamlFile,
3213
+ testModule,
3214
+ resultFile,
3215
+ testName
3876
3216
  };
3877
- }
3878
- async executeFiles(fileContextList) {
3879
- const executedResults = [];
3880
- const notExecutedContexts = [];
3881
- const allFileContexts = [];
3882
- for (const context of fileContextList){
3883
- const player = await createYamlPlayer(context.file, context.executionConfig, context.options);
3884
- allFileContexts.push({
3885
- file: context.file,
3886
- player
3887
- });
3888
- }
3889
- let ttyRenderer;
3890
- if (isTTY) {
3891
- const summaryContents = ()=>{
3892
- const summary = [
3893
- ''
3894
- ];
3895
- for (const context of allFileContexts)summary.push(contextTaskListSummary(context.player.taskStatusList, context));
3896
- summary.push('');
3897
- return summary;
3898
- };
3899
- ttyRenderer = new TTYWindowRenderer({
3900
- outputStream: process.stdout,
3901
- errorStream: process.stderr,
3902
- getWindow: summaryContents,
3903
- interval: spinnerInterval
3904
- });
3905
- ttyRenderer.start();
3906
- }
3907
- try {
3908
- const executeFile = async (context)=>{
3909
- const allFileContext = allFileContexts.find((c)=>c.file === context.file);
3910
- if (!allFileContext) throw new Error(`Player not found for file: ${context.file}`);
3911
- if (!isTTY) {
3912
- const { mergedText } = contextInfo(allFileContext);
3913
- console.log(mergedText);
3914
- }
3915
- if (context.outputPath) allFileContext.player.output = context.outputPath;
3916
- const startTime = Date.now();
3917
- await allFileContext.player.run();
3918
- const endTime = Date.now();
3919
- const duration = endTime - startTime;
3920
- const executedContext = {
3921
- file: context.file,
3922
- player: allFileContext.player,
3923
- duration
3924
- };
3925
- if (!isTTY) console.log(contextTaskListSummary(allFileContext.player.taskStatusList, executedContext));
3926
- return executedContext;
3927
- };
3928
- await this.executeConcurrently(fileContextList, executeFile, executedResults, notExecutedContexts);
3929
- if (!isTTY) {
3930
- console.log('\n📋 Execution Results:');
3931
- for (const context of executedResults)console.log(contextTaskListSummary(context.player.taskStatusList, context));
3932
- }
3933
- } finally{
3934
- if (ttyRenderer) ttyRenderer.stop();
3935
- }
3217
+ });
3218
+ if (options.batchConfig) {
3219
+ const batchModule = 'virtual:midscene-yaml/batch.test.ts';
3220
+ const resultFiles = Object.fromEntries(cases.map((item)=>[
3221
+ item.yamlFile,
3222
+ item.resultFile
3223
+ ]));
3936
3224
  return {
3937
- executedResults,
3938
- notExecutedContexts
3225
+ projectDir,
3226
+ outputDir,
3227
+ resultDir,
3228
+ include: [
3229
+ batchModule
3230
+ ],
3231
+ virtualModules: {
3232
+ [batchModule]: createGeneratedBatchTestContent({
3233
+ frameworkImport,
3234
+ testName: 'midscene yaml batch',
3235
+ config: options.batchConfig,
3236
+ resultFiles
3237
+ })
3238
+ },
3239
+ cases,
3240
+ maxConcurrency: 1,
3241
+ testTimeout,
3242
+ bail: options.bail
3939
3243
  };
3940
3244
  }
3941
- async executeConcurrently(fileContextList, executeFile, executedResults, notExecutedContexts) {
3942
- const limit = pLimit(this.config.concurrent);
3943
- if (this.config.continueOnError) {
3944
- const tasks = fileContextList.map((context)=>limit(async ()=>{
3945
- const executedContext = await executeFile(context);
3946
- executedResults.push(executedContext);
3947
- }));
3948
- await Promise.allSettled(tasks);
3949
- } else {
3950
- let shouldStop = false;
3951
- const stopLock = {
3952
- value: false
3953
- };
3954
- const tasks = fileContextList.map((context)=>limit(async ()=>{
3955
- if (stopLock.value) return void notExecutedContexts.push({
3956
- file: context.file,
3957
- player: null
3958
- });
3959
- const executedContext = await executeFile(context);
3960
- executedResults.push(executedContext);
3961
- if ('error' === executedContext.player.status && !stopLock.value) {
3962
- stopLock.value = true;
3963
- shouldStop = true;
3964
- }
3965
- }));
3966
- await Promise.allSettled(tasks);
3967
- if (shouldStop) {
3968
- for (const context of fileContextList)if (!executedResults.some((r)=>r.file === context.file) && !notExecutedContexts.some((ctx)=>ctx.file === context.file)) notExecutedContexts.push({
3969
- file: context.file,
3970
- player: null
3971
- });
3245
+ return {
3246
+ projectDir,
3247
+ outputDir,
3248
+ resultDir,
3249
+ include: cases.map((item)=>item.testModule),
3250
+ virtualModules,
3251
+ cases,
3252
+ maxConcurrency: options.maxConcurrency,
3253
+ testTimeout,
3254
+ bail: options.bail
3255
+ };
3256
+ }
3257
+ const external_node_module_namespaceObject = require("node:module");
3258
+ var external_node_url_ = __webpack_require__("node:url");
3259
+ const requireFromCliEntry = ()=>{
3260
+ const entry = process.argv[1] ? (0, external_node_path_.resolve)(process.argv[1]) : (0, external_node_path_.join)(process.cwd(), 'midscene-cli.js');
3261
+ return (0, external_node_module_namespaceObject.createRequire)(entry);
3262
+ };
3263
+ const resolvePackageFromRstestCore = (packageName)=>{
3264
+ const require1 = requireFromCliEntry();
3265
+ const rstestPackageJsonPath = require1.resolve('@rstest/core/package.json');
3266
+ return (0, external_node_module_namespaceObject.createRequire)(rstestPackageJsonPath).resolve(packageName);
3267
+ };
3268
+ const formatRunError = (error)=>error.stack || `${error.name}: ${error.message}`;
3269
+ async function runRstestYamlProject(options) {
3270
+ const [{ runRstest }, { rspack }] = await Promise.all([
3271
+ import("@rstest/core/api"),
3272
+ import((0, external_node_url_.pathToFileURL)(resolvePackageFromRstestCore('@rsbuild/core')).href)
3273
+ ]);
3274
+ const { project } = options;
3275
+ const maxConcurrency = void 0 !== project.maxConcurrency ? Math.max(1, project.maxConcurrency) : void 0;
3276
+ const inlineConfig = {
3277
+ root: project.projectDir,
3278
+ include: project.include,
3279
+ testEnvironment: 'node',
3280
+ testTimeout: project.testTimeout,
3281
+ ...void 0 !== maxConcurrency ? {
3282
+ maxConcurrency
3283
+ } : {},
3284
+ ...void 0 !== maxConcurrency ? {
3285
+ pool: {
3286
+ maxWorkers: maxConcurrency,
3287
+ minWorkers: maxConcurrency
3288
+ }
3289
+ } : {},
3290
+ ...void 0 !== project.bail ? {
3291
+ bail: project.bail
3292
+ } : {},
3293
+ reporters: [],
3294
+ tools: {
3295
+ rspack: (_config, { appendPlugins })=>{
3296
+ appendPlugins(new rspack.experiments.VirtualModulesPlugin(project.virtualModules));
3972
3297
  }
3973
3298
  }
3974
- }
3975
- async processResults(executedContexts, notExecutedContexts) {
3976
- const results = [];
3977
- for (const context of executedContexts){
3978
- const { file, player, duration } = context;
3979
- const hasFailedTasks = player.taskStatusList?.some((task)=>'error' === task.status) ?? false;
3980
- const hasPlayerError = 'error' === player.status;
3981
- let success;
3982
- let resultType;
3983
- if (hasPlayerError) {
3984
- success = false;
3985
- resultType = 'failed';
3986
- } else if (hasFailedTasks) {
3987
- success = false;
3988
- resultType = 'partialFailed';
3989
- } else {
3990
- success = true;
3991
- resultType = 'success';
3992
- }
3993
- let reportFile;
3994
- if (player.reportFile) reportFile = player.reportFile;
3995
- let outputPath = player.output || void 0;
3996
- if (outputPath && !(0, external_node_fs_namespaceObject.existsSync)(outputPath)) outputPath = void 0;
3997
- let errorMessage;
3998
- if (player.errorInSetup?.message) errorMessage = player.errorInSetup.message;
3999
- else if (hasPlayerError || hasFailedTasks) {
4000
- const taskErrors = player.taskStatusList?.filter((task)=>'error' === task.status && task.error?.message).map((task)=>task.error.message);
4001
- errorMessage = taskErrors && taskErrors.length > 0 ? taskErrors.join('; ') : hasPlayerError ? 'Execution failed' : 'Some tasks failed';
4002
- }
4003
- results.push({
4004
- file,
4005
- success,
4006
- executed: true,
4007
- output: outputPath,
4008
- report: reportFile,
4009
- duration,
4010
- resultType,
4011
- error: errorMessage
4012
- });
4013
- }
4014
- for (const context of notExecutedContexts)results.push({
4015
- file: context.file,
4016
- success: false,
4017
- executed: false,
4018
- output: void 0,
4019
- report: void 0,
4020
- duration: 0,
4021
- resultType: 'notExecuted',
4022
- error: 'Not executed (previous task failed)'
4023
- });
4024
- return results;
4025
- }
4026
- async loadFileConfig(file) {
4027
- const content = (0, external_node_fs_namespaceObject.readFileSync)(file, 'utf8');
4028
- return (0, yaml_namespaceObject.parseYamlScript)(content, file);
4029
- }
4030
- getSummaryAbsolutePath() {
4031
- return (0, external_node_path_namespaceObject.resolve)((0, common_namespaceObject.getMidsceneRunSubDir)('output'), this.config.summary);
4032
- }
4033
- printExecutionPlan() {
4034
- console.log(' Scripts:');
4035
- for (const file of this.config.files)console.log(` - ${file}`);
4036
- console.log('📋 Execution plan');
4037
- console.log(` Concurrency: ${this.config.concurrent}`);
4038
- console.log(` Keep window: ${this.config.keepWindow}`);
4039
- console.log(` Headed: ${this.config.headed}`);
4040
- console.log(` Continue on error: ${this.config.continueOnError}`);
4041
- console.log(` Share browser context: ${this.config.shareBrowserContext ?? false}`);
4042
- console.log(` Summary output: ${this.config.summary}`);
4043
- }
4044
- async generateOutputIndex() {
4045
- const indexPath = (0, external_node_path_namespaceObject.resolve)((0, common_namespaceObject.getMidsceneRunSubDir)('output'), this.config.summary);
4046
- const outputDir = (0, external_node_path_namespaceObject.dirname)(indexPath);
4047
- try {
4048
- (0, external_node_fs_namespaceObject.mkdirSync)(outputDir, {
4049
- recursive: true
4050
- });
4051
- const indexData = {
4052
- summary: {
4053
- total: this.results.length,
4054
- successful: this.results.filter((r)=>'success' === r.resultType).length,
4055
- failed: this.results.filter((r)=>'failed' === r.resultType).length,
4056
- partialFailed: this.results.filter((r)=>'partialFailed' === r.resultType).length,
4057
- notExecuted: this.results.filter((r)=>'notExecuted' === r.resultType).length,
4058
- totalDuration: this.results.reduce((sum, r)=>sum + (r.duration || 0), 0),
4059
- generatedAt: new Date().toLocaleString()
4060
- },
4061
- results: this.results.map((result)=>({
4062
- script: (0, external_node_path_namespaceObject.relative)(outputDir, result.file),
4063
- success: result.success,
4064
- resultType: result.resultType,
4065
- output: result.output ? (()=>{
4066
- const relativePath = (0, external_node_path_namespaceObject.relative)(outputDir, result.output);
4067
- return relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
4068
- })() : void 0,
4069
- report: result.report ? (0, external_node_path_namespaceObject.relative)(outputDir, result.report) : void 0,
4070
- error: result.error,
4071
- duration: result.duration
4072
- }))
4073
- };
4074
- (0, external_node_fs_namespaceObject.writeFileSync)(indexPath, JSON.stringify(indexData, null, 2));
4075
- console.log('Execution finished:');
4076
- } catch (error) {
4077
- console.error('Failed to generate output index:', error);
4078
- }
4079
- }
4080
- getExecutionSummary() {
4081
- const successful = this.results.filter((r)=>'success' === r.resultType).length;
4082
- const failed = this.results.filter((r)=>'failed' === r.resultType).length;
4083
- const partialFailed = this.results.filter((r)=>'partialFailed' === r.resultType).length;
4084
- const notExecuted = this.results.filter((r)=>'notExecuted' === r.resultType).length;
4085
- return {
4086
- total: this.results.length,
4087
- successful,
4088
- failed,
4089
- partialFailed,
4090
- notExecuted,
4091
- totalDuration: this.results.reduce((sum, r)=>sum + (r.duration || 0), 0)
4092
- };
4093
- }
4094
- getFailedFiles() {
4095
- return this.results.filter((r)=>'failed' === r.resultType).map((r)=>r.file);
4096
- }
4097
- getPartialFailedFiles() {
4098
- return this.results.filter((r)=>'partialFailed' === r.resultType).map((r)=>r.file);
4099
- }
4100
- getNotExecutedFiles() {
4101
- return this.results.filter((r)=>'notExecuted' === r.resultType).map((r)=>r.file);
4102
- }
4103
- getSuccessfulFiles() {
4104
- return this.results.filter((r)=>'success' === r.resultType).map((r)=>r.file);
4105
- }
4106
- getResults() {
4107
- return [
4108
- ...this.results
4109
- ];
4110
- }
4111
- printExecutionSummary() {
4112
- const summary = this.getExecutionSummary();
4113
- const success = 0 === summary.failed && 0 === summary.partialFailed && 0 === summary.notExecuted;
4114
- console.log('\n📊 Execution Summary:');
4115
- console.log(` Total files: ${summary.total}`);
4116
- console.log(` Successful: ${summary.successful}`);
4117
- console.log(` Failed: ${summary.failed}`);
4118
- console.log(` Partial failed: ${summary.partialFailed}`);
4119
- console.log(` Not executed: ${summary.notExecuted}`);
4120
- console.log(` Duration: ${(summary.totalDuration / 1000).toFixed(2)}s`);
4121
- console.log(` Summary: ${this.getSummaryAbsolutePath()}`);
4122
- if (summary.successful > 0) {
4123
- console.log('\n✅ Successful files:');
4124
- this.getSuccessfulFiles().forEach((file)=>{
4125
- console.log(` ${file}`);
4126
- });
4127
- }
4128
- if (summary.failed > 0) {
4129
- console.log('\n❌ Failed files');
4130
- this.getFailedFiles().forEach((file)=>{
4131
- console.log(` ${file}`);
4132
- });
4133
- }
4134
- if (summary.partialFailed > 0) {
4135
- console.log('\n⚠️ Partial failed files (some tasks failed with continueOnError)');
4136
- this.getPartialFailedFiles().forEach((file)=>{
4137
- console.log(` ${file}`);
4138
- });
4139
- }
4140
- if (summary.notExecuted > 0) {
4141
- console.log('\n⏸️ Not executed files');
4142
- this.getNotExecutedFiles().forEach((file)=>{
4143
- console.log(` ${file}`);
4144
- });
4145
- }
4146
- if (success) console.log('\n🎉 All files executed successfully!');
4147
- else console.log('\n⚠️ Some files failed or were not executed.');
4148
- return success;
4149
- }
4150
- constructor(config){
4151
- batch_runner_define_property(this, "config", void 0);
4152
- batch_runner_define_property(this, "results", []);
4153
- this.config = config;
4154
- }
3299
+ };
3300
+ const result = await runRstest({
3301
+ cwd: options.cwd || project.projectDir,
3302
+ inlineConfig
3303
+ });
3304
+ if (!result.ok && 'pipe' !== options.stdio && result.unhandledErrors.length) console.error(result.unhandledErrors.map((error)=>formatRunError(error)).join('\n'));
3305
+ return result.ok ? 0 : 1;
4155
3306
  }
3307
+ const createCaseOptions = (config)=>{
3308
+ const caseOptions = {};
3309
+ for (const file of config.files)caseOptions[(0, external_node_path_.resolve)(file)] = {
3310
+ globalConfig: config.globalConfig
3311
+ };
3312
+ return caseOptions;
3313
+ };
3314
+ const createWebRuntimeOptions = (config, runtimeOptions)=>{
3315
+ const caseOptions = {};
3316
+ for (const file of config.files)caseOptions[(0, external_node_path_.resolve)(file)] = {
3317
+ headed: runtimeOptions.headed ?? config.headed,
3318
+ keepWindow: runtimeOptions.keepWindow ?? config.keepWindow
3319
+ };
3320
+ return caseOptions;
3321
+ };
3322
+ const readProjectResults = (project)=>project.cases.map((item)=>{
3323
+ if ((0, external_node_fs_.existsSync)(item.resultFile)) return JSON.parse((0, external_node_fs_.readFileSync)(item.resultFile, 'utf8'));
3324
+ return execution_summary_createNotExecutedYamlResult(item.yamlFile);
3325
+ });
3326
+ async function runFrameworkTestConfig(config, commandOptions = {}) {
3327
+ execution_summary_printExecutionPlan(config);
3328
+ const projectDir = (0, external_node_path_.resolve)(commandOptions.projectDir || process.cwd());
3329
+ const project = createRstestYamlProject({
3330
+ files: config.files,
3331
+ projectDir,
3332
+ outputDir: commandOptions.outputDir,
3333
+ frameworkImport: commandOptions.frameworkImport,
3334
+ caseOptions: createCaseOptions(config),
3335
+ webRuntimeOptions: createWebRuntimeOptions(config, commandOptions),
3336
+ maxConcurrency: commandOptions.concurrent ?? config.concurrent,
3337
+ bail: config.continueOnError ? 0 : 1,
3338
+ batchConfig: config.shareBrowserContext ? config : void 0
3339
+ });
3340
+ const runner = commandOptions.rstestRunner || runRstestYamlProject;
3341
+ const exitCode = await runner({
3342
+ project,
3343
+ cwd: projectDir,
3344
+ stdio: commandOptions.stdio
3345
+ });
3346
+ const results = readProjectResults(project);
3347
+ const summaryPath = execution_summary_writeExecutionSummaryFile(config.summary, results);
3348
+ execution_summary_printExecutionFinished();
3349
+ const success = printExecutionSummary(results, summaryPath);
3350
+ return success ? exitCode : 1;
3351
+ }
3352
+ require("@rstest/core");
3353
+ __webpack_require__("@midscene/core/yaml");
3354
+ require("@midscene/web/puppeteer-agent-launcher");
3355
+ __webpack_require__("lodash.merge");
3356
+ require("puppeteer");
3357
+ require("http-server");
3358
+ require("node:assert");
3359
+ require("@midscene/core/agent");
3360
+ require("@midscene/core/utils");
3361
+ var logger_ = __webpack_require__("@midscene/shared/logger");
3362
+ require("@midscene/web/bridge-mode");
3363
+ (0, logger_.getDebug)('create-yaml-player');
3364
+ __webpack_require__("../../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/source/index.js");
3365
+ process.env.MIDSCENE_CLI_LOG_ON_NON_TTY || process.stdout.isTTY;
3366
+ require("node:util");
3367
+ },
3368
+ "./src/index.ts" (__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
3369
+ "use strict";
3370
+ var external_node_fs_ = __webpack_require__("node:fs");
3371
+ var external_node_fs_namespaceObject = /*#__PURE__*/ __webpack_require__.t(external_node_fs_, 2);
3372
+ var external_node_path_ = __webpack_require__("node:path");
3373
+ const core_namespaceObject = require("@midscene/core");
3374
+ const cli_namespaceObject = require("@midscene/shared/cli");
3375
+ var main = __webpack_require__("../../node_modules/.pnpm/dotenv@16.4.5/node_modules/dotenv/lib/main.js");
3376
+ var main_default = /*#__PURE__*/ __webpack_require__.n(main);
3377
+ var package_namespaceObject = JSON.parse('{"rE":"1.8.8-beta-20260601092817.0"}');
3378
+ var logger_ = __webpack_require__("@midscene/shared/logger");
4156
3379
  var brace_expansion = __webpack_require__("../../node_modules/.pnpm/brace-expansion@2.0.1/node_modules/brace-expansion/index.js");
4157
3380
  const MAX_PATTERN_LENGTH = 65536;
4158
3381
  const assertValidPattern = (pattern)=>{
@@ -5287,7 +4510,7 @@ var __webpack_modules__ = {
5287
4510
  minimatch.Minimatch = esm_Minimatch;
5288
4511
  minimatch.escape = escape_escape;
5289
4512
  minimatch.unescape = unescape_unescape;
5290
- const external_node_url_namespaceObject = require("node:url");
4513
+ var external_node_url_ = __webpack_require__("node:url");
5291
4514
  const perf = 'object' == typeof performance && performance && 'function' == typeof performance.now ? performance : Date;
5292
4515
  const warned = new Set();
5293
4516
  const PROCESS = 'object' == typeof process && process ? process : {};
@@ -6798,7 +6021,7 @@ var __webpack_modules__ = {
6798
6021
  realpath: promises_namespaceObject.realpath
6799
6022
  }
6800
6023
  };
6801
- const fsFromOption = (fsOption)=>fsOption && fsOption !== defaultFS && fsOption !== external_node_fs_namespaceObject_0 ? {
6024
+ const fsFromOption = (fsOption)=>fsOption && fsOption !== defaultFS && fsOption !== external_node_fs_namespaceObject ? {
6802
6025
  ...defaultFS,
6803
6026
  ...fsOption,
6804
6027
  promises: {
@@ -7405,7 +6628,7 @@ var __webpack_modules__ = {
7405
6628
  return new PathWin32(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts);
7406
6629
  }
7407
6630
  getRootString(path) {
7408
- return external_node_path_namespaceObject.win32.parse(path).root;
6631
+ return external_node_path_.win32.parse(path).root;
7409
6632
  }
7410
6633
  getRoot(rootPath) {
7411
6634
  rootPath = uncToDrive(rootPath.toUpperCase());
@@ -7446,7 +6669,7 @@ var __webpack_modules__ = {
7446
6669
  #fs;
7447
6670
  constructor(cwd = process.cwd(), pathImpl, sep, { nocase, childrenCacheSize = 16384, fs = defaultFS } = {}){
7448
6671
  this.#fs = fsFromOption(fs);
7449
- if (cwd instanceof URL || cwd.startsWith('file://')) cwd = (0, external_node_url_namespaceObject.fileURLToPath)(cwd);
6672
+ if (cwd instanceof URL || cwd.startsWith('file://')) cwd = (0, external_node_url_.fileURLToPath)(cwd);
7450
6673
  const cwdPath = pathImpl.resolve(cwd);
7451
6674
  this.roots = Object.create(null);
7452
6675
  this.rootPath = this.parseRootPath(cwdPath);
@@ -7814,7 +7037,7 @@ var __webpack_modules__ = {
7814
7037
  sep = '\\';
7815
7038
  constructor(cwd = process.cwd(), opts = {}){
7816
7039
  const { nocase = true } = opts;
7817
- super(cwd, external_node_path_namespaceObject.win32, '\\', {
7040
+ super(cwd, external_node_path_.win32, '\\', {
7818
7041
  ...opts,
7819
7042
  nocase
7820
7043
  });
@@ -7822,7 +7045,7 @@ var __webpack_modules__ = {
7822
7045
  for(let p = this.cwd; p; p = p.parent)p.nocase = this.nocase;
7823
7046
  }
7824
7047
  parseRootPath(dir) {
7825
- return external_node_path_namespaceObject.win32.parse(dir).root.toUpperCase();
7048
+ return external_node_path_.win32.parse(dir).root.toUpperCase();
7826
7049
  }
7827
7050
  newRoot(fs) {
7828
7051
  return new PathWin32(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), {
@@ -7837,7 +7060,7 @@ var __webpack_modules__ = {
7837
7060
  sep = '/';
7838
7061
  constructor(cwd = process.cwd(), opts = {}){
7839
7062
  const { nocase = false } = opts;
7840
- super(cwd, external_node_path_namespaceObject.posix, '/', {
7063
+ super(cwd, external_node_path_.posix, '/', {
7841
7064
  ...opts,
7842
7065
  nocase
7843
7066
  });
@@ -8536,7 +7759,7 @@ var __webpack_modules__ = {
8536
7759
  this.nodir = !!opts.nodir;
8537
7760
  this.mark = !!opts.mark;
8538
7761
  if (opts.cwd) {
8539
- if (opts.cwd instanceof URL || opts.cwd.startsWith('file://')) opts.cwd = (0, external_node_url_namespaceObject.fileURLToPath)(opts.cwd);
7762
+ if (opts.cwd instanceof URL || opts.cwd.startsWith('file://')) opts.cwd = (0, external_node_url_.fileURLToPath)(opts.cwd);
8540
7763
  } else this.cwd = '';
8541
7764
  this.cwd = opts.cwd || '';
8542
7765
  this.root = opts.root;
@@ -8688,7 +7911,7 @@ var __webpack_modules__ = {
8688
7911
  return new Glob(pattern, options).iterate();
8689
7912
  }
8690
7913
  const streamSync = globStreamSync;
8691
- const esm_stream = Object.assign(globStream, {
7914
+ const stream = Object.assign(globStream, {
8692
7915
  sync: globStreamSync
8693
7916
  });
8694
7917
  const iterateSync = globIterateSync;
@@ -8704,7 +7927,7 @@ var __webpack_modules__ = {
8704
7927
  globSync,
8705
7928
  sync: esm_sync,
8706
7929
  globStream,
8707
- stream: esm_stream,
7930
+ stream,
8708
7931
  globStreamSync,
8709
7932
  streamSync,
8710
7933
  globIterate,
@@ -9564,7 +8787,7 @@ var __webpack_modules__ = {
9564
8787
  var external_assert_ = __webpack_require__("assert");
9565
8788
  new RegExp("\x1b(?:\\[(?:\\d+[ABCDEFGJKSTm]|\\d+;\\d+[Hfm]|\\d+;\\d+;\\d+m|6n|s|u|\\?25[lh])|\\w)", 'g');
9566
8789
  const external_url_namespaceObject = require("url");
9567
- const platform_shims_node = {
8790
+ const node = {
9568
8791
  fs: {
9569
8792
  readFileSync: external_fs_.readFileSync,
9570
8793
  writeFile: external_fs_.writeFile
@@ -9714,7 +8937,7 @@ var __webpack_modules__ = {
9714
8937
  locale: y18n.locale
9715
8938
  };
9716
8939
  }
9717
- const y18n_y18n = (opts)=>lib_y18n(opts, platform_shims_node);
8940
+ const y18n_y18n = (opts)=>lib_y18n(opts, node);
9718
8941
  const node_modules_y18n = y18n_y18n;
9719
8942
  let esm_dirname;
9720
8943
  try {
@@ -9733,6 +8956,8 @@ var __webpack_modules__ = {
9733
8956
  Yargs.hideBin = processArgv.hideBin;
9734
8957
  Yargs.Parser = yargs_Parser;
9735
8958
  const yargs = Yargs;
8959
+ const external_node_process_namespaceObject = require("node:process");
8960
+ var yaml_ = __webpack_require__("@midscene/core/yaml");
9736
8961
  /*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ function isNothing(subject) {
9737
8962
  return null == subject;
9738
8963
  }
@@ -11805,12 +11030,14 @@ var __webpack_modules__ = {
11805
11030
  throw new Error('Function yaml.' + from + " is removed in js-yaml 4. Use yaml." + to + ' instead, which is now safe by default.');
11806
11031
  };
11807
11032
  }
11808
- var js_yaml_load = loader.load;
11033
+ var load = loader.load;
11809
11034
  loader.loadAll;
11810
11035
  dumper.dump;
11811
11036
  renamed('safeLoad', 'load');
11812
11037
  renamed('safeLoadAll', 'loadAll');
11813
11038
  renamed('safeDump', 'dump');
11039
+ var external_lodash_merge_ = __webpack_require__("lodash.merge");
11040
+ var external_lodash_merge_default = /*#__PURE__*/ __webpack_require__.n(external_lodash_merge_);
11814
11041
  const config_factory_defaultConfig = {
11815
11042
  concurrent: 1,
11816
11043
  continueOnError: false,
@@ -11833,19 +11060,19 @@ var __webpack_modules__ = {
11833
11060
  return allFiles;
11834
11061
  }
11835
11062
  async function parseConfigYaml(configYamlPath) {
11836
- const basePath = (0, external_node_path_namespaceObject.dirname)((0, external_node_path_namespaceObject.resolve)(configYamlPath));
11837
- const configContent = (0, external_node_fs_namespaceObject.readFileSync)(configYamlPath, 'utf8');
11838
- const interpolatedContent = (0, yaml_namespaceObject.interpolateEnvVars)(configContent);
11063
+ const basePath = (0, external_node_path_.dirname)((0, external_node_path_.resolve)(configYamlPath));
11064
+ const configContent = (0, external_node_fs_.readFileSync)(configYamlPath, 'utf8');
11065
+ const interpolatedContent = (0, yaml_.interpolateEnvVars)(configContent);
11839
11066
  let configYaml;
11840
11067
  try {
11841
- configYaml = js_yaml_load(interpolatedContent);
11068
+ configYaml = load(interpolatedContent);
11842
11069
  } catch (error) {
11843
11070
  throw new Error(`Failed to parse config YAML: ${error}`);
11844
11071
  }
11845
11072
  if (!configYaml?.files || !Array.isArray(configYaml?.files)) throw new Error('Config YAML must contain a "files" array');
11846
11073
  const files = await expandFilePatterns(configYaml?.files, basePath);
11847
11074
  if (0 === files.length) throw new Error('No YAML files found matching the patterns in "files"');
11848
- const configFileName = (0, external_node_path_namespaceObject.basename)(configYamlPath, (0, external_node_path_namespaceObject.extname)(configYamlPath));
11075
+ const configFileName = (0, external_node_path_.basename)(configYamlPath, (0, external_node_path_.extname)(configYamlPath));
11849
11076
  const timestamp = Date.now();
11850
11077
  const defaultSummary = `${configFileName}-${timestamp}.json`;
11851
11078
  const config = {
@@ -11882,7 +11109,7 @@ var __webpack_modules__ = {
11882
11109
  const finalHeaded = keepWindow || headed;
11883
11110
  let files = parsedConfig.files;
11884
11111
  if (options?.files && options.files.length > 0) {
11885
- const basePath = (0, external_node_path_namespaceObject.dirname)((0, external_node_path_namespaceObject.resolve)(configYamlPath));
11112
+ const basePath = (0, external_node_path_.dirname)((0, external_node_path_.resolve)(configYamlPath));
11886
11113
  files = await expandFilePatterns(options.files, basePath);
11887
11114
  }
11888
11115
  return {
@@ -11922,7 +11149,7 @@ var __webpack_modules__ = {
11922
11149
  }
11923
11150
  };
11924
11151
  }
11925
- const cli_utils_debug = (0, logger_namespaceObject.getDebug)('midscene:cli');
11152
+ const debug = (0, logger_.getDebug)('midscene:cli');
11926
11153
  function kebabToCamel(str) {
11927
11154
  return str.replace(/-([a-z])/g, (_, letter)=>letter.toUpperCase());
11928
11155
  }
@@ -11981,7 +11208,7 @@ Usage:
11981
11208
  type: 'boolean',
11982
11209
  description: `Turn on logging to help debug why certain keys or values are not being set as you expect, default is ${config_factory_defaultConfig.dotenvDebug}`
11983
11210
  }
11984
- }).version('version', 'Show version number', "1.8.7").help().epilogue(`For complete list of configuration options, please visit:
11211
+ }).version('version', 'Show version number', "1.8.8-beta-20260601092817.0").help().epilogue(`For complete list of configuration options, please visit:
11985
11212
  • Web options: https://midscenejs.com/automate-with-scripts-in-yaml#the-web-part
11986
11213
  • Android options: https://midscenejs.com/automate-with-scripts-in-yaml#the-android-part
11987
11214
  • iOS options: https://midscenejs.com/automate-with-scripts-in-yaml#the-ios-part
@@ -11991,7 +11218,7 @@ Examples:
11991
11218
  $0 script.yaml --android.device-id emulator-5554 --android.ime-strategy yadb-for-non-ascii
11992
11219
  $0 script.yaml --ios.wda-port 8100 --ios.auto-dismiss-keyboard`).wrap(yargs().terminalWidth());
11993
11220
  const argv = await args.argv;
11994
- cli_utils_debug('argv', argv);
11221
+ debug('argv', argv);
11995
11222
  const transformedArgv = {
11996
11223
  ...argv
11997
11224
  };
@@ -12015,7 +11242,7 @@ Examples:
12015
11242
  };
12016
11243
  };
12017
11244
  async function matchYamlFiles(fileGlob, options) {
12018
- if ((0, external_node_fs_namespaceObject.existsSync)(fileGlob) && (0, external_node_fs_namespaceObject.statSync)(fileGlob).isDirectory()) fileGlob = (0, external_node_path_namespaceObject.join)(fileGlob, '**/*.{yml,yaml}');
11245
+ if ((0, external_node_fs_.existsSync)(fileGlob) && (0, external_node_fs_.statSync)(fileGlob).isDirectory()) fileGlob = (0, external_node_path_.join)(fileGlob, '**/*.{yml,yaml}');
12019
11246
  const { cwd } = options || {};
12020
11247
  const ignore = [
12021
11248
  '**/node_modules/**'
@@ -12029,6 +11256,7 @@ Examples:
12029
11256
  });
12030
11257
  return files.filter((file)=>file.endsWith('.yml') || file.endsWith('.yaml')).sort();
12031
11258
  }
11259
+ var framework = __webpack_require__("./src/framework/index.ts");
12032
11260
  Promise.resolve((async ()=>{
12033
11261
  const rawArgs = process.argv.slice(2);
12034
11262
  const [firstArg] = rawArgs;
@@ -12087,8 +11315,8 @@ Examples:
12087
11315
  console.error('Could not create a valid configuration.');
12088
11316
  process.exit(1);
12089
11317
  }
12090
- const dotEnvConfigFile = (0, external_node_path_namespaceObject.join)(process.cwd(), '.env');
12091
- if ((0, external_node_fs_namespaceObject.existsSync)(dotEnvConfigFile)) {
11318
+ const dotEnvConfigFile = (0, external_node_path_.join)(process.cwd(), '.env');
11319
+ if ((0, external_node_fs_.existsSync)(dotEnvConfigFile)) {
12092
11320
  console.log(` Env file: ${dotEnvConfigFile}`);
12093
11321
  main_default().config({
12094
11322
  path: dotEnvConfigFile,
@@ -12096,21 +11324,24 @@ Examples:
12096
11324
  override: config.dotenvOverride
12097
11325
  });
12098
11326
  }
12099
- const executor = new BatchRunner(config);
12100
- await executor.run();
12101
- const success = executor.printExecutionSummary();
11327
+ const exitCode = await (0, framework.runFrameworkTestConfig)(config);
12102
11328
  if (config.keepWindow) setInterval(()=>{
12103
11329
  console.log('browser is still running, use ctrl+c to stop it');
12104
11330
  }, 5000);
12105
- else {
12106
- if (!success) process.exit(1);
12107
- process.exit(0);
12108
- }
11331
+ else process.exit(exitCode);
12109
11332
  })().catch((e1)=>{
12110
11333
  console.error(e1);
12111
11334
  process.exit(1);
12112
11335
  }));
12113
11336
  },
11337
+ "@midscene/core/yaml" (module) {
11338
+ "use strict";
11339
+ module.exports = require("@midscene/core/yaml");
11340
+ },
11341
+ "@midscene/shared/logger" (module) {
11342
+ "use strict";
11343
+ module.exports = require("@midscene/shared/logger");
11344
+ },
12114
11345
  assert (module) {
12115
11346
  "use strict";
12116
11347
  module.exports = require("assert");
@@ -12123,6 +11354,22 @@ Examples:
12123
11354
  "use strict";
12124
11355
  module.exports = require("fs");
12125
11356
  },
11357
+ "lodash.merge" (module) {
11358
+ "use strict";
11359
+ module.exports = require("lodash.merge");
11360
+ },
11361
+ "node:fs" (module) {
11362
+ "use strict";
11363
+ module.exports = require("node:fs");
11364
+ },
11365
+ "node:path" (module) {
11366
+ "use strict";
11367
+ module.exports = require("node:path");
11368
+ },
11369
+ "node:url" (module) {
11370
+ "use strict";
11371
+ module.exports = require("node:url");
11372
+ },
12126
11373
  os (module) {
12127
11374
  "use strict";
12128
11375
  module.exports = require("os");