@midscene/cli 1.12.10-beta-20260920072249.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.
@@ -1,20 +1,16 @@
1
1
  import * as __rspack_external_os from "os";
2
2
  import * as __rspack_external_tty from "tty";
3
3
  import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
4
- import node_path, { basename, dirname, extname, join, relative, resolve as external_node_path_resolve, sep } from "node:path";
4
+ import { basename, dirname, extname, join, relative, resolve as external_node_path_resolve, sep } from "node:path";
5
5
  import { createHash } from "node:crypto";
6
6
  import { ReportMergingTool } from "@midscene/core";
7
7
  import { getMidsceneRunSubDir } from "@midscene/shared/common";
8
8
  import { getDebug } from "@midscene/shared/logger";
9
- import { ScriptPlayer, parseYamlScript, resolveWebTarget } from "@midscene/core/yaml";
10
- import { buildChromeArgs, buildDownloadBehavior, defaultViewportHeight, defaultViewportWidth, puppeteerAgentForTarget } from "@midscene/web/puppeteer-agent-launcher";
9
+ import { parseYamlScript, resolveWebTarget } from "@midscene/core/yaml";
10
+ import { buildChromeArgs, buildDownloadBehavior, defaultViewportHeight, defaultViewportWidth } from "@midscene/web/puppeteer-agent-launcher";
11
11
  import lodash_merge from "lodash.merge";
12
12
  import puppeteer from "puppeteer";
13
- import { createServer } from "http-server";
14
- import node_assert from "node:assert";
15
- import { createAgent, getReportFileName } from "@midscene/core/agent";
16
- import { processCacheConfig } from "@midscene/core/utils";
17
- import { AgentOverChromeBridge } from "@midscene/web/bridge-mode";
13
+ import { createYamlPlayer } from "@midscene/test/internal/yaml-runtime";
18
14
  import { stripVTControlCharacters } from "node:util";
19
15
  import node_process from "node:process";
20
16
  import { createRequire } from "node:module";
@@ -2841,276 +2837,6 @@ function pLimit(concurrency) {
2841
2837
  function validateConcurrency(concurrency) {
2842
2838
  if (!((Number.isInteger(concurrency) || concurrency === 1 / 0) && concurrency > 0)) throw new TypeError('Expected `concurrency` to be a number from 1 and up');
2843
2839
  }
2844
- const debug = getDebug('create-yaml-player');
2845
- const cleanupWarning = getDebug('create-yaml-player', {
2846
- console: true
2847
- });
2848
- async function cleanupFailedPlayerSetup(freeFn) {
2849
- for (const cleanup of [
2850
- ...freeFn
2851
- ].reverse())try {
2852
- await cleanup.fn();
2853
- } catch (error) {
2854
- cleanupWarning(`failed to run ${cleanup.name} after YAML player setup failed`, error);
2855
- }
2856
- }
2857
- const launchServer = async (dir)=>new Promise((resolve)=>{
2858
- const server = createServer({
2859
- root: dir
2860
- });
2861
- server.listen(0, '127.0.0.1', ()=>{
2862
- resolve(server);
2863
- });
2864
- });
2865
- function resolveReportFileName(yamlReportFileName, cliTestId, yamlTestId, fileName) {
2866
- if (void 0 !== yamlReportFileName) return yamlReportFileName;
2867
- const baseName = cliTestId ?? yamlTestId ?? fileName;
2868
- return getReportFileName(baseName);
2869
- }
2870
- function buildAgentOptions(yamlAgent, reportFileName, fileName) {
2871
- return {
2872
- ...yamlAgent || {},
2873
- cache: processCacheConfig(yamlAgent?.cache, fileName),
2874
- reportFileName
2875
- };
2876
- }
2877
- async function createYamlPlayer(file, script, options) {
2878
- const yamlScript = script || parseYamlScript(readFileSync(file, 'utf-8'), file);
2879
- const clonedYamlScript = structuredClone(yamlScript);
2880
- const fileName = basename(file, extname(file));
2881
- const preference = {
2882
- headed: options?.headed,
2883
- keepWindow: options?.keepWindow,
2884
- reportFileName: resolveReportFileName(clonedYamlScript.agent?.reportFileName, options?.testId, clonedYamlScript.agent?.testId, fileName)
2885
- };
2886
- const player = new ScriptPlayer(clonedYamlScript, async ()=>{
2887
- const freeFn = [];
2888
- try {
2889
- const resolvedWebTarget = resolveWebTarget(clonedYamlScript);
2890
- const webTarget = resolvedWebTarget?.target;
2891
- const targetCount = [
2892
- void 0 !== resolvedWebTarget,
2893
- void 0 !== clonedYamlScript.android,
2894
- void 0 !== clonedYamlScript.ios,
2895
- void 0 !== clonedYamlScript.harmony,
2896
- void 0 !== clonedYamlScript.computer,
2897
- void 0 !== clonedYamlScript.interface
2898
- ].filter(Boolean).length;
2899
- if (targetCount > 1) {
2900
- const specifiedTargets = [
2901
- resolvedWebTarget?.source ?? null,
2902
- void 0 !== clonedYamlScript.android ? 'android' : null,
2903
- void 0 !== clonedYamlScript.ios ? 'ios' : null,
2904
- void 0 !== clonedYamlScript.harmony ? 'harmony' : null,
2905
- void 0 !== clonedYamlScript.computer ? 'computer' : null,
2906
- void 0 !== clonedYamlScript.interface ? 'interface' : null
2907
- ].filter(Boolean);
2908
- 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.`);
2909
- }
2910
- if (void 0 !== webTarget) {
2911
- if (resolvedWebTarget?.source === 'target') console.warn("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.");
2912
- let localServer;
2913
- let urlToVisit;
2914
- if (webTarget.serve) {
2915
- node_assert('string' == typeof webTarget.url, 'url is required in serve mode');
2916
- localServer = await launchServer(webTarget.serve);
2917
- const serverAddress = localServer.server.address();
2918
- freeFn.push({
2919
- name: 'local_server',
2920
- fn: async ()=>{
2921
- if (!localServer) return;
2922
- await new Promise((resolve, reject)=>{
2923
- localServer?.server.close((error)=>{
2924
- if (error) reject(error);
2925
- else resolve();
2926
- });
2927
- });
2928
- }
2929
- });
2930
- urlToVisit = webTarget.url.startsWith('/') ? `http://${serverAddress?.address}:${serverAddress?.port}${webTarget.url}` : `http://${serverAddress?.address}:${serverAddress?.port}/${webTarget.url}`;
2931
- webTarget.url = urlToVisit;
2932
- }
2933
- if (webTarget.cdpEndpoint && webTarget.bridgeMode) throw new Error('cdpEndpoint and bridgeMode are mutually exclusive. Please specify only one.');
2934
- 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.');
2935
- if (webTarget.cdpEndpoint) {
2936
- const ownsCdpBrowser = !options?.browser;
2937
- const cdpBrowser = options?.browser ?? await puppeteer.connect({
2938
- browserWSEndpoint: webTarget.cdpEndpoint,
2939
- defaultViewport: null,
2940
- downloadBehavior: buildDownloadBehavior(webTarget.downloadPath)
2941
- });
2942
- if (webTarget.chromeArgs) console.warn('chromeArgs are not supported in CDP mode (browser is already running). They will be ignored.');
2943
- let launchedAgent;
2944
- try {
2945
- launchedAgent = await puppeteerAgentForTarget(webTarget, {
2946
- ...preference,
2947
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
2948
- }, cdpBrowser, options?.page, options?.browserContext);
2949
- } catch (error) {
2950
- if (ownsCdpBrowser) try {
2951
- cdpBrowser.disconnect();
2952
- } catch (cleanupError) {
2953
- cleanupWarning('failed to disconnect the CDP browser after agent setup failed', cleanupError);
2954
- }
2955
- throw error;
2956
- }
2957
- const { agent, freeFn: newFreeFn } = launchedAgent;
2958
- const cleanFreeFn = newFreeFn.filter((f)=>'puppeteer_browser' !== f.name);
2959
- freeFn.push(...cleanFreeFn);
2960
- if (ownsCdpBrowser) freeFn.push({
2961
- name: 'cdp_browser_disconnect',
2962
- fn: ()=>cdpBrowser.disconnect()
2963
- });
2964
- return {
2965
- agent,
2966
- freeFn
2967
- };
2968
- }
2969
- if (!webTarget.bridgeMode) {
2970
- const { agent, freeFn: newFreeFn } = await puppeteerAgentForTarget(webTarget, {
2971
- ...preference,
2972
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
2973
- }, options?.browser, options?.page, options?.browserContext);
2974
- freeFn.push(...newFreeFn);
2975
- return {
2976
- agent,
2977
- freeFn
2978
- };
2979
- }
2980
- node_assert('newTabWithUrl' === webTarget.bridgeMode || 'currentTab' === webTarget.bridgeMode, `bridgeMode config value must be either "newTabWithUrl" or "currentTab", but got ${webTarget.bridgeMode}`);
2981
- const bridgeUnsupportedKeys = [
2982
- 'userAgent',
2983
- 'viewportWidth',
2984
- 'viewportHeight',
2985
- 'deviceScaleFactor',
2986
- 'waitForNetworkIdle',
2987
- 'cookie',
2988
- 'extraHTTPHeaders',
2989
- 'downloadPath',
2990
- 'chromeArgs'
2991
- ];
2992
- const ignoredKeys = bridgeUnsupportedKeys.filter((key)=>null != webTarget[key]);
2993
- if (ignoredKeys.length > 0) console.warn(`puppeteer options (${ignoredKeys.join(', ')}) are not supported in bridge mode. They will be ignored.`);
2994
- const agent = new AgentOverChromeBridge({
2995
- closeNewTabsAfterDisconnect: webTarget.closeNewTabsAfterDisconnect,
2996
- closeConflictServer: true,
2997
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
2998
- });
2999
- if ('newTabWithUrl' === webTarget.bridgeMode) await agent.connectNewTabWithUrl(webTarget.url);
3000
- else {
3001
- if (webTarget.url) console.warn('url will be ignored in bridge mode with "currentTab"');
3002
- await agent.connectCurrentTab();
3003
- }
3004
- freeFn.push({
3005
- name: 'destroy_agent_over_chrome_bridge',
3006
- fn: ()=>agent.destroy()
3007
- });
3008
- return {
3009
- agent,
3010
- freeFn
3011
- };
3012
- }
3013
- if (void 0 !== clonedYamlScript.android) {
3014
- const androidTarget = clonedYamlScript.android;
3015
- const { agentFromAdbDevice } = await import("@midscene/android");
3016
- const agent = await agentFromAdbDevice(androidTarget?.deviceId, {
3017
- ...androidTarget,
3018
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3019
- });
3020
- if (androidTarget?.launch) await agent.launch(androidTarget.launch);
3021
- freeFn.push({
3022
- name: 'destroy_android_agent',
3023
- fn: ()=>agent.destroy()
3024
- });
3025
- return {
3026
- agent,
3027
- freeFn
3028
- };
3029
- }
3030
- if (void 0 !== clonedYamlScript.ios) {
3031
- const iosTarget = clonedYamlScript.ios;
3032
- const { agentFromWebDriverAgent } = await import("@midscene/ios");
3033
- const agent = await agentFromWebDriverAgent({
3034
- ...iosTarget,
3035
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3036
- });
3037
- if (iosTarget?.launch) await agent.launch(iosTarget.launch);
3038
- freeFn.push({
3039
- name: 'destroy_ios_agent',
3040
- fn: ()=>agent.destroy()
3041
- });
3042
- return {
3043
- agent,
3044
- freeFn
3045
- };
3046
- }
3047
- if (void 0 !== clonedYamlScript.harmony) {
3048
- const harmonyTarget = clonedYamlScript.harmony;
3049
- const { agentFromHdcDevice } = await import("@midscene/harmony");
3050
- const agent = await agentFromHdcDevice(harmonyTarget?.deviceId, {
3051
- ...harmonyTarget,
3052
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3053
- });
3054
- if (harmonyTarget?.launch) await agent.launch(harmonyTarget.launch);
3055
- freeFn.push({
3056
- name: 'destroy_harmony_agent',
3057
- fn: ()=>agent.destroy()
3058
- });
3059
- return {
3060
- agent,
3061
- freeFn
3062
- };
3063
- }
3064
- if (void 0 !== clonedYamlScript.computer) {
3065
- const computerTarget = clonedYamlScript.computer;
3066
- const { agentForComputer } = await import("@midscene/computer");
3067
- const agent = await agentForComputer({
3068
- ...computerTarget,
3069
- ...buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName)
3070
- });
3071
- freeFn.push({
3072
- name: 'destroy_computer_agent',
3073
- fn: ()=>agent.destroy()
3074
- });
3075
- return {
3076
- agent,
3077
- freeFn
3078
- };
3079
- }
3080
- if (void 0 !== clonedYamlScript.interface) {
3081
- const interfaceTarget = clonedYamlScript.interface;
3082
- const moduleSpecifier = interfaceTarget.module;
3083
- let finalModuleSpecifier;
3084
- if (moduleSpecifier.startsWith('./') || moduleSpecifier.startsWith('../') || node_path.isAbsolute(moduleSpecifier)) {
3085
- const resolvedPath = join(process.cwd(), moduleSpecifier);
3086
- finalModuleSpecifier = resolvedPath;
3087
- } else finalModuleSpecifier = moduleSpecifier;
3088
- debug('importing module config', interfaceTarget.module, 'with export config', interfaceTarget.export, 'final module specifier', finalModuleSpecifier);
3089
- const importedModule = await import(finalModuleSpecifier);
3090
- const DeviceClass = interfaceTarget.export ? importedModule[interfaceTarget.export] : importedModule.default || importedModule;
3091
- debug('DeviceClass', DeviceClass, 'with param', interfaceTarget.param);
3092
- const device = new DeviceClass(interfaceTarget.param || {});
3093
- debug('creating agent from device', device);
3094
- const agent = createAgent(device, buildAgentOptions(clonedYamlScript.agent, preference.reportFileName, fileName));
3095
- freeFn.push({
3096
- name: 'destroy_general_interface_agent',
3097
- fn: ()=>{
3098
- agent.destroy();
3099
- }
3100
- });
3101
- return {
3102
- agent,
3103
- freeFn
3104
- };
3105
- }
3106
- throw new Error('No valid interface configuration found in the yaml script, should be either "web", "android", "ios", "harmony", "computer", or "interface"');
3107
- } catch (error) {
3108
- await cleanupFailedPlayerSetup(freeFn);
3109
- throw error;
3110
- }
3111
- }, void 0, file);
3112
- return player;
3113
- }
3114
2840
  var chalk_source = __webpack_require__("../../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/source/index.js");
3115
2841
  var source_default = /*#__PURE__*/ __webpack_require__.n(chalk_source);
3116
2842
  function resolveIsTTY(env = process.env, stdoutIsTTY = process.stdout.isTTY) {
@@ -4463,6 +4189,7 @@ const taskErrorMessage = (task)=>{
4463
4189
  if ('error' === task.status) return `Task "${task.name}" failed`;
4464
4190
  };
4465
4191
  const cloneJson = (value)=>JSON.parse(JSON.stringify(value));
4192
+ const yaml_case_errorMessageOf = (error)=>error instanceof Error ? error.message : String(error);
4466
4193
  const normalizeTargetConfig = (config)=>{
4467
4194
  if (config.target) {
4468
4195
  config.web = {
@@ -4533,12 +4260,22 @@ const executeYamlCaseResult = async (options, onPlayerSnapshot)=>{
4533
4260
  });
4534
4261
  };
4535
4262
  reportSnapshot();
4263
+ let runError;
4536
4264
  try {
4537
4265
  await player.run();
4266
+ } catch (error) {
4267
+ runError = error;
4538
4268
  } finally{
4539
4269
  reportSnapshot();
4540
4270
  }
4541
- return createYamlCaseResult(file, player, Date.now() - startTime);
4271
+ const result = createYamlCaseResult(file, player, Date.now() - startTime);
4272
+ if (void 0 === runError) return result;
4273
+ return {
4274
+ ...result,
4275
+ success: false,
4276
+ resultType: 'failed',
4277
+ error: result.error ?? yaml_case_errorMessageOf(runError)
4278
+ };
4542
4279
  };
4543
4280
  function runYamlCaseResultWithSnapshots(options, onPlayerSnapshot) {
4544
4281
  return executeYamlCaseResult(options, onPlayerSnapshot);