@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.
- package/dist/es/1~891.mjs +430 -0
- package/dist/es/1~891.mjs.map +1 -0
- package/dist/es/android/index.mjs +0 -4
- package/dist/es/android/index.mjs.map +1 -1
- package/dist/es/harmony/index.mjs +0 -4
- package/dist/es/harmony/index.mjs.map +1 -1
- package/dist/es/ios/index.mjs +0 -4
- package/dist/es/ios/index.mjs.map +1 -1
- package/dist/es/midscene/index.mjs +1 -43
- package/dist/es/midscene/index.mjs.map +1 -1
- package/dist/es/test-cli.mjs +205 -71
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/0~458.js +431 -0
- package/dist/lib/0~458.js.map +1 -0
- package/dist/lib/android/index.js +0 -4
- package/dist/lib/android/index.js.map +1 -1
- package/dist/lib/harmony/index.js +0 -4
- package/dist/lib/harmony/index.js.map +1 -1
- package/dist/lib/ios/index.js +0 -4
- package/dist/lib/ios/index.js.map +1 -1
- package/dist/lib/midscene/index.js +0 -46
- package/dist/lib/midscene/index.js.map +1 -1
- package/dist/lib/test-cli.js +2152 -2023
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/types/cli/index.d.ts +10 -0
- package/dist/types/midscene/index.d.ts +0 -24
- package/package.json +14 -9
package/dist/es/test-cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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('
|
|
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 (
|
|
95
|
-
|
|
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 = '
|
|
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 ('
|
|
2107
|
-
if ('
|
|
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
|
|
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
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
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 ('
|
|
2147
|
-
await
|
|
2280
|
+
if ('nodes' === options.command) {
|
|
2281
|
+
await runNodesCommand(options, io);
|
|
2148
2282
|
return 0;
|
|
2149
2283
|
}
|
|
2150
2284
|
const result = await runTestProject({
|