@midscene/test 1.12.4 → 1.12.5-beta-20260907092148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
1
+ import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
2
+ import * as __rspack_external_node_path_c5b9b54f from "node:path";
3
3
  import { z } from "zod/v4";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { require as api_require } from "tsx/cjs/api";
@@ -8,18 +8,134 @@ import { createHash, randomUUID } from "node:crypto";
8
8
  import { setMaxListeners } from "node:events";
9
9
  import { globSync } from "tinyglobby";
10
10
  import { JSON_SCHEMA, load } from "js-yaml";
11
- const GENERATED_COMMENT = '<!-- Generated by `midscene-test describe-nodes`. Do not edit directly. -->';
11
+ var __webpack_modules__ = {
12
+ "node:fs" (module) {
13
+ module.exports = __rspack_external_node_fs_5ea92f0c;
14
+ },
15
+ "node:path" (module) {
16
+ module.exports = __rspack_external_node_path_c5b9b54f;
17
+ }
18
+ };
19
+ var __webpack_module_cache__ = {};
20
+ function __webpack_require__(moduleId) {
21
+ var cachedModule = __webpack_module_cache__[moduleId];
22
+ if (void 0 !== cachedModule) return cachedModule.exports;
23
+ var module = __webpack_module_cache__[moduleId] = {
24
+ exports: {}
25
+ };
26
+ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
27
+ return module.exports;
28
+ }
29
+ __webpack_require__.m = __webpack_modules__;
30
+ (()=>{
31
+ __webpack_require__.d = (exports, definition)=>{
32
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) Object.defineProperty(exports, key, {
33
+ enumerable: true,
34
+ get: definition[key]
35
+ });
36
+ };
37
+ })();
38
+ (()=>{
39
+ __webpack_require__.f = {};
40
+ __webpack_require__.e = (chunkId)=>Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key)=>{
41
+ __webpack_require__.f[key](chunkId, promises);
42
+ return promises;
43
+ }, []));
44
+ })();
45
+ (()=>{
46
+ __webpack_require__.u = (chunkId)=>"1~" + chunkId + ".mjs";
47
+ })();
48
+ (()=>{
49
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
50
+ })();
51
+ (()=>{
52
+ var installedChunks = {
53
+ 443: 0
54
+ };
55
+ var installChunk = (data)=>{
56
+ var __rspack_esm_ids = data.__rspack_esm_ids;
57
+ var __webpack_modules__ = data.__webpack_modules__;
58
+ var __rspack_esm_runtime = data.__rspack_esm_runtime;
59
+ var moduleId, chunkId, i = 0;
60
+ for(moduleId in __webpack_modules__)if (__webpack_require__.o(__webpack_modules__, moduleId)) __webpack_require__.m[moduleId] = __webpack_modules__[moduleId];
61
+ if (__rspack_esm_runtime) __rspack_esm_runtime(__webpack_require__);
62
+ for(; i < __rspack_esm_ids.length; i++){
63
+ chunkId = __rspack_esm_ids[i];
64
+ if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) installedChunks[chunkId][0]();
65
+ installedChunks[__rspack_esm_ids[i]] = 0;
66
+ }
67
+ };
68
+ __webpack_require__.f.j = function(chunkId, promises) {
69
+ var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : void 0;
70
+ if (0 !== installedChunkData) if (installedChunkData) promises.push(installedChunkData[1]);
71
+ else {
72
+ var promise = import("./" + __webpack_require__.u(chunkId)).then(installChunk, (e)=>{
73
+ if (0 !== installedChunks[chunkId]) installedChunks[chunkId] = void 0;
74
+ throw e;
75
+ });
76
+ var promise = Promise.race([
77
+ promise,
78
+ new Promise((resolve)=>{
79
+ installedChunkData = installedChunks[chunkId] = [
80
+ resolve
81
+ ];
82
+ })
83
+ ]);
84
+ promises.push(installedChunkData[1] = promise);
85
+ }
86
+ };
87
+ })();
88
+ var external_node_fs_ = __webpack_require__("node:fs");
89
+ var external_node_path_ = __webpack_require__("node:path");
90
+ const GENERATED_COMMENT = '<!-- Generated by `midscene-test nodes`. Do not edit directly. -->';
91
+ const sortNodesForReference = (nodes)=>{
92
+ const priority = (name)=>'aiAct' === name ? 0 : 'aiAssert' === name ? 1 : 2;
93
+ return [
94
+ ...nodes
95
+ ].sort((left, right)=>priority(left.name) - priority(right.name) || (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
96
+ };
97
+ const escapeTableCell = (value)=>value.trim().replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/\r\n|\r|\n/g, '<br>');
98
+ const renderNodeOverview = (nodes)=>{
99
+ const heading = '## Available Nodes';
100
+ if (0 === nodes.length) return `${heading}\n\nNo nodes are registered by the current Test Project.`;
101
+ const rows = nodes.map((node)=>`| ${escapeTableCell(node.name)} | ${escapeTableCell(node.description || "Description not declared.")} |`);
102
+ return `${heading}\n\nThis table lists all available Nodes. See [Node Details](#node-details) below for each Node's usage and input schema.\n\n${[
103
+ "| Node | Description |",
104
+ '| --- | --- |',
105
+ ...rows
106
+ ].join('\n')}`;
107
+ };
108
+ const renderWorkflowLocations = (location)=>{
109
+ const sections = [
110
+ '## Where to use these Nodes',
111
+ '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 reference file. Case file patterns use glob syntax. A custom `--config` path does not change where case files are selected.',
112
+ '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, the runner searches `**/*.{yaml,yml}` recursively instead. Check `projects[].files` in your configuration before choosing a file location.'
113
+ ];
114
+ if (location) {
115
+ sections.push(`**Config file:** ${location.configPath ? `\`${location.configPath}\`` : 'No config file loaded.'}`);
116
+ for (const project of location.projects){
117
+ const patterns = project.files.include.map((pattern)=>`\`${pattern}\``).join(', ');
118
+ const projectLabel = location.projects.length > 1 ? ` (Execution Project: ${project.name})` : '';
119
+ const excludes = project.files.exclude?.length ? `; excludes: ${project.files.exclude.map((pattern)=>`\`${pattern}\``).join(', ')}` : '';
120
+ sections.push(`**Case files:** ${patterns}${projectLabel}${excludes}`);
121
+ }
122
+ }
123
+ sections.push('Files matching `files.exclude` are not selected. The runner always ignores `.git/`, `.midscene/`, `midscene_run/`, and `node_modules/` directories, including nested ones.', 'Call a Node in `cases[].steps`, or in the Workflow Document lifecycle steps `beforeAll`, `beforeEach`, `afterEach`, and `afterAll`. Node registrations are shared across the Test Project; each Execution Project supplies its own runtime environment.');
124
+ return sections.join('\n\n');
125
+ };
12
126
  const WORKFLOW_STEP_CONTRACT = `## Workflow Step Contract
13
127
 
14
128
  Each step uses a node name as its key. The \`$\` object contains engine metadata and is not part of the node input.
15
129
 
16
130
  \`\`\`yaml
17
- workflow:
18
- - node.name:
19
- prompt: Describe the task.
20
- $:
21
- timeout: 30000
22
- continue-on-error: false
131
+ cases:
132
+ - name: Example case
133
+ steps:
134
+ - node.name:
135
+ prompt: Describe the task.
136
+ $:
137
+ timeout: 30000
138
+ continue-on-error: false
23
139
  \`\`\`
24
140
 
25
141
  A Node may map a string step value to one declared input field. Each Node section states whether it supports this shorthand and names the target field.`;
@@ -62,7 +178,7 @@ const toJsonSchema = (node)=>{
62
178
  };
63
179
  const renderNode = (node, warnings)=>{
64
180
  const sections = [
65
- `## \`${node.name}\``
181
+ `### \`${node.name}\``
66
182
  ];
67
183
  if (node.title) sections.push(`**Title:** ${node.title.trim()}`);
68
184
  if (node.description) sections.push(node.description.trim());
@@ -72,7 +188,7 @@ const renderNode = (node, warnings)=>{
72
188
  }
73
189
  if (false === node.stringInputKey || void 0 === node.stringInputKey) sections.push('**String shorthand:** Not supported by this Node.');
74
190
  else sections.push(`**String shorthand:** Maps to \`{ ${node.stringInputKey}: value }\`.`);
75
- sections.push('### Input Schema');
191
+ sections.push('#### Input Schema');
76
192
  if (node.inputSchema) sections.push(`\`\`\`json\n${JSON.stringify(toJsonSchema(node), null, 2)}\n\`\`\``);
77
193
  else {
78
194
  warnings.push(`node "${node.name}" has no inputSchema`);
@@ -80,19 +196,21 @@ const renderNode = (node, warnings)=>{
80
196
  }
81
197
  return sections.join('\n\n');
82
198
  };
83
- const renderNodeReference = (nodes)=>{
199
+ const renderNodeReference = (nodes, location)=>{
84
200
  const warnings = [];
85
- const sortedNodes = [
86
- ...nodes
87
- ].sort((left, right)=>left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
201
+ const sortedNodes = sortNodesForReference(nodes);
88
202
  const sections = [
89
203
  GENERATED_COMMENT,
90
204
  '# Midscene Test Node Reference',
91
205
  'This document describes the nodes registered by the current Test Project.',
206
+ renderNodeOverview(sortedNodes),
207
+ renderWorkflowLocations(location),
92
208
  WORKFLOW_STEP_CONTRACT
93
209
  ];
94
- if (0 === sortedNodes.length) sections.push('No nodes are registered by the current Test Project.');
95
- else for (const node of sortedNodes)sections.push(renderNode(node, warnings));
210
+ if (sortedNodes.length > 0) {
211
+ sections.push('## Node Details');
212
+ for (const node of sortedNodes)sections.push(renderNode(node, warnings));
213
+ }
96
214
  return {
97
215
  markdown: `${sections.join('\n\n')}\n`,
98
216
  warnings
@@ -385,7 +503,7 @@ const rejectUnknownKeys = (value, allowedKeys, label)=>{
385
503
  const unknown = Object.keys(value).find((key)=>!allowed.has(key));
386
504
  if (unknown) throw new TypeError(`Midscene config ${label}.${unknown} is not supported.`);
387
505
  };
388
- const isAbsolutePattern = (pattern)=>isAbsolute(pattern) || /^[A-Za-z]:[\\/]/.test(pattern) || pattern.startsWith('\\\\');
506
+ const isAbsolutePattern = (pattern)=>(0, external_node_path_.isAbsolute)(pattern) || /^[A-Za-z]:[\\/]/.test(pattern) || pattern.startsWith('\\\\');
389
507
  const validatePatterns = (value, field, label = 'files', allowEmptyInclude = false)=>{
390
508
  if (!Array.isArray(value)) throw new TypeError(`Midscene config ${label}.${field} must be an array.`);
391
509
  if ('include' === field && 0 === value.length && !allowEmptyInclude) throw new TypeError(`Midscene config ${label}.include must be a non-empty array.`);
@@ -625,12 +743,12 @@ async function loadTestProject(configPath) {
625
743
  if (!configPath) return validateTestProjectDefinition({
626
744
  nodes: []
627
745
  });
628
- const absolutePath = resolve(configPath);
746
+ const absolutePath = (0, external_node_path_.resolve)(configPath);
629
747
  assertTypeScriptConfig(absolutePath);
630
748
  let loaded;
631
749
  try {
632
750
  loaded = await tsImport(pathToFileURL(absolutePath).href, {
633
- parentURL: pathToFileURL(`${dirname(absolutePath)}${sep}`).href,
751
+ parentURL: pathToFileURL(`${(0, external_node_path_.dirname)(absolutePath)}${external_node_path_.sep}`).href,
634
752
  tsconfig: false
635
753
  });
636
754
  } catch (error) {
@@ -911,7 +1029,7 @@ const reportPathsFromTeardown = (result)=>{
911
1029
  if (!Array.isArray(paths)) throw new TypeError('Node teardown reportPaths must be an array.');
912
1030
  return paths.map((path, index)=>{
913
1031
  if ('string' != typeof path || 0 === path.trim().length) throw new TypeError(`Node teardown reportPaths[${index}] must be a non-empty string.`);
914
- if (!isAbsolute(path)) throw new TypeError(`Node teardown reportPaths[${index}] must be an absolute path.`);
1032
+ if (!(0, external_node_path_.isAbsolute)(path)) throw new TypeError(`Node teardown reportPaths[${index}] must be an absolute path.`);
915
1033
  return path;
916
1034
  });
917
1035
  };
@@ -1427,7 +1545,7 @@ const createWorkflowDocumentId = (projectId, sourcePath)=>createHash('sha256').u
1427
1545
  function collectWorkflowDocument(source, options) {
1428
1546
  let parsed;
1429
1547
  try {
1430
- parsed = load(readFileSync(source.absolutePath, 'utf8'), {
1548
+ parsed = load((0, external_node_fs_.readFileSync)(source.absolutePath, 'utf8'), {
1431
1549
  schema: JSON_SCHEMA
1432
1550
  });
1433
1551
  } catch (error) {
@@ -1444,7 +1562,7 @@ function collectWorkflowDocument(source, options) {
1444
1562
  'afterAll'
1445
1563
  ], 'Workflow document');
1446
1564
  if (!Array.isArray(parsed.cases) || 0 === parsed.cases.length) throw new WorkflowParseError('Workflow document must contain a non-empty cases array.');
1447
- const sourcePath = source.sourcePath.split(sep).join('/');
1565
+ const sourcePath = source.sourcePath.split(external_node_path_.sep).join('/');
1448
1566
  const resolveStepVariables = (step, phase, stepIndex, caseIndex)=>({
1449
1567
  ...step,
1450
1568
  input: resolveWorkflowVariables(step.input, {
@@ -1537,40 +1655,40 @@ function collectWorkflowDocument(source, options) {
1537
1655
  };
1538
1656
  }
1539
1657
  const writeJson = (path, value)=>{
1540
- mkdirSync(dirname(path), {
1658
+ (0, external_node_fs_.mkdirSync)((0, external_node_path_.dirname)(path), {
1541
1659
  recursive: true
1542
1660
  });
1543
- writeFileSync(path, JSON.stringify(value, null, 2));
1661
+ (0, external_node_fs_.writeFileSync)(path, JSON.stringify(value, null, 2));
1544
1662
  };
1545
- const toPosix = (value)=>value.split(sep).join('/');
1546
- const caseAttemptResultPath = (projectId, documentId, result)=>toPosix(join(projectId, 'documents', documentId, 'cases', result.caseId, `${result.runId}.json`));
1547
- const workflowDocumentResultPath = (result)=>toPosix(join(result.projectId, 'documents', result.documentId, 'document.json'));
1663
+ const toPosix = (value)=>value.split(external_node_path_.sep).join('/');
1664
+ const caseAttemptResultPath = (projectId, documentId, result)=>toPosix((0, external_node_path_.join)(projectId, 'documents', documentId, 'cases', result.caseId, `${result.runId}.json`));
1665
+ const workflowDocumentResultPath = (result)=>toPosix((0, external_node_path_.join)(result.projectId, 'documents', result.documentId, 'document.json'));
1548
1666
  const collectionErrorPath = (projectId, sourcePath)=>{
1549
1667
  const id = createHash('sha256').update(JSON.stringify([
1550
1668
  projectId,
1551
1669
  sourcePath
1552
1670
  ])).digest('hex');
1553
- return toPosix(join(projectId, 'collection-errors', `${id}.json`));
1671
+ return toPosix((0, external_node_path_.join)(projectId, 'collection-errors', `${id}.json`));
1554
1672
  };
1555
1673
  const writeWorkflowDocumentResult = (runDir, result)=>{
1556
1674
  const { documentRunId: _documentRunId, ...persistedResult } = result;
1557
- writeJson(join(runDir, workflowDocumentResultPath(result)), persistedResult);
1675
+ writeJson((0, external_node_path_.join)(runDir, workflowDocumentResultPath(result)), persistedResult);
1558
1676
  };
1559
1677
  const writeCaseAttemptResult = (runDir, projectId, documentId, result)=>{
1560
1678
  const { runId: attemptId, ...persistedResult } = result;
1561
- writeJson(join(runDir, caseAttemptResultPath(projectId, documentId, result)), {
1679
+ writeJson((0, external_node_path_.join)(runDir, caseAttemptResultPath(projectId, documentId, result)), {
1562
1680
  ...persistedResult,
1563
1681
  attemptId
1564
1682
  });
1565
1683
  };
1566
1684
  const writeCollectionError = (runDir, collectionError)=>{
1567
- writeJson(join(runDir, collectionErrorPath(collectionError.projectId, collectionError.sourcePath)), {
1685
+ writeJson((0, external_node_path_.join)(runDir, collectionErrorPath(collectionError.projectId, collectionError.sourcePath)), {
1568
1686
  kind: 'collection-error',
1569
1687
  ...collectionError
1570
1688
  });
1571
1689
  };
1572
1690
  const relativeToSummary = (summaryPath, target)=>{
1573
- const value = relative(dirname(summaryPath), target);
1691
+ const value = (0, external_node_path_.relative)((0, external_node_path_.dirname)(summaryPath), target);
1574
1692
  return toPosix(value || '.');
1575
1693
  };
1576
1694
  const errorJson = (error)=>error instanceof Error && 'toJSON' in error ? error.toJSON() : error;
@@ -1672,9 +1790,9 @@ const DEFAULT_TEST_FILE_SELECTION = {
1672
1790
  '**/*.{yaml,yml}'
1673
1791
  ]
1674
1792
  };
1675
- const test_project_runner_toPosix = (value)=>value.split(sep).join('/');
1793
+ const test_project_runner_toPosix = (value)=>value.split(external_node_path_.sep).join('/');
1676
1794
  const discoverTestFiles = (projectRoot, selection = DEFAULT_TEST_FILE_SELECTION)=>{
1677
- const root = resolve(projectRoot);
1795
+ const root = (0, external_node_path_.resolve)(projectRoot);
1678
1796
  const normalized = validateTestFileSelection(selection);
1679
1797
  if (!normalized) throw new TypeError('Test file selection is required.');
1680
1798
  const files = globSync(normalized.include, {
@@ -1691,25 +1809,25 @@ const discoverTestFiles = (projectRoot, selection = DEFAULT_TEST_FILE_SELECTION)
1691
1809
  onlyFiles: true
1692
1810
  }).filter((file)=>/\.ya?ml$/i.test(file));
1693
1811
  return [
1694
- ...new Set(files.map((file)=>resolve(file)))
1812
+ ...new Set(files.map((file)=>(0, external_node_path_.resolve)(file)))
1695
1813
  ].sort((a, b)=>{
1696
- const relativeA = test_project_runner_toPosix(relative(root, a));
1697
- const relativeB = test_project_runner_toPosix(relative(root, b));
1814
+ const relativeA = test_project_runner_toPosix((0, external_node_path_.relative)(root, a));
1815
+ const relativeB = test_project_runner_toPosix((0, external_node_path_.relative)(root, b));
1698
1816
  return relativeA < relativeB ? -1 : relativeA > relativeB ? 1 : 0;
1699
1817
  });
1700
1818
  };
1701
1819
  const discoverTestConfig = (projectRoot)=>{
1702
- const root = resolve(projectRoot);
1703
- const candidates = readdirSync(root).filter((name)=>name.startsWith(CONFIG_PREFIX)).sort();
1820
+ const root = (0, external_node_path_.resolve)(projectRoot);
1821
+ const candidates = (0, external_node_fs_.readdirSync)(root).filter((name)=>name.startsWith(CONFIG_PREFIX)).sort();
1704
1822
  const unsupported = candidates.filter((name)=>name !== CONFIG_NAME);
1705
1823
  if (unsupported.length > 0) throw new Error([
1706
1824
  `Unsupported or conflicting Midscene configs found in ${root}:`,
1707
1825
  ...candidates.map((name)=>`- ${name}`),
1708
1826
  `Only ${CONFIG_NAME} is supported.`
1709
1827
  ].join('\n'));
1710
- return candidates.includes(CONFIG_NAME) ? join(root, CONFIG_NAME) : void 0;
1828
+ return candidates.includes(CONFIG_NAME) ? (0, external_node_path_.join)(root, CONFIG_NAME) : void 0;
1711
1829
  };
1712
- const defaultResultDir = (projectRoot)=>join(projectRoot, '.midscene', 'test-results');
1830
+ const defaultResultDir = (projectRoot)=>(0, external_node_path_.join)(projectRoot, '.midscene', 'test-results');
1713
1831
  const padDatePart = (value)=>String(value).padStart(2, '0');
1714
1832
  const createTestRunId = (date = new Date(), uuid = randomUUID())=>{
1715
1833
  const timestamp = [
@@ -1723,7 +1841,7 @@ const createTestRunId = (date = new Date(), uuid = randomUUID())=>{
1723
1841
  return `${timestamp}-${uuid.slice(0, 8)}`;
1724
1842
  };
1725
1843
  const assertDirectory = (path, label)=>{
1726
- if (!existsSync(path) || !statSync(path).isDirectory()) throw new Error(`${label} does not exist or is not a directory: ${path}`);
1844
+ if (!(0, external_node_fs_.existsSync)(path) || !(0, external_node_fs_.statSync)(path).isDirectory()) throw new Error(`${label} does not exist or is not a directory: ${path}`);
1727
1845
  };
1728
1846
  const asCollectionError = (projectId, projectName, sourcePath, error)=>({
1729
1847
  projectId,
@@ -1812,7 +1930,7 @@ const prepareProject = (project, projectRoot, resolveNode, runDir)=>{
1812
1930
  const sources = files.map((absolutePath)=>({
1813
1931
  projectId: project.projectId,
1814
1932
  projectName: project.name,
1815
- sourcePath: test_project_runner_toPosix(relative(projectRoot, absolutePath)),
1933
+ sourcePath: test_project_runner_toPosix((0, external_node_path_.relative)(projectRoot, absolutePath)),
1816
1934
  absolutePath
1817
1935
  }));
1818
1936
  const collectionErrors = [];
@@ -1854,23 +1972,23 @@ const notRunSuite = (prepared, reason)=>prepared.documents.flatMap((document)=>d
1854
1972
  async function runTestProject(options = {}) {
1855
1973
  const startedAt = new Date();
1856
1974
  const runId = createTestRunId(startedAt);
1857
- const cwd = resolve(options.cwd ?? process.cwd());
1975
+ const cwd = (0, external_node_path_.resolve)(options.cwd ?? process.cwd());
1858
1976
  assertDirectory(cwd, 'Test working directory');
1859
- const cliProjectRoot = options.projectRoot ? resolve(cwd, options.projectRoot) : void 0;
1977
+ const cliProjectRoot = options.projectRoot ? (0, external_node_path_.resolve)(cwd, options.projectRoot) : void 0;
1860
1978
  if (cliProjectRoot) assertDirectory(cliProjectRoot, 'Test project directory');
1861
1979
  const configSearchRoot = cliProjectRoot ?? cwd;
1862
- const configPath = options.configPath ? resolve(configSearchRoot, options.configPath) : discoverTestConfig(configSearchRoot);
1863
- if (options.configPath && (!configPath || !existsSync(configPath))) throw new Error(`Midscene config does not exist: ${configPath}`);
1980
+ const configPath = options.configPath ? (0, external_node_path_.resolve)(configSearchRoot, options.configPath) : discoverTestConfig(configSearchRoot);
1981
+ if (options.configPath && (!configPath || !(0, external_node_fs_.existsSync)(configPath))) throw new Error(`Midscene config does not exist: ${configPath}`);
1864
1982
  const definition = await loadTestProject(configPath);
1865
1983
  const projectRoot = cliProjectRoot ?? cwd;
1866
- const resultDir = options.resultDir ? resolve(cwd, options.resultDir) : defaultResultDir(projectRoot);
1867
- const runDir = join(resultDir, runId);
1868
- const summaryPath = join(runDir, 'summary.json');
1869
- const reportDir = resolve(projectRoot, definition.output.reportDir);
1870
- mkdirSync(resultDir, {
1984
+ const resultDir = options.resultDir ? (0, external_node_path_.resolve)(cwd, options.resultDir) : defaultResultDir(projectRoot);
1985
+ const runDir = (0, external_node_path_.join)(resultDir, runId);
1986
+ const summaryPath = (0, external_node_path_.join)(runDir, 'summary.json');
1987
+ const reportDir = (0, external_node_path_.resolve)(projectRoot, definition.output.reportDir);
1988
+ (0, external_node_fs_.mkdirSync)(resultDir, {
1871
1989
  recursive: true
1872
1990
  });
1873
- mkdirSync(runDir);
1991
+ (0, external_node_fs_.mkdirSync)(runDir);
1874
1992
  const selectedProjects = selectProjects(definition.projects, options.projectNames);
1875
1993
  const preparedProjects = selectedProjects.map((project)=>prepareProject(project, projectRoot, definition.resolveNode, runDir));
1876
1994
  const progress = options.onProgress ?? (()=>{});
@@ -2081,7 +2199,7 @@ async function runTestProject(options = {}) {
2081
2199
  return result;
2082
2200
  }
2083
2201
  const parseTestCliArgs = (args, cwd = process.cwd())=>{
2084
- const command = 'describe-nodes' === args[0] ? args[0] : void 0;
2202
+ const command = 'nodes' === args[0] ? args[0] : void 0;
2085
2203
  const commandOffset = command ? 1 : 0;
2086
2204
  let projectRoot;
2087
2205
  let configPath;
@@ -2091,20 +2209,20 @@ const parseTestCliArgs = (args, cwd = process.cwd())=>{
2091
2209
  const arg = args[index];
2092
2210
  if (!arg.startsWith('-')) {
2093
2211
  if (projectRoot) throw new Error('Only one test project directory is allowed.');
2094
- projectRoot = resolve(cwd, arg);
2212
+ projectRoot = (0, external_node_path_.resolve)(cwd, arg);
2095
2213
  continue;
2096
2214
  }
2097
2215
  if ('--config' === arg || '--result-dir' === arg || '--project' === arg) {
2098
2216
  const value = args[index + 1];
2099
2217
  if (!value) throw new Error(`${arg} requires a value.`);
2100
2218
  if ('--config' === arg) configPath = value;
2101
- else if ('--result-dir' === arg) resultDir = resolve(cwd, value);
2219
+ else if ('--result-dir' === arg) resultDir = (0, external_node_path_.resolve)(cwd, value);
2102
2220
  else projectNames.push(value);
2103
2221
  index += 1;
2104
2222
  } else throw new Error(`Unknown option: ${arg}`);
2105
2223
  }
2106
- if ('describe-nodes' === command && resultDir) throw new Error('--result-dir is not supported by describe-nodes.');
2107
- if ('describe-nodes' === command && projectNames.length > 0) throw new Error('--project is not supported by describe-nodes.');
2224
+ if ('nodes' === command && resultDir) throw new Error('--result-dir is not supported by nodes.');
2225
+ if ('nodes' === command && projectNames.length > 0) throw new Error('--project is not supported by nodes.');
2108
2226
  return {
2109
2227
  ...command ? {
2110
2228
  command
@@ -2124,27 +2242,43 @@ const defaultCliIO = {
2124
2242
  write: (message)=>process.stdout.write(message)
2125
2243
  };
2126
2244
  const test_command_assertDirectory = (path, label)=>{
2127
- if (!existsSync(path) || !statSync(path).isDirectory()) throw new Error(`${label} does not exist or is not a directory: ${path}`);
2245
+ if (!(0, external_node_fs_.existsSync)(path) || !(0, external_node_fs_.statSync)(path).isDirectory()) throw new Error(`${label} does not exist or is not a directory: ${path}`);
2128
2246
  };
2129
- const describeNodes = async (options, io)=>{
2130
- const cwd = resolve(options.cwd);
2247
+ const runNodesCommand = async (options, io)=>{
2248
+ const cwd = (0, external_node_path_.resolve)(options.cwd);
2131
2249
  test_command_assertDirectory(cwd, 'Test working directory');
2132
- const projectRoot = options.projectRoot ? resolve(cwd, options.projectRoot) : void 0;
2250
+ const projectRoot = options.projectRoot ? (0, external_node_path_.resolve)(cwd, options.projectRoot) : void 0;
2133
2251
  if (projectRoot) test_command_assertDirectory(projectRoot, 'Test project directory');
2134
2252
  const configSearchRoot = projectRoot ?? cwd;
2135
- const configPath = options.configPath ? resolve(configSearchRoot, options.configPath) : discoverTestConfig(configSearchRoot);
2136
- if (options.configPath && (!configPath || !existsSync(configPath))) throw new Error(`Midscene config does not exist: ${configPath}`);
2253
+ const configPath = options.configPath ? (0, external_node_path_.resolve)(configSearchRoot, options.configPath) : discoverTestConfig(configSearchRoot);
2254
+ if (options.configPath && (!configPath || !(0, external_node_fs_.existsSync)(configPath))) throw new Error(`Midscene config does not exist: ${configPath}`);
2137
2255
  const project = await loadTestProject(configPath);
2138
- const document = renderNodeReference(project.nodes.definitions());
2139
- for (const warning of document.warnings)io.error(`midscene-test describe-nodes: ${warning}`);
2140
- if (io.write) io.write(document.markdown);
2141
- else io.log(document.markdown.trimEnd());
2256
+ const nodes = sortNodesForReference(project.nodes.definitions());
2257
+ const document = renderNodeReference(nodes, {
2258
+ configPath: configPath ? (0, external_node_path_.relative)(configSearchRoot, configPath).split(external_node_path_.sep).join('/') : void 0,
2259
+ projects: project.projects.map((executionProject)=>({
2260
+ name: executionProject.name,
2261
+ files: executionProject.files ?? DEFAULT_TEST_FILE_SELECTION
2262
+ }))
2263
+ });
2264
+ for (const warning of document.warnings)io.error(`midscene-test nodes: ${warning}`);
2265
+ const referencePath = (0, external_node_path_.resolve)(configSearchRoot, 'midscene-node-reference.md');
2266
+ (0, external_node_fs_.writeFileSync)(referencePath, document.markdown);
2267
+ io.log(`Registered Nodes (${nodes.length}):`);
2268
+ if (0 === nodes.length) io.log('No nodes are registered by the current Test Project.');
2269
+ for (const node of nodes)io.log(`- ${node.name}: ${node.description?.trim() || "Description not declared."}`);
2270
+ io.log(`\nNode reference generated: ${referencePath}`);
2142
2271
  };
2143
2272
  async function runTestCli(args, io = defaultCliIO) {
2144
2273
  try {
2274
+ if ('create' === args[0]) {
2275
+ const { runCreateCommand } = await __webpack_require__.e("891").then(__webpack_require__.bind(__webpack_require__, "./src/cli/create-command.ts"));
2276
+ await runCreateCommand(args.slice(1), io);
2277
+ return 0;
2278
+ }
2145
2279
  const options = parseTestCliArgs(args);
2146
- if ('describe-nodes' === options.command) {
2147
- await describeNodes(options, io);
2280
+ if ('nodes' === options.command) {
2281
+ await runNodesCommand(options, io);
2148
2282
  return 0;
2149
2283
  }
2150
2284
  const result = await runTestProject({