@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/es/1~891.mjs
CHANGED
|
@@ -48,7 +48,9 @@ export const __webpack_modules__ = {
|
|
|
48
48
|
function detectPackageManager(userAgent) {
|
|
49
49
|
return userAgent?.startsWith('pnpm/') ? 'pnpm' : 'npm';
|
|
50
50
|
}
|
|
51
|
-
var package_namespaceObject =
|
|
51
|
+
var package_namespaceObject = {
|
|
52
|
+
rE: "1.12.6"
|
|
53
|
+
};
|
|
52
54
|
const createPlatforms = [
|
|
53
55
|
'web',
|
|
54
56
|
'android',
|
|
@@ -63,17 +65,8 @@ export const __webpack_modules__ = {
|
|
|
63
65
|
harmony: 'HarmonyOS',
|
|
64
66
|
computer: 'Desktop (Computer)'
|
|
65
67
|
};
|
|
66
|
-
function parseNodePackageSpec(spec) {
|
|
67
|
-
const match = /^(?:(@[a-z0-9][a-z0-9._-]*)\/)?([a-z0-9][a-z0-9._-]*)(?:@([a-zA-Z0-9^~*>=<|. +_-]+))?$/.exec(spec);
|
|
68
|
-
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.`);
|
|
69
|
-
return {
|
|
70
|
-
name: `${match[1] ? `${match[1]}/` : ''}${match[2]}`,
|
|
71
|
-
version: match[3] ?? 'latest'
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
68
|
const platformImports = {
|
|
75
69
|
web: `import { PlaywrightAgent } from '@midscene/web/playwright/agent';
|
|
76
|
-
import { createPlaywrightNodes } from '@midscene/test/playwright';
|
|
77
70
|
import { chromium, type Page } from 'playwright';`,
|
|
78
71
|
android: "import { AndroidAgent, agentFromAdbDevice } from '@midscene/android';",
|
|
79
72
|
ios: "import { IOSAgent, agentFromWebDriverAgent } from '@midscene/ios';",
|
|
@@ -123,24 +116,32 @@ import { chromium, type Page } from 'playwright';`,
|
|
|
123
116
|
harmony: '# Optional: choose a device from hdc list targets.\nHARMONY_DEVICE_ID=\n',
|
|
124
117
|
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'
|
|
125
118
|
};
|
|
119
|
+
const modelEnv = `# Copy this file to .env before running tests.
|
|
120
|
+
# Do not commit .env to your repository. In CI, inject these values as environment variables instead.
|
|
121
|
+
# Choose a supported model and copy its four configuration values from:
|
|
122
|
+
# https://midscenejs.com/model-common-config.html
|
|
123
|
+
# For the complete environment variable reference, see:
|
|
124
|
+
# https://midscenejs.com/model-config.html
|
|
125
|
+
MIDSCENE_MODEL_BASE_URL=
|
|
126
|
+
MIDSCENE_MODEL_API_KEY=
|
|
127
|
+
MIDSCENE_MODEL_NAME=
|
|
128
|
+
MIDSCENE_MODEL_FAMILY=
|
|
129
|
+
`;
|
|
126
130
|
const platformInstructions = {
|
|
127
131
|
android: 'Connect an Android device and verify it with `adb devices`. Set ANDROID_DEVICE_ID to select a device.',
|
|
128
132
|
ios: 'Start WebDriverAgent and set WDA_HOST and WDA_PORT for your iOS device.',
|
|
129
133
|
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.',
|
|
130
134
|
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.'
|
|
131
135
|
};
|
|
132
|
-
function createProjectFiles(name, platform,
|
|
136
|
+
function createProjectFiles(name, platform, packageManager) {
|
|
133
137
|
const instructions = 'web' === platform ? `Install Chromium before the first test: \`${packageManagerCommands[packageManager].installChromium}\`.` : platformInstructions[platform];
|
|
134
138
|
const agentClass = agentClasses[platform];
|
|
135
|
-
const imports = packages.map((pkg, index)=>`import { createMidsceneTestNodes as createPackageNodes${index} } from ${JSON.stringify(pkg.name)};`).join('\n');
|
|
136
|
-
const extraNodes = packages.map((_, index)=>` ...createPackageNodes${index}<ProjectContext>({ platform: '${platform}', getAgent }),`).join('\n');
|
|
137
139
|
const config = `import { fileURLToPath } from 'node:url';
|
|
138
140
|
import { config as loadEnv } from 'dotenv';
|
|
139
141
|
import type { NodeExecutionContext } from '@midscene/test';
|
|
140
142
|
import { defineProjectSetup, defineTestProject } from '@midscene/test/config';
|
|
141
143
|
import { createMidsceneNodes } from '@midscene/test/midscene';
|
|
142
144
|
${platformImports[platform]}
|
|
143
|
-
${imports}
|
|
144
145
|
|
|
145
146
|
loadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });
|
|
146
147
|
|
|
@@ -155,7 +156,6 @@ ${'web' === platform ? ' context.agent ??= new PlaywrightAgent(context.page);'
|
|
|
155
156
|
|
|
156
157
|
const setup = defineProjectSetup<ProjectContext>({
|
|
157
158
|
name: '${platform}',
|
|
158
|
-
platform: '${platform}',
|
|
159
159
|
async setup({ env, onTeardown }) {
|
|
160
160
|
${platformSetup[platform]}
|
|
161
161
|
},
|
|
@@ -164,14 +164,11 @@ ${platformSetup[platform]}
|
|
|
164
164
|
export default defineTestProject<ProjectContext>({
|
|
165
165
|
projects: [{
|
|
166
166
|
name: '${platform}',
|
|
167
|
-
platform: '${platform}',
|
|
168
167
|
setup,
|
|
169
168
|
files: { include: ['cases/**/*.{yaml,yml}'] },
|
|
170
169
|
}],
|
|
171
170
|
nodes: [
|
|
172
171
|
...createMidsceneNodes<ProjectContext>({ agentClass: ${agentClass}, getAgent }),
|
|
173
|
-
${'web' === platform ? ' ...createPlaywrightNodes<ProjectContext>({ getPage: ({ context }) => context.page }),' : ''}
|
|
174
|
-
${extraNodes}
|
|
175
172
|
],
|
|
176
173
|
});
|
|
177
174
|
`;
|
|
@@ -185,10 +182,6 @@ ${extraNodes}
|
|
|
185
182
|
playwright: '^1.45.0'
|
|
186
183
|
} : {}
|
|
187
184
|
};
|
|
188
|
-
for (const pkg of packages){
|
|
189
|
-
if (Object.hasOwn(dependencies, pkg.name)) throw new Error(`Node package "${pkg.name}" conflicts with a generated project dependency.`);
|
|
190
|
-
dependencies[pkg.name] = pkg.version;
|
|
191
|
-
}
|
|
192
185
|
const env = platformEnv[platform];
|
|
193
186
|
return {
|
|
194
187
|
'package.json': `${JSON.stringify({
|
|
@@ -223,9 +216,9 @@ ${extraNodes}
|
|
|
223
216
|
]
|
|
224
217
|
}, null, 2)}\n`,
|
|
225
218
|
'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',
|
|
226
|
-
'.env.example':
|
|
219
|
+
'.env.example': `${modelEnv}\n${env}`,
|
|
227
220
|
'.gitignore': 'node_modules/\n.env\nmidscene_run/\n',
|
|
228
|
-
'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
|
|
221
|
+
'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`
|
|
229
222
|
};
|
|
230
223
|
}
|
|
231
224
|
const createParser = ()=>yargs().scriptName('midscene-test create').usage('Usage: $0 [directory] [options]').parserConfiguration({
|
|
@@ -238,12 +231,6 @@ ${extraNodes}
|
|
|
238
231
|
choices: createPlatforms,
|
|
239
232
|
requiresArg: true,
|
|
240
233
|
description: 'Platform preset (prompt if omitted)'
|
|
241
|
-
}).option('with', {
|
|
242
|
-
type: 'array',
|
|
243
|
-
string: true,
|
|
244
|
-
nargs: 1,
|
|
245
|
-
requiresArg: true,
|
|
246
|
-
description: 'npm Node package, optionally versioned (repeatable)'
|
|
247
234
|
}).option('package-manager', {
|
|
248
235
|
type: 'string',
|
|
249
236
|
choices: createPackageManagers,
|
|
@@ -258,7 +245,7 @@ ${extraNodes}
|
|
|
258
245
|
type: 'boolean',
|
|
259
246
|
default: false,
|
|
260
247
|
description: 'Create files without installing dependencies or generating midscene-node-reference.md'
|
|
261
|
-
}).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').
|
|
248
|
+
}).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)=>{
|
|
262
249
|
throw error ?? new Error(message);
|
|
263
250
|
}).wrap(100);
|
|
264
251
|
function parseCreateArgs(args, onOutput) {
|
|
@@ -268,17 +255,10 @@ ${extraNodes}
|
|
|
268
255
|
});
|
|
269
256
|
const directory = void 0 === values._[0] ? void 0 : String(values._[0]);
|
|
270
257
|
if (void 0 !== directory && !directory.trim()) throw new Error('Project directory must not be empty.');
|
|
271
|
-
const packages = (values.with ?? []).map(parseNodePackageSpec);
|
|
272
|
-
const seen = new Set();
|
|
273
|
-
for (const pkg of packages){
|
|
274
|
-
if (seen.has(pkg.name)) throw new Error(`Node package "${pkg.name}" was specified more than once.`);
|
|
275
|
-
seen.add(pkg.name);
|
|
276
|
-
}
|
|
277
258
|
return {
|
|
278
259
|
directory,
|
|
279
260
|
platform: values.platform,
|
|
280
261
|
packageManager: values['package-manager'],
|
|
281
|
-
packages,
|
|
282
262
|
yes: values.yes ?? false,
|
|
283
263
|
skipInstall: values['skip-install'] ?? false,
|
|
284
264
|
help: true === values.help
|
|
@@ -375,7 +355,7 @@ ${extraNodes}
|
|
|
375
355
|
const root = (0, external_node_path_.resolve)(services.cwd, directory);
|
|
376
356
|
const name = (0, external_node_path_.basename)(root).toLowerCase().replace(/[^a-z0-9._-]/g, '-').replace(/^[._-]+/, '') || 'midscene-tests';
|
|
377
357
|
const commands = packageManagerCommands[packageManager];
|
|
378
|
-
const files = createProjectFiles(name, platform,
|
|
358
|
+
const files = createProjectFiles(name, platform, packageManager);
|
|
379
359
|
checkDestinations(root, [
|
|
380
360
|
...Object.keys(files),
|
|
381
361
|
'midscene-node-reference.md',
|
package/dist/es/1~891.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1~891.mjs","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":"1~891.mjs","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/es/cli/index.mjs
CHANGED
|
@@ -373,26 +373,15 @@ const validateVariables = (value, label)=>{
|
|
|
373
373
|
assertJsonValue(value, label, new Set());
|
|
374
374
|
return deepFreezeJson(value);
|
|
375
375
|
};
|
|
376
|
-
const
|
|
377
|
-
'web',
|
|
378
|
-
'android',
|
|
379
|
-
'ios',
|
|
380
|
-
'harmony',
|
|
381
|
-
'computer'
|
|
382
|
-
]);
|
|
383
|
-
const validatePlatform = (value, label)=>{
|
|
384
|
-
if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, harmony, computer.`);
|
|
385
|
-
return value;
|
|
386
|
-
};
|
|
387
|
-
const validateProjectSetup = (value, projectPlatform, label)=>{
|
|
376
|
+
const validateProjectSetup = (value, label)=>{
|
|
388
377
|
if (void 0 === value) return;
|
|
389
378
|
if (!isRecord(value)) throw new TypeError(`Midscene config ${label} must be an object.`);
|
|
379
|
+
rejectUnknownKeys(value, [
|
|
380
|
+
'name',
|
|
381
|
+
'setup'
|
|
382
|
+
], label);
|
|
390
383
|
if ('string' != typeof value.name || 0 === value.name.trim().length) throw new TypeError(`Midscene config ${label}.name must be non-empty.`);
|
|
391
384
|
if ('function' != typeof value.setup) throw new TypeError(`Midscene config ${label}.setup must be a function.`);
|
|
392
|
-
const supported = void 0 === value.platform ? void 0 : Array.isArray(value.platform) ? value.platform.map((item, index)=>validatePlatform(item, `${label}.platform[${index}]`)) : [
|
|
393
|
-
validatePlatform(value.platform, `${label}.platform`)
|
|
394
|
-
];
|
|
395
|
-
if (supported && !supported.includes(projectPlatform)) throw new TypeError(`Midscene config ${label} does not support project platform "${projectPlatform}".`);
|
|
396
385
|
return value;
|
|
397
386
|
};
|
|
398
387
|
const validatePositiveInteger = (value, fallback, label)=>{
|
|
@@ -405,24 +394,15 @@ const validateNonNegativeInteger = (value, fallback, label)=>{
|
|
|
405
394
|
if (!Number.isInteger(value) || value < 0) throw new TypeError(`Midscene config ${label} must be a non-negative integer.`);
|
|
406
395
|
return value;
|
|
407
396
|
};
|
|
408
|
-
const validateExecutionProjects = (value, defaultSetup)=>{
|
|
397
|
+
const validateExecutionProjects = (value, defaultSetup, globalNodes)=>{
|
|
409
398
|
if (void 0 === value) {
|
|
410
|
-
|
|
411
|
-
if (void 0 !== defaultSetup) {
|
|
412
|
-
if (!isRecord(defaultSetup)) throw new TypeError('Midscene config setup must be an object.');
|
|
413
|
-
if (Array.isArray(defaultSetup.platform)) {
|
|
414
|
-
if (1 !== defaultSetup.platform.length) throw new TypeError('Midscene config setup.platform must select exactly one platform when projects is omitted.');
|
|
415
|
-
platform = validatePlatform(defaultSetup.platform[0], 'setup.platform[0]');
|
|
416
|
-
} else if (void 0 !== defaultSetup.platform) platform = validatePlatform(defaultSetup.platform, 'setup.platform');
|
|
417
|
-
}
|
|
418
|
-
const setup = validateProjectSetup(defaultSetup, platform, 'setup');
|
|
399
|
+
const setup = validateProjectSetup(defaultSetup, 'setup');
|
|
419
400
|
return {
|
|
420
401
|
hasExplicitProjects: false,
|
|
421
402
|
projects: Object.freeze([
|
|
422
403
|
Object.freeze({
|
|
423
404
|
projectId: projectIdFromIndex(0),
|
|
424
405
|
name: 'default',
|
|
425
|
-
platform,
|
|
426
406
|
...setup ? {
|
|
427
407
|
setup
|
|
428
408
|
} : {},
|
|
@@ -431,7 +411,8 @@ const validateExecutionProjects = (value, defaultSetup)=>{
|
|
|
431
411
|
exclude: []
|
|
432
412
|
}),
|
|
433
413
|
retry: 0,
|
|
434
|
-
variables: Object.freeze({})
|
|
414
|
+
variables: Object.freeze({}),
|
|
415
|
+
nodes: new NodeRegistry(globalNodes.definitions())
|
|
435
416
|
})
|
|
436
417
|
])
|
|
437
418
|
};
|
|
@@ -444,8 +425,8 @@ const validateExecutionProjects = (value, defaultSetup)=>{
|
|
|
444
425
|
if (!isRecord(candidate)) throw new TypeError(`Midscene config ${label} must be an object.`);
|
|
445
426
|
rejectUnknownKeys(candidate, [
|
|
446
427
|
'name',
|
|
447
|
-
'platform',
|
|
448
428
|
'setup',
|
|
429
|
+
'nodes',
|
|
449
430
|
'files',
|
|
450
431
|
'tags',
|
|
451
432
|
'retry',
|
|
@@ -454,12 +435,17 @@ const validateExecutionProjects = (value, defaultSetup)=>{
|
|
|
454
435
|
if ('string' != typeof candidate.name || 0 === candidate.name.trim().length) throw new TypeError(`Midscene config ${label}.name must be non-empty.`);
|
|
455
436
|
if (names.has(candidate.name)) throw new TypeError(`Midscene config project name "${candidate.name}" must be unique.`);
|
|
456
437
|
names.add(candidate.name);
|
|
457
|
-
const platform = validatePlatform(candidate.platform, `${label}.platform`);
|
|
458
438
|
const files = validateTestFileSelection(candidate.files, `${label}.files`);
|
|
439
|
+
if (void 0 !== candidate.nodes && !Array.isArray(candidate.nodes)) throw new TypeError(`Midscene config ${label}.nodes must be an array.`);
|
|
440
|
+
const localNodes = new NodeRegistry(candidate.nodes);
|
|
441
|
+
const nodes = new NodeRegistry([
|
|
442
|
+
...globalNodes.definitions().filter((node)=>!localNodes.has(node.name)),
|
|
443
|
+
...localNodes.definitions()
|
|
444
|
+
]);
|
|
459
445
|
return Object.freeze({
|
|
460
446
|
projectId: projectIdFromIndex(index),
|
|
461
447
|
name: candidate.name,
|
|
462
|
-
|
|
448
|
+
nodes,
|
|
463
449
|
...files ? {
|
|
464
450
|
files
|
|
465
451
|
} : {},
|
|
@@ -467,7 +453,7 @@ const validateExecutionProjects = (value, defaultSetup)=>{
|
|
|
467
453
|
retry: validateNonNegativeInteger(candidate.retry, 0, `${label}.retry`),
|
|
468
454
|
variables: validateVariables(candidate.variables, `${label}.variables`),
|
|
469
455
|
...void 0 === candidate.setup ? {} : {
|
|
470
|
-
setup: validateProjectSetup(candidate.setup,
|
|
456
|
+
setup: validateProjectSetup(candidate.setup, `${label}.setup`)
|
|
471
457
|
}
|
|
472
458
|
});
|
|
473
459
|
});
|
|
@@ -501,7 +487,8 @@ const validateOutput = (value)=>{
|
|
|
501
487
|
});
|
|
502
488
|
};
|
|
503
489
|
const validateTestProjectDefinition = (definition)=>{
|
|
504
|
-
if (!isRecord(definition)
|
|
490
|
+
if (!isRecord(definition)) throw new TypeError('Midscene config must default export an object.');
|
|
491
|
+
if (void 0 !== definition.nodes && !Array.isArray(definition.nodes)) throw new TypeError('Midscene config nodes must be an array.');
|
|
505
492
|
if ('setupWorkflow' in definition) throw new TypeError('Midscene config setupWorkflow is no longer supported. Use setup or projects[].setup instead.');
|
|
506
493
|
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.');
|
|
507
494
|
if ('root' in definition) throw new TypeError('Midscene config root is not supported. Pass the Test Project directory to midscene-test instead.');
|
|
@@ -514,8 +501,8 @@ const validateTestProjectDefinition = (definition)=>{
|
|
|
514
501
|
'output',
|
|
515
502
|
'nodes'
|
|
516
503
|
], 'root');
|
|
517
|
-
const resolvedProjects = validateExecutionProjects(definition.projects, definition.setup);
|
|
518
504
|
const nodes = new NodeRegistry(definition.nodes);
|
|
505
|
+
const resolvedProjects = validateExecutionProjects(definition.projects, definition.setup, nodes);
|
|
519
506
|
return {
|
|
520
507
|
...resolvedProjects,
|
|
521
508
|
test: validateTestOptions(definition.test),
|
|
@@ -579,7 +566,6 @@ const createProjectRuntime = (options)=>{
|
|
|
579
566
|
const endedAt = new Date();
|
|
580
567
|
return {
|
|
581
568
|
projectName: options.project.name,
|
|
582
|
-
platform: options.project.platform,
|
|
583
569
|
status: 'failed' === requestedStatus || setupError || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
584
570
|
...setupError ? {
|
|
585
571
|
setupError
|
|
@@ -880,7 +866,6 @@ function createDocumentRuntime(document, options) {
|
|
|
880
866
|
const project = options.project ?? Object.freeze({
|
|
881
867
|
projectId: document.projectId,
|
|
882
868
|
name: document.projectId,
|
|
883
|
-
platform: 'web',
|
|
884
869
|
tags: Object.freeze({
|
|
885
870
|
include: [],
|
|
886
871
|
exclude: []
|
|
@@ -1813,7 +1798,6 @@ const buildProject = (project, context)=>{
|
|
|
1813
1798
|
return {
|
|
1814
1799
|
projectId: project.projectId,
|
|
1815
1800
|
name: project.name,
|
|
1816
|
-
platform: project.platform,
|
|
1817
1801
|
status: project.status,
|
|
1818
1802
|
retry: project.retry,
|
|
1819
1803
|
...project.lifecycle ? {
|
|
@@ -1964,7 +1948,6 @@ const writeTestProjectRunResult = (options)=>{
|
|
|
1964
1948
|
projects: result.projects.map((project)=>({
|
|
1965
1949
|
projectId: project.projectId,
|
|
1966
1950
|
name: project.name,
|
|
1967
|
-
platform: project.platform,
|
|
1968
1951
|
status: project.status,
|
|
1969
1952
|
retry: project.retry,
|
|
1970
1953
|
fileSelection: project.fileSelection,
|
|
@@ -2175,7 +2158,7 @@ const selectProjects = (projects, names)=>{
|
|
|
2175
2158
|
if (unknown) throw new Error(`Unknown Midscene project: ${unknown}`);
|
|
2176
2159
|
return projects.filter((project)=>requested.has(project.name));
|
|
2177
2160
|
};
|
|
2178
|
-
const prepareProject = (project, projectRoot,
|
|
2161
|
+
const prepareProject = (project, projectRoot, runDir)=>{
|
|
2179
2162
|
const fileSelection = project.files ?? DEFAULT_TEST_FILE_SELECTION;
|
|
2180
2163
|
const files = discoverTestFiles(projectRoot, fileSelection);
|
|
2181
2164
|
const sources = files.map((absolutePath)=>({
|
|
@@ -2197,7 +2180,7 @@ const prepareProject = (project, projectRoot, resolveNode, runDir)=>{
|
|
|
2197
2180
|
}
|
|
2198
2181
|
for (const source of sources)try {
|
|
2199
2182
|
const collected = collectWorkflowDocument(source, {
|
|
2200
|
-
resolveNode,
|
|
2183
|
+
resolveNode: project.nodes.get.bind(project.nodes),
|
|
2201
2184
|
variables: project.variables,
|
|
2202
2185
|
env: process.env
|
|
2203
2186
|
});
|
|
@@ -2241,7 +2224,7 @@ async function runTestProject(options = {}) {
|
|
|
2241
2224
|
});
|
|
2242
2225
|
mkdirSync(runDir);
|
|
2243
2226
|
const selectedProjects = selectProjects(definition.projects, options.projectNames);
|
|
2244
|
-
const preparedProjects = selectedProjects.map((project)=>prepareProject(project, projectRoot,
|
|
2227
|
+
const preparedProjects = selectedProjects.map((project)=>prepareProject(project, projectRoot, runDir));
|
|
2245
2228
|
const progress = options.onProgress ?? (()=>{});
|
|
2246
2229
|
const totalDocuments = preparedProjects.reduce((total, prepared)=>total + prepared.documents.length, 0);
|
|
2247
2230
|
const totalCases = preparedProjects.reduce((total, prepared)=>total + prepared.selectedCaseCount, 0);
|
|
@@ -2277,7 +2260,7 @@ async function runTestProject(options = {}) {
|
|
|
2277
2260
|
};
|
|
2278
2261
|
const announceProject = (prepared, projectIndex)=>{
|
|
2279
2262
|
const { project } = prepared;
|
|
2280
|
-
runInfrastructureCallback(()=>progress(`[project ${projectIndex + 1}/${preparedProjects.length}] ${project.name}
|
|
2263
|
+
runInfrastructureCallback(()=>progress(`[project ${projectIndex + 1}/${preparedProjects.length}] ${project.name}`));
|
|
2281
2264
|
};
|
|
2282
2265
|
const buildProjectResult = (prepared, cases, documents, lifecycle)=>{
|
|
2283
2266
|
const { project } = prepared;
|
|
@@ -2285,7 +2268,6 @@ async function runTestProject(options = {}) {
|
|
|
2285
2268
|
return {
|
|
2286
2269
|
projectId: project.projectId,
|
|
2287
2270
|
name: project.name,
|
|
2288
|
-
platform: project.platform,
|
|
2289
2271
|
status: projectFailed ? 'failed' : 'success',
|
|
2290
2272
|
retry: project.retry,
|
|
2291
2273
|
fileSelection: prepared.fileSelection,
|
|
@@ -2334,7 +2316,7 @@ async function runTestProject(options = {}) {
|
|
|
2334
2316
|
}
|
|
2335
2317
|
projectProgress(` [document ${documentIndex + 1}/${prepared.documents.length}] ${document.sourcePath}`);
|
|
2336
2318
|
const execution = await runWorkflowDocument(document, {
|
|
2337
|
-
resolveNode:
|
|
2319
|
+
resolveNode: project.nodes.require.bind(project.nodes),
|
|
2338
2320
|
project,
|
|
2339
2321
|
projectContext: runtime.context,
|
|
2340
2322
|
retry: project.retry,
|