@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.
- package/dist/es/framework/index.mjs +16 -279
- package/dist/es/framework/index.mjs.map +1 -1
- package/dist/es/index.mjs +59 -285
- package/dist/es/index.mjs.map +1 -1
- package/dist/lib/framework/index.js +16 -281
- package/dist/lib/framework/index.js.map +1 -1
- package/dist/lib/index.js +56 -284
- package/dist/lib/index.js.map +1 -1
- package/package.json +22 -8
|
@@ -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
|
|
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 {
|
|
10
|
-
import { buildChromeArgs, buildDownloadBehavior, defaultViewportHeight, defaultViewportWidth
|
|
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 {
|
|
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
|
-
|
|
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);
|