@midscene/test 1.12.6-beta-20260910093036.0 → 1.12.6
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/1~891.mjs +19 -39
- package/dist/es/1~891.mjs.map +1 -1
- package/dist/es/cli/index.mjs +26 -44
- package/dist/es/cli/index.mjs.map +1 -1
- package/dist/es/index.mjs +0 -3
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/midscene/index.mjs +43 -32
- package/dist/es/midscene/index.mjs.map +1 -1
- package/dist/es/test-cli.mjs +37 -49
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/0~458.js +19 -39
- package/dist/lib/0~458.js.map +1 -1
- package/dist/lib/cli/index.js +26 -44
- package/dist/lib/cli/index.js.map +1 -1
- package/dist/lib/index.js +0 -3
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/midscene/index.js +253 -262
- package/dist/lib/midscene/index.js.map +1 -1
- package/dist/lib/test-cli.js +37 -49
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/types/cli/index.d.ts +12 -13
- package/dist/types/index.d.ts +1 -7
- package/dist/types/midscene/index.d.ts +21 -21
- package/package.json +8 -48
- package/dist/es/android/index.mjs +0 -182
- package/dist/es/android/index.mjs.map +0 -1
- package/dist/es/harmony/index.mjs +0 -182
- package/dist/es/harmony/index.mjs.map +0 -1
- package/dist/es/ios/index.mjs +0 -182
- package/dist/es/ios/index.mjs.map +0 -1
- package/dist/es/playwright/index.mjs +0 -366
- package/dist/es/playwright/index.mjs.map +0 -1
- package/dist/lib/android/index.js +0 -255
- package/dist/lib/android/index.js.map +0 -1
- package/dist/lib/harmony/index.js +0 -252
- package/dist/lib/harmony/index.js.map +0 -1
- package/dist/lib/ios/index.js +0 -255
- package/dist/lib/ios/index.js.map +0 -1
- package/dist/lib/playwright/index.js +0 -412
- package/dist/lib/playwright/index.js.map +0 -1
- package/dist/types/android/index.d.ts +0 -140
- package/dist/types/harmony/index.d.ts +0 -137
- package/dist/types/ios/index.d.ts +0 -143
- package/dist/types/playwright/index.d.ts +0 -229
package/dist/lib/0~458.js
CHANGED
|
@@ -49,7 +49,9 @@ exports.modules = {
|
|
|
49
49
|
function detectPackageManager(userAgent) {
|
|
50
50
|
return userAgent?.startsWith('pnpm/') ? 'pnpm' : 'npm';
|
|
51
51
|
}
|
|
52
|
-
var package_namespaceObject =
|
|
52
|
+
var package_namespaceObject = {
|
|
53
|
+
rE: "1.12.6"
|
|
54
|
+
};
|
|
53
55
|
const createPlatforms = [
|
|
54
56
|
'web',
|
|
55
57
|
'android',
|
|
@@ -64,17 +66,8 @@ exports.modules = {
|
|
|
64
66
|
harmony: 'HarmonyOS',
|
|
65
67
|
computer: 'Desktop (Computer)'
|
|
66
68
|
};
|
|
67
|
-
function parseNodePackageSpec(spec) {
|
|
68
|
-
const match = /^(?:(@[a-z0-9][a-z0-9._-]*)\/)?([a-z0-9][a-z0-9._-]*)(?:@([a-zA-Z0-9^~*>=<|. +_-]+))?$/.exec(spec);
|
|
69
|
-
if (!match || void 0 !== match[3] && !match[3].trim()) throw new Error(`Invalid Node package "${spec}". Use an npm package name with an optional version, such as @acme/test-nodes@1.2.0.`);
|
|
70
|
-
return {
|
|
71
|
-
name: `${match[1] ? `${match[1]}/` : ''}${match[2]}`,
|
|
72
|
-
version: match[3] ?? 'latest'
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
69
|
const platformImports = {
|
|
76
70
|
web: `import { PlaywrightAgent } from '@midscene/web/playwright/agent';
|
|
77
|
-
import { createPlaywrightNodes } from '@midscene/test/playwright';
|
|
78
71
|
import { chromium, type Page } from 'playwright';`,
|
|
79
72
|
android: "import { AndroidAgent, agentFromAdbDevice } from '@midscene/android';",
|
|
80
73
|
ios: "import { IOSAgent, agentFromWebDriverAgent } from '@midscene/ios';",
|
|
@@ -124,24 +117,32 @@ import { chromium, type Page } from 'playwright';`,
|
|
|
124
117
|
harmony: '# Optional: choose a device from hdc list targets.\nHARMONY_DEVICE_ID=\n',
|
|
125
118
|
computer: '# Optional: select a display; leave empty for the default display.\nCOMPUTER_DISPLAY_ID=\n# Enable Xvfb on headless Linux after installing its dependencies.\nMIDSCENE_COMPUTER_HEADLESS_LINUX=false\n'
|
|
126
119
|
};
|
|
120
|
+
const modelEnv = `# Copy this file to .env before running tests.
|
|
121
|
+
# Do not commit .env to your repository. In CI, inject these values as environment variables instead.
|
|
122
|
+
# Choose a supported model and copy its four configuration values from:
|
|
123
|
+
# https://midscenejs.com/model-common-config.html
|
|
124
|
+
# For the complete environment variable reference, see:
|
|
125
|
+
# https://midscenejs.com/model-config.html
|
|
126
|
+
MIDSCENE_MODEL_BASE_URL=
|
|
127
|
+
MIDSCENE_MODEL_API_KEY=
|
|
128
|
+
MIDSCENE_MODEL_NAME=
|
|
129
|
+
MIDSCENE_MODEL_FAMILY=
|
|
130
|
+
`;
|
|
127
131
|
const platformInstructions = {
|
|
128
132
|
android: 'Connect an Android device and verify it with `adb devices`. Set ANDROID_DEVICE_ID to select a device.',
|
|
129
133
|
ios: 'Start WebDriverAgent and set WDA_HOST and WDA_PORT for your iOS device.',
|
|
130
134
|
harmony: 'Connect a HarmonyOS device and verify it with `hdc list targets`. Set HARMONY_DEVICE_ID to select a device. Set HDC_HOME if hdc is not on PATH.',
|
|
131
135
|
computer: 'Prepare the local desktop using the [desktop setup guide](https://midscenejs.com/platforms/desktop.html), including system dependencies and permissions. Set COMPUTER_DISPLAY_ID to select a display. For headless Linux, install Xvfb and enable MIDSCENE_COMPUTER_HEADLESS_LINUX.'
|
|
132
136
|
};
|
|
133
|
-
function createProjectFiles(name, platform,
|
|
137
|
+
function createProjectFiles(name, platform, packageManager) {
|
|
134
138
|
const instructions = 'web' === platform ? `Install Chromium before the first test: \`${packageManagerCommands[packageManager].installChromium}\`.` : platformInstructions[platform];
|
|
135
139
|
const agentClass = agentClasses[platform];
|
|
136
|
-
const imports = packages.map((pkg, index)=>`import { createMidsceneTestNodes as createPackageNodes${index} } from ${JSON.stringify(pkg.name)};`).join('\n');
|
|
137
|
-
const extraNodes = packages.map((_, index)=>` ...createPackageNodes${index}<ProjectContext>({ platform: '${platform}', getAgent }),`).join('\n');
|
|
138
140
|
const config = `import { fileURLToPath } from 'node:url';
|
|
139
141
|
import { config as loadEnv } from 'dotenv';
|
|
140
142
|
import type { NodeExecutionContext } from '@midscene/test';
|
|
141
143
|
import { defineProjectSetup, defineTestProject } from '@midscene/test/config';
|
|
142
144
|
import { createMidsceneNodes } from '@midscene/test/midscene';
|
|
143
145
|
${platformImports[platform]}
|
|
144
|
-
${imports}
|
|
145
146
|
|
|
146
147
|
loadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });
|
|
147
148
|
|
|
@@ -156,7 +157,6 @@ ${'web' === platform ? ' context.agent ??= new PlaywrightAgent(context.page);'
|
|
|
156
157
|
|
|
157
158
|
const setup = defineProjectSetup<ProjectContext>({
|
|
158
159
|
name: '${platform}',
|
|
159
|
-
platform: '${platform}',
|
|
160
160
|
async setup({ env, onTeardown }) {
|
|
161
161
|
${platformSetup[platform]}
|
|
162
162
|
},
|
|
@@ -165,14 +165,11 @@ ${platformSetup[platform]}
|
|
|
165
165
|
export default defineTestProject<ProjectContext>({
|
|
166
166
|
projects: [{
|
|
167
167
|
name: '${platform}',
|
|
168
|
-
platform: '${platform}',
|
|
169
168
|
setup,
|
|
170
169
|
files: { include: ['cases/**/*.{yaml,yml}'] },
|
|
171
170
|
}],
|
|
172
171
|
nodes: [
|
|
173
172
|
...createMidsceneNodes<ProjectContext>({ agentClass: ${agentClass}, getAgent }),
|
|
174
|
-
${'web' === platform ? ' ...createPlaywrightNodes<ProjectContext>({ getPage: ({ context }) => context.page }),' : ''}
|
|
175
|
-
${extraNodes}
|
|
176
173
|
],
|
|
177
174
|
});
|
|
178
175
|
`;
|
|
@@ -186,10 +183,6 @@ ${extraNodes}
|
|
|
186
183
|
playwright: '^1.45.0'
|
|
187
184
|
} : {}
|
|
188
185
|
};
|
|
189
|
-
for (const pkg of packages){
|
|
190
|
-
if (Object.hasOwn(dependencies, pkg.name)) throw new Error(`Node package "${pkg.name}" conflicts with a generated project dependency.`);
|
|
191
|
-
dependencies[pkg.name] = pkg.version;
|
|
192
|
-
}
|
|
193
186
|
const env = platformEnv[platform];
|
|
194
187
|
return {
|
|
195
188
|
'package.json': `${JSON.stringify({
|
|
@@ -224,9 +217,9 @@ ${extraNodes}
|
|
|
224
217
|
]
|
|
225
218
|
}, null, 2)}\n`,
|
|
226
219
|
'cases/example.yaml': 'web' === platform ? 'cases:\n - name: Open example.com\n steps:\n - gotoUrl: https://example.com\n - aiAssert: The page displays the heading Example Domain\n' : 'computer' === platform ? 'cases:\n - name: Inspect the desktop\n steps:\n - aiAsk: Describe the current desktop\n' : 'cases:\n - name: Inspect the home screen\n steps:\n - home: {}\n - aiAsk: Describe the current screen\n',
|
|
227
|
-
'.env.example':
|
|
220
|
+
'.env.example': `${modelEnv}\n${env}`,
|
|
228
221
|
'.gitignore': 'node_modules/\n.env\nmidscene_run/\n',
|
|
229
|
-
'README.md': `# ${name}\n\nA Midscene Test project for ${platform}.\n\nIf you skipped installation during creation, run \`${packageManager} ${packageManagerCommands[packageManager].install.join(' ')}\`. The \`postinstall\` script automatically generates \`midscene-node-reference.md\` after each dependency installation. If lifecycle scripts are disabled, run \`${packageManager} run nodes\` manually.\n\nCopy \`.env.example\` to \`.env
|
|
222
|
+
'README.md': `# ${name}\n\nA Midscene Test project for ${platform}.\n\nIf you skipped installation during creation, run \`${packageManager} ${packageManagerCommands[packageManager].install.join(' ')}\`. The \`postinstall\` script automatically generates \`midscene-node-reference.md\` after each dependency installation. If lifecycle scripts are disabled, run \`${packageManager} run nodes\` manually.\n\nCopy \`.env.example\` to \`.env\`, then choose and configure a model using the [supported models and setup guide](https://midscenejs.com/model-common-config.html). Do not commit \`.env\` to your repository; inject the model configuration as environment variables in CI.\n\n${instructions}\n\nRun tests with \`${packageManager} test\`. Read \`midscene-node-reference.md\` for the available Nodes and their inputs.\n\nAfter changing Node registrations in \`midscene.config.ts\`, run \`${packageManager} run nodes\` to refresh the reference. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.\n\nReports are written to \`midscene_run/report/\`.\n`
|
|
230
223
|
};
|
|
231
224
|
}
|
|
232
225
|
const createParser = ()=>yargs_default()().scriptName('midscene-test create').usage('Usage: $0 [directory] [options]').parserConfiguration({
|
|
@@ -239,12 +232,6 @@ ${extraNodes}
|
|
|
239
232
|
choices: createPlatforms,
|
|
240
233
|
requiresArg: true,
|
|
241
234
|
description: 'Platform preset (prompt if omitted)'
|
|
242
|
-
}).option('with', {
|
|
243
|
-
type: 'array',
|
|
244
|
-
string: true,
|
|
245
|
-
nargs: 1,
|
|
246
|
-
requiresArg: true,
|
|
247
|
-
description: 'npm Node package, optionally versioned (repeatable)'
|
|
248
235
|
}).option('package-manager', {
|
|
249
236
|
type: 'string',
|
|
250
237
|
choices: createPackageManagers,
|
|
@@ -259,7 +246,7 @@ ${extraNodes}
|
|
|
259
246
|
type: 'boolean',
|
|
260
247
|
default: false,
|
|
261
248
|
description: 'Create files without installing dependencies or generating midscene-node-reference.md'
|
|
262
|
-
}).demandCommand(0, 1, '', 'Only one project directory is allowed.').example('$0', 'Choose a directory and platform interactively').example('$0 my-tests --platform web', 'Create a Web project').
|
|
249
|
+
}).demandCommand(0, 1, '', 'Only one project directory is allowed.').example('$0', 'Choose a directory and platform interactively').example('$0 my-tests --platform web', 'Create a Web project').epilogue("Optionally installs dependencies with the selected package manager. The generated postinstall script creates midscene-node-reference.md after installation.\nExisting files are never overwritten. Setup and tests are not run during creation.").help('help').alias('help', 'h').version(false).strict().exitProcess(false).showHelpOnFail(false).fail((message, error)=>{
|
|
263
250
|
throw error ?? new Error(message);
|
|
264
251
|
}).wrap(100);
|
|
265
252
|
function parseCreateArgs(args, onOutput) {
|
|
@@ -269,17 +256,10 @@ ${extraNodes}
|
|
|
269
256
|
});
|
|
270
257
|
const directory = void 0 === values._[0] ? void 0 : String(values._[0]);
|
|
271
258
|
if (void 0 !== directory && !directory.trim()) throw new Error('Project directory must not be empty.');
|
|
272
|
-
const packages = (values.with ?? []).map(parseNodePackageSpec);
|
|
273
|
-
const seen = new Set();
|
|
274
|
-
for (const pkg of packages){
|
|
275
|
-
if (seen.has(pkg.name)) throw new Error(`Node package "${pkg.name}" was specified more than once.`);
|
|
276
|
-
seen.add(pkg.name);
|
|
277
|
-
}
|
|
278
259
|
return {
|
|
279
260
|
directory,
|
|
280
261
|
platform: values.platform,
|
|
281
262
|
packageManager: values['package-manager'],
|
|
282
|
-
packages,
|
|
283
263
|
yes: values.yes ?? false,
|
|
284
264
|
skipInstall: values['skip-install'] ?? false,
|
|
285
265
|
help: true === values.help
|
|
@@ -376,7 +356,7 @@ ${extraNodes}
|
|
|
376
356
|
const root = (0, external_node_path_.resolve)(services.cwd, directory);
|
|
377
357
|
const name = (0, external_node_path_.basename)(root).toLowerCase().replace(/[^a-z0-9._-]/g, '-').replace(/^[._-]+/, '') || 'midscene-tests';
|
|
378
358
|
const commands = packageManagerCommands[packageManager];
|
|
379
|
-
const files = createProjectFiles(name, platform,
|
|
359
|
+
const files = createProjectFiles(name, platform, packageManager);
|
|
380
360
|
checkDestinations(root, [
|
|
381
361
|
...Object.keys(files),
|
|
382
362
|
'midscene-node-reference.md',
|
package/dist/lib/0~458.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"0~458.js","sources":["../../src/cli/create-package-manager.ts","../../src/cli/create-template.ts","../../src/cli/create-command.ts"],"sourcesContent":["export const createPackageManagers = ['npm', 'pnpm'] as const;\nexport type CreatePackageManager = (typeof createPackageManagers)[number];\n\nexport const packageManagerCommands: Record<\n CreatePackageManager,\n { install: string[]; describe: string[]; installChromium: string }\n> = {\n npm: {\n install: ['install', '--workspaces=false'],\n describe: [\n 'exec',\n '--no',\n '--workspaces=false',\n '--',\n 'midscene-test',\n 'nodes',\n ],\n installChromium: 'npm exec -- playwright install chromium',\n },\n pnpm: {\n install: ['install', '--ignore-workspace'],\n describe: ['exec', 'midscene-test', 'nodes'],\n installChromium: 'pnpm exec playwright install chromium',\n },\n};\n\nexport function detectPackageManager(userAgent?: string): CreatePackageManager {\n return userAgent?.startsWith('pnpm/') ? 'pnpm' : 'npm';\n}\n","import { version } from '../../package.json';\nimport {\n type CreatePackageManager,\n packageManagerCommands,\n} from './create-package-manager';\n\nexport const createPlatforms = [\n 'web',\n 'android',\n 'ios',\n 'harmony',\n 'computer',\n] as const;\nexport type CreatePlatform = (typeof createPlatforms)[number];\n\nexport const createPlatformLabels: Record<CreatePlatform, string> = {\n web: 'Web (Playwright)',\n android: 'Android',\n ios: 'iOS',\n harmony: 'HarmonyOS',\n computer: 'Desktop (Computer)',\n};\n\nexport interface NodePackageSpec {\n name: string;\n version: string;\n}\n\n/** Registry packages only: keep the install spec separate from the import name. */\nexport function parseNodePackageSpec(spec: string): NodePackageSpec {\n const match =\n /^(?:(@[a-z0-9][a-z0-9._-]*)\\/)?([a-z0-9][a-z0-9._-]*)(?:@([a-zA-Z0-9^~*>=<|. +_-]+))?$/.exec(\n spec,\n );\n if (!match || (match[3] !== undefined && !match[3].trim())) {\n throw new Error(\n `Invalid Node package \"${spec}\". Use an npm package name with an optional version, such as @acme/test-nodes@1.2.0.`,\n );\n }\n return {\n name: `${match[1] ? `${match[1]}/` : ''}${match[2]}`,\n version: match[3] ?? 'latest',\n };\n}\n\nconst platformImports: Record<CreatePlatform, string> = {\n web: `import { PlaywrightAgent } from '@midscene/web/playwright/agent';\nimport { createPlaywrightNodes } from '@midscene/test/playwright';\nimport { chromium, type Page } from 'playwright';`,\n android: `import { AndroidAgent, agentFromAdbDevice } from '@midscene/android';`,\n ios: `import { IOSAgent, agentFromWebDriverAgent } from '@midscene/ios';`,\n harmony: `import { HarmonyAgent, agentFromHdcDevice } from '@midscene/harmony';`,\n computer: `import { ComputerAgent, agentForComputer } from '@midscene/computer';`,\n};\n\nconst agentClasses: Record<CreatePlatform, string> = {\n web: 'PlaywrightAgent',\n android: 'AndroidAgent',\n ios: 'IOSAgent',\n harmony: 'HarmonyAgent',\n computer: 'ComputerAgent',\n};\n\nconst platformSetup: Record<CreatePlatform, string> = {\n web: ` const browser = await chromium.launch({ headless: env.HEADLESS !== 'false' });\n onTeardown(() => browser.close());\n const page = await browser.newPage();\n const context: ProjectContext = { page };\n onTeardown(async () => { await context.agent?.destroy(); });\n return context;`,\n android: ` const agent = await agentFromAdbDevice(env.ANDROID_DEVICE_ID || undefined);\n onTeardown(() => agent.destroy());\n return { agent };`,\n ios: ` const port = Number(env.WDA_PORT || 8100);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error('WDA_PORT must be an integer between 1 and 65535.');\n }\n const agent = await agentFromWebDriverAgent({\n wdaHost: env.WDA_HOST || 'localhost',\n wdaPort: port,\n });\n onTeardown(() => agent.destroy());\n return { agent };`,\n harmony: ` const agent = await agentFromHdcDevice(env.HARMONY_DEVICE_ID || undefined);\n onTeardown(() => agent.destroy());\n return { agent };`,\n computer: ` const agent = await agentForComputer({\n displayId: env.COMPUTER_DISPLAY_ID || undefined,\n });\n onTeardown(() => agent.destroy());\n return { agent };`,\n};\n\nconst platformEnv: Record<CreatePlatform, string> = {\n web: 'HEADLESS=true\\n',\n android:\n '# Optional: choose a device from adb devices.\\nANDROID_DEVICE_ID=\\n',\n ios: 'WDA_HOST=localhost\\nWDA_PORT=8100\\n',\n harmony:\n '# Optional: choose a device from hdc list targets.\\nHARMONY_DEVICE_ID=\\n',\n computer:\n '# Optional: select a display; leave empty for the default display.\\nCOMPUTER_DISPLAY_ID=\\n# Enable Xvfb on headless Linux after installing its dependencies.\\nMIDSCENE_COMPUTER_HEADLESS_LINUX=false\\n',\n};\n\nconst platformInstructions: Record<Exclude<CreatePlatform, 'web'>, string> = {\n android:\n 'Connect an Android device and verify it with `adb devices`. Set ANDROID_DEVICE_ID to select a device.',\n ios: 'Start WebDriverAgent and set WDA_HOST and WDA_PORT for your iOS device.',\n harmony:\n 'Connect a HarmonyOS device and verify it with `hdc list targets`. Set HARMONY_DEVICE_ID to select a device. Set HDC_HOME if hdc is not on PATH.',\n computer:\n 'Prepare the local desktop using the [desktop setup guide](https://midscenejs.com/platforms/desktop.html), including system dependencies and permissions. Set COMPUTER_DISPLAY_ID to select a display. For headless Linux, install Xvfb and enable MIDSCENE_COMPUTER_HEADLESS_LINUX.',\n};\n\nexport function createProjectFiles(\n name: string,\n platform: CreatePlatform,\n packages: readonly NodePackageSpec[],\n packageManager: CreatePackageManager,\n): Record<string, string> {\n const instructions =\n platform === 'web'\n ? `Install Chromium before the first test: \\`${packageManagerCommands[packageManager].installChromium}\\`.`\n : platformInstructions[platform];\n const agentClass = agentClasses[platform];\n const imports = packages\n .map(\n (pkg, index) =>\n `import { createMidsceneTestNodes as createPackageNodes${index} } from ${JSON.stringify(pkg.name)};`,\n )\n .join('\\n');\n const extraNodes = packages\n .map(\n (_, index) =>\n ` ...createPackageNodes${index}<ProjectContext>({ platform: '${platform}', getAgent }),`,\n )\n .join('\\n');\n const config = `import { fileURLToPath } from 'node:url';\nimport { config as loadEnv } from 'dotenv';\nimport type { NodeExecutionContext } from '@midscene/test';\nimport { defineProjectSetup, defineTestProject } from '@midscene/test/config';\nimport { createMidsceneNodes } from '@midscene/test/midscene';\n${platformImports[platform]}\n${imports}\n\nloadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });\n\ninterface ProjectContext {\n${platform === 'web' ? ' page: Page;\\n agent?: PlaywrightAgent;' : ` agent: ${agentClass};`}\n}\n\nconst getAgent = ({ context }: NodeExecutionContext<unknown, ProjectContext>) => {\n${platform === 'web' ? ' context.agent ??= new PlaywrightAgent(context.page);' : ''}\n return context.agent;\n};\n\nconst setup = defineProjectSetup<ProjectContext>({\n name: '${platform}',\n platform: '${platform}',\n async setup({ env, onTeardown }) {\n${platformSetup[platform]}\n },\n});\n\nexport default defineTestProject<ProjectContext>({\n projects: [{\n name: '${platform}',\n platform: '${platform}',\n setup,\n files: { include: ['cases/**/*.{yaml,yml}'] },\n }],\n nodes: [\n ...createMidsceneNodes<ProjectContext>({ agentClass: ${agentClass}, getAgent }),\n${platform === 'web' ? ' ...createPlaywrightNodes<ProjectContext>({ getPage: ({ context }) => context.page }),' : ''}\n${extraNodes}\n ],\n});\n`;\n const dependencies: Record<string, string> = {\n '@midscene/test': version,\n [`@midscene/${platform}`]: version,\n '@types/node': '^20.0.0',\n dotenv: '^16.4.5',\n typescript: '^5.8.3',\n ...(platform === 'web' ? { playwright: '^1.45.0' } : {}),\n };\n for (const pkg of packages) {\n if (Object.hasOwn(dependencies, pkg.name)) {\n throw new Error(\n `Node package \"${pkg.name}\" conflicts with a generated project dependency.`,\n );\n }\n dependencies[pkg.name] = pkg.version;\n }\n const env = platformEnv[platform];\n return {\n 'package.json': `${JSON.stringify(\n {\n name,\n private: true,\n type: 'module',\n scripts: {\n test: 'midscene-test',\n nodes: 'midscene-test nodes',\n postinstall: 'midscene-test nodes',\n },\n engines: { node: '^20.19.0 || ^22.12.0 || >=24.0.0' },\n devDependencies: dependencies,\n },\n null,\n 2,\n )}\\n`,\n 'midscene.config.ts': config,\n 'tsconfig.json': `${JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'Bundler',\n strict: true,\n noEmit: true,\n skipLibCheck: true,\n types: ['node'],\n },\n include: ['midscene.config.ts'],\n },\n null,\n 2,\n )}\\n`,\n 'cases/example.yaml':\n platform === 'web'\n ? 'cases:\\n - name: Open example.com\\n steps:\\n - gotoUrl: https://example.com\\n - aiAssert: The page displays the heading Example Domain\\n'\n : platform === 'computer'\n ? 'cases:\\n - name: Inspect the desktop\\n steps:\\n - aiAsk: Describe the current desktop\\n'\n : 'cases:\\n - name: Inspect the home screen\\n steps:\\n - home: {}\\n - aiAsk: Describe the current screen\\n',\n '.env.example': `# Copy this file to .env and configure your model before running tests.\\n# See https://midscenejs.com/model-config.html\\nMIDSCENE_MODEL_BASE_URL=\\nMIDSCENE_MODEL_API_KEY=\\nMIDSCENE_MODEL_NAME=\\nMIDSCENE_MODEL_FAMILY=\\n\\n${env}`,\n '.gitignore': 'node_modules/\\n.env\\nmidscene_run/\\n',\n 'README.md': `# ${name}\\n\\nA Midscene Test project for ${platform}.\\n\\nIf you skipped installation during creation, run \\`${packageManager} ${packageManagerCommands[packageManager].install.join(' ')}\\`. The \\`postinstall\\` script automatically generates \\`midscene-node-reference.md\\` after each dependency installation. If lifecycle scripts are disabled, run \\`${packageManager} run nodes\\` manually.\\n\\nCopy \\`.env.example\\` to \\`.env\\` and fill in your [model configuration](https://midscenejs.com/model-config.html).\\n\\n${instructions}\\n\\nRun tests with \\`${packageManager} test\\`. Read \\`midscene-node-reference.md\\` for the available Nodes and their inputs.\\n\\nAfter changing Node registrations in \\`midscene.config.ts\\`, run \\`${packageManager} run nodes\\` to refresh the reference. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.\\n\\n${packages.length ? `Node packages: ${packages.map((pkg) => `\\`${pkg.name}\\``).join(', ')}. Their \\`createMidsceneTestNodes\\` factories are imported in the configuration.\\n\\n` : ''}Reports are written to \\`midscene_run/report/\\`.\\n`,\n };\n}\n","import { lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, resolve } from 'node:path';\nimport { confirm, input, select } from '@inquirer/prompts';\nimport { execa } from 'execa';\nimport yargs from 'yargs/yargs';\nimport {\n type CreatePackageManager,\n createPackageManagers,\n detectPackageManager,\n packageManagerCommands,\n} from './create-package-manager';\nimport {\n type CreatePlatform,\n type NodePackageSpec,\n createPlatformLabels,\n createPlatforms,\n createProjectFiles,\n parseNodePackageSpec,\n} from './create-template';\nimport type { TestCliIO } from './test-command';\n\nconst createParser = () =>\n yargs()\n .scriptName('midscene-test create')\n .usage('Usage: $0 [directory] [options]')\n .parserConfiguration({\n 'parse-positional-numbers': false,\n 'greedy-arrays': false,\n 'camel-case-expansion': false,\n 'boolean-negation': false,\n })\n .option('platform', {\n type: 'string',\n choices: createPlatforms,\n requiresArg: true,\n description: 'Platform preset (prompt if omitted)',\n })\n .option('with', {\n type: 'array',\n string: true,\n nargs: 1,\n requiresArg: true,\n description: 'npm Node package, optionally versioned (repeatable)',\n })\n .option('package-manager', {\n type: 'string',\n choices: createPackageManagers,\n requiresArg: true,\n description:\n 'Package manager (prompt if omitted; detect without prompts, falling back to npm)',\n })\n .option('yes', {\n type: 'boolean',\n alias: 'y',\n default: false,\n description:\n 'Disable prompts and install dependencies; directory and platform must be supplied',\n })\n .option('skip-install', {\n type: 'boolean',\n default: false,\n description:\n 'Create files without installing dependencies or generating midscene-node-reference.md',\n })\n .demandCommand(0, 1, '', 'Only one project directory is allowed.')\n .example('$0', 'Choose a directory and platform interactively')\n .example('$0 my-tests --platform web', 'Create a Web project')\n .example(\n '$0 . --platform android --with @acme/test-nodes@1.2.0',\n 'Include a Node package',\n )\n .epilogue(\n 'Optionally installs dependencies with the selected package manager. The generated postinstall script creates midscene-node-reference.md after installation.\\nNode packages must export a synchronous createMidsceneTestNodes(options) factory.\\nExisting files are never overwritten. Setup and tests are not run during creation.',\n )\n .help('help')\n .alias('help', 'h')\n .version(false)\n .strict()\n .exitProcess(false)\n .showHelpOnFail(false)\n .fail((message, error) => {\n throw error ?? new Error(message);\n })\n .wrap(100);\n\nexport interface CreateOptions {\n directory?: string;\n platform?: CreatePlatform;\n packageManager?: CreatePackageManager;\n packages: NodePackageSpec[];\n yes: boolean;\n skipInstall: boolean;\n help: boolean;\n}\n\nexport function parseCreateArgs(\n args: string[],\n onOutput?: (message: string) => void,\n): CreateOptions {\n const values = createParser().parseSync(args, {}, (error, _argv, output) => {\n if (error) throw error;\n if (output) onOutput?.(output);\n });\n const directory = values._[0] === undefined ? undefined : String(values._[0]);\n if (directory !== undefined && !directory.trim()) {\n throw new Error('Project directory must not be empty.');\n }\n const packages = (values.with ?? []).map(parseNodePackageSpec);\n const seen = new Set<string>();\n for (const pkg of packages) {\n if (seen.has(pkg.name)) {\n throw new Error(\n `Node package \"${pkg.name}\" was specified more than once.`,\n );\n }\n seen.add(pkg.name);\n }\n return {\n directory,\n platform: values.platform,\n packageManager: values['package-manager'],\n packages,\n yes: values.yes ?? false,\n skipInstall: values['skip-install'] ?? false,\n help: values.help === true,\n };\n}\n\nexport interface CreateServices {\n cwd: string;\n interactive: boolean;\n userAgent: string | undefined;\n promptDirectory(): Promise<string>;\n selectPlatform(): Promise<CreatePlatform>;\n selectPackageManager(\n defaultValue: CreatePackageManager,\n ): Promise<CreatePackageManager>;\n confirmInstall(): Promise<boolean>;\n runPackageManager(\n packageManager: CreatePackageManager,\n args: string[],\n cwd: string,\n captureOutput?: boolean,\n ): Promise<string>;\n}\n\nconst promptDirectory = () =>\n input({\n message: 'Project directory:',\n default: 'my-tests',\n validate: (value) =>\n value.trim().length > 0 || 'Project directory must not be empty.',\n });\n\nconst selectPlatform = () =>\n select<CreatePlatform>({\n message: 'Select a platform:',\n choices: createPlatforms.map((platform) => ({\n name: createPlatformLabels[platform],\n value: platform,\n })),\n });\n\nconst selectPackageManager: CreateServices['selectPackageManager'] = (\n defaultValue,\n) =>\n select<CreatePackageManager>({\n message: 'Select a package manager:',\n choices: createPackageManagers.map((value) => ({ name: value, value })),\n default: defaultValue,\n });\n\nconst runPackageManager: CreateServices['runPackageManager'] = async (\n packageManager,\n args,\n cwd,\n captureOutput = false,\n) => {\n const result = await execa(packageManager, args, {\n cwd,\n stdin: 'inherit',\n stdout: captureOutput ? 'pipe' : 'inherit',\n stderr: 'inherit',\n stripFinalNewline: false,\n });\n return result.stdout ?? '';\n};\n\nconst statIfPresent = (path: string) => {\n try {\n return lstatSync(path);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n};\n\n/** Check every destination before writing anything, including dangling links. */\nfunction checkDestinations(root: string, filenames: string[]): void {\n const conflicts: string[] = [];\n for (const filename of filenames) {\n const path = resolve(root, filename);\n if (statIfPresent(path)) conflicts.push(path);\n let parent = dirname(path);\n while (true) {\n const stat = statIfPresent(parent);\n if (stat && !stat.isDirectory()) conflicts.push(parent);\n if (parent === root || parent === dirname(parent)) break;\n parent = dirname(parent);\n }\n }\n if (conflicts.length) {\n throw new Error(\n `Cannot create project; these paths already exist or are not directories:\\n${[...new Set(conflicts)].join('\\n')}\\nChoose another directory or move the conflicting files.`,\n );\n }\n}\n\nexport async function runCreateCommand(\n args: string[],\n io: TestCliIO,\n overrides: Partial<CreateServices> = {},\n): Promise<void> {\n const options = parseCreateArgs(args, (message) => io.log(message));\n if (options.help) return;\n const services: CreateServices = {\n cwd: process.cwd(),\n interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),\n userAgent: process.env.npm_config_user_agent,\n promptDirectory,\n selectPlatform,\n selectPackageManager,\n confirmInstall: () =>\n confirm({\n message:\n 'Install dependencies and generate midscene-node-reference.md now?',\n default: true,\n }),\n runPackageManager,\n ...overrides,\n };\n if (\n (!options.directory || !options.platform) &&\n (options.yes || !services.interactive)\n ) {\n throw new Error(\n 'Project directory and --platform are required without prompts. Example: midscene-test create my-tests --platform web',\n );\n }\n let directory: string;\n let platform: CreatePlatform;\n let packageManager: CreatePackageManager;\n try {\n directory = options.directory ?? (await services.promptDirectory()).trim();\n platform = options.platform ?? (await services.selectPlatform());\n const defaultPackageManager = detectPackageManager(services.userAgent);\n packageManager =\n options.packageManager ??\n (services.interactive && !options.yes\n ? await services.selectPackageManager(defaultPackageManager)\n : defaultPackageManager);\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error('Project creation cancelled.');\n }\n throw error;\n }\n const root = resolve(services.cwd, directory);\n const name =\n basename(root)\n .toLowerCase()\n .replace(/[^a-z0-9._-]/g, '-')\n .replace(/^[._-]+/, '') || 'midscene-tests';\n const commands = packageManagerCommands[packageManager];\n const files = createProjectFiles(\n name,\n platform,\n options.packages,\n packageManager,\n );\n checkDestinations(root, [\n ...Object.keys(files),\n 'midscene-node-reference.md',\n 'pnpm-lock.yaml',\n 'package-lock.json',\n 'npm-shrinkwrap.json',\n ]);\n for (const [filename, content] of Object.entries(files)) {\n const path = resolve(root, filename);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, content, { flag: 'wx' });\n }\n io.log(`Created ${platform} project files in ${root}`);\n let install = !options.skipInstall;\n if (install && services.interactive && !options.yes) {\n try {\n install = await services.confirmInstall();\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error(\n `Project creation cancelled. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-reference.md.`,\n );\n }\n throw error;\n }\n }\n if (!install) {\n io.log(\n `Project files ready: ${root}\\nNext: run ${packageManager} ${commands.install.join(' ')} in the project directory. The postinstall script will generate midscene-node-reference.md.\\nThen follow README.md to configure your model and run tests.`,\n );\n return;\n }\n io.log(`Installing dependencies with ${packageManager}...`);\n try {\n await services.runPackageManager(packageManager, commands.install, root);\n } catch (error) {\n throw new Error(\n `Dependency installation or postinstall Node reference generation failed. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')}, then ${packageManager} run nodes if lifecycle scripts are disabled.\\nIf postinstall failed, check midscene.config.ts and the Node package factories, then run ${packageManager} run nodes.\\n${error instanceof Error ? error.message : String(error)}`,\n );\n }\n try {\n const referencePath = resolve(root, 'midscene-node-reference.md');\n const generatedByPostinstall = Boolean(statIfPresent(referencePath));\n // Fall back to explicit generation if lifecycle scripts were disabled.\n if (!generatedByPostinstall) {\n io.log('Generating Node reference...');\n await services.runPackageManager(packageManager, commands.describe, root);\n }\n const markdown = readFileSync(referencePath, 'utf8');\n if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) {\n throw new Error('nodes did not generate a valid Node reference.');\n }\n } catch (error) {\n throw new Error(\n `Node reference generation failed. Project files are preserved in ${root}.\\nCheck that each Node package exports createMidsceneTestNodes(options), returns a Node array synchronously, and does not register duplicate names.\\nFix midscene.config.ts, then run ${packageManager} run nodes in the project directory.\\n${error instanceof Error ? error.message : String(error)}`,\n );\n }\n io.log(\n `Project ready: ${root}\\nNode reference: ${resolve(root, 'midscene-node-reference.md')}\\nNext: copy .env.example to .env and configure your model.${platform === 'web' ? `\\nInstall Chromium: ${commands.installChromium}` : platform === 'computer' ? '\\nPrepare desktop dependencies and permissions as described in README.md.' : '\\nConfigure your device connection in .env.'}\\nRun tests from the project directory: ${packageManager} test`,\n );\n}\n"],"names":["createPackageManagers","packageManagerCommands","detectPackageManager","userAgent","createPlatforms","createPlatformLabels","parseNodePackageSpec","spec","match","undefined","Error","platformImports","agentClasses","platformSetup","platformEnv","platformInstructions","createProjectFiles","name","platform","packages","packageManager","instructions","agentClass","imports","pkg","index","JSON","extraNodes","_","config","dependencies","version","Object","env","createParser","yargs","message","error","parseCreateArgs","args","onOutput","values","_argv","output","directory","String","seen","Set","promptDirectory","input","value","selectPlatform","select","selectPackageManager","defaultValue","runPackageManager","cwd","captureOutput","result","execa","statIfPresent","path","lstatSync","checkDestinations","root","filenames","conflicts","filename","resolve","parent","dirname","stat","runCreateCommand","io","overrides","options","services","process","Boolean","confirm","defaultPackageManager","basename","commands","files","content","mkdirSync","writeFileSync","install","referencePath","generatedByPostinstall","markdown","readFileSync"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"0~458.js","sources":["../../src/cli/create-package-manager.ts","../../src/cli/create-template.ts","../../src/cli/create-command.ts"],"sourcesContent":["export const createPackageManagers = ['npm', 'pnpm'] as const;\nexport type CreatePackageManager = (typeof createPackageManagers)[number];\n\nexport const packageManagerCommands: Record<\n CreatePackageManager,\n { install: string[]; describe: string[]; installChromium: string }\n> = {\n npm: {\n install: ['install', '--workspaces=false'],\n describe: [\n 'exec',\n '--no',\n '--workspaces=false',\n '--',\n 'midscene-test',\n 'nodes',\n ],\n installChromium: 'npm exec -- playwright install chromium',\n },\n pnpm: {\n install: ['install', '--ignore-workspace'],\n describe: ['exec', 'midscene-test', 'nodes'],\n installChromium: 'pnpm exec playwright install chromium',\n },\n};\n\nexport function detectPackageManager(userAgent?: string): CreatePackageManager {\n return userAgent?.startsWith('pnpm/') ? 'pnpm' : 'npm';\n}\n","import { version } from '../../package.json';\nimport {\n type CreatePackageManager,\n packageManagerCommands,\n} from './create-package-manager';\n\nexport const createPlatforms = [\n 'web',\n 'android',\n 'ios',\n 'harmony',\n 'computer',\n] as const;\nexport type CreatePlatform = (typeof createPlatforms)[number];\n\nexport const createPlatformLabels: Record<CreatePlatform, string> = {\n web: 'Web (Playwright)',\n android: 'Android',\n ios: 'iOS',\n harmony: 'HarmonyOS',\n computer: 'Desktop (Computer)',\n};\n\nconst platformImports: Record<CreatePlatform, string> = {\n web: `import { PlaywrightAgent } from '@midscene/web/playwright/agent';\nimport { chromium, type Page } from 'playwright';`,\n android: `import { AndroidAgent, agentFromAdbDevice } from '@midscene/android';`,\n ios: `import { IOSAgent, agentFromWebDriverAgent } from '@midscene/ios';`,\n harmony: `import { HarmonyAgent, agentFromHdcDevice } from '@midscene/harmony';`,\n computer: `import { ComputerAgent, agentForComputer } from '@midscene/computer';`,\n};\n\nconst agentClasses: Record<CreatePlatform, string> = {\n web: 'PlaywrightAgent',\n android: 'AndroidAgent',\n ios: 'IOSAgent',\n harmony: 'HarmonyAgent',\n computer: 'ComputerAgent',\n};\n\nconst platformSetup: Record<CreatePlatform, string> = {\n web: ` const browser = await chromium.launch({ headless: env.HEADLESS !== 'false' });\n onTeardown(() => browser.close());\n const page = await browser.newPage();\n const context: ProjectContext = { page };\n onTeardown(async () => { await context.agent?.destroy(); });\n return context;`,\n android: ` const agent = await agentFromAdbDevice(env.ANDROID_DEVICE_ID || undefined);\n onTeardown(() => agent.destroy());\n return { agent };`,\n ios: ` const port = Number(env.WDA_PORT || 8100);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error('WDA_PORT must be an integer between 1 and 65535.');\n }\n const agent = await agentFromWebDriverAgent({\n wdaHost: env.WDA_HOST || 'localhost',\n wdaPort: port,\n });\n onTeardown(() => agent.destroy());\n return { agent };`,\n harmony: ` const agent = await agentFromHdcDevice(env.HARMONY_DEVICE_ID || undefined);\n onTeardown(() => agent.destroy());\n return { agent };`,\n computer: ` const agent = await agentForComputer({\n displayId: env.COMPUTER_DISPLAY_ID || undefined,\n });\n onTeardown(() => agent.destroy());\n return { agent };`,\n};\n\nconst platformEnv: Record<CreatePlatform, string> = {\n web: 'HEADLESS=true\\n',\n android:\n '# Optional: choose a device from adb devices.\\nANDROID_DEVICE_ID=\\n',\n ios: 'WDA_HOST=localhost\\nWDA_PORT=8100\\n',\n harmony:\n '# Optional: choose a device from hdc list targets.\\nHARMONY_DEVICE_ID=\\n',\n computer:\n '# Optional: select a display; leave empty for the default display.\\nCOMPUTER_DISPLAY_ID=\\n# Enable Xvfb on headless Linux after installing its dependencies.\\nMIDSCENE_COMPUTER_HEADLESS_LINUX=false\\n',\n};\n\nconst modelEnv = `# Copy this file to .env before running tests.\n# Do not commit .env to your repository. In CI, inject these values as environment variables instead.\n# Choose a supported model and copy its four configuration values from:\n# https://midscenejs.com/model-common-config.html\n# For the complete environment variable reference, see:\n# https://midscenejs.com/model-config.html\nMIDSCENE_MODEL_BASE_URL=\nMIDSCENE_MODEL_API_KEY=\nMIDSCENE_MODEL_NAME=\nMIDSCENE_MODEL_FAMILY=\n`;\n\nconst platformInstructions: Record<Exclude<CreatePlatform, 'web'>, string> = {\n android:\n 'Connect an Android device and verify it with `adb devices`. Set ANDROID_DEVICE_ID to select a device.',\n ios: 'Start WebDriverAgent and set WDA_HOST and WDA_PORT for your iOS device.',\n harmony:\n 'Connect a HarmonyOS device and verify it with `hdc list targets`. Set HARMONY_DEVICE_ID to select a device. Set HDC_HOME if hdc is not on PATH.',\n computer:\n 'Prepare the local desktop using the [desktop setup guide](https://midscenejs.com/platforms/desktop.html), including system dependencies and permissions. Set COMPUTER_DISPLAY_ID to select a display. For headless Linux, install Xvfb and enable MIDSCENE_COMPUTER_HEADLESS_LINUX.',\n};\n\nexport function createProjectFiles(\n name: string,\n platform: CreatePlatform,\n packageManager: CreatePackageManager,\n): Record<string, string> {\n const instructions =\n platform === 'web'\n ? `Install Chromium before the first test: \\`${packageManagerCommands[packageManager].installChromium}\\`.`\n : platformInstructions[platform];\n const agentClass = agentClasses[platform];\n const config = `import { fileURLToPath } from 'node:url';\nimport { config as loadEnv } from 'dotenv';\nimport type { NodeExecutionContext } from '@midscene/test';\nimport { defineProjectSetup, defineTestProject } from '@midscene/test/config';\nimport { createMidsceneNodes } from '@midscene/test/midscene';\n${platformImports[platform]}\n\nloadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });\n\ninterface ProjectContext {\n${platform === 'web' ? ' page: Page;\\n agent?: PlaywrightAgent;' : ` agent: ${agentClass};`}\n}\n\nconst getAgent = ({ context }: NodeExecutionContext<unknown, ProjectContext>) => {\n${platform === 'web' ? ' context.agent ??= new PlaywrightAgent(context.page);' : ''}\n return context.agent;\n};\n\nconst setup = defineProjectSetup<ProjectContext>({\n name: '${platform}',\n async setup({ env, onTeardown }) {\n${platformSetup[platform]}\n },\n});\n\nexport default defineTestProject<ProjectContext>({\n projects: [{\n name: '${platform}',\n setup,\n files: { include: ['cases/**/*.{yaml,yml}'] },\n }],\n nodes: [\n ...createMidsceneNodes<ProjectContext>({ agentClass: ${agentClass}, getAgent }),\n ],\n});\n`;\n const dependencies: Record<string, string> = {\n '@midscene/test': version,\n [`@midscene/${platform}`]: version,\n '@types/node': '^20.0.0',\n dotenv: '^16.4.5',\n typescript: '^5.8.3',\n ...(platform === 'web' ? { playwright: '^1.45.0' } : {}),\n };\n const env = platformEnv[platform];\n return {\n 'package.json': `${JSON.stringify(\n {\n name,\n private: true,\n type: 'module',\n scripts: {\n test: 'midscene-test',\n nodes: 'midscene-test nodes',\n postinstall: 'midscene-test nodes',\n },\n engines: { node: '^20.19.0 || ^22.12.0 || >=24.0.0' },\n devDependencies: dependencies,\n },\n null,\n 2,\n )}\\n`,\n 'midscene.config.ts': config,\n 'tsconfig.json': `${JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'Bundler',\n strict: true,\n noEmit: true,\n skipLibCheck: true,\n types: ['node'],\n },\n include: ['midscene.config.ts'],\n },\n null,\n 2,\n )}\\n`,\n 'cases/example.yaml':\n platform === 'web'\n ? 'cases:\\n - name: Open example.com\\n steps:\\n - gotoUrl: https://example.com\\n - aiAssert: The page displays the heading Example Domain\\n'\n : platform === 'computer'\n ? 'cases:\\n - name: Inspect the desktop\\n steps:\\n - aiAsk: Describe the current desktop\\n'\n : 'cases:\\n - name: Inspect the home screen\\n steps:\\n - home: {}\\n - aiAsk: Describe the current screen\\n',\n '.env.example': `${modelEnv}\\n${env}`,\n '.gitignore': 'node_modules/\\n.env\\nmidscene_run/\\n',\n 'README.md': `# ${name}\\n\\nA Midscene Test project for ${platform}.\\n\\nIf you skipped installation during creation, run \\`${packageManager} ${packageManagerCommands[packageManager].install.join(' ')}\\`. The \\`postinstall\\` script automatically generates \\`midscene-node-reference.md\\` after each dependency installation. If lifecycle scripts are disabled, run \\`${packageManager} run nodes\\` manually.\\n\\nCopy \\`.env.example\\` to \\`.env\\`, then choose and configure a model using the [supported models and setup guide](https://midscenejs.com/model-common-config.html). Do not commit \\`.env\\` to your repository; inject the model configuration as environment variables in CI.\\n\\n${instructions}\\n\\nRun tests with \\`${packageManager} test\\`. Read \\`midscene-node-reference.md\\` for the available Nodes and their inputs.\\n\\nAfter changing Node registrations in \\`midscene.config.ts\\`, run \\`${packageManager} run nodes\\` to refresh the reference. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.\\n\\nReports are written to \\`midscene_run/report/\\`.\\n`,\n };\n}\n","import { lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, resolve } from 'node:path';\nimport { confirm, input, select } from '@inquirer/prompts';\nimport { execa } from 'execa';\nimport yargs from 'yargs/yargs';\nimport {\n type CreatePackageManager,\n createPackageManagers,\n detectPackageManager,\n packageManagerCommands,\n} from './create-package-manager';\nimport {\n type CreatePlatform,\n createPlatformLabels,\n createPlatforms,\n createProjectFiles,\n} from './create-template';\nimport type { TestCliIO } from './test-command';\n\nconst createParser = () =>\n yargs()\n .scriptName('midscene-test create')\n .usage('Usage: $0 [directory] [options]')\n .parserConfiguration({\n 'parse-positional-numbers': false,\n 'greedy-arrays': false,\n 'camel-case-expansion': false,\n 'boolean-negation': false,\n })\n .option('platform', {\n type: 'string',\n choices: createPlatforms,\n requiresArg: true,\n description: 'Platform preset (prompt if omitted)',\n })\n .option('package-manager', {\n type: 'string',\n choices: createPackageManagers,\n requiresArg: true,\n description:\n 'Package manager (prompt if omitted; detect without prompts, falling back to npm)',\n })\n .option('yes', {\n type: 'boolean',\n alias: 'y',\n default: false,\n description:\n 'Disable prompts and install dependencies; directory and platform must be supplied',\n })\n .option('skip-install', {\n type: 'boolean',\n default: false,\n description:\n 'Create files without installing dependencies or generating midscene-node-reference.md',\n })\n .demandCommand(0, 1, '', 'Only one project directory is allowed.')\n .example('$0', 'Choose a directory and platform interactively')\n .example('$0 my-tests --platform web', 'Create a Web project')\n .epilogue(\n 'Optionally installs dependencies with the selected package manager. The generated postinstall script creates midscene-node-reference.md after installation.\\nExisting files are never overwritten. Setup and tests are not run during creation.',\n )\n .help('help')\n .alias('help', 'h')\n .version(false)\n .strict()\n .exitProcess(false)\n .showHelpOnFail(false)\n .fail((message, error) => {\n throw error ?? new Error(message);\n })\n .wrap(100);\n\nexport interface CreateOptions {\n directory?: string;\n platform?: CreatePlatform;\n packageManager?: CreatePackageManager;\n yes: boolean;\n skipInstall: boolean;\n help: boolean;\n}\n\nexport function parseCreateArgs(\n args: string[],\n onOutput?: (message: string) => void,\n): CreateOptions {\n const values = createParser().parseSync(args, {}, (error, _argv, output) => {\n if (error) throw error;\n if (output) onOutput?.(output);\n });\n const directory = values._[0] === undefined ? undefined : String(values._[0]);\n if (directory !== undefined && !directory.trim()) {\n throw new Error('Project directory must not be empty.');\n }\n return {\n directory,\n platform: values.platform,\n packageManager: values['package-manager'],\n yes: values.yes ?? false,\n skipInstall: values['skip-install'] ?? false,\n help: values.help === true,\n };\n}\n\nexport interface CreateServices {\n cwd: string;\n interactive: boolean;\n userAgent: string | undefined;\n promptDirectory(): Promise<string>;\n selectPlatform(): Promise<CreatePlatform>;\n selectPackageManager(\n defaultValue: CreatePackageManager,\n ): Promise<CreatePackageManager>;\n confirmInstall(): Promise<boolean>;\n runPackageManager(\n packageManager: CreatePackageManager,\n args: string[],\n cwd: string,\n captureOutput?: boolean,\n ): Promise<string>;\n}\n\nconst promptDirectory = () =>\n input({\n message: 'Project directory:',\n default: 'my-tests',\n validate: (value) =>\n value.trim().length > 0 || 'Project directory must not be empty.',\n });\n\nconst selectPlatform = () =>\n select<CreatePlatform>({\n message: 'Select a platform:',\n choices: createPlatforms.map((platform) => ({\n name: createPlatformLabels[platform],\n value: platform,\n })),\n });\n\nconst selectPackageManager: CreateServices['selectPackageManager'] = (\n defaultValue,\n) =>\n select<CreatePackageManager>({\n message: 'Select a package manager:',\n choices: createPackageManagers.map((value) => ({ name: value, value })),\n default: defaultValue,\n });\n\nconst runPackageManager: CreateServices['runPackageManager'] = async (\n packageManager,\n args,\n cwd,\n captureOutput = false,\n) => {\n const result = await execa(packageManager, args, {\n cwd,\n stdin: 'inherit',\n stdout: captureOutput ? 'pipe' : 'inherit',\n stderr: 'inherit',\n stripFinalNewline: false,\n });\n return result.stdout ?? '';\n};\n\nconst statIfPresent = (path: string) => {\n try {\n return lstatSync(path);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n};\n\n/** Check every destination before writing anything, including dangling links. */\nfunction checkDestinations(root: string, filenames: string[]): void {\n const conflicts: string[] = [];\n for (const filename of filenames) {\n const path = resolve(root, filename);\n if (statIfPresent(path)) conflicts.push(path);\n let parent = dirname(path);\n while (true) {\n const stat = statIfPresent(parent);\n if (stat && !stat.isDirectory()) conflicts.push(parent);\n if (parent === root || parent === dirname(parent)) break;\n parent = dirname(parent);\n }\n }\n if (conflicts.length) {\n throw new Error(\n `Cannot create project; these paths already exist or are not directories:\\n${[...new Set(conflicts)].join('\\n')}\\nChoose another directory or move the conflicting files.`,\n );\n }\n}\n\nexport async function runCreateCommand(\n args: string[],\n io: TestCliIO,\n overrides: Partial<CreateServices> = {},\n): Promise<void> {\n const options = parseCreateArgs(args, (message) => io.log(message));\n if (options.help) return;\n const services: CreateServices = {\n cwd: process.cwd(),\n interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),\n userAgent: process.env.npm_config_user_agent,\n promptDirectory,\n selectPlatform,\n selectPackageManager,\n confirmInstall: () =>\n confirm({\n message:\n 'Install dependencies and generate midscene-node-reference.md now?',\n default: true,\n }),\n runPackageManager,\n ...overrides,\n };\n if (\n (!options.directory || !options.platform) &&\n (options.yes || !services.interactive)\n ) {\n throw new Error(\n 'Project directory and --platform are required without prompts. Example: midscene-test create my-tests --platform web',\n );\n }\n let directory: string;\n let platform: CreatePlatform;\n let packageManager: CreatePackageManager;\n try {\n directory = options.directory ?? (await services.promptDirectory()).trim();\n platform = options.platform ?? (await services.selectPlatform());\n const defaultPackageManager = detectPackageManager(services.userAgent);\n packageManager =\n options.packageManager ??\n (services.interactive && !options.yes\n ? await services.selectPackageManager(defaultPackageManager)\n : defaultPackageManager);\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error('Project creation cancelled.');\n }\n throw error;\n }\n const root = resolve(services.cwd, directory);\n const name =\n basename(root)\n .toLowerCase()\n .replace(/[^a-z0-9._-]/g, '-')\n .replace(/^[._-]+/, '') || 'midscene-tests';\n const commands = packageManagerCommands[packageManager];\n const files = createProjectFiles(name, platform, packageManager);\n checkDestinations(root, [\n ...Object.keys(files),\n 'midscene-node-reference.md',\n 'pnpm-lock.yaml',\n 'package-lock.json',\n 'npm-shrinkwrap.json',\n ]);\n for (const [filename, content] of Object.entries(files)) {\n const path = resolve(root, filename);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, content, { flag: 'wx' });\n }\n io.log(`Created ${platform} project files in ${root}`);\n let install = !options.skipInstall;\n if (install && services.interactive && !options.yes) {\n try {\n install = await services.confirmInstall();\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error(\n `Project creation cancelled. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-reference.md.`,\n );\n }\n throw error;\n }\n }\n if (!install) {\n io.log(\n `Project files ready: ${root}\\nNext: run ${packageManager} ${commands.install.join(' ')} in the project directory. The postinstall script will generate midscene-node-reference.md.\\nThen follow README.md to configure your model and run tests.`,\n );\n return;\n }\n io.log(`Installing dependencies with ${packageManager}...`);\n try {\n await services.runPackageManager(packageManager, commands.install, root);\n } catch (error) {\n throw new Error(\n `Dependency installation or postinstall Node reference generation failed. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')}, then ${packageManager} run nodes if lifecycle scripts are disabled.\\nIf postinstall failed, check midscene.config.ts and the Node package factories, then run ${packageManager} run nodes.\\n${error instanceof Error ? error.message : String(error)}`,\n );\n }\n try {\n const referencePath = resolve(root, 'midscene-node-reference.md');\n const generatedByPostinstall = Boolean(statIfPresent(referencePath));\n // Fall back to explicit generation if lifecycle scripts were disabled.\n if (!generatedByPostinstall) {\n io.log('Generating Node reference...');\n await services.runPackageManager(packageManager, commands.describe, root);\n }\n const markdown = readFileSync(referencePath, 'utf8');\n if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) {\n throw new Error('nodes did not generate a valid Node reference.');\n }\n } catch (error) {\n throw new Error(\n `Node reference generation failed. Project files are preserved in ${root}.\\nCheck that each Node package exports createMidsceneTestNodes(options), returns a Node array synchronously, and does not register duplicate names.\\nFix midscene.config.ts, then run ${packageManager} run nodes in the project directory.\\n${error instanceof Error ? error.message : String(error)}`,\n );\n }\n io.log(\n `Project ready: ${root}\\nNode reference: ${resolve(root, 'midscene-node-reference.md')}\\nNext: copy .env.example to .env and configure your model.${platform === 'web' ? `\\nInstall Chromium: ${commands.installChromium}` : platform === 'computer' ? '\\nPrepare desktop dependencies and permissions as described in README.md.' : '\\nConfigure your device connection in .env.'}\\nRun tests from the project directory: ${packageManager} test`,\n );\n}\n"],"names":["createPackageManagers","packageManagerCommands","detectPackageManager","userAgent","createPlatforms","createPlatformLabels","platformImports","agentClasses","platformSetup","platformEnv","modelEnv","platformInstructions","createProjectFiles","name","platform","packageManager","instructions","agentClass","config","dependencies","version","env","JSON","createParser","yargs","message","error","Error","parseCreateArgs","args","onOutput","values","_argv","output","directory","undefined","String","promptDirectory","input","value","selectPlatform","select","selectPackageManager","defaultValue","runPackageManager","cwd","captureOutput","result","execa","statIfPresent","path","lstatSync","checkDestinations","root","filenames","conflicts","filename","resolve","parent","dirname","stat","Set","runCreateCommand","io","overrides","options","services","process","Boolean","confirm","defaultPackageManager","basename","commands","files","Object","content","mkdirSync","writeFileSync","install","referencePath","generatedByPostinstall","markdown","readFileSync"],"mappings":"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"}
|
package/dist/lib/cli/index.js
CHANGED
|
@@ -402,26 +402,15 @@ const validateVariables = (value, label)=>{
|
|
|
402
402
|
assertJsonValue(value, label, new Set());
|
|
403
403
|
return deepFreezeJson(value);
|
|
404
404
|
};
|
|
405
|
-
const
|
|
406
|
-
'web',
|
|
407
|
-
'android',
|
|
408
|
-
'ios',
|
|
409
|
-
'harmony',
|
|
410
|
-
'computer'
|
|
411
|
-
]);
|
|
412
|
-
const validatePlatform = (value, label)=>{
|
|
413
|
-
if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, harmony, computer.`);
|
|
414
|
-
return value;
|
|
415
|
-
};
|
|
416
|
-
const validateProjectSetup = (value, projectPlatform, label)=>{
|
|
405
|
+
const validateProjectSetup = (value, label)=>{
|
|
417
406
|
if (void 0 === value) return;
|
|
418
407
|
if (!isRecord(value)) throw new TypeError(`Midscene config ${label} must be an object.`);
|
|
408
|
+
rejectUnknownKeys(value, [
|
|
409
|
+
'name',
|
|
410
|
+
'setup'
|
|
411
|
+
], label);
|
|
419
412
|
if ('string' != typeof value.name || 0 === value.name.trim().length) throw new TypeError(`Midscene config ${label}.name must be non-empty.`);
|
|
420
413
|
if ('function' != typeof value.setup) throw new TypeError(`Midscene config ${label}.setup must be a function.`);
|
|
421
|
-
const supported = void 0 === value.platform ? void 0 : Array.isArray(value.platform) ? value.platform.map((item, index)=>validatePlatform(item, `${label}.platform[${index}]`)) : [
|
|
422
|
-
validatePlatform(value.platform, `${label}.platform`)
|
|
423
|
-
];
|
|
424
|
-
if (supported && !supported.includes(projectPlatform)) throw new TypeError(`Midscene config ${label} does not support project platform "${projectPlatform}".`);
|
|
425
414
|
return value;
|
|
426
415
|
};
|
|
427
416
|
const validatePositiveInteger = (value, fallback, label)=>{
|
|
@@ -434,24 +423,15 @@ const validateNonNegativeInteger = (value, fallback, label)=>{
|
|
|
434
423
|
if (!Number.isInteger(value) || value < 0) throw new TypeError(`Midscene config ${label} must be a non-negative integer.`);
|
|
435
424
|
return value;
|
|
436
425
|
};
|
|
437
|
-
const validateExecutionProjects = (value, defaultSetup)=>{
|
|
426
|
+
const validateExecutionProjects = (value, defaultSetup, globalNodes)=>{
|
|
438
427
|
if (void 0 === value) {
|
|
439
|
-
|
|
440
|
-
if (void 0 !== defaultSetup) {
|
|
441
|
-
if (!isRecord(defaultSetup)) throw new TypeError('Midscene config setup must be an object.');
|
|
442
|
-
if (Array.isArray(defaultSetup.platform)) {
|
|
443
|
-
if (1 !== defaultSetup.platform.length) throw new TypeError('Midscene config setup.platform must select exactly one platform when projects is omitted.');
|
|
444
|
-
platform = validatePlatform(defaultSetup.platform[0], 'setup.platform[0]');
|
|
445
|
-
} else if (void 0 !== defaultSetup.platform) platform = validatePlatform(defaultSetup.platform, 'setup.platform');
|
|
446
|
-
}
|
|
447
|
-
const setup = validateProjectSetup(defaultSetup, platform, 'setup');
|
|
428
|
+
const setup = validateProjectSetup(defaultSetup, 'setup');
|
|
448
429
|
return {
|
|
449
430
|
hasExplicitProjects: false,
|
|
450
431
|
projects: Object.freeze([
|
|
451
432
|
Object.freeze({
|
|
452
433
|
projectId: projectIdFromIndex(0),
|
|
453
434
|
name: 'default',
|
|
454
|
-
platform,
|
|
455
435
|
...setup ? {
|
|
456
436
|
setup
|
|
457
437
|
} : {},
|
|
@@ -460,7 +440,8 @@ const validateExecutionProjects = (value, defaultSetup)=>{
|
|
|
460
440
|
exclude: []
|
|
461
441
|
}),
|
|
462
442
|
retry: 0,
|
|
463
|
-
variables: Object.freeze({})
|
|
443
|
+
variables: Object.freeze({}),
|
|
444
|
+
nodes: new NodeRegistry(globalNodes.definitions())
|
|
464
445
|
})
|
|
465
446
|
])
|
|
466
447
|
};
|
|
@@ -473,8 +454,8 @@ const validateExecutionProjects = (value, defaultSetup)=>{
|
|
|
473
454
|
if (!isRecord(candidate)) throw new TypeError(`Midscene config ${label} must be an object.`);
|
|
474
455
|
rejectUnknownKeys(candidate, [
|
|
475
456
|
'name',
|
|
476
|
-
'platform',
|
|
477
457
|
'setup',
|
|
458
|
+
'nodes',
|
|
478
459
|
'files',
|
|
479
460
|
'tags',
|
|
480
461
|
'retry',
|
|
@@ -483,12 +464,17 @@ const validateExecutionProjects = (value, defaultSetup)=>{
|
|
|
483
464
|
if ('string' != typeof candidate.name || 0 === candidate.name.trim().length) throw new TypeError(`Midscene config ${label}.name must be non-empty.`);
|
|
484
465
|
if (names.has(candidate.name)) throw new TypeError(`Midscene config project name "${candidate.name}" must be unique.`);
|
|
485
466
|
names.add(candidate.name);
|
|
486
|
-
const platform = validatePlatform(candidate.platform, `${label}.platform`);
|
|
487
467
|
const files = validateTestFileSelection(candidate.files, `${label}.files`);
|
|
468
|
+
if (void 0 !== candidate.nodes && !Array.isArray(candidate.nodes)) throw new TypeError(`Midscene config ${label}.nodes must be an array.`);
|
|
469
|
+
const localNodes = new NodeRegistry(candidate.nodes);
|
|
470
|
+
const nodes = new NodeRegistry([
|
|
471
|
+
...globalNodes.definitions().filter((node)=>!localNodes.has(node.name)),
|
|
472
|
+
...localNodes.definitions()
|
|
473
|
+
]);
|
|
488
474
|
return Object.freeze({
|
|
489
475
|
projectId: projectIdFromIndex(index),
|
|
490
476
|
name: candidate.name,
|
|
491
|
-
|
|
477
|
+
nodes,
|
|
492
478
|
...files ? {
|
|
493
479
|
files
|
|
494
480
|
} : {},
|
|
@@ -496,7 +482,7 @@ const validateExecutionProjects = (value, defaultSetup)=>{
|
|
|
496
482
|
retry: validateNonNegativeInteger(candidate.retry, 0, `${label}.retry`),
|
|
497
483
|
variables: validateVariables(candidate.variables, `${label}.variables`),
|
|
498
484
|
...void 0 === candidate.setup ? {} : {
|
|
499
|
-
setup: validateProjectSetup(candidate.setup,
|
|
485
|
+
setup: validateProjectSetup(candidate.setup, `${label}.setup`)
|
|
500
486
|
}
|
|
501
487
|
});
|
|
502
488
|
});
|
|
@@ -530,7 +516,8 @@ const validateOutput = (value)=>{
|
|
|
530
516
|
});
|
|
531
517
|
};
|
|
532
518
|
const validateTestProjectDefinition = (definition)=>{
|
|
533
|
-
if (!isRecord(definition)
|
|
519
|
+
if (!isRecord(definition)) throw new TypeError('Midscene config must default export an object.');
|
|
520
|
+
if (void 0 !== definition.nodes && !Array.isArray(definition.nodes)) throw new TypeError('Midscene config nodes must be an array.');
|
|
534
521
|
if ('setupWorkflow' in definition) throw new TypeError('Midscene config setupWorkflow is no longer supported. Use setup or projects[].setup instead.');
|
|
535
522
|
if ('setupDocument' in definition) throw new TypeError('Midscene config setupDocument is not supported. Use setup or projects[].setup for resources, and beforeAll/afterAll for document lifecycle steps.');
|
|
536
523
|
if ('root' in definition) throw new TypeError('Midscene config root is not supported. Pass the Test Project directory to midscene-test instead.');
|
|
@@ -543,8 +530,8 @@ const validateTestProjectDefinition = (definition)=>{
|
|
|
543
530
|
'output',
|
|
544
531
|
'nodes'
|
|
545
532
|
], 'root');
|
|
546
|
-
const resolvedProjects = validateExecutionProjects(definition.projects, definition.setup);
|
|
547
533
|
const nodes = new NodeRegistry(definition.nodes);
|
|
534
|
+
const resolvedProjects = validateExecutionProjects(definition.projects, definition.setup, nodes);
|
|
548
535
|
return {
|
|
549
536
|
...resolvedProjects,
|
|
550
537
|
test: validateTestOptions(definition.test),
|
|
@@ -613,7 +600,6 @@ const createProjectRuntime = (options)=>{
|
|
|
613
600
|
const endedAt = new Date();
|
|
614
601
|
return {
|
|
615
602
|
projectName: options.project.name,
|
|
616
|
-
platform: options.project.platform,
|
|
617
603
|
status: 'failed' === requestedStatus || setupError || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
618
604
|
...setupError ? {
|
|
619
605
|
setupError
|
|
@@ -914,7 +900,6 @@ function createDocumentRuntime(document, options) {
|
|
|
914
900
|
const project = options.project ?? Object.freeze({
|
|
915
901
|
projectId: document.projectId,
|
|
916
902
|
name: document.projectId,
|
|
917
|
-
platform: 'web',
|
|
918
903
|
tags: Object.freeze({
|
|
919
904
|
include: [],
|
|
920
905
|
exclude: []
|
|
@@ -1849,7 +1834,6 @@ const buildProject = (project, context)=>{
|
|
|
1849
1834
|
return {
|
|
1850
1835
|
projectId: project.projectId,
|
|
1851
1836
|
name: project.name,
|
|
1852
|
-
platform: project.platform,
|
|
1853
1837
|
status: project.status,
|
|
1854
1838
|
retry: project.retry,
|
|
1855
1839
|
...project.lifecycle ? {
|
|
@@ -2000,7 +1984,6 @@ const writeTestProjectRunResult = (options)=>{
|
|
|
2000
1984
|
projects: result.projects.map((project)=>({
|
|
2001
1985
|
projectId: project.projectId,
|
|
2002
1986
|
name: project.name,
|
|
2003
|
-
platform: project.platform,
|
|
2004
1987
|
status: project.status,
|
|
2005
1988
|
retry: project.retry,
|
|
2006
1989
|
fileSelection: project.fileSelection,
|
|
@@ -2211,7 +2194,7 @@ const selectProjects = (projects, names)=>{
|
|
|
2211
2194
|
if (unknown) throw new Error(`Unknown Midscene project: ${unknown}`);
|
|
2212
2195
|
return projects.filter((project)=>requested.has(project.name));
|
|
2213
2196
|
};
|
|
2214
|
-
const prepareProject = (project, projectRoot,
|
|
2197
|
+
const prepareProject = (project, projectRoot, runDir)=>{
|
|
2215
2198
|
const fileSelection = project.files ?? DEFAULT_TEST_FILE_SELECTION;
|
|
2216
2199
|
const files = discoverTestFiles(projectRoot, fileSelection);
|
|
2217
2200
|
const sources = files.map((absolutePath)=>({
|
|
@@ -2233,7 +2216,7 @@ const prepareProject = (project, projectRoot, resolveNode, runDir)=>{
|
|
|
2233
2216
|
}
|
|
2234
2217
|
for (const source of sources)try {
|
|
2235
2218
|
const collected = collectWorkflowDocument(source, {
|
|
2236
|
-
resolveNode,
|
|
2219
|
+
resolveNode: project.nodes.get.bind(project.nodes),
|
|
2237
2220
|
variables: project.variables,
|
|
2238
2221
|
env: process.env
|
|
2239
2222
|
});
|
|
@@ -2277,7 +2260,7 @@ async function runTestProject(options = {}) {
|
|
|
2277
2260
|
});
|
|
2278
2261
|
(0, external_node_fs_namespaceObject.mkdirSync)(runDir);
|
|
2279
2262
|
const selectedProjects = selectProjects(definition.projects, options.projectNames);
|
|
2280
|
-
const preparedProjects = selectedProjects.map((project)=>prepareProject(project, projectRoot,
|
|
2263
|
+
const preparedProjects = selectedProjects.map((project)=>prepareProject(project, projectRoot, runDir));
|
|
2281
2264
|
const progress = options.onProgress ?? (()=>{});
|
|
2282
2265
|
const totalDocuments = preparedProjects.reduce((total, prepared)=>total + prepared.documents.length, 0);
|
|
2283
2266
|
const totalCases = preparedProjects.reduce((total, prepared)=>total + prepared.selectedCaseCount, 0);
|
|
@@ -2313,7 +2296,7 @@ async function runTestProject(options = {}) {
|
|
|
2313
2296
|
};
|
|
2314
2297
|
const announceProject = (prepared, projectIndex)=>{
|
|
2315
2298
|
const { project } = prepared;
|
|
2316
|
-
runInfrastructureCallback(()=>progress(`[project ${projectIndex + 1}/${preparedProjects.length}] ${project.name}
|
|
2299
|
+
runInfrastructureCallback(()=>progress(`[project ${projectIndex + 1}/${preparedProjects.length}] ${project.name}`));
|
|
2317
2300
|
};
|
|
2318
2301
|
const buildProjectResult = (prepared, cases, documents, lifecycle)=>{
|
|
2319
2302
|
const { project } = prepared;
|
|
@@ -2321,7 +2304,6 @@ async function runTestProject(options = {}) {
|
|
|
2321
2304
|
return {
|
|
2322
2305
|
projectId: project.projectId,
|
|
2323
2306
|
name: project.name,
|
|
2324
|
-
platform: project.platform,
|
|
2325
2307
|
status: projectFailed ? 'failed' : 'success',
|
|
2326
2308
|
retry: project.retry,
|
|
2327
2309
|
fileSelection: prepared.fileSelection,
|
|
@@ -2370,7 +2352,7 @@ async function runTestProject(options = {}) {
|
|
|
2370
2352
|
}
|
|
2371
2353
|
projectProgress(` [document ${documentIndex + 1}/${prepared.documents.length}] ${document.sourcePath}`);
|
|
2372
2354
|
const execution = await runWorkflowDocument(document, {
|
|
2373
|
-
resolveNode:
|
|
2355
|
+
resolveNode: project.nodes.require.bind(project.nodes),
|
|
2374
2356
|
project,
|
|
2375
2357
|
projectContext: runtime.context,
|
|
2376
2358
|
retry: project.retry,
|