@midscene/test 1.12.10-beta-20260920031616.0 → 1.13.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 → 1~214.mjs} +6 -6
- package/dist/es/{1~891.mjs.map → 1~214.mjs.map} +1 -1
- package/dist/es/cli/index.mjs +534 -1700
- package/dist/es/cli/index.mjs.map +1 -1
- package/dist/es/index.mjs +57 -1525
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/midscene/index.mjs +93 -168
- package/dist/es/midscene/index.mjs.map +1 -1
- package/dist/es/runtime/index.mjs +317 -0
- package/dist/es/runtime/index.mjs.map +1 -0
- package/dist/es/test-cli.mjs +666 -1780
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/{0~458.js → 0~160.js} +5 -5
- package/dist/lib/{0~458.js.map → 0~160.js.map} +1 -1
- package/dist/lib/cli/index.js +545 -1711
- package/dist/lib/cli/index.js.map +1 -1
- package/dist/lib/index.js +92 -1557
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/midscene/index.js +130 -172
- package/dist/lib/midscene/index.js.map +1 -1
- package/dist/lib/runtime/index.js +372 -0
- package/dist/lib/runtime/index.js.map +1 -0
- package/dist/lib/test-cli.js +670 -1784
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/types/cli/index.d.ts +62 -233
- package/dist/types/index.d.ts +186 -492
- package/dist/types/midscene/index.d.ts +82 -96
- package/dist/types/runtime/index.d.ts +52 -0
- package/package.json +51 -8
package/dist/lib/index.js
CHANGED
|
@@ -24,1147 +24,77 @@ var __webpack_require__ = {};
|
|
|
24
24
|
var __webpack_exports__ = {};
|
|
25
25
|
__webpack_require__.r(__webpack_exports__);
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
-
NodeInputValidationError: ()=>NodeInputValidationError,
|
|
27
|
+
NodeInputValidationError: ()=>test_runner_namespaceObject.NodeInputValidationError,
|
|
28
28
|
createCaseRunner: ()=>createCaseRunner,
|
|
29
|
-
isFatalDeviceError: ()=>isFatalDeviceError,
|
|
29
|
+
isFatalDeviceError: ()=>test_runner_namespaceObject.isFatalDeviceError,
|
|
30
30
|
normalizeSteps: ()=>normalizeSteps,
|
|
31
|
-
FatalDeviceError: ()=>FatalDeviceError,
|
|
31
|
+
FatalDeviceError: ()=>test_runner_namespaceObject.FatalDeviceError,
|
|
32
32
|
runWorkflowDocument: ()=>runWorkflowDocument,
|
|
33
33
|
defineNode: ()=>defineNode,
|
|
34
34
|
z: ()=>v4_namespaceObject.z,
|
|
35
|
-
createProjectRuntime: ()=>createProjectRuntime,
|
|
36
|
-
sanitizeReportValue: ()=>sanitizeReportValue,
|
|
37
|
-
buildTestRunReportDump: ()=>buildTestRunReportDump,
|
|
38
|
-
sanitizeReportError: ()=>sanitizeReportError,
|
|
35
|
+
createProjectRuntime: ()=>test_runner_namespaceObject.createProjectRuntime,
|
|
36
|
+
sanitizeReportValue: ()=>test_runner_namespaceObject.sanitizeReportValue,
|
|
37
|
+
buildTestRunReportDump: ()=>test_runner_namespaceObject.buildTestRunReportDump,
|
|
38
|
+
sanitizeReportError: ()=>test_runner_namespaceObject.sanitizeReportError,
|
|
39
39
|
createWorkflowDocumentId: ()=>createWorkflowDocumentId,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
NativeWorkflowFormatError: ()=>NativeWorkflowFormatError,
|
|
41
|
+
NodeDefinitionError: ()=>test_runner_namespaceObject.NodeDefinitionError,
|
|
42
|
+
normalizeNodeExecutionError: ()=>test_runner_namespaceObject.normalizeNodeExecutionError,
|
|
43
|
+
NodeExecutionError: ()=>test_runner_namespaceObject.NodeExecutionError,
|
|
44
|
+
CaseExecutionError: ()=>test_runner_namespaceObject.CaseExecutionError,
|
|
45
|
+
WorkflowLifecycleError: ()=>test_runner_namespaceObject.WorkflowLifecycleError,
|
|
46
|
+
NodeNotFoundError: ()=>test_runner_namespaceObject.NodeNotFoundError,
|
|
47
|
+
ProjectSetupError: ()=>test_runner_namespaceObject.ProjectSetupError,
|
|
48
|
+
WorkflowError: ()=>test_runner_namespaceObject.WorkflowError,
|
|
49
|
+
DuplicateNodeError: ()=>test_runner_namespaceObject.DuplicateNodeError,
|
|
49
50
|
NodeRegistry: ()=>NodeRegistry,
|
|
50
51
|
CaseRunner: ()=>CaseRunner,
|
|
51
|
-
StepTimeoutError: ()=>StepTimeoutError,
|
|
52
|
-
sanitizeReportText: ()=>sanitizeReportText,
|
|
53
|
-
WorkflowParseError: ()=>WorkflowParseError,
|
|
54
|
-
NodeScopeTeardownError: ()=>NodeScopeTeardownError,
|
|
55
|
-
WorkflowDocumentExecutionError: ()=>WorkflowDocumentExecutionError,
|
|
56
|
-
ProjectTeardownError: ()=>ProjectTeardownError,
|
|
57
|
-
collectTestRunReportSources: ()=>collectTestRunReportSources,
|
|
52
|
+
StepTimeoutError: ()=>test_runner_namespaceObject.StepTimeoutError,
|
|
53
|
+
sanitizeReportText: ()=>test_runner_namespaceObject.sanitizeReportText,
|
|
54
|
+
WorkflowParseError: ()=>test_runner_namespaceObject.WorkflowParseError,
|
|
55
|
+
NodeScopeTeardownError: ()=>test_runner_namespaceObject.NodeScopeTeardownError,
|
|
56
|
+
WorkflowDocumentExecutionError: ()=>test_runner_namespaceObject.WorkflowDocumentExecutionError,
|
|
57
|
+
ProjectTeardownError: ()=>test_runner_namespaceObject.ProjectTeardownError,
|
|
58
|
+
collectTestRunReportSources: ()=>test_runner_namespaceObject.collectTestRunReportSources,
|
|
58
59
|
normalizeStep: ()=>normalizeStep,
|
|
59
60
|
resolveWorkflowVariables: ()=>resolveWorkflowVariables,
|
|
60
61
|
createCaseId: ()=>createCaseId,
|
|
61
62
|
createDocumentRuntime: ()=>createDocumentRuntime,
|
|
62
63
|
collectWorkflowDocument: ()=>collectWorkflowDocument
|
|
63
64
|
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
else obj[key] = value;
|
|
72
|
-
return obj;
|
|
73
|
-
}
|
|
74
|
-
class WorkflowError extends Error {
|
|
75
|
-
toJSON() {
|
|
76
|
-
return {
|
|
77
|
-
name: this.name,
|
|
78
|
-
message: this.message,
|
|
79
|
-
code: this.code,
|
|
80
|
-
...void 0 === this.details ? {} : {
|
|
81
|
-
details: this.details
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
constructor(message, options = {}){
|
|
86
|
-
super(message, {
|
|
87
|
-
cause: options.cause
|
|
88
|
-
}), _define_property(this, "code", void 0), _define_property(this, "details", void 0);
|
|
89
|
-
this.name = new.target.name;
|
|
90
|
-
this.code = options.code ?? 'WORKFLOW_ERROR';
|
|
91
|
-
this.details = options.details;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
class WorkflowParseError extends WorkflowError {
|
|
95
|
-
constructor(message, details, cause){
|
|
96
|
-
super(message, {
|
|
97
|
-
code: 'WORKFLOW_PARSE_ERROR',
|
|
98
|
-
details,
|
|
99
|
-
cause
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
class NodeDefinitionError extends WorkflowError {
|
|
104
|
-
constructor(message, details){
|
|
105
|
-
super(message, {
|
|
106
|
-
code: 'NODE_DEFINITION_ERROR',
|
|
107
|
-
details
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
class DuplicateNodeError extends WorkflowError {
|
|
112
|
-
constructor(node){
|
|
113
|
-
super(`Node "${node}" is already registered.`, {
|
|
114
|
-
code: 'DUPLICATE_NODE',
|
|
115
|
-
details: {
|
|
116
|
-
node
|
|
117
|
-
}
|
|
118
|
-
}), _define_property(this, "node", void 0);
|
|
119
|
-
this.node = node;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
class NodeNotFoundError extends WorkflowError {
|
|
123
|
-
constructor(node){
|
|
124
|
-
super(`Node "${node}" is not registered.`, {
|
|
125
|
-
code: 'NODE_NOT_FOUND',
|
|
126
|
-
details: {
|
|
127
|
-
node
|
|
128
|
-
}
|
|
129
|
-
}), _define_property(this, "node", void 0);
|
|
130
|
-
this.node = node;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
class NodeInputValidationError extends WorkflowError {
|
|
134
|
-
static fromZod(node, error) {
|
|
135
|
-
const issues = error.issues.map((issue)=>({
|
|
136
|
-
code: issue.code,
|
|
137
|
-
path: issue.path.map(String).join('.'),
|
|
138
|
-
message: issue.message
|
|
139
|
-
}));
|
|
140
|
-
const firstIssue = issues[0];
|
|
141
|
-
const path = firstIssue?.path || '<root>';
|
|
142
|
-
const message = firstIssue?.message ?? 'invalid input';
|
|
143
|
-
return new NodeInputValidationError(`Node "${node}" input validation failed at "${path}": ${message}`, {
|
|
144
|
-
node,
|
|
145
|
-
issues
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
constructor(message, details){
|
|
149
|
-
super(message, {
|
|
150
|
-
code: 'NODE_INPUT_VALIDATION_ERROR',
|
|
151
|
-
details
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
class StepTimeoutError extends WorkflowError {
|
|
156
|
-
constructor(timeoutMs, node){
|
|
157
|
-
super(node ? `Node "${node}" timed out after ${timeoutMs}ms.` : `Step timed out after ${timeoutMs}ms.`, {
|
|
158
|
-
code: 'STEP_TIMEOUT',
|
|
159
|
-
details: {
|
|
160
|
-
timeoutMs,
|
|
161
|
-
...void 0 === node ? {} : {
|
|
162
|
-
node
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}), _define_property(this, "timeoutMs", void 0), _define_property(this, "node", void 0);
|
|
166
|
-
this.timeoutMs = timeoutMs;
|
|
167
|
-
this.node = node;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
class NodeExecutionError extends WorkflowError {
|
|
171
|
-
constructor(node, cause){
|
|
172
|
-
const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
|
|
173
|
-
super(`Node "${node}" failed: ${causeMessage}`, {
|
|
174
|
-
code: 'NODE_EXECUTION_ERROR',
|
|
175
|
-
details: {
|
|
176
|
-
node
|
|
177
|
-
},
|
|
178
|
-
cause
|
|
179
|
-
}), _define_property(this, "node", void 0);
|
|
180
|
-
this.node = node;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
class WorkflowLifecycleError extends WorkflowError {
|
|
184
|
-
constructor(message, details){
|
|
185
|
-
super(message, {
|
|
186
|
-
code: 'WORKFLOW_LIFECYCLE_ERROR',
|
|
187
|
-
details
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
class ProjectSetupError extends WorkflowError {
|
|
192
|
-
constructor(cause, details){
|
|
193
|
-
const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
|
|
194
|
-
super(`Project "${details.projectName}" setup failed: ${causeMessage}`, {
|
|
195
|
-
code: 'PROJECT_SETUP_ERROR',
|
|
196
|
-
details,
|
|
197
|
-
cause
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
class ProjectTeardownError extends WorkflowError {
|
|
202
|
-
constructor(cause, details){
|
|
203
|
-
const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
|
|
204
|
-
super(`Project "${details.projectName}" teardown failed: ${causeMessage}`, {
|
|
205
|
-
code: 'PROJECT_TEARDOWN_ERROR',
|
|
206
|
-
details,
|
|
207
|
-
cause
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
class NodeScopeTeardownError extends WorkflowError {
|
|
212
|
-
constructor(cause, details){
|
|
213
|
-
const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
|
|
214
|
-
super(`${'case' === details.scope ? 'Case attempt' : 'Workflow document'} node teardown failed for "${details.node}": ${causeMessage}`, {
|
|
215
|
-
code: 'NODE_SCOPE_TEARDOWN_ERROR',
|
|
216
|
-
details,
|
|
217
|
-
cause
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
class FatalDeviceError extends WorkflowError {
|
|
222
|
-
constructor(message, cause){
|
|
223
|
-
super(message, {
|
|
224
|
-
code: 'FATAL_DEVICE_ERROR',
|
|
225
|
-
cause
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
const isFatalDeviceError = (error)=>{
|
|
230
|
-
if (error instanceof FatalDeviceError) return true;
|
|
231
|
-
if (error instanceof WorkflowError && 'FATAL_DEVICE_ERROR' === error.code) return true;
|
|
232
|
-
if (error instanceof Error && /device offline|device not found|(?:adb|bdc|device) connection (?:was )?closed/i.test(error.message)) return true;
|
|
233
|
-
return error instanceof Error && void 0 !== error.cause ? isFatalDeviceError(error.cause) : false;
|
|
234
|
-
};
|
|
235
|
-
class CaseExecutionError extends WorkflowError {
|
|
236
|
-
constructor(result){
|
|
237
|
-
super(`Case "${result.name}" failed.`, {
|
|
238
|
-
code: 'CASE_EXECUTION_FAILED',
|
|
239
|
-
details: {
|
|
240
|
-
caseId: result.caseId,
|
|
241
|
-
runId: result.runId
|
|
242
|
-
}
|
|
243
|
-
}), _define_property(this, "result", void 0);
|
|
244
|
-
this.result = result;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
class WorkflowDocumentExecutionError extends WorkflowError {
|
|
248
|
-
constructor(result){
|
|
249
|
-
super(`Workflow document "${result.sourcePath}" failed.`, {
|
|
250
|
-
code: 'WORKFLOW_DOCUMENT_EXECUTION_FAILED',
|
|
251
|
-
details: {
|
|
252
|
-
documentId: result.documentId,
|
|
253
|
-
documentRunId: result.documentRunId
|
|
254
|
-
}
|
|
255
|
-
}), _define_property(this, "result", void 0);
|
|
256
|
-
this.result = result;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function normalizeNodeExecutionError(error, node) {
|
|
260
|
-
return error instanceof WorkflowError ? error : new NodeExecutionError(node, error);
|
|
261
|
-
}
|
|
262
|
-
const supportedMetaKeys = new Set([
|
|
263
|
-
'timeout',
|
|
264
|
-
'continue-on-error'
|
|
265
|
-
]);
|
|
266
|
-
function isMapping(value) {
|
|
267
|
-
return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
|
|
268
|
-
}
|
|
269
|
-
function formatStep(index) {
|
|
270
|
-
return `step ${index + 1}`;
|
|
271
|
-
}
|
|
272
|
-
function normalizeMeta(value, index) {
|
|
273
|
-
if (void 0 === value) return {
|
|
274
|
-
continueOnError: false
|
|
275
|
-
};
|
|
276
|
-
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
|
|
277
|
-
index
|
|
278
|
-
});
|
|
279
|
-
for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
|
|
280
|
-
index,
|
|
281
|
-
key
|
|
282
|
-
});
|
|
283
|
-
const timeout = value.timeout;
|
|
284
|
-
if (void 0 !== timeout && ('number' != typeof timeout || !Number.isFinite(timeout) || timeout <= 0)) throw new WorkflowParseError(`${formatStep(index)} "$.timeout" must be a positive number of milliseconds.`, {
|
|
285
|
-
index,
|
|
286
|
-
timeout
|
|
287
|
-
});
|
|
288
|
-
const continueOnError = value['continue-on-error'];
|
|
289
|
-
if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
|
|
290
|
-
index,
|
|
291
|
-
continueOnError
|
|
292
|
-
});
|
|
293
|
-
return {
|
|
294
|
-
...void 0 === timeout ? {} : {
|
|
295
|
-
timeoutMs: timeout
|
|
296
|
-
},
|
|
297
|
-
continueOnError: continueOnError ?? false
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
function normalizeStep(value, index = 0, resolveNode) {
|
|
301
|
-
if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
|
|
302
|
-
index
|
|
303
|
-
});
|
|
304
|
-
const entries = Object.entries(value);
|
|
305
|
-
if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
|
|
306
|
-
index,
|
|
307
|
-
nodes: Object.keys(value)
|
|
308
|
-
});
|
|
309
|
-
const [node, rawValue] = entries[0];
|
|
310
|
-
if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
|
|
311
|
-
index
|
|
312
|
-
});
|
|
313
|
-
if ('string' == typeof rawValue) {
|
|
314
|
-
const resolvedNode = resolveNode?.(node);
|
|
315
|
-
const stringInputKey = resolvedNode ? resolvedNode.stringInputKey : 'prompt';
|
|
316
|
-
if (false === stringInputKey || void 0 === stringInputKey) throw new WorkflowParseError(`${formatStep(index)} node "${node}" does not accept string shorthand.`, {
|
|
317
|
-
index,
|
|
318
|
-
node
|
|
319
|
-
});
|
|
320
|
-
return {
|
|
321
|
-
node,
|
|
322
|
-
input: {
|
|
323
|
-
[stringInputKey]: rawValue
|
|
324
|
-
},
|
|
325
|
-
meta: {
|
|
326
|
-
continueOnError: false
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
|
|
331
|
-
index,
|
|
332
|
-
node
|
|
333
|
-
});
|
|
334
|
-
const { $: rawMeta, ...input } = rawValue;
|
|
335
|
-
return {
|
|
336
|
-
node,
|
|
337
|
-
input,
|
|
338
|
-
meta: normalizeMeta(rawMeta, index)
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
function normalizeSteps(steps, resolveNode) {
|
|
342
|
-
if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
|
|
343
|
-
return steps.map((step, index)=>normalizeStep(step, index, resolveNode));
|
|
344
|
-
}
|
|
345
|
-
const v4_namespaceObject = require("zod/v4");
|
|
346
|
-
const validateOptionalText = (value, field, node)=>{
|
|
347
|
-
if (void 0 !== value && ('string' != typeof value || 0 === value.trim().length)) throw new NodeDefinitionError(`Node "${node}" ${field} must be a non-empty string.`, {
|
|
348
|
-
node,
|
|
349
|
-
field
|
|
350
|
-
});
|
|
351
|
-
};
|
|
352
|
-
const validateInputSchema = (schema, node)=>{
|
|
353
|
-
if (void 0 === schema) return;
|
|
354
|
-
if (!(schema instanceof v4_namespaceObject.z.ZodObject)) throw new NodeDefinitionError(`Node "${node}" inputSchema must be a Zod object schema.`, {
|
|
355
|
-
node,
|
|
356
|
-
field: 'inputSchema'
|
|
357
|
-
});
|
|
358
|
-
if ('$' in schema.shape) throw new NodeDefinitionError(`Node "${node}" inputSchema must not declare "$" as an input property.`, {
|
|
359
|
-
node,
|
|
360
|
-
field: 'inputSchema.$'
|
|
361
|
-
});
|
|
362
|
-
};
|
|
363
|
-
const validateDefinition = (options)=>{
|
|
364
|
-
if (!options || 'object' != typeof options) throw new NodeDefinitionError('Node definition must be an object.');
|
|
365
|
-
if ('string' != typeof options.name || 0 === options.name.trim().length) throw new NodeDefinitionError('Node name must be a non-empty string.');
|
|
366
|
-
validateOptionalText(options.title, 'title', options.name);
|
|
367
|
-
validateOptionalText(options.description, "description", options.name);
|
|
368
|
-
if (void 0 !== options.stringInputKey && false !== options.stringInputKey && ('string' != typeof options.stringInputKey || 0 === options.stringInputKey.trim().length)) throw new NodeDefinitionError(`Node "${options.name}" stringInputKey must be a non-empty string or false.`, {
|
|
369
|
-
node: options.name,
|
|
370
|
-
field: 'stringInputKey'
|
|
371
|
-
});
|
|
372
|
-
validateInputSchema(options.inputSchema, options.name);
|
|
373
|
-
if ('string' == typeof options.stringInputKey && options.inputSchema instanceof v4_namespaceObject.z.ZodObject && !(options.stringInputKey in options.inputSchema.shape)) throw new NodeDefinitionError(`Node "${options.name}" stringInputKey must reference a field in inputSchema.`, {
|
|
374
|
-
node: options.name,
|
|
375
|
-
field: 'stringInputKey'
|
|
376
|
-
});
|
|
377
|
-
if ('function' != typeof options.execute) throw new NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
|
|
378
|
-
node: options.name
|
|
379
|
-
});
|
|
380
|
-
};
|
|
381
|
-
function defineNode(options) {
|
|
382
|
-
validateDefinition(options);
|
|
383
|
-
return options;
|
|
384
|
-
}
|
|
385
|
-
function _check_private_redeclaration(obj, privateCollection) {
|
|
386
|
-
if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
387
|
-
}
|
|
388
|
-
function _class_apply_descriptor_get(receiver, descriptor) {
|
|
389
|
-
if (descriptor.get) return descriptor.get.call(receiver);
|
|
390
|
-
return descriptor.value;
|
|
391
|
-
}
|
|
392
|
-
function _class_extract_field_descriptor(receiver, privateMap, action) {
|
|
393
|
-
if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
|
|
394
|
-
return privateMap.get(receiver);
|
|
395
|
-
}
|
|
396
|
-
function _class_private_field_get(receiver, privateMap) {
|
|
397
|
-
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
|
|
398
|
-
return _class_apply_descriptor_get(receiver, descriptor);
|
|
399
|
-
}
|
|
400
|
-
function _class_private_field_init(obj, privateMap, value) {
|
|
401
|
-
_check_private_redeclaration(obj, privateMap);
|
|
402
|
-
privateMap.set(obj, value);
|
|
403
|
-
}
|
|
404
|
-
var _nodes = /*#__PURE__*/ new WeakMap();
|
|
405
|
-
class NodeRegistry {
|
|
406
|
-
register(node) {
|
|
407
|
-
defineNode(node);
|
|
408
|
-
if (_class_private_field_get(this, _nodes).has(node.name)) throw new DuplicateNodeError(node.name);
|
|
409
|
-
_class_private_field_get(this, _nodes).set(node.name, node);
|
|
410
|
-
return this;
|
|
411
|
-
}
|
|
412
|
-
get(name) {
|
|
413
|
-
return _class_private_field_get(this, _nodes).get(name);
|
|
414
|
-
}
|
|
415
|
-
require(name) {
|
|
416
|
-
const node = this.get(name);
|
|
417
|
-
if (!node) throw new NodeNotFoundError(name);
|
|
418
|
-
return node;
|
|
419
|
-
}
|
|
420
|
-
has(name) {
|
|
421
|
-
return _class_private_field_get(this, _nodes).has(name);
|
|
422
|
-
}
|
|
423
|
-
names() {
|
|
424
|
-
return [
|
|
425
|
-
..._class_private_field_get(this, _nodes).keys()
|
|
426
|
-
];
|
|
427
|
-
}
|
|
428
|
-
definitions() {
|
|
429
|
-
return [
|
|
430
|
-
..._class_private_field_get(this, _nodes).values()
|
|
431
|
-
];
|
|
432
|
-
}
|
|
433
|
-
constructor(nodes = []){
|
|
434
|
-
_class_private_field_init(this, _nodes, {
|
|
435
|
-
writable: true,
|
|
436
|
-
value: new Map()
|
|
437
|
-
});
|
|
438
|
-
for (const node of nodes)this.register(node);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
442
|
-
const assertJsonSerializable = (value, label, node)=>{
|
|
443
|
-
const visit = (candidate, path, seen)=>{
|
|
444
|
-
if (null === candidate || 'string' == typeof candidate || 'boolean' == typeof candidate) return;
|
|
445
|
-
if ('number' == typeof candidate) {
|
|
446
|
-
if (Number.isFinite(candidate)) return;
|
|
447
|
-
throw new TypeError(`${path} contains a non-finite number.`);
|
|
448
|
-
}
|
|
449
|
-
if (void 0 === candidate) throw new TypeError(`${path} contains undefined.`);
|
|
450
|
-
if ('object' != typeof candidate) throw new TypeError(`${path} is not JSON-serializable.`);
|
|
451
|
-
if (!Array.isArray(candidate) && Object.getPrototypeOf(candidate) !== Object.prototype && null !== Object.getPrototypeOf(candidate)) throw new TypeError(`${path} must contain only plain objects.`);
|
|
452
|
-
if (seen.has(candidate)) throw new TypeError(`${path} contains a cycle.`);
|
|
453
|
-
seen.add(candidate);
|
|
454
|
-
if (Array.isArray(candidate)) candidate.forEach((child, index)=>visit(child, `${path}[${index}]`, seen));
|
|
455
|
-
else for (const [key, child] of Object.entries(candidate))visit(child, `${path}.${key}`, seen);
|
|
456
|
-
seen.delete(candidate);
|
|
457
|
-
};
|
|
458
|
-
try {
|
|
459
|
-
visit(value, label, new Set());
|
|
460
|
-
} catch (error) {
|
|
461
|
-
throw new NodeExecutionError(node, error);
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
function validateNodeOutput(output, node) {
|
|
465
|
-
if (void 0 === output) return;
|
|
466
|
-
if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
|
|
467
|
-
if ('summary' in output && void 0 !== output.summary && 'string' != typeof output.summary) throw new NodeExecutionError(node, new TypeError('Node output summary must be a string.'));
|
|
468
|
-
if ('data' in output && void 0 !== output.data) assertJsonSerializable(output.data, 'output.data', node);
|
|
469
|
-
return output;
|
|
470
|
-
}
|
|
471
|
-
function createStepResultBase(step, startedAt, phase, stepIndex) {
|
|
472
|
-
const endedAt = new Date();
|
|
473
|
-
return {
|
|
474
|
-
node: step.node,
|
|
475
|
-
phase,
|
|
476
|
-
stepIndex,
|
|
477
|
-
input: step.input,
|
|
478
|
-
meta: step.meta,
|
|
479
|
-
startedAt: startedAt.toISOString(),
|
|
480
|
-
endedAt: endedAt.toISOString(),
|
|
481
|
-
durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime())
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
async function executeNode(step, execute, phase, stepIndex, options) {
|
|
485
|
-
const startedAt = new Date();
|
|
486
|
-
const abortController = new AbortController();
|
|
487
|
-
const parentSignal = options.parentSignal;
|
|
488
|
-
const abortFromParent = ()=>abortController.abort(parentSignal?.reason);
|
|
489
|
-
if (parentSignal?.aborted) abortFromParent();
|
|
490
|
-
else parentSignal?.addEventListener('abort', abortFromParent, {
|
|
491
|
-
once: true
|
|
492
|
-
});
|
|
493
|
-
const timeoutMs = step.meta.timeoutMs ?? options.defaultTimeoutMs;
|
|
494
|
-
let timeout;
|
|
495
|
-
let abortGraceTimeout;
|
|
496
|
-
const execution = Promise.resolve().then(()=>{
|
|
497
|
-
abortController.signal.throwIfAborted();
|
|
498
|
-
return execute(abortController.signal);
|
|
499
|
-
});
|
|
500
|
-
let settleOnAbort;
|
|
501
|
-
const abortedExecution = new Promise((_, reject)=>{
|
|
502
|
-
settleOnAbort = ()=>{
|
|
503
|
-
abortGraceTimeout = setTimeout(()=>{
|
|
504
|
-
reject(abortController.signal.reason);
|
|
505
|
-
}, 0);
|
|
506
|
-
};
|
|
507
|
-
if (abortController.signal.aborted) settleOnAbort();
|
|
508
|
-
else abortController.signal.addEventListener('abort', settleOnAbort, {
|
|
509
|
-
once: true
|
|
510
|
-
});
|
|
511
|
-
});
|
|
512
|
-
const timeoutExecution = void 0 === timeoutMs ? void 0 : new Promise((_, reject)=>{
|
|
513
|
-
timeout = setTimeout(()=>{
|
|
514
|
-
if (abortController.signal.aborted) return void reject(abortController.signal.reason);
|
|
515
|
-
const timeoutError = new StepTimeoutError(timeoutMs, step.node);
|
|
516
|
-
abortController.abort(timeoutError);
|
|
517
|
-
reject(timeoutError);
|
|
518
|
-
}, timeoutMs);
|
|
519
|
-
});
|
|
520
|
-
const settledExecution = Promise.race([
|
|
521
|
-
execution,
|
|
522
|
-
abortedExecution,
|
|
523
|
-
...void 0 === timeoutExecution ? [] : [
|
|
524
|
-
timeoutExecution
|
|
525
|
-
]
|
|
526
|
-
]);
|
|
527
|
-
try {
|
|
528
|
-
const output = validateNodeOutput(await settledExecution, step.node);
|
|
529
|
-
return {
|
|
530
|
-
...createStepResultBase(step, startedAt, phase, stepIndex),
|
|
531
|
-
status: 'success',
|
|
532
|
-
continuedAfterError: false,
|
|
533
|
-
...void 0 === output ? {} : {
|
|
534
|
-
output
|
|
535
|
-
}
|
|
536
|
-
};
|
|
537
|
-
} catch (error) {
|
|
538
|
-
return {
|
|
539
|
-
...createStepResultBase(step, startedAt, phase, stepIndex),
|
|
540
|
-
status: 'failed',
|
|
541
|
-
continuedAfterError: step.meta.continueOnError,
|
|
542
|
-
error: normalizeNodeExecutionError(error, step.node)
|
|
543
|
-
};
|
|
544
|
-
} finally{
|
|
545
|
-
if (void 0 !== timeout) clearTimeout(timeout);
|
|
546
|
-
if (void 0 !== abortGraceTimeout) clearTimeout(abortGraceTimeout);
|
|
547
|
-
if (void 0 !== settleOnAbort) abortController.signal.removeEventListener('abort', settleOnAbort);
|
|
548
|
-
parentSignal?.removeEventListener('abort', abortFromParent);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
function createNodeReportCollector(node) {
|
|
552
|
-
const traces = new Map();
|
|
553
|
-
let accepting = true;
|
|
554
|
-
return {
|
|
555
|
-
collector: Object.freeze({
|
|
556
|
-
addTrace (trace) {
|
|
557
|
-
if (!accepting) throw new NodeExecutionError(node, new Error('Report traces can only be added while the current Step is running.'));
|
|
558
|
-
if (!trace || 'object' != typeof trace || 'midscene-execution' !== trace.type) throw new NodeExecutionError(node, new TypeError('Report trace type must be "midscene-execution".'));
|
|
559
|
-
if ('string' != typeof trace.executionId || 0 === trace.executionId.trim().length) throw new NodeExecutionError(node, new TypeError('Report trace executionId must be non-empty.'));
|
|
560
|
-
const normalized = Object.freeze({
|
|
561
|
-
type: 'midscene-execution',
|
|
562
|
-
executionId: trace.executionId
|
|
563
|
-
});
|
|
564
|
-
traces.set(`${normalized.type}:${normalized.executionId}`, normalized);
|
|
565
|
-
}
|
|
566
|
-
}),
|
|
567
|
-
close () {
|
|
568
|
-
accepting = false;
|
|
569
|
-
return Object.freeze([
|
|
570
|
-
...traces.values()
|
|
571
|
-
]);
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
async function parseNodeInput(node, input) {
|
|
576
|
-
if (!node.inputSchema) return input;
|
|
577
|
-
const parsed = await node.inputSchema.safeParseAsync(input);
|
|
578
|
-
if (!parsed.success) throw NodeInputValidationError.fromZod(node.name, parsed.error);
|
|
579
|
-
return parsed.data;
|
|
580
|
-
}
|
|
581
|
-
async function executeStep(step, node, target, context, execution = {}) {
|
|
582
|
-
const phase = 'case' === target.scope ? target.case.phase : target.document.phase;
|
|
583
|
-
const stepIndex = 'case' === target.scope ? target.case.stepIndex : target.document.stepIndex;
|
|
584
|
-
const reportCollector = createNodeReportCollector(step.node);
|
|
585
|
-
let result;
|
|
586
|
-
try {
|
|
587
|
-
result = await executeNode(step, async (signal)=>{
|
|
588
|
-
const input = await parseNodeInput(node, step.input);
|
|
589
|
-
signal.throwIfAborted();
|
|
590
|
-
const common = {
|
|
591
|
-
input,
|
|
592
|
-
$: step.meta,
|
|
593
|
-
signal,
|
|
594
|
-
context,
|
|
595
|
-
report: reportCollector.collector,
|
|
596
|
-
onTeardown: (teardown)=>{
|
|
597
|
-
if (!execution.onTeardown) throw new NodeExecutionError(step.node, new Error('The current execution scope cannot register teardown.'));
|
|
598
|
-
execution.onTeardown(step.node, teardown);
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
return 'case' === target.scope ? node.execute({
|
|
602
|
-
...common,
|
|
603
|
-
scope: 'case',
|
|
604
|
-
case: target.case
|
|
605
|
-
}) : node.execute({
|
|
606
|
-
...common,
|
|
607
|
-
scope: 'document',
|
|
608
|
-
document: target.document
|
|
609
|
-
});
|
|
610
|
-
}, phase, stepIndex, {
|
|
611
|
-
parentSignal: execution.signal,
|
|
612
|
-
defaultTimeoutMs: execution.defaultTimeoutMs
|
|
613
|
-
});
|
|
614
|
-
} finally{
|
|
615
|
-
const traces = reportCollector.close();
|
|
616
|
-
if (traces.length > 0 && result) result = {
|
|
617
|
-
...result,
|
|
618
|
-
report: {
|
|
619
|
-
traces
|
|
620
|
-
}
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
if (!result) throw new NodeExecutionError(step.node, new Error('Step execution completed without a result.'));
|
|
624
|
-
return result;
|
|
625
|
-
}
|
|
626
|
-
const external_node_path_namespaceObject = require("node:path");
|
|
627
|
-
const reportPathsFromTeardown = (result)=>{
|
|
628
|
-
if (void 0 === result) return [];
|
|
629
|
-
if ('object' != typeof result || null === result || Array.isArray(result)) throw new TypeError('Node teardown result must be an object or undefined.');
|
|
630
|
-
const paths = result.reportPaths;
|
|
631
|
-
if (void 0 === paths) return [];
|
|
632
|
-
if (!Array.isArray(paths)) throw new TypeError('Node teardown reportPaths must be an array.');
|
|
633
|
-
return paths.map((path, index)=>{
|
|
634
|
-
if ('string' != typeof path || 0 === path.trim().length) throw new TypeError(`Node teardown reportPaths[${index}] must be a non-empty string.`);
|
|
635
|
-
if (!(0, external_node_path_namespaceObject.isAbsolute)(path)) throw new TypeError(`Node teardown reportPaths[${index}] must be an absolute path.`);
|
|
636
|
-
return path;
|
|
637
|
-
});
|
|
638
|
-
};
|
|
639
|
-
async function runCollectedCase(collectedCase, options) {
|
|
640
|
-
const phases = {
|
|
641
|
-
beforeEach: options.beforeEach ?? [],
|
|
642
|
-
steps: collectedCase.definition.steps,
|
|
643
|
-
afterEach: options.afterEach ?? []
|
|
644
|
-
};
|
|
645
|
-
const nodes = {
|
|
646
|
-
beforeEach: phases.beforeEach.map((step)=>options.resolveNode(step.node)),
|
|
647
|
-
steps: phases.steps.map((step)=>options.resolveNode(step.node)),
|
|
648
|
-
afterEach: phases.afterEach.map((step)=>options.resolveNode(step.node))
|
|
649
|
-
};
|
|
650
|
-
const runId = options.createRunId?.() ?? (0, external_node_crypto_namespaceObject.randomUUID)();
|
|
651
|
-
const startedAt = new Date();
|
|
652
|
-
const beforeEach = [];
|
|
653
|
-
const steps = [];
|
|
654
|
-
const afterEach = [];
|
|
655
|
-
const teardownStack = [];
|
|
656
|
-
let acceptingTeardowns = true;
|
|
657
|
-
const onTeardown = (node, teardown)=>{
|
|
658
|
-
if (!acceptingTeardowns) throw new WorkflowLifecycleError('Node teardown can only be registered while a case attempt is running.', {
|
|
659
|
-
runId,
|
|
660
|
-
node
|
|
661
|
-
});
|
|
662
|
-
if ('function' != typeof teardown) throw new WorkflowLifecycleError('Node onTeardown() requires a teardown function.', {
|
|
663
|
-
runId,
|
|
664
|
-
node
|
|
665
|
-
});
|
|
666
|
-
teardownStack.push({
|
|
667
|
-
registrationIndex: teardownStack.length,
|
|
668
|
-
node,
|
|
669
|
-
teardown
|
|
670
|
-
});
|
|
671
|
-
};
|
|
672
|
-
const runPhase = async (phase, results, signal = options.signal)=>{
|
|
673
|
-
for (const [stepIndex, step] of phases[phase].entries()){
|
|
674
|
-
const caseContext = {
|
|
675
|
-
caseId: collectedCase.caseId,
|
|
676
|
-
runId,
|
|
677
|
-
projectName: options.projectName ?? collectedCase.projectId,
|
|
678
|
-
attemptIndex: options.attemptIndex ?? 0,
|
|
679
|
-
name: collectedCase.definition.name,
|
|
680
|
-
sourcePath: collectedCase.sourcePath,
|
|
681
|
-
caseIndex: collectedCase.caseIndex,
|
|
682
|
-
phase,
|
|
683
|
-
stepIndex
|
|
684
|
-
};
|
|
685
|
-
const stepInfo = {
|
|
686
|
-
scope: 'case',
|
|
687
|
-
node: step.node,
|
|
688
|
-
stepCount: phases[phase].length,
|
|
689
|
-
case: caseContext
|
|
690
|
-
};
|
|
691
|
-
await options.onStepStart?.(stepInfo);
|
|
692
|
-
const result = await executeStep(step, nodes[phase][stepIndex], {
|
|
693
|
-
scope: 'case',
|
|
694
|
-
case: caseContext
|
|
695
|
-
}, options.context, {
|
|
696
|
-
signal,
|
|
697
|
-
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
698
|
-
onTeardown
|
|
699
|
-
});
|
|
700
|
-
await options.onStepResult?.(stepInfo, result);
|
|
701
|
-
results.push(result);
|
|
702
|
-
if ('failed' === result.status && !result.continuedAfterError) break;
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
let executionError;
|
|
706
|
-
try {
|
|
707
|
-
await runPhase('beforeEach', beforeEach);
|
|
708
|
-
if (!beforeEach.some((step)=>'failed' === step.status)) await runPhase('steps', steps);
|
|
709
|
-
} catch (error) {
|
|
710
|
-
executionError = error;
|
|
711
|
-
}
|
|
712
|
-
try {
|
|
713
|
-
const cleanupSignal = options.signal?.aborted ? new AbortController().signal : options.signal;
|
|
714
|
-
await runPhase('afterEach', afterEach, cleanupSignal);
|
|
715
|
-
} catch (error) {
|
|
716
|
-
executionError ??= error;
|
|
717
|
-
}
|
|
718
|
-
acceptingTeardowns = false;
|
|
719
|
-
const teardownErrors = [];
|
|
720
|
-
const reportPaths = new Set();
|
|
721
|
-
for (const { registrationIndex, node, teardown } of teardownStack.reverse())try {
|
|
722
|
-
for (const path of reportPathsFromTeardown(await teardown()))reportPaths.add(path);
|
|
723
|
-
} catch (error) {
|
|
724
|
-
teardownErrors.push(new NodeScopeTeardownError(error, {
|
|
725
|
-
scope: 'case',
|
|
726
|
-
scopeId: runId,
|
|
727
|
-
node,
|
|
728
|
-
registrationIndex
|
|
729
|
-
}));
|
|
730
|
-
}
|
|
731
|
-
if (executionError) throw executionError;
|
|
732
|
-
const endedAt = new Date();
|
|
733
|
-
const result = {
|
|
734
|
-
caseId: collectedCase.caseId,
|
|
735
|
-
runId,
|
|
736
|
-
projectName: options.projectName ?? collectedCase.projectId,
|
|
737
|
-
attemptIndex: options.attemptIndex ?? 0,
|
|
738
|
-
name: collectedCase.definition.name,
|
|
739
|
-
sourcePath: collectedCase.sourcePath,
|
|
740
|
-
caseIndex: collectedCase.caseIndex,
|
|
741
|
-
status: beforeEach.some((step)=>'failed' === step.status) || steps.some((step)=>'failed' === step.status) || afterEach.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
742
|
-
beforeEach,
|
|
743
|
-
steps,
|
|
744
|
-
afterEach,
|
|
745
|
-
...teardownErrors.length > 0 ? {
|
|
746
|
-
teardownErrors
|
|
747
|
-
} : {},
|
|
748
|
-
...reportPaths.size > 0 ? {
|
|
749
|
-
reportPaths: [
|
|
750
|
-
...reportPaths
|
|
751
|
-
]
|
|
752
|
-
} : {},
|
|
753
|
-
startedAt: startedAt.toISOString(),
|
|
754
|
-
endedAt: endedAt.toISOString(),
|
|
755
|
-
durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime())
|
|
756
|
-
};
|
|
757
|
-
await options.onResult?.(result);
|
|
758
|
-
return result;
|
|
759
|
-
}
|
|
760
|
-
function case_runner_define_property(obj, key, value) {
|
|
761
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
762
|
-
value: value,
|
|
763
|
-
enumerable: true,
|
|
764
|
-
configurable: true,
|
|
765
|
-
writable: true
|
|
766
|
-
});
|
|
767
|
-
else obj[key] = value;
|
|
768
|
-
return obj;
|
|
769
|
-
}
|
|
770
|
-
class CaseRunner {
|
|
771
|
-
register(node) {
|
|
772
|
-
this.registry.register(node);
|
|
773
|
-
return this;
|
|
774
|
-
}
|
|
775
|
-
async run(input) {
|
|
776
|
-
const collectedCase = {
|
|
777
|
-
caseId: 'standalone-case',
|
|
778
|
-
projectId: 'standalone-project',
|
|
779
|
-
sourcePath: '',
|
|
780
|
-
caseIndex: 0,
|
|
781
|
-
definition: {
|
|
782
|
-
name: input.name ?? 'case',
|
|
783
|
-
tags: input.tags ?? [],
|
|
784
|
-
steps: normalizeSteps(input.steps, (name)=>this.registry.get(name))
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
|
-
const result = await runCollectedCase(collectedCase, {
|
|
788
|
-
resolveNode: (name)=>this.registry.require(name),
|
|
789
|
-
context: this.context,
|
|
790
|
-
createRunId: ()=>'standalone-case'
|
|
791
|
-
});
|
|
792
|
-
const fatalStepError = result.steps.find((step)=>'failed' === step.status && !step.continuedAfterError)?.error;
|
|
793
|
-
if (fatalStepError) throw fatalStepError;
|
|
794
|
-
return result;
|
|
795
|
-
}
|
|
796
|
-
constructor(options = {}){
|
|
797
|
-
case_runner_define_property(this, "registry", void 0);
|
|
798
|
-
case_runner_define_property(this, "context", void 0);
|
|
799
|
-
this.registry = new NodeRegistry(options.nodes);
|
|
800
|
-
this.context = options.context;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
function createCaseRunner(options = {}) {
|
|
804
|
-
return new CaseRunner(options);
|
|
805
|
-
}
|
|
806
|
-
function createDocumentRuntime(document, options) {
|
|
807
|
-
const documentRunId = options.createDocumentRunId?.() ?? (0, external_node_crypto_namespaceObject.randomUUID)();
|
|
808
|
-
const startedAt = new Date();
|
|
809
|
-
const beforeAll = [];
|
|
810
|
-
const afterAll = [];
|
|
811
|
-
const nodes = {
|
|
812
|
-
beforeAll: document.lifecycle.beforeAll.map((step)=>options.resolveNode(step.node)),
|
|
813
|
-
afterAll: document.lifecycle.afterAll.map((step)=>options.resolveNode(step.node))
|
|
814
|
-
};
|
|
815
|
-
const project = options.project ?? Object.freeze({
|
|
816
|
-
projectId: document.projectId,
|
|
817
|
-
name: document.projectId,
|
|
818
|
-
tags: Object.freeze({
|
|
819
|
-
include: [],
|
|
820
|
-
exclude: []
|
|
821
|
-
}),
|
|
822
|
-
retry: 0,
|
|
823
|
-
variables: Object.freeze({})
|
|
824
|
-
});
|
|
825
|
-
const context = options.projectContext;
|
|
826
|
-
const signal = options.signal ?? new AbortController().signal;
|
|
827
|
-
const nodeTeardownStack = [];
|
|
828
|
-
let acceptingNodeTeardowns = true;
|
|
829
|
-
let started = false;
|
|
830
|
-
let finishedResult;
|
|
831
|
-
const reportPaths = new Set();
|
|
832
|
-
const createResult = (teardownErrors = [])=>{
|
|
833
|
-
const endedAt = new Date();
|
|
834
|
-
return {
|
|
835
|
-
documentId: document.documentId,
|
|
836
|
-
documentRunId,
|
|
837
|
-
projectId: document.projectId,
|
|
838
|
-
projectName: project.name,
|
|
839
|
-
sourcePath: document.sourcePath,
|
|
840
|
-
status: beforeAll.some((step)=>'failed' === step.status) || afterAll.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
841
|
-
startedAt: startedAt.toISOString(),
|
|
842
|
-
endedAt: endedAt.toISOString(),
|
|
843
|
-
durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
|
|
844
|
-
beforeAll: [
|
|
845
|
-
...beforeAll
|
|
846
|
-
],
|
|
847
|
-
afterAll: [
|
|
848
|
-
...afterAll
|
|
849
|
-
],
|
|
850
|
-
...teardownErrors.length > 0 ? {
|
|
851
|
-
teardownErrors
|
|
852
|
-
} : {},
|
|
853
|
-
...reportPaths.size > 0 ? {
|
|
854
|
-
reportPaths: [
|
|
855
|
-
...reportPaths
|
|
856
|
-
]
|
|
857
|
-
} : {}
|
|
858
|
-
};
|
|
859
|
-
};
|
|
860
|
-
const runPhase = async (phase, results, executionSignal = signal)=>{
|
|
861
|
-
for (const [stepIndex, step] of document.lifecycle[phase].entries()){
|
|
862
|
-
const documentContext = {
|
|
863
|
-
documentId: document.documentId,
|
|
864
|
-
documentRunId,
|
|
865
|
-
projectId: document.projectId,
|
|
866
|
-
projectName: project.name,
|
|
867
|
-
sourcePath: document.sourcePath,
|
|
868
|
-
phase,
|
|
869
|
-
stepIndex
|
|
870
|
-
};
|
|
871
|
-
const stepInfo = {
|
|
872
|
-
scope: 'document',
|
|
873
|
-
node: step.node,
|
|
874
|
-
stepCount: document.lifecycle[phase].length,
|
|
875
|
-
document: documentContext
|
|
876
|
-
};
|
|
877
|
-
await options.onStepStart?.(stepInfo);
|
|
878
|
-
const result = await executeStep(step, nodes[phase][stepIndex], {
|
|
879
|
-
scope: 'document',
|
|
880
|
-
document: documentContext
|
|
881
|
-
}, context, {
|
|
882
|
-
signal: executionSignal,
|
|
883
|
-
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
884
|
-
onTeardown (node, teardown) {
|
|
885
|
-
if (!acceptingNodeTeardowns) throw new WorkflowLifecycleError('Node teardown can only be registered while a workflow document is running.', {
|
|
886
|
-
documentId: document.documentId,
|
|
887
|
-
documentRunId,
|
|
888
|
-
node
|
|
889
|
-
});
|
|
890
|
-
if ('function' != typeof teardown) throw new WorkflowLifecycleError('Node onTeardown() requires a teardown function.', {
|
|
891
|
-
documentId: document.documentId,
|
|
892
|
-
documentRunId,
|
|
893
|
-
node
|
|
894
|
-
});
|
|
895
|
-
nodeTeardownStack.push({
|
|
896
|
-
registrationIndex: nodeTeardownStack.length,
|
|
897
|
-
node,
|
|
898
|
-
teardown
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
});
|
|
902
|
-
await options.onStepResult?.(stepInfo, result);
|
|
903
|
-
results.push(result);
|
|
904
|
-
if ('failed' === result.status && !result.continuedAfterError) break;
|
|
905
|
-
}
|
|
906
|
-
};
|
|
907
|
-
return {
|
|
908
|
-
get context () {
|
|
909
|
-
return context;
|
|
910
|
-
},
|
|
911
|
-
get canRunCases () {
|
|
912
|
-
return started && !signal.aborted && !beforeAll.some((step)=>'failed' === step.status);
|
|
913
|
-
},
|
|
914
|
-
async start () {
|
|
915
|
-
if (started) throw new WorkflowLifecycleError('Workflow document runtime has already started.', {
|
|
916
|
-
documentId: document.documentId,
|
|
917
|
-
documentRunId
|
|
918
|
-
});
|
|
919
|
-
started = true;
|
|
920
|
-
if (!signal.aborted) await runPhase('beforeAll', beforeAll);
|
|
921
|
-
return createResult();
|
|
922
|
-
},
|
|
923
|
-
async finish () {
|
|
924
|
-
if (finishedResult) return finishedResult;
|
|
925
|
-
if (!started) throw new WorkflowLifecycleError('Workflow document runtime must start before it can finish.', {
|
|
926
|
-
documentId: document.documentId,
|
|
927
|
-
documentRunId
|
|
928
|
-
});
|
|
929
|
-
let finishError;
|
|
930
|
-
try {
|
|
931
|
-
await runPhase('afterAll', afterAll, signal.aborted ? new AbortController().signal : signal);
|
|
932
|
-
} catch (error) {
|
|
933
|
-
finishError = error;
|
|
934
|
-
}
|
|
935
|
-
acceptingNodeTeardowns = false;
|
|
936
|
-
const teardownErrors = [];
|
|
937
|
-
for (const { registrationIndex, node, teardown } of nodeTeardownStack.reverse())try {
|
|
938
|
-
for (const path of reportPathsFromTeardown(await teardown()))reportPaths.add(path);
|
|
939
|
-
} catch (error) {
|
|
940
|
-
teardownErrors.push(new NodeScopeTeardownError(error, {
|
|
941
|
-
scope: 'document',
|
|
942
|
-
scopeId: documentRunId,
|
|
943
|
-
node,
|
|
944
|
-
registrationIndex
|
|
945
|
-
}));
|
|
946
|
-
}
|
|
947
|
-
finishedResult = createResult(teardownErrors);
|
|
948
|
-
if (finishError) throw finishError;
|
|
949
|
-
await options.onResult?.(finishedResult);
|
|
950
|
-
return finishedResult;
|
|
951
|
-
}
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
const asNotRun = (collectedCase, projectName, reason)=>({
|
|
955
|
-
caseId: collectedCase.caseId,
|
|
956
|
-
projectName,
|
|
957
|
-
name: collectedCase.definition.name,
|
|
958
|
-
sourcePath: collectedCase.sourcePath,
|
|
959
|
-
caseIndex: collectedCase.caseIndex,
|
|
960
|
-
status: 'not-run',
|
|
961
|
-
notRunReason: reason
|
|
962
|
-
});
|
|
963
|
-
async function runWorkflowDocument(document, options) {
|
|
964
|
-
const createDocumentRunId = options.createDocumentRunId;
|
|
965
|
-
const createCaseRunId = options.createCaseRunId;
|
|
966
|
-
const projectName = options.project?.name ?? document.projectId;
|
|
967
|
-
const retry = options.retry ?? options.project?.retry ?? 0;
|
|
968
|
-
if (!Number.isInteger(retry) || retry < 0) throw new TypeError('Workflow document retry must be a non-negative integer.');
|
|
969
|
-
const runtime = createDocumentRuntime(document, {
|
|
970
|
-
resolveNode: options.resolveNode,
|
|
971
|
-
project: options.project,
|
|
972
|
-
projectContext: options.projectContext,
|
|
973
|
-
signal: options.signal,
|
|
974
|
-
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
975
|
-
onStepStart: options.onStepStart,
|
|
976
|
-
onStepResult: options.onStepResult,
|
|
977
|
-
onResult: options.onDocumentResult,
|
|
978
|
-
createDocumentRunId: createDocumentRunId ? ()=>createDocumentRunId(document) : void 0
|
|
979
|
-
});
|
|
980
|
-
const cases = [];
|
|
981
|
-
let runtimeStarted = false;
|
|
982
|
-
let documentResult;
|
|
983
|
-
let executionError;
|
|
984
|
-
let fatalError = false;
|
|
985
|
-
try {
|
|
986
|
-
runtimeStarted = true;
|
|
987
|
-
await runtime.start();
|
|
988
|
-
if (runtime.canRunCases) for (const collectedCase of document.cases){
|
|
989
|
-
if (fatalError || options.shouldStop?.()) {
|
|
990
|
-
const outcome = asNotRun(collectedCase, projectName, fatalError ? 'fatal-error' : options.stopReason?.() ?? 'interrupted');
|
|
991
|
-
cases.push(outcome);
|
|
992
|
-
await options.onCaseOutcome?.(outcome);
|
|
993
|
-
continue;
|
|
994
|
-
}
|
|
995
|
-
await options.onCaseStart?.(collectedCase);
|
|
996
|
-
const attempts = [];
|
|
997
|
-
for(let attemptIndex = 0; attemptIndex <= retry; attemptIndex += 1){
|
|
998
|
-
const run = await runCollectedCase(collectedCase, {
|
|
999
|
-
resolveNode: options.resolveNode,
|
|
1000
|
-
beforeEach: document.lifecycle.beforeEach,
|
|
1001
|
-
afterEach: document.lifecycle.afterEach,
|
|
1002
|
-
context: runtime.context,
|
|
1003
|
-
projectName,
|
|
1004
|
-
attemptIndex,
|
|
1005
|
-
signal: options.signal,
|
|
1006
|
-
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
1007
|
-
onStepStart: options.onStepStart,
|
|
1008
|
-
onStepResult: options.onStepResult,
|
|
1009
|
-
onResult: options.onCaseResult,
|
|
1010
|
-
createRunId: createCaseRunId ? ()=>createCaseRunId(collectedCase, attemptIndex) : void 0
|
|
1011
|
-
});
|
|
1012
|
-
attempts.push(run);
|
|
1013
|
-
fatalError = options.isFatalError?.(run) ?? false;
|
|
1014
|
-
if ('success' === run.status || fatalError) break;
|
|
1015
|
-
}
|
|
1016
|
-
const run = attempts.at(-1);
|
|
1017
|
-
const outcome = {
|
|
1018
|
-
caseId: collectedCase.caseId,
|
|
1019
|
-
projectName,
|
|
1020
|
-
name: collectedCase.definition.name,
|
|
1021
|
-
sourcePath: collectedCase.sourcePath,
|
|
1022
|
-
caseIndex: collectedCase.caseIndex,
|
|
1023
|
-
status: run.status,
|
|
1024
|
-
run,
|
|
1025
|
-
attempts
|
|
1026
|
-
};
|
|
1027
|
-
cases.push(outcome);
|
|
1028
|
-
await options.onCaseOutcome?.(outcome);
|
|
1029
|
-
}
|
|
1030
|
-
else {
|
|
1031
|
-
const reason = options.signal?.aborted ? options.stopReason?.() ?? 'interrupted' : 'document-start-failed';
|
|
1032
|
-
cases.push(...document.cases.map((collectedCase)=>asNotRun(collectedCase, projectName, reason)));
|
|
1033
|
-
}
|
|
1034
|
-
} catch (error) {
|
|
1035
|
-
executionError = error;
|
|
1036
|
-
} finally{
|
|
1037
|
-
if (runtimeStarted) try {
|
|
1038
|
-
documentResult = await runtime.finish();
|
|
1039
|
-
} catch (error) {
|
|
1040
|
-
executionError ??= error;
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
if (executionError) throw executionError;
|
|
1044
|
-
if (!documentResult) throw new Error(`Workflow document "${document.sourcePath}" did not produce a result.`);
|
|
1045
|
-
return {
|
|
1046
|
-
document: documentResult,
|
|
1047
|
-
cases
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
const createProjectRuntime = (options)=>{
|
|
1051
|
-
const startedAt = new Date();
|
|
1052
|
-
const controller = new AbortController();
|
|
1053
|
-
const parentSignal = options.signal;
|
|
1054
|
-
const abortFromParent = ()=>controller.abort(parentSignal?.reason);
|
|
1055
|
-
if (parentSignal?.aborted) abortFromParent();
|
|
1056
|
-
else parentSignal?.addEventListener('abort', abortFromParent, {
|
|
1057
|
-
once: true
|
|
1058
|
-
});
|
|
1059
|
-
const teardownStack = [];
|
|
1060
|
-
let context;
|
|
1061
|
-
let setupError;
|
|
1062
|
-
let started = false;
|
|
1063
|
-
let acceptingTeardowns = void 0 !== options.setup;
|
|
1064
|
-
let finishedResult;
|
|
1065
|
-
const createResult = (requestedStatus, teardownErrors = [])=>{
|
|
1066
|
-
const endedAt = new Date();
|
|
1067
|
-
return {
|
|
1068
|
-
projectName: options.project.name,
|
|
1069
|
-
status: 'failed' === requestedStatus || setupError || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
1070
|
-
...setupError ? {
|
|
1071
|
-
setupError
|
|
1072
|
-
} : {},
|
|
1073
|
-
...teardownErrors.length > 0 ? {
|
|
1074
|
-
teardownErrors
|
|
1075
|
-
} : {},
|
|
1076
|
-
startedAt: startedAt.toISOString(),
|
|
1077
|
-
endedAt: endedAt.toISOString(),
|
|
1078
|
-
durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime())
|
|
1079
|
-
};
|
|
1080
|
-
};
|
|
1081
|
-
const onTeardown = (teardown)=>{
|
|
1082
|
-
if (!acceptingTeardowns) throw new WorkflowLifecycleError('onTeardown() can only be called while Project setup is running.', {
|
|
1083
|
-
projectName: options.project.name
|
|
1084
|
-
});
|
|
1085
|
-
if ('function' != typeof teardown) throw new WorkflowLifecycleError('Project onTeardown() requires a teardown function.', {
|
|
1086
|
-
projectName: options.project.name
|
|
1087
|
-
});
|
|
1088
|
-
teardownStack.push({
|
|
1089
|
-
registrationIndex: teardownStack.length,
|
|
1090
|
-
teardown
|
|
1091
|
-
});
|
|
1092
|
-
};
|
|
1093
|
-
return {
|
|
1094
|
-
get context () {
|
|
1095
|
-
return context;
|
|
1096
|
-
},
|
|
1097
|
-
get signal () {
|
|
1098
|
-
return controller.signal;
|
|
1099
|
-
},
|
|
1100
|
-
get canRun () {
|
|
1101
|
-
return started && !setupError && !controller.signal.aborted;
|
|
1102
|
-
},
|
|
1103
|
-
async start () {
|
|
1104
|
-
if (started) throw new WorkflowLifecycleError('Project runtime has already started.', {
|
|
1105
|
-
projectName: options.project.name
|
|
1106
|
-
});
|
|
1107
|
-
started = true;
|
|
1108
|
-
if (controller.signal.aborted) {
|
|
1109
|
-
acceptingTeardowns = false;
|
|
1110
|
-
return createResult('failed');
|
|
1111
|
-
}
|
|
1112
|
-
if (options.setup) try {
|
|
1113
|
-
context = await options.setup.setup({
|
|
1114
|
-
project: options.project,
|
|
1115
|
-
env: Object.freeze({
|
|
1116
|
-
...process.env
|
|
1117
|
-
}),
|
|
1118
|
-
signal: controller.signal,
|
|
1119
|
-
onTeardown
|
|
1120
|
-
});
|
|
1121
|
-
} catch (error) {
|
|
1122
|
-
setupError = new ProjectSetupError(error, {
|
|
1123
|
-
projectName: options.project.name
|
|
1124
|
-
});
|
|
1125
|
-
} finally{
|
|
1126
|
-
acceptingTeardowns = false;
|
|
1127
|
-
}
|
|
1128
|
-
return createResult(setupError ? 'failed' : 'success');
|
|
1129
|
-
},
|
|
1130
|
-
async finish (requestedStatus = 'success') {
|
|
1131
|
-
if (finishedResult) return finishedResult;
|
|
1132
|
-
if (!started) throw new WorkflowLifecycleError('Project runtime must start before it can finish.', {
|
|
1133
|
-
projectName: options.project.name
|
|
1134
|
-
});
|
|
1135
|
-
const statusBeforeTeardown = createResult(requestedStatus).status;
|
|
1136
|
-
const teardownErrors = [];
|
|
1137
|
-
for (const { registrationIndex, teardown } of teardownStack.reverse())try {
|
|
1138
|
-
await teardown({
|
|
1139
|
-
project: options.project,
|
|
1140
|
-
context,
|
|
1141
|
-
status: statusBeforeTeardown,
|
|
1142
|
-
...setupError ? {
|
|
1143
|
-
setupError
|
|
1144
|
-
} : {}
|
|
1145
|
-
});
|
|
1146
|
-
} catch (error) {
|
|
1147
|
-
teardownErrors.push(new ProjectTeardownError(error, {
|
|
1148
|
-
projectName: options.project.name,
|
|
1149
|
-
registrationIndex
|
|
1150
|
-
}));
|
|
1151
|
-
}
|
|
1152
|
-
parentSignal?.removeEventListener('abort', abortFromParent);
|
|
1153
|
-
finishedResult = createResult(requestedStatus, teardownErrors);
|
|
1154
|
-
return finishedResult;
|
|
1155
|
-
},
|
|
1156
|
-
abort (reason) {
|
|
1157
|
-
controller.abort(reason);
|
|
1158
|
-
}
|
|
1159
|
-
};
|
|
1160
|
-
};
|
|
65
|
+
const test_runner_namespaceObject = require("@midscene/core/internal/test-runner");
|
|
66
|
+
const CaseRunner = test_runner_namespaceObject.CaseRunner;
|
|
67
|
+
const createCaseRunner = test_runner_namespaceObject.createCaseRunner;
|
|
68
|
+
const createDocumentRuntime = test_runner_namespaceObject.createDocumentRuntime;
|
|
69
|
+
const NodeRegistry = test_runner_namespaceObject.NodeRegistry;
|
|
70
|
+
const runWorkflowDocument = test_runner_namespaceObject.runWorkflowDocument;
|
|
71
|
+
const defineNode = test_runner_namespaceObject.defineNode;
|
|
1161
72
|
const external_node_fs_namespaceObject = require("node:fs");
|
|
73
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
1162
74
|
const external_js_yaml_namespaceObject = require("js-yaml");
|
|
75
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
76
|
+
const createCaseInvocationId = (projectId, sourcePath, caseIndex, invocationIndex = 0)=>(0, external_node_crypto_namespaceObject.createHash)('sha256').update(JSON.stringify([
|
|
77
|
+
projectId,
|
|
78
|
+
sourcePath,
|
|
79
|
+
caseIndex,
|
|
80
|
+
...invocationIndex ? [
|
|
81
|
+
invocationIndex
|
|
82
|
+
] : []
|
|
83
|
+
])).digest('hex');
|
|
84
|
+
const createDocumentInvocationId = (projectId, sourcePath, invocationIndex = 0)=>(0, external_node_crypto_namespaceObject.createHash)('sha256').update(JSON.stringify([
|
|
85
|
+
projectId,
|
|
86
|
+
sourcePath,
|
|
87
|
+
...invocationIndex ? [
|
|
88
|
+
invocationIndex
|
|
89
|
+
] : []
|
|
90
|
+
])).digest('hex');
|
|
91
|
+
const normalizeStep = test_runner_namespaceObject.normalizeStep;
|
|
92
|
+
const normalizeSteps = test_runner_namespaceObject.normalizeSteps;
|
|
1163
93
|
const interpolationExpression = /\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}|\$\{([^{}]+)\}/g;
|
|
1164
94
|
const exactEnvExpression = /^\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}$/;
|
|
1165
95
|
const exactProjectExpression = /^\$\{([^{}]+)\}$/;
|
|
1166
96
|
const missingVariable = (kind, name, options)=>{
|
|
1167
|
-
throw new WorkflowParseError(`Undefined ${kind} variable "${name}" in ${options.location.sourcePath}.`, {
|
|
97
|
+
throw new test_runner_namespaceObject.WorkflowParseError(`Undefined ${kind} variable "${name}" in ${options.location.sourcePath}.`, {
|
|
1168
98
|
...options.location,
|
|
1169
99
|
variable: name,
|
|
1170
100
|
variableKind: kind
|
|
@@ -1180,7 +110,7 @@ const environmentVariable = (name, options)=>{
|
|
|
1180
110
|
return value;
|
|
1181
111
|
};
|
|
1182
112
|
const interpolationValue = (name, value, options)=>{
|
|
1183
|
-
if ('object' == typeof value && null !== value) throw new WorkflowParseError(`Project variable "${name}" must be a primitive when embedded in a string.`, {
|
|
113
|
+
if ('object' == typeof value && null !== value) throw new test_runner_namespaceObject.WorkflowParseError(`Project variable "${name}" must be a primitive when embedded in a string.`, {
|
|
1184
114
|
...options.location,
|
|
1185
115
|
variable: name,
|
|
1186
116
|
variableKind: 'project'
|
|
@@ -1203,23 +133,36 @@ const resolveWorkflowVariables = (value, options)=>{
|
|
|
1203
133
|
]));
|
|
1204
134
|
return value;
|
|
1205
135
|
};
|
|
1206
|
-
|
|
136
|
+
function _define_property(obj, key, value) {
|
|
137
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
138
|
+
value: value,
|
|
139
|
+
enumerable: true,
|
|
140
|
+
configurable: true,
|
|
141
|
+
writable: true
|
|
142
|
+
});
|
|
143
|
+
else obj[key] = value;
|
|
144
|
+
return obj;
|
|
145
|
+
}
|
|
146
|
+
const isMapping = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
|
|
147
|
+
class NativeWorkflowFormatError extends test_runner_namespaceObject.WorkflowParseError {
|
|
148
|
+
constructor(actualFormat, sourcePath){
|
|
149
|
+
super('legacy' === actualFormat ? `Legacy tasks/flow YAML is not supported by midscene-test: ${sourcePath}. Run it with \`midscene ${sourcePath}\`, or migrate the file to cases/steps.` : 'legacy-batch' === actualFormat ? `Legacy YAML batch configuration is not a midscene-test workflow document: ${sourcePath}. Run it with \`midscene --config ${sourcePath}\`, or migrate it to a native project config.` : `A workflow file cannot mix legacy tasks/flow with native cases/steps or lifecycle hooks: ${sourcePath}. Keep the formats in separate files and run each file with its matching command.`, {
|
|
150
|
+
sourcePath,
|
|
151
|
+
actualFormat,
|
|
152
|
+
expectedFormat: 'native'
|
|
153
|
+
}), _define_property(this, "actualFormat", void 0);
|
|
154
|
+
this.actualFormat = actualFormat;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
1207
157
|
const rejectUnknownKeys = (value, allowed, label)=>{
|
|
1208
158
|
const allowedSet = new Set(allowed);
|
|
1209
159
|
const unknown = Object.keys(value).filter((key)=>!allowedSet.has(key));
|
|
1210
|
-
if (unknown.length) throw new WorkflowParseError(`${label} has unsupported field "${unknown[0]}".`, {
|
|
160
|
+
if (unknown.length) throw new test_runner_namespaceObject.WorkflowParseError(`${label} has unsupported field "${unknown[0]}".`, {
|
|
1211
161
|
field: unknown[0]
|
|
1212
162
|
});
|
|
1213
163
|
};
|
|
1214
|
-
const createCaseId = (projectId, sourcePath, caseIndex)=>(
|
|
1215
|
-
|
|
1216
|
-
sourcePath,
|
|
1217
|
-
caseIndex
|
|
1218
|
-
])).digest('hex');
|
|
1219
|
-
const createWorkflowDocumentId = (projectId, sourcePath)=>(0, external_node_crypto_namespaceObject.createHash)('sha256').update(JSON.stringify([
|
|
1220
|
-
projectId,
|
|
1221
|
-
sourcePath
|
|
1222
|
-
])).digest('hex');
|
|
164
|
+
const createCaseId = (projectId, sourcePath, caseIndex)=>createCaseInvocationId(projectId, sourcePath, caseIndex);
|
|
165
|
+
const createWorkflowDocumentId = (projectId, sourcePath)=>createDocumentInvocationId(projectId, sourcePath);
|
|
1223
166
|
function collectWorkflowDocument(source, options) {
|
|
1224
167
|
let parsed;
|
|
1225
168
|
try {
|
|
@@ -1227,11 +170,13 @@ function collectWorkflowDocument(source, options) {
|
|
|
1227
170
|
schema: external_js_yaml_namespaceObject.JSON_SCHEMA
|
|
1228
171
|
});
|
|
1229
172
|
} catch (error) {
|
|
1230
|
-
throw new WorkflowParseError(`Failed to parse workflow document "${source.sourcePath}".`, {
|
|
173
|
+
throw new test_runner_namespaceObject.WorkflowParseError(`Failed to parse workflow document "${source.sourcePath}".`, {
|
|
1231
174
|
sourcePath: source.sourcePath
|
|
1232
175
|
}, error);
|
|
1233
176
|
}
|
|
1234
|
-
if (!
|
|
177
|
+
if (!isMapping(parsed)) throw new test_runner_namespaceObject.WorkflowParseError('Workflow document must be a mapping.');
|
|
178
|
+
const format = (0, test_runner_namespaceObject.classifyWorkflowDocumentFormat)(parsed);
|
|
179
|
+
if ('legacy' === format || 'legacy-batch' === format || 'mixed' === format) throw new NativeWorkflowFormatError(format, source.sourcePath);
|
|
1235
180
|
rejectUnknownKeys(parsed, [
|
|
1236
181
|
'beforeAll',
|
|
1237
182
|
'beforeEach',
|
|
@@ -1239,7 +184,7 @@ function collectWorkflowDocument(source, options) {
|
|
|
1239
184
|
'afterEach',
|
|
1240
185
|
'afterAll'
|
|
1241
186
|
], 'Workflow document');
|
|
1242
|
-
if (!Array.isArray(parsed.cases) || 0 === parsed.cases.length) throw new WorkflowParseError('Workflow document must contain a non-empty cases array.');
|
|
187
|
+
if (!Array.isArray(parsed.cases) || 0 === parsed.cases.length) throw new test_runner_namespaceObject.WorkflowParseError('Workflow document must contain a non-empty cases array.');
|
|
1243
188
|
const sourcePath = source.sourcePath.split(external_node_path_namespaceObject.sep).join('/');
|
|
1244
189
|
const resolveStepVariables = (step, phase, stepIndex, caseIndex)=>({
|
|
1245
190
|
...step,
|
|
@@ -1260,12 +205,12 @@ function collectWorkflowDocument(source, options) {
|
|
|
1260
205
|
const normalizeLifecycle = (phase)=>{
|
|
1261
206
|
const value = parsed[phase];
|
|
1262
207
|
if (void 0 === value) return [];
|
|
1263
|
-
if (!Array.isArray(value)) throw new WorkflowParseError(`Workflow document ${phase} must be an array.`, {
|
|
208
|
+
if (!Array.isArray(value)) throw new test_runner_namespaceObject.WorkflowParseError(`Workflow document ${phase} must be an array.`, {
|
|
1264
209
|
phase
|
|
1265
210
|
});
|
|
1266
211
|
return normalizeSteps(value, options.resolveNode).map((normalized, stepIndex)=>{
|
|
1267
212
|
const resolved = options.resolveNode(normalized.node);
|
|
1268
|
-
if (!resolved) throw new WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
|
|
213
|
+
if (!resolved) throw new test_runner_namespaceObject.WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
|
|
1269
214
|
phase,
|
|
1270
215
|
stepIndex,
|
|
1271
216
|
node: normalized.node
|
|
@@ -1281,7 +226,7 @@ function collectWorkflowDocument(source, options) {
|
|
|
1281
226
|
};
|
|
1282
227
|
const ids = new Set();
|
|
1283
228
|
const cases = parsed.cases.map((definition, caseIndex)=>{
|
|
1284
|
-
if (!
|
|
229
|
+
if (!isMapping(definition)) throw new test_runner_namespaceObject.WorkflowParseError(`Case ${caseIndex + 1} must be a mapping.`, {
|
|
1285
230
|
caseIndex
|
|
1286
231
|
});
|
|
1287
232
|
rejectUnknownKeys(definition, [
|
|
@@ -1289,18 +234,18 @@ function collectWorkflowDocument(source, options) {
|
|
|
1289
234
|
'tags',
|
|
1290
235
|
'steps'
|
|
1291
236
|
], `Case ${caseIndex + 1}`);
|
|
1292
|
-
if ('string' != typeof definition.name || 0 === definition.name.trim().length) throw new WorkflowParseError(`Case ${caseIndex + 1} name must be a non-empty string.`, {
|
|
237
|
+
if ('string' != typeof definition.name || 0 === definition.name.trim().length) throw new test_runner_namespaceObject.WorkflowParseError(`Case ${caseIndex + 1} name must be a non-empty string.`, {
|
|
1293
238
|
caseIndex
|
|
1294
239
|
});
|
|
1295
|
-
if (!Array.isArray(definition.steps) || 0 === definition.steps.length) throw new WorkflowParseError(`Case ${caseIndex + 1} steps must be a non-empty array.`, {
|
|
240
|
+
if (!Array.isArray(definition.steps) || 0 === definition.steps.length) throw new test_runner_namespaceObject.WorkflowParseError(`Case ${caseIndex + 1} steps must be a non-empty array.`, {
|
|
1296
241
|
caseIndex
|
|
1297
242
|
});
|
|
1298
243
|
const tags = definition.tags ?? [];
|
|
1299
|
-
if (!Array.isArray(tags) || tags.some((tag)=>'string' != typeof tag || 0 === tag.trim().length)) throw new WorkflowParseError(`Case ${caseIndex + 1} tags must be an array of non-empty strings.`, {
|
|
244
|
+
if (!Array.isArray(tags) || tags.some((tag)=>'string' != typeof tag || 0 === tag.trim().length)) throw new test_runner_namespaceObject.WorkflowParseError(`Case ${caseIndex + 1} tags must be an array of non-empty strings.`, {
|
|
1300
245
|
caseIndex
|
|
1301
246
|
});
|
|
1302
247
|
const steps = normalizeSteps(definition.steps, options.resolveNode).map((normalized, stepIndex)=>{
|
|
1303
|
-
if (!options.resolveNode(normalized.node)) throw new WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
|
|
248
|
+
if (!options.resolveNode(normalized.node)) throw new test_runner_namespaceObject.WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
|
|
1304
249
|
caseIndex,
|
|
1305
250
|
stepIndex,
|
|
1306
251
|
node: normalized.node
|
|
@@ -1308,7 +253,7 @@ function collectWorkflowDocument(source, options) {
|
|
|
1308
253
|
return resolveStepVariables(normalized, 'steps', stepIndex, caseIndex);
|
|
1309
254
|
});
|
|
1310
255
|
const caseId = createCaseId(source.projectId, sourcePath, caseIndex);
|
|
1311
|
-
if (ids.has(caseId)) throw new WorkflowParseError(`Case id collision: ${caseId}.`, {
|
|
256
|
+
if (ids.has(caseId)) throw new test_runner_namespaceObject.WorkflowParseError(`Case id collision: ${caseId}.`, {
|
|
1312
257
|
caseId
|
|
1313
258
|
});
|
|
1314
259
|
ids.add(caseId);
|
|
@@ -1332,423 +277,12 @@ function collectWorkflowDocument(source, options) {
|
|
|
1332
277
|
cases
|
|
1333
278
|
};
|
|
1334
279
|
}
|
|
1335
|
-
const
|
|
1336
|
-
const MAX_STRING_LENGTH = 2000;
|
|
1337
|
-
const MAX_ARRAY_ITEMS = 50;
|
|
1338
|
-
const MAX_OBJECT_KEYS = 50;
|
|
1339
|
-
const MAX_SERIALIZED_BYTES = 65536;
|
|
1340
|
-
const TRUNCATED_VALUE = '[Truncated: display value exceeded 64 KiB]';
|
|
1341
|
-
const normalizedSecretKeys = [
|
|
1342
|
-
'password',
|
|
1343
|
-
'passwd',
|
|
1344
|
-
'secret',
|
|
1345
|
-
'token',
|
|
1346
|
-
'authorization',
|
|
1347
|
-
'cookie',
|
|
1348
|
-
'apikey'
|
|
1349
|
-
];
|
|
1350
|
-
const normalizedKey = (key)=>key.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
1351
|
-
const isSecretKey = (key)=>{
|
|
1352
|
-
const normalized = normalizedKey(key);
|
|
1353
|
-
return normalizedSecretKeys.some((secret)=>normalized.includes(secret));
|
|
1354
|
-
};
|
|
1355
|
-
const objectPath = (parent, key)=>/^[A-Za-z_$][\w$]*$/.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`;
|
|
1356
|
-
const stringByteLength = (value)=>Buffer.byteLength(value, 'utf8');
|
|
1357
|
-
function sanitizeReportText(value, maximumLength = MAX_STRING_LENGTH) {
|
|
1358
|
-
return value.length <= maximumLength ? value : `${value.slice(0, maximumLength)}… [truncated]`;
|
|
1359
|
-
}
|
|
1360
|
-
function sanitizeReportValue(value) {
|
|
1361
|
-
const truncatedPaths = new Set();
|
|
1362
|
-
const redactedPaths = new Set();
|
|
1363
|
-
const ancestors = new WeakSet();
|
|
1364
|
-
let remainingBytes = MAX_SERIALIZED_BYTES;
|
|
1365
|
-
const consume = (path, text)=>{
|
|
1366
|
-
remainingBytes -= stringByteLength(text);
|
|
1367
|
-
if (remainingBytes >= 0) return true;
|
|
1368
|
-
truncatedPaths.add(path);
|
|
1369
|
-
return false;
|
|
1370
|
-
};
|
|
1371
|
-
const visit = (current, path, depth)=>{
|
|
1372
|
-
if (remainingBytes <= 0) {
|
|
1373
|
-
truncatedPaths.add(path);
|
|
1374
|
-
return '[Truncated: size limit]';
|
|
1375
|
-
}
|
|
1376
|
-
if (null === current || 'boolean' == typeof current) {
|
|
1377
|
-
consume(path, String(current));
|
|
1378
|
-
return current;
|
|
1379
|
-
}
|
|
1380
|
-
if ('string' == typeof current) {
|
|
1381
|
-
if (current.length > MAX_STRING_LENGTH) {
|
|
1382
|
-
truncatedPaths.add(path);
|
|
1383
|
-
const shortened = `${current.slice(0, MAX_STRING_LENGTH)}… [truncated]`;
|
|
1384
|
-
consume(path, shortened);
|
|
1385
|
-
return shortened;
|
|
1386
|
-
}
|
|
1387
|
-
consume(path, current);
|
|
1388
|
-
return current;
|
|
1389
|
-
}
|
|
1390
|
-
if ('number' == typeof current) {
|
|
1391
|
-
if (Number.isFinite(current)) {
|
|
1392
|
-
consume(path, String(current));
|
|
1393
|
-
return current;
|
|
1394
|
-
}
|
|
1395
|
-
truncatedPaths.add(path);
|
|
1396
|
-
return `[Unsupported number: ${String(current)}]`;
|
|
1397
|
-
}
|
|
1398
|
-
if ('bigint' == typeof current) {
|
|
1399
|
-
truncatedPaths.add(path);
|
|
1400
|
-
return `[Unsupported bigint: ${String(current)}]`;
|
|
1401
|
-
}
|
|
1402
|
-
if (void 0 === current || 'function' == typeof current || 'symbol' == typeof current) {
|
|
1403
|
-
truncatedPaths.add(path);
|
|
1404
|
-
return `[Unsupported ${typeof current}]`;
|
|
1405
|
-
}
|
|
1406
|
-
if (depth >= MAX_DEPTH) {
|
|
1407
|
-
truncatedPaths.add(path);
|
|
1408
|
-
return '[Truncated: maximum depth]';
|
|
1409
|
-
}
|
|
1410
|
-
if (current instanceof Date) {
|
|
1411
|
-
const date = Number.isNaN(current.getTime()) ? '[Invalid Date]' : current.toISOString();
|
|
1412
|
-
consume(path, date);
|
|
1413
|
-
return date;
|
|
1414
|
-
}
|
|
1415
|
-
if (ancestors.has(current)) {
|
|
1416
|
-
truncatedPaths.add(path);
|
|
1417
|
-
return '[Unsupported circular value]';
|
|
1418
|
-
}
|
|
1419
|
-
if (Array.isArray(current)) {
|
|
1420
|
-
ancestors.add(current);
|
|
1421
|
-
const output = [];
|
|
1422
|
-
const itemCount = Math.min(current.length, MAX_ARRAY_ITEMS);
|
|
1423
|
-
for(let index = 0; index < itemCount; index += 1){
|
|
1424
|
-
if (remainingBytes <= 0) break;
|
|
1425
|
-
output.push(visit(current[index], `${path}[${index}]`, depth + 1));
|
|
1426
|
-
}
|
|
1427
|
-
if (current.length > output.length) truncatedPaths.add(path);
|
|
1428
|
-
ancestors.delete(current);
|
|
1429
|
-
return output;
|
|
1430
|
-
}
|
|
1431
|
-
const prototype = Object.getPrototypeOf(current);
|
|
1432
|
-
if (prototype !== Object.prototype && null !== prototype) {
|
|
1433
|
-
truncatedPaths.add(path);
|
|
1434
|
-
const constructorName = current.constructor?.name ?? 'object';
|
|
1435
|
-
return `[Unsupported object: ${constructorName}]`;
|
|
1436
|
-
}
|
|
1437
|
-
ancestors.add(current);
|
|
1438
|
-
const output = {};
|
|
1439
|
-
const keys = Object.keys(current);
|
|
1440
|
-
for (const key of keys.slice(0, MAX_OBJECT_KEYS)){
|
|
1441
|
-
const childPath = objectPath(path, key);
|
|
1442
|
-
if (!consume(childPath, key)) break;
|
|
1443
|
-
if (isSecretKey(key)) {
|
|
1444
|
-
output[key] = '[REDACTED]';
|
|
1445
|
-
redactedPaths.add(childPath);
|
|
1446
|
-
} else output[key] = visit(current[key], childPath, depth + 1);
|
|
1447
|
-
if (remainingBytes <= 0) break;
|
|
1448
|
-
}
|
|
1449
|
-
if (keys.length > Object.keys(output).length) truncatedPaths.add(path);
|
|
1450
|
-
ancestors.delete(current);
|
|
1451
|
-
return output;
|
|
1452
|
-
};
|
|
1453
|
-
const sanitized = visit(value, '$', 0);
|
|
1454
|
-
const serialized = JSON.stringify(sanitized);
|
|
1455
|
-
if (void 0 === serialized || stringByteLength(serialized) > MAX_SERIALIZED_BYTES) return {
|
|
1456
|
-
value: TRUNCATED_VALUE,
|
|
1457
|
-
truncatedPaths: [
|
|
1458
|
-
'$'
|
|
1459
|
-
]
|
|
1460
|
-
};
|
|
1461
|
-
return {
|
|
1462
|
-
value: sanitized,
|
|
1463
|
-
...truncatedPaths.size > 0 ? {
|
|
1464
|
-
truncatedPaths: [
|
|
1465
|
-
...truncatedPaths
|
|
1466
|
-
]
|
|
1467
|
-
} : {},
|
|
1468
|
-
...redactedPaths.size > 0 ? {
|
|
1469
|
-
redactedPaths: [
|
|
1470
|
-
...redactedPaths
|
|
1471
|
-
]
|
|
1472
|
-
} : {}
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
function sanitizeReportError(error) {
|
|
1476
|
-
if (error instanceof Error) {
|
|
1477
|
-
const workflowLike = error;
|
|
1478
|
-
return {
|
|
1479
|
-
name: sanitizeReportText(error.name || 'Error', 200),
|
|
1480
|
-
message: sanitizeReportText(error.message || String(error)),
|
|
1481
|
-
...'string' == typeof workflowLike.code ? {
|
|
1482
|
-
code: sanitizeReportText(workflowLike.code, 200)
|
|
1483
|
-
} : {},
|
|
1484
|
-
...void 0 === workflowLike.details ? {} : {
|
|
1485
|
-
details: sanitizeReportValue(workflowLike.details)
|
|
1486
|
-
}
|
|
1487
|
-
};
|
|
1488
|
-
}
|
|
1489
|
-
return {
|
|
1490
|
-
name: 'Error',
|
|
1491
|
-
message: sanitizeReportText(String(error))
|
|
1492
|
-
};
|
|
1493
|
-
}
|
|
1494
|
-
const logger_namespaceObject = require("@midscene/shared/logger");
|
|
1495
|
-
const warnReport = (0, logger_namespaceObject.getDebug)('test-runner:report-assembler', {
|
|
1496
|
-
console: true
|
|
1497
|
-
});
|
|
1498
|
-
const exactMidsceneDetailNodes = new Set([
|
|
1499
|
-
'aiAct',
|
|
1500
|
-
'aiAssert',
|
|
1501
|
-
'recordToReport'
|
|
1502
|
-
]);
|
|
1503
|
-
const scopedExecutionKey = (scopeId, executionId)=>`${scopeId}\u0000${executionId}`;
|
|
1504
|
-
const reportSourcesForScope = (context, scopeId)=>context.sourcesByScope.get(scopeId) ?? [];
|
|
1505
|
-
const addDiagnostic = (context, diagnostic)=>{
|
|
1506
|
-
context.diagnostics.push(diagnostic);
|
|
1507
|
-
const key = JSON.stringify([
|
|
1508
|
-
diagnostic.code,
|
|
1509
|
-
diagnostic.scopeId,
|
|
1510
|
-
diagnostic.executionId
|
|
1511
|
-
]);
|
|
1512
|
-
if (!context.warnedDiagnostics.has(key)) {
|
|
1513
|
-
context.warnedDiagnostics.add(key);
|
|
1514
|
-
warnReport(diagnostic.message);
|
|
1515
|
-
}
|
|
1516
|
-
};
|
|
1517
|
-
const buildStep = (step, scopeId, context)=>{
|
|
1518
|
-
const details = [];
|
|
1519
|
-
const unresolvedExecutionIds = [];
|
|
1520
|
-
for (const trace of step.report?.traces ?? []){
|
|
1521
|
-
const previousScope = context.tracedScopeByExecution.get(trace.executionId);
|
|
1522
|
-
if (previousScope && previousScope !== scopeId) throw new Error(`Agent execution ${trace.executionId} was traced by multiple test scopes (${previousScope}, ${scopeId}).`);
|
|
1523
|
-
context.tracedScopeByExecution.set(trace.executionId, scopeId);
|
|
1524
|
-
const detail = context.detailsByScopeAndExecution.get(scopedExecutionKey(scopeId, trace.executionId));
|
|
1525
|
-
if (detail) details.push(detail);
|
|
1526
|
-
else unresolvedExecutionIds.push(trace.executionId);
|
|
1527
|
-
}
|
|
1528
|
-
let agentDetailDiagnostic;
|
|
1529
|
-
if (unresolvedExecutionIds.length > 0) {
|
|
1530
|
-
agentDetailDiagnostic = `Agent detail could not be resolved for ${unresolvedExecutionIds.join(', ')}.`;
|
|
1531
|
-
const sources = reportSourcesForScope(context, scopeId);
|
|
1532
|
-
for (const executionId of unresolvedExecutionIds)addDiagnostic(context, {
|
|
1533
|
-
level: 'warning',
|
|
1534
|
-
code: 'agent-detail-unresolved',
|
|
1535
|
-
message: `Agent detail could not be resolved for execution ${executionId} in test scope ${scopeId}.`,
|
|
1536
|
-
scopeId,
|
|
1537
|
-
executionId,
|
|
1538
|
-
...sources.length > 0 ? {
|
|
1539
|
-
sourcePaths: sources.map((source)=>source.sourcePath)
|
|
1540
|
-
} : {}
|
|
1541
|
-
});
|
|
1542
|
-
} else if (0 === details.length && exactMidsceneDetailNodes.has(step.node)) {
|
|
1543
|
-
agentDetailDiagnostic = 'This Agent did not expose a stable execution reference for the Step.';
|
|
1544
|
-
addDiagnostic(context, {
|
|
1545
|
-
level: 'warning',
|
|
1546
|
-
code: 'agent-detail-unavailable',
|
|
1547
|
-
message: `No stable Agent execution reference was captured for ${step.node} in test scope ${scopeId}.`,
|
|
1548
|
-
scopeId,
|
|
1549
|
-
...reportSourcesForScope(context, scopeId).length > 0 ? {
|
|
1550
|
-
sourcePaths: reportSourcesForScope(context, scopeId).map((source)=>source.sourcePath)
|
|
1551
|
-
} : {}
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
const prompt = 'string' == typeof step.input?.prompt ? step.input.prompt : void 0;
|
|
1555
|
-
return {
|
|
1556
|
-
id: `${scopeId}:${step.phase}:${step.stepIndex}`,
|
|
1557
|
-
phase: step.phase,
|
|
1558
|
-
stepIndex: step.stepIndex,
|
|
1559
|
-
node: step.node,
|
|
1560
|
-
...prompt ? {
|
|
1561
|
-
title: sanitizeReportText(prompt, 240)
|
|
1562
|
-
} : {},
|
|
1563
|
-
status: step.status,
|
|
1564
|
-
continuedAfterError: step.continuedAfterError,
|
|
1565
|
-
startedAt: step.startedAt,
|
|
1566
|
-
endedAt: step.endedAt,
|
|
1567
|
-
durationMs: step.durationMs,
|
|
1568
|
-
input: sanitizeReportValue(step.input),
|
|
1569
|
-
...step.output ? {
|
|
1570
|
-
output: {
|
|
1571
|
-
...void 0 === step.output.summary ? {} : {
|
|
1572
|
-
summary: sanitizeReportText(step.output.summary)
|
|
1573
|
-
},
|
|
1574
|
-
...void 0 === step.output.data ? {} : {
|
|
1575
|
-
data: sanitizeReportValue(step.output.data)
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
} : {},
|
|
1579
|
-
...step.error ? {
|
|
1580
|
-
error: sanitizeReportError(step.error)
|
|
1581
|
-
} : {},
|
|
1582
|
-
...details.length > 0 ? {
|
|
1583
|
-
agentDetails: details
|
|
1584
|
-
} : {},
|
|
1585
|
-
...agentDetailDiagnostic ? {
|
|
1586
|
-
agentDetailDiagnostic
|
|
1587
|
-
} : {}
|
|
1588
|
-
};
|
|
1589
|
-
};
|
|
1590
|
-
const buildAttempt = (attempt, context)=>({
|
|
1591
|
-
attemptId: attempt.runId,
|
|
1592
|
-
attemptIndex: attempt.attemptIndex,
|
|
1593
|
-
status: attempt.status,
|
|
1594
|
-
startedAt: attempt.startedAt,
|
|
1595
|
-
endedAt: attempt.endedAt,
|
|
1596
|
-
durationMs: attempt.durationMs,
|
|
1597
|
-
beforeEach: attempt.beforeEach.map((step)=>buildStep(step, attempt.runId, context)),
|
|
1598
|
-
steps: attempt.steps.map((step)=>buildStep(step, attempt.runId, context)),
|
|
1599
|
-
afterEach: attempt.afterEach.map((step)=>buildStep(step, attempt.runId, context)),
|
|
1600
|
-
...attempt.teardownErrors?.length ? {
|
|
1601
|
-
teardownErrors: attempt.teardownErrors.map(sanitizeReportError)
|
|
1602
|
-
} : {},
|
|
1603
|
-
...reportSourcesForScope(context, attempt.runId).length > 0 ? {
|
|
1604
|
-
scopeReportIds: reportSourcesForScope(context, attempt.runId).map((source)=>source.reportId)
|
|
1605
|
-
} : {}
|
|
1606
|
-
});
|
|
1607
|
-
const buildCase = (outcome, context)=>({
|
|
1608
|
-
caseId: outcome.caseId,
|
|
1609
|
-
name: outcome.name,
|
|
1610
|
-
caseIndex: outcome.caseIndex,
|
|
1611
|
-
status: outcome.status,
|
|
1612
|
-
...outcome.notRunReason ? {
|
|
1613
|
-
notRunReason: outcome.notRunReason
|
|
1614
|
-
} : {},
|
|
1615
|
-
attempts: (outcome.attempts ?? (outcome.run ? [
|
|
1616
|
-
outcome.run
|
|
1617
|
-
] : [])).map((attempt)=>buildAttempt(attempt, context))
|
|
1618
|
-
});
|
|
1619
|
-
const buildDocument = (documentId, document, cases, context)=>{
|
|
1620
|
-
const sourcePath = document?.sourcePath ?? cases[0]?.sourcePath ?? '<unknown>';
|
|
1621
|
-
const status = document?.status ?? (cases.some((item)=>'success' !== item.status) ? 'failed' : 'success');
|
|
1622
|
-
return {
|
|
1623
|
-
documentId,
|
|
1624
|
-
sourcePath,
|
|
1625
|
-
status,
|
|
1626
|
-
...document ? {
|
|
1627
|
-
startedAt: document.startedAt,
|
|
1628
|
-
endedAt: document.endedAt,
|
|
1629
|
-
durationMs: document.durationMs
|
|
1630
|
-
} : {},
|
|
1631
|
-
beforeAll: (document?.beforeAll ?? []).map((step)=>buildStep(step, document.documentRunId, context)),
|
|
1632
|
-
cases: cases.map((outcome)=>buildCase(outcome, context)),
|
|
1633
|
-
afterAll: (document?.afterAll ?? []).map((step)=>buildStep(step, document.documentRunId, context)),
|
|
1634
|
-
...document?.teardownErrors?.length ? {
|
|
1635
|
-
teardownErrors: document.teardownErrors.map(sanitizeReportError)
|
|
1636
|
-
} : {},
|
|
1637
|
-
...document && reportSourcesForScope(context, document.documentRunId).length > 0 ? {
|
|
1638
|
-
scopeReportIds: reportSourcesForScope(context, document.documentRunId).map((source)=>source.reportId)
|
|
1639
|
-
} : {}
|
|
1640
|
-
};
|
|
1641
|
-
};
|
|
1642
|
-
const buildProject = (project, context)=>{
|
|
1643
|
-
const documentById = new Map(project.documents.map((document)=>[
|
|
1644
|
-
document.documentId,
|
|
1645
|
-
document
|
|
1646
|
-
]));
|
|
1647
|
-
const casesByDocumentId = new Map();
|
|
1648
|
-
for (const outcome of project.cases){
|
|
1649
|
-
const current = casesByDocumentId.get(outcome.documentId) ?? [];
|
|
1650
|
-
current.push(outcome);
|
|
1651
|
-
casesByDocumentId.set(outcome.documentId, current);
|
|
1652
|
-
}
|
|
1653
|
-
const documentIds = new Set([
|
|
1654
|
-
...project.documents.map((document)=>document.documentId),
|
|
1655
|
-
...project.cases.map((outcome)=>outcome.documentId)
|
|
1656
|
-
]);
|
|
1657
|
-
return {
|
|
1658
|
-
projectId: project.projectId,
|
|
1659
|
-
name: project.name,
|
|
1660
|
-
status: project.status,
|
|
1661
|
-
retry: project.retry,
|
|
1662
|
-
...project.lifecycle ? {
|
|
1663
|
-
lifecycle: {
|
|
1664
|
-
status: project.lifecycle.status,
|
|
1665
|
-
startedAt: project.lifecycle.startedAt,
|
|
1666
|
-
endedAt: project.lifecycle.endedAt,
|
|
1667
|
-
durationMs: project.lifecycle.durationMs,
|
|
1668
|
-
...project.lifecycle.setupError ? {
|
|
1669
|
-
setupError: sanitizeReportError(project.lifecycle.setupError)
|
|
1670
|
-
} : {},
|
|
1671
|
-
...project.lifecycle.teardownErrors?.length ? {
|
|
1672
|
-
teardownErrors: project.lifecycle.teardownErrors.map(sanitizeReportError)
|
|
1673
|
-
} : {}
|
|
1674
|
-
}
|
|
1675
|
-
} : {},
|
|
1676
|
-
documents: [
|
|
1677
|
-
...documentIds
|
|
1678
|
-
].map((documentId)=>buildDocument(documentId, documentById.get(documentId), casesByDocumentId.get(documentId) ?? [], context)),
|
|
1679
|
-
collectionErrors: project.collectionErrors.map((collectionError)=>({
|
|
1680
|
-
sourcePath: collectionError.sourcePath,
|
|
1681
|
-
error: sanitizeReportError(collectionError.error)
|
|
1682
|
-
}))
|
|
1683
|
-
};
|
|
1684
|
-
};
|
|
1685
|
-
function collectTestRunReportSources(result) {
|
|
1686
|
-
const sources = [];
|
|
1687
|
-
for (const project of result.projects){
|
|
1688
|
-
for (const document of project.documents)for (const sourcePath of document.reportPaths ?? [])sources.push({
|
|
1689
|
-
scopeId: document.documentRunId,
|
|
1690
|
-
sourcePath
|
|
1691
|
-
});
|
|
1692
|
-
for (const outcome of project.cases)for (const attempt of outcome.attempts ?? (outcome.run ? [
|
|
1693
|
-
outcome.run
|
|
1694
|
-
] : []))for (const sourcePath of attempt.reportPaths ?? [])sources.push({
|
|
1695
|
-
scopeId: attempt.runId,
|
|
1696
|
-
sourcePath
|
|
1697
|
-
});
|
|
1698
|
-
}
|
|
1699
|
-
return sources;
|
|
1700
|
-
}
|
|
1701
|
-
function buildTestRunReportDump(result, index) {
|
|
1702
|
-
const sourcesByScope = new Map();
|
|
1703
|
-
const detailsByScopeAndExecution = new Map();
|
|
1704
|
-
for (const source of index.sources){
|
|
1705
|
-
const scopeSources = sourcesByScope.get(source.scopeId) ?? [];
|
|
1706
|
-
scopeSources.push(source);
|
|
1707
|
-
sourcesByScope.set(source.scopeId, scopeSources);
|
|
1708
|
-
for (const executionId of source.executionIds)detailsByScopeAndExecution.set(scopedExecutionKey(source.scopeId, executionId), {
|
|
1709
|
-
reportId: source.reportId,
|
|
1710
|
-
executionId
|
|
1711
|
-
});
|
|
1712
|
-
}
|
|
1713
|
-
const context = {
|
|
1714
|
-
diagnostics: [],
|
|
1715
|
-
sourcesByScope,
|
|
1716
|
-
detailsByScopeAndExecution,
|
|
1717
|
-
tracedScopeByExecution: new Map(),
|
|
1718
|
-
warnedDiagnostics: new Set()
|
|
1719
|
-
};
|
|
1720
|
-
for (const source of index.sources)if (0 === source.executionIds.length) addDiagnostic(context, {
|
|
1721
|
-
level: 'warning',
|
|
1722
|
-
code: 'source-without-executions',
|
|
1723
|
-
message: `Agent report contains no executions: ${source.sourcePath}`,
|
|
1724
|
-
scopeId: source.scopeId,
|
|
1725
|
-
sourcePaths: [
|
|
1726
|
-
source.sourcePath
|
|
1727
|
-
]
|
|
1728
|
-
});
|
|
1729
|
-
const projects = result.projects.map((project)=>buildProject(project, context));
|
|
1730
|
-
return {
|
|
1731
|
-
schemaVersion: 1,
|
|
1732
|
-
kind: 'test-runner',
|
|
1733
|
-
runId: result.runId,
|
|
1734
|
-
status: result.status,
|
|
1735
|
-
startedAt: result.startedAt,
|
|
1736
|
-
endedAt: result.endedAt,
|
|
1737
|
-
durationMs: result.durationMs,
|
|
1738
|
-
summary: {
|
|
1739
|
-
...result.summary
|
|
1740
|
-
},
|
|
1741
|
-
metrics: index.metrics,
|
|
1742
|
-
projects,
|
|
1743
|
-
...context.diagnostics.length > 0 ? {
|
|
1744
|
-
diagnostics: context.diagnostics
|
|
1745
|
-
} : {}
|
|
1746
|
-
};
|
|
1747
|
-
}
|
|
280
|
+
const v4_namespaceObject = require("zod/v4");
|
|
1748
281
|
exports.CaseExecutionError = __webpack_exports__.CaseExecutionError;
|
|
1749
282
|
exports.CaseRunner = __webpack_exports__.CaseRunner;
|
|
1750
283
|
exports.DuplicateNodeError = __webpack_exports__.DuplicateNodeError;
|
|
1751
284
|
exports.FatalDeviceError = __webpack_exports__.FatalDeviceError;
|
|
285
|
+
exports.NativeWorkflowFormatError = __webpack_exports__.NativeWorkflowFormatError;
|
|
1752
286
|
exports.NodeDefinitionError = __webpack_exports__.NodeDefinitionError;
|
|
1753
287
|
exports.NodeExecutionError = __webpack_exports__.NodeExecutionError;
|
|
1754
288
|
exports.NodeInputValidationError = __webpack_exports__.NodeInputValidationError;
|
|
@@ -1786,6 +320,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
|
1786
320
|
"CaseRunner",
|
|
1787
321
|
"DuplicateNodeError",
|
|
1788
322
|
"FatalDeviceError",
|
|
323
|
+
"NativeWorkflowFormatError",
|
|
1789
324
|
"NodeDefinitionError",
|
|
1790
325
|
"NodeExecutionError",
|
|
1791
326
|
"NodeInputValidationError",
|