@midscene/test 1.13.2-beta-20260923035443.0 → 1.13.3
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~214.mjs +15 -15
- package/dist/es/1~214.mjs.map +1 -1
- package/dist/es/test-cli.mjs +47 -24
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/0~160.js +15 -15
- package/dist/lib/0~160.js.map +1 -1
- package/dist/lib/test-cli.js +47 -24
- package/dist/lib/test-cli.js.map +1 -1
- package/package.json +13 -13
package/dist/es/1~214.mjs
CHANGED
|
@@ -216,7 +216,7 @@ export default defineTestProject<ProjectContext>({
|
|
|
216
216
|
'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',
|
|
217
217
|
'.env.example': `${modelEnv}\n${env}`,
|
|
218
218
|
'.gitignore': 'node_modules/\n.env\nmidscene_run/\n',
|
|
219
|
-
'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-
|
|
219
|
+
'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-spec.${platform}.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-spec.${platform}.md\` for the available Nodes and their inputs.\n\nAfter changing Node registrations in \`midscene.config.ts\`, run \`${packageManager} run nodes\` to refresh the spec. 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`
|
|
220
220
|
};
|
|
221
221
|
}
|
|
222
222
|
const createParser = ()=>yargs().scriptName('midscene-test create').usage('Usage: $0 [directory] [options]').parserConfiguration({
|
|
@@ -242,8 +242,8 @@ export default defineTestProject<ProjectContext>({
|
|
|
242
242
|
}).option('skip-install', {
|
|
243
243
|
type: 'boolean',
|
|
244
244
|
default: false,
|
|
245
|
-
description: 'Create files without installing dependencies or generating
|
|
246
|
-
}).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
|
|
245
|
+
description: 'Create files without installing dependencies or generating Node Specs'
|
|
246
|
+
}).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 a Project-specific Node Spec 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)=>{
|
|
247
247
|
throw error ?? new Error(message);
|
|
248
248
|
}).wrap(100);
|
|
249
249
|
function parseCreateArgs(args, onOutput) {
|
|
@@ -328,7 +328,7 @@ export default defineTestProject<ProjectContext>({
|
|
|
328
328
|
selectPlatform,
|
|
329
329
|
selectPackageManager,
|
|
330
330
|
confirmInstall: ()=>prompts_confirm({
|
|
331
|
-
message: 'Install dependencies and generate
|
|
331
|
+
message: 'Install dependencies and generate Node Specs now?',
|
|
332
332
|
default: true
|
|
333
333
|
}),
|
|
334
334
|
runPackageManager,
|
|
@@ -356,7 +356,7 @@ export default defineTestProject<ProjectContext>({
|
|
|
356
356
|
const files = createProjectFiles(name, platform, packageManager);
|
|
357
357
|
checkDestinations(root, [
|
|
358
358
|
...Object.keys(files),
|
|
359
|
-
|
|
359
|
+
`midscene-node-spec.${platform}.md`,
|
|
360
360
|
'pnpm-lock.yaml',
|
|
361
361
|
'package-lock.json',
|
|
362
362
|
'npm-shrinkwrap.json'
|
|
@@ -378,29 +378,29 @@ export default defineTestProject<ProjectContext>({
|
|
|
378
378
|
if (error instanceof Error && [
|
|
379
379
|
'ExitPromptError',
|
|
380
380
|
'AbortPromptError'
|
|
381
|
-
].includes(error.name)) throw new Error(`Project creation cancelled. Project files are preserved in ${root}.\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-
|
|
381
|
+
].includes(error.name)) throw new Error(`Project creation cancelled. Project files are preserved in ${root}.\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-spec.${platform}.md.`);
|
|
382
382
|
throw error;
|
|
383
383
|
}
|
|
384
|
-
if (!install) return void io.log(`Project files ready: ${root}\nNext: run ${packageManager} ${commands.install.join(' ')} in the project directory. The postinstall script will generate midscene-node-
|
|
384
|
+
if (!install) return void io.log(`Project files ready: ${root}\nNext: run ${packageManager} ${commands.install.join(' ')} in the project directory. The postinstall script will generate midscene-node-spec.${platform}.md.\nThen follow README.md to configure your model and run tests.`);
|
|
385
385
|
io.log(`Installing dependencies with ${packageManager}...`);
|
|
386
386
|
try {
|
|
387
387
|
await services.runPackageManager(packageManager, commands.install, root);
|
|
388
388
|
} catch (error) {
|
|
389
|
-
throw new Error(`Dependency installation or postinstall Node
|
|
389
|
+
throw new Error(`Dependency installation or postinstall Node Spec 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)}`);
|
|
390
390
|
}
|
|
391
391
|
try {
|
|
392
|
-
const
|
|
393
|
-
const generatedByPostinstall = Boolean(statIfPresent(
|
|
392
|
+
const specPath = (0, external_node_path_.resolve)(root, `midscene-node-spec.${platform}.md`);
|
|
393
|
+
const generatedByPostinstall = Boolean(statIfPresent(specPath));
|
|
394
394
|
if (!generatedByPostinstall) {
|
|
395
|
-
io.log('Generating Node
|
|
395
|
+
io.log('Generating Node Spec...');
|
|
396
396
|
await services.runPackageManager(packageManager, commands.describe, root);
|
|
397
397
|
}
|
|
398
|
-
const markdown = (0, external_node_fs_.readFileSync)(
|
|
399
|
-
if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) throw new Error('nodes did not generate a valid Node
|
|
398
|
+
const markdown = (0, external_node_fs_.readFileSync)(specPath, 'utf8');
|
|
399
|
+
if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) throw new Error('nodes did not generate a valid Node Spec.');
|
|
400
400
|
} catch (error) {
|
|
401
|
-
throw new Error(`Node
|
|
401
|
+
throw new Error(`Node Spec 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)}`);
|
|
402
402
|
}
|
|
403
|
-
io.log(`Project ready: ${root}\nNode
|
|
403
|
+
io.log(`Project ready: ${root}\nNode Spec: ${(0, external_node_path_.resolve)(root, `midscene-node-spec.${platform}.md`)}\nNext: copy .env.example to .env and configure your model.${'web' === platform ? `\nInstall Chromium: ${commands.installChromium}` : 'computer' === platform ? '\nPrepare desktop dependencies and permissions as described in README.md.' : '\nConfigure your device connection in .env.'}\nRun tests from the project directory: ${packageManager} test`);
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
406
|
};
|
package/dist/es/1~214.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1~214.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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"}
|
|
1
|
+
{"version":3,"file":"1~214.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-spec.${platform}.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-spec.${platform}.md\\` for the available Nodes and their inputs.\\n\\nAfter changing Node registrations in \\`midscene.config.ts\\`, run \\`${packageManager} run nodes\\` to refresh the spec. 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 Node Specs',\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 a Project-specific Node Spec 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: 'Install dependencies and generate Node Specs 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-spec.${platform}.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-spec.${platform}.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-spec.${platform}.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 Spec 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 specPath = resolve(root, `midscene-node-spec.${platform}.md`);\n const generatedByPostinstall = Boolean(statIfPresent(specPath));\n // Fall back to explicit generation if lifecycle scripts were disabled.\n if (!generatedByPostinstall) {\n io.log('Generating Node Spec...');\n await services.runPackageManager(packageManager, commands.describe, root);\n }\n const markdown = readFileSync(specPath, 'utf8');\n if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) {\n throw new Error('nodes did not generate a valid Node Spec.');\n }\n } catch (error) {\n throw new Error(\n `Node Spec 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 Spec: ${resolve(root, `midscene-node-spec.${platform}.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","specPath","generatedByPostinstall","markdown","readFileSync"],"mappings":"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"}
|
package/dist/es/test-cli.mjs
CHANGED
|
@@ -20,7 +20,9 @@ var __webpack_modules__ = {
|
|
|
20
20
|
module.exports = __rspack_external_node_path_c5b9b54f;
|
|
21
21
|
},
|
|
22
22
|
"./package.json" (module) {
|
|
23
|
-
module.exports =
|
|
23
|
+
module.exports = {
|
|
24
|
+
rE: "1.13.3"
|
|
25
|
+
};
|
|
24
26
|
}
|
|
25
27
|
};
|
|
26
28
|
var __webpack_module_cache__ = {};
|
|
@@ -96,7 +98,7 @@ var external_node_fs_ = __webpack_require__("node:fs");
|
|
|
96
98
|
var external_node_path_ = __webpack_require__("node:path");
|
|
97
99
|
var package_0 = __webpack_require__("./package.json");
|
|
98
100
|
const GENERATED_COMMENT = '<!-- Generated by `midscene-test nodes`. Do not edit directly. -->';
|
|
99
|
-
const
|
|
101
|
+
const sortNodesForSpec = (nodes)=>{
|
|
100
102
|
const priority = (name)=>'aiAct' === name ? 0 : 'aiAssert' === name ? 1 : 2;
|
|
101
103
|
return [
|
|
102
104
|
...nodes
|
|
@@ -116,7 +118,7 @@ const renderNodeOverview = (nodes)=>{
|
|
|
116
118
|
const renderWorkflowLocations = (location)=>{
|
|
117
119
|
const sections = [
|
|
118
120
|
'## Where to use these Nodes',
|
|
119
|
-
'Use these Nodes in Workflow Documents (`.yaml` or `.yml` files) selected by an Execution Project. Case file patterns and the config file path below are relative to this
|
|
121
|
+
'Use these Nodes in Workflow Documents (`.yaml` or `.yml` files) selected by an Execution Project. Case file patterns and the config file path below are relative to this spec file. Case file patterns use glob syntax. A custom `--config` path does not change where case files are selected.',
|
|
120
122
|
'Projects created by `midscene-test create` select `cases/**/*.{yaml,yml}` by default, so you can write test cases in `cases/example.yaml` or subdirectories of `cases/`. If an Execution Project has no `files` configuration, Midscene Test searches `**/*.{yaml,yml}` recursively instead. Check `projects[].files` in your configuration before choosing a file location.'
|
|
121
123
|
];
|
|
122
124
|
if (location) {
|
|
@@ -206,13 +208,14 @@ const renderNode = (node, warnings)=>{
|
|
|
206
208
|
}
|
|
207
209
|
return sections.join('\n\n');
|
|
208
210
|
};
|
|
209
|
-
const
|
|
211
|
+
const renderNodeSpec = (nodes, location)=>{
|
|
210
212
|
const warnings = [];
|
|
211
|
-
const sortedNodes =
|
|
213
|
+
const sortedNodes = sortNodesForSpec(nodes);
|
|
212
214
|
const sections = [
|
|
213
215
|
GENERATED_COMMENT,
|
|
214
|
-
'# Midscene Test Node
|
|
215
|
-
'This document describes the
|
|
216
|
+
'# Midscene Test Node Spec',
|
|
217
|
+
'This document describes the Nodes available in YAML test files (`.yaml` or `.yml`) for the Midscene Test framework (`@midscene/test`). It lists the Nodes registered by the current Test Project, their inputs, and how to call them in test steps and lifecycle hooks.',
|
|
218
|
+
"Learn about the framework and YAML test authoring:\n\n- [Midscene Test overview](https://midscenejs.com/midscene-test/overview): framework concepts and the role of Nodes.\n- [Create and use test projects](https://midscenejs.com/midscene-test/use): write YAML cases, run tests, and inspect results.\n- [Configure test projects](https://midscenejs.com/midscene-test/configuration): register Nodes, select case files, and manage runtime resources.",
|
|
216
219
|
renderNodeOverview(sortedNodes),
|
|
217
220
|
renderWorkflowLocations(location),
|
|
218
221
|
WORKFLOW_STEP_CONTRACT
|
|
@@ -1603,24 +1606,44 @@ const runNodesCommand = async (options, io)=>{
|
|
|
1603
1606
|
const projectName = options.projectNames?.[0];
|
|
1604
1607
|
const selectedProjects = projectName ? project.projects.filter((candidate)=>candidate.name === projectName) : project.projects;
|
|
1605
1608
|
if (0 === selectedProjects.length) throw new Error(`Unknown Midscene project: ${projectName}`);
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1609
|
+
const filenames = new Map();
|
|
1610
|
+
const owners = new Map();
|
|
1611
|
+
for (const executionProject of project.projects){
|
|
1612
|
+
const safeName = executionProject.name.normalize('NFC').replace(/[<>:"/\\|?*\u0000-\u001f]/g, '-').replace(/[. ]+$/g, '-');
|
|
1613
|
+
const filename = project.hasExplicitProjects ? `midscene-node-spec.${safeName}.md` : 'midscene-node-spec.md';
|
|
1614
|
+
const key = filename.toLowerCase();
|
|
1615
|
+
const owner = owners.get(key);
|
|
1616
|
+
if (void 0 !== owner) throw new Error(`Projects "${owner}" and "${executionProject.name}" generate the same Node Spec filename: ${filename}`);
|
|
1617
|
+
owners.set(key, executionProject.name);
|
|
1618
|
+
filenames.set(executionProject.name, filename);
|
|
1619
|
+
}
|
|
1620
|
+
const specs = selectedProjects.map((executionProject)=>{
|
|
1621
|
+
const nodes = sortNodesForSpec(executionProject.nodes.definitions());
|
|
1622
|
+
const document = renderNodeSpec(nodes, {
|
|
1623
|
+
configPath: configPath ? (0, external_node_path_.relative)(configSearchRoot, configPath).split(external_node_path_.sep).join('/') : void 0,
|
|
1624
|
+
projects: [
|
|
1625
|
+
{
|
|
1626
|
+
name: executionProject.name,
|
|
1627
|
+
files: executionProject.files ?? DEFAULT_TEST_FILE_SELECTION
|
|
1628
|
+
}
|
|
1629
|
+
]
|
|
1630
|
+
});
|
|
1631
|
+
return {
|
|
1632
|
+
executionProject,
|
|
1633
|
+
nodes,
|
|
1634
|
+
document
|
|
1635
|
+
};
|
|
1615
1636
|
});
|
|
1616
|
-
for (const
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1637
|
+
for (const { executionProject, nodes, document } of specs){
|
|
1638
|
+
for (const warning of document.warnings)io.error(`midscene-test nodes: ${warning}`);
|
|
1639
|
+
const specPath = (0, external_node_path_.resolve)(configSearchRoot, filenames.get(executionProject.name));
|
|
1640
|
+
(0, external_node_fs_.writeFileSync)(specPath, document.markdown);
|
|
1641
|
+
if (project.hasExplicitProjects || projectName) io.log(`Execution Project: ${executionProject.name}`);
|
|
1642
|
+
io.log(`Registered Nodes (${nodes.length}):`);
|
|
1643
|
+
if (0 === nodes.length) io.log('No nodes are registered by the current Test Project.');
|
|
1644
|
+
for (const node of nodes)io.log(`- ${node.name}: ${node.description?.trim() || "Description not declared."}`);
|
|
1645
|
+
io.log(`\nNode Spec generated: ${specPath}`);
|
|
1646
|
+
}
|
|
1624
1647
|
};
|
|
1625
1648
|
async function runTestCli(args, io = defaultCliIO) {
|
|
1626
1649
|
try {
|