@midscene/test 0.0.0 → 1.10.4-beta-20260715034325.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.
@@ -0,0 +1,843 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { sep } from "node:path";
4
+ import { JSON_SCHEMA, load } from "js-yaml";
5
+ function _define_property(obj, key, value) {
6
+ if (key in obj) Object.defineProperty(obj, key, {
7
+ value: value,
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true
11
+ });
12
+ else obj[key] = value;
13
+ return obj;
14
+ }
15
+ class WorkflowError extends Error {
16
+ toJSON() {
17
+ return {
18
+ name: this.name,
19
+ message: this.message,
20
+ code: this.code,
21
+ ...void 0 === this.details ? {} : {
22
+ details: this.details
23
+ }
24
+ };
25
+ }
26
+ constructor(message, options = {}){
27
+ super(message, {
28
+ cause: options.cause
29
+ }), _define_property(this, "code", void 0), _define_property(this, "details", void 0);
30
+ this.name = new.target.name;
31
+ this.code = options.code ?? 'WORKFLOW_ERROR';
32
+ this.details = options.details;
33
+ }
34
+ }
35
+ class WorkflowParseError extends WorkflowError {
36
+ constructor(message, details, cause){
37
+ super(message, {
38
+ code: 'WORKFLOW_PARSE_ERROR',
39
+ details,
40
+ cause
41
+ });
42
+ }
43
+ }
44
+ class NodeDefinitionError extends WorkflowError {
45
+ constructor(message, details){
46
+ super(message, {
47
+ code: 'NODE_DEFINITION_ERROR',
48
+ details
49
+ });
50
+ }
51
+ }
52
+ class DuplicateNodeError extends WorkflowError {
53
+ constructor(node){
54
+ super(`Node "${node}" is already registered.`, {
55
+ code: 'DUPLICATE_NODE',
56
+ details: {
57
+ node
58
+ }
59
+ }), _define_property(this, "node", void 0);
60
+ this.node = node;
61
+ }
62
+ }
63
+ class NodeNotFoundError extends WorkflowError {
64
+ constructor(node){
65
+ super(`Node "${node}" is not registered.`, {
66
+ code: 'NODE_NOT_FOUND',
67
+ details: {
68
+ node
69
+ }
70
+ }), _define_property(this, "node", void 0);
71
+ this.node = node;
72
+ }
73
+ }
74
+ class NodeInputValidationError extends WorkflowError {
75
+ constructor(message, details){
76
+ super(message, {
77
+ code: 'NODE_INPUT_VALIDATION_ERROR',
78
+ details
79
+ });
80
+ }
81
+ }
82
+ class StepTimeoutError extends WorkflowError {
83
+ constructor(timeoutMs, node){
84
+ super(node ? `Node "${node}" timed out after ${timeoutMs}ms.` : `Step timed out after ${timeoutMs}ms.`, {
85
+ code: 'STEP_TIMEOUT',
86
+ details: {
87
+ timeoutMs,
88
+ ...void 0 === node ? {} : {
89
+ node
90
+ }
91
+ }
92
+ }), _define_property(this, "timeoutMs", void 0), _define_property(this, "node", void 0);
93
+ this.timeoutMs = timeoutMs;
94
+ this.node = node;
95
+ }
96
+ }
97
+ class NodeExecutionError extends WorkflowError {
98
+ constructor(node, cause){
99
+ const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
100
+ super(`Node "${node}" failed: ${causeMessage}`, {
101
+ code: 'NODE_EXECUTION_ERROR',
102
+ details: {
103
+ node
104
+ },
105
+ cause
106
+ }), _define_property(this, "node", void 0);
107
+ this.node = node;
108
+ }
109
+ }
110
+ class WorkflowDocumentSetupError extends WorkflowError {
111
+ constructor(cause, details){
112
+ const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
113
+ super(`Workflow document setup failed: ${causeMessage}`, {
114
+ code: 'WORKFLOW_DOCUMENT_SETUP_ERROR',
115
+ details,
116
+ cause
117
+ });
118
+ }
119
+ }
120
+ class WorkflowDocumentTeardownError extends WorkflowError {
121
+ constructor(cause, details){
122
+ const causeMessage = cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');
123
+ super(`Workflow document teardown failed: ${causeMessage}`, {
124
+ code: 'WORKFLOW_DOCUMENT_TEARDOWN_ERROR',
125
+ details,
126
+ cause
127
+ });
128
+ }
129
+ }
130
+ class WorkflowLifecycleError extends WorkflowError {
131
+ constructor(message, details){
132
+ super(message, {
133
+ code: 'WORKFLOW_LIFECYCLE_ERROR',
134
+ details
135
+ });
136
+ }
137
+ }
138
+ class CaseExecutionError extends WorkflowError {
139
+ constructor(result){
140
+ super(`Case "${result.name}" failed.`, {
141
+ code: 'CASE_EXECUTION_FAILED',
142
+ details: {
143
+ caseId: result.caseId,
144
+ runId: result.runId
145
+ }
146
+ }), _define_property(this, "result", void 0);
147
+ this.result = result;
148
+ }
149
+ }
150
+ class WorkflowDocumentExecutionError extends WorkflowError {
151
+ constructor(result){
152
+ super(`Workflow document "${result.sourcePath}" failed.`, {
153
+ code: 'WORKFLOW_DOCUMENT_EXECUTION_FAILED',
154
+ details: {
155
+ documentId: result.documentId,
156
+ documentRunId: result.documentRunId
157
+ }
158
+ }), _define_property(this, "result", void 0);
159
+ this.result = result;
160
+ }
161
+ }
162
+ function normalizeNodeExecutionError(error, node) {
163
+ return error instanceof WorkflowError ? error : new NodeExecutionError(node, error);
164
+ }
165
+ const supportedMetaKeys = new Set([
166
+ 'timeout',
167
+ 'continue-on-error'
168
+ ]);
169
+ function isMapping(value) {
170
+ return 'object' == typeof value && null !== value && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
171
+ }
172
+ function formatStep(index) {
173
+ return `step ${index + 1}`;
174
+ }
175
+ function normalizeMeta(value, index) {
176
+ if (void 0 === value) return {
177
+ continueOnError: false
178
+ };
179
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} "$" must be a mapping.`, {
180
+ index
181
+ });
182
+ for (const key of Object.keys(value))if (!supportedMetaKeys.has(key)) throw new WorkflowParseError(`${formatStep(index)} has unsupported engine metadata "${key}".`, {
183
+ index,
184
+ key
185
+ });
186
+ const timeout = value.timeout;
187
+ 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.`, {
188
+ index,
189
+ timeout
190
+ });
191
+ const continueOnError = value['continue-on-error'];
192
+ if (void 0 !== continueOnError && 'boolean' != typeof continueOnError) throw new WorkflowParseError(`${formatStep(index)} "$.continue-on-error" must be a boolean.`, {
193
+ index,
194
+ continueOnError
195
+ });
196
+ return {
197
+ ...void 0 === timeout ? {} : {
198
+ timeoutMs: timeout
199
+ },
200
+ continueOnError: continueOnError ?? false
201
+ };
202
+ }
203
+ function validateCommonNodeInput(input, index) {
204
+ if (void 0 !== input.prompt && 'string' != typeof input.prompt) throw new WorkflowParseError(`${formatStep(index)} "prompt" must be a string.`, {
205
+ index,
206
+ prompt: input.prompt
207
+ });
208
+ }
209
+ function normalizeStep(value, index = 0) {
210
+ if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
211
+ index
212
+ });
213
+ const entries = Object.entries(value);
214
+ if (1 !== entries.length) throw new WorkflowParseError(`${formatStep(index)} must contain exactly one node.`, {
215
+ index,
216
+ nodes: Object.keys(value)
217
+ });
218
+ const [node, rawValue] = entries[0];
219
+ if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
220
+ index
221
+ });
222
+ if ('string' == typeof rawValue) return {
223
+ node,
224
+ input: {
225
+ prompt: rawValue
226
+ },
227
+ meta: {
228
+ continueOnError: false
229
+ }
230
+ };
231
+ if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
232
+ index,
233
+ node
234
+ });
235
+ const { $: rawMeta, ...input } = rawValue;
236
+ validateCommonNodeInput(input, index);
237
+ return {
238
+ node,
239
+ input,
240
+ meta: normalizeMeta(rawMeta, index)
241
+ };
242
+ }
243
+ function normalizeSteps(steps) {
244
+ if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
245
+ return steps.map((step, index)=>normalizeStep(step, index));
246
+ }
247
+ const validateDefinition = (options)=>{
248
+ if (!options || 'object' != typeof options) throw new NodeDefinitionError('Node definition must be an object.');
249
+ if ('string' != typeof options.name || 0 === options.name.trim().length) throw new NodeDefinitionError('Node name must be a non-empty string.');
250
+ if ('function' != typeof options.execute) throw new NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
251
+ node: options.name
252
+ });
253
+ };
254
+ function defineNode(options) {
255
+ validateDefinition(options);
256
+ return options;
257
+ }
258
+ function _check_private_redeclaration(obj, privateCollection) {
259
+ if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
260
+ }
261
+ function _class_apply_descriptor_get(receiver, descriptor) {
262
+ if (descriptor.get) return descriptor.get.call(receiver);
263
+ return descriptor.value;
264
+ }
265
+ function _class_extract_field_descriptor(receiver, privateMap, action) {
266
+ if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
267
+ return privateMap.get(receiver);
268
+ }
269
+ function _class_private_field_get(receiver, privateMap) {
270
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
271
+ return _class_apply_descriptor_get(receiver, descriptor);
272
+ }
273
+ function _class_private_field_init(obj, privateMap, value) {
274
+ _check_private_redeclaration(obj, privateMap);
275
+ privateMap.set(obj, value);
276
+ }
277
+ var _nodes = /*#__PURE__*/ new WeakMap();
278
+ class NodeRegistry {
279
+ register(node) {
280
+ defineNode(node);
281
+ if (_class_private_field_get(this, _nodes).has(node.name)) throw new DuplicateNodeError(node.name);
282
+ _class_private_field_get(this, _nodes).set(node.name, node);
283
+ return this;
284
+ }
285
+ get(name) {
286
+ return _class_private_field_get(this, _nodes).get(name);
287
+ }
288
+ require(name) {
289
+ const node = this.get(name);
290
+ if (!node) throw new NodeNotFoundError(name);
291
+ return node;
292
+ }
293
+ has(name) {
294
+ return _class_private_field_get(this, _nodes).has(name);
295
+ }
296
+ names() {
297
+ return [
298
+ ..._class_private_field_get(this, _nodes).keys()
299
+ ];
300
+ }
301
+ constructor(nodes = []){
302
+ _class_private_field_init(this, _nodes, {
303
+ writable: true,
304
+ value: new Map()
305
+ });
306
+ for (const node of nodes)this.register(node);
307
+ }
308
+ }
309
+ function validateNodeOutput(output, node) {
310
+ if (void 0 === output) return;
311
+ if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
312
+ 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.'));
313
+ return output;
314
+ }
315
+ function createStepResultBase(step, startedAt, phase, stepIndex) {
316
+ const endedAt = new Date();
317
+ return {
318
+ node: step.node,
319
+ phase,
320
+ stepIndex,
321
+ input: step.input,
322
+ meta: step.meta,
323
+ startedAt: startedAt.toISOString(),
324
+ endedAt: endedAt.toISOString(),
325
+ durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime())
326
+ };
327
+ }
328
+ async function executeNode(step, execute, phase, stepIndex) {
329
+ validateCommonNodeInput(step.input, stepIndex);
330
+ const startedAt = new Date();
331
+ const abortController = new AbortController();
332
+ const timeoutMs = step.meta.timeoutMs;
333
+ let timeout;
334
+ let timeoutError;
335
+ const execution = Promise.resolve().then(()=>execute(abortController.signal));
336
+ const timedExecution = void 0 === timeoutMs ? execution : Promise.race([
337
+ execution,
338
+ new Promise((_, reject)=>{
339
+ timeout = setTimeout(()=>{
340
+ timeoutError = new StepTimeoutError(timeoutMs, step.node);
341
+ abortController.abort(timeoutError);
342
+ reject(timeoutError);
343
+ }, timeoutMs);
344
+ })
345
+ ]);
346
+ try {
347
+ const output = validateNodeOutput(await timedExecution, step.node);
348
+ return {
349
+ ...createStepResultBase(step, startedAt, phase, stepIndex),
350
+ status: 'success',
351
+ continuedAfterError: false,
352
+ ...void 0 === output ? {} : {
353
+ output
354
+ }
355
+ };
356
+ } catch (error) {
357
+ return {
358
+ ...createStepResultBase(step, startedAt, phase, stepIndex),
359
+ status: 'failed',
360
+ continuedAfterError: step.meta.continueOnError,
361
+ error: normalizeNodeExecutionError(timeoutError ?? error, step.node)
362
+ };
363
+ } finally{
364
+ if (void 0 !== timeout) clearTimeout(timeout);
365
+ }
366
+ }
367
+ async function executeStep(step, node, target, context) {
368
+ const phase = 'case' === target.scope ? target.case.phase : target.document.phase;
369
+ const stepIndex = 'case' === target.scope ? target.case.stepIndex : target.document.stepIndex;
370
+ return executeNode(step, (signal)=>{
371
+ const common = {
372
+ input: step.input,
373
+ $: step.meta,
374
+ signal,
375
+ context
376
+ };
377
+ return 'case' === target.scope ? node.execute({
378
+ ...common,
379
+ scope: 'case',
380
+ case: target.case
381
+ }) : node.execute({
382
+ ...common,
383
+ scope: 'document',
384
+ document: target.document
385
+ });
386
+ }, phase, stepIndex);
387
+ }
388
+ async function runCollectedCase(collectedCase, options) {
389
+ const phases = {
390
+ beforeEach: options.beforeEach ?? [],
391
+ steps: collectedCase.definition.steps,
392
+ afterEach: options.afterEach ?? []
393
+ };
394
+ const nodes = {
395
+ beforeEach: phases.beforeEach.map((step)=>options.resolveNode(step.node)),
396
+ steps: phases.steps.map((step)=>options.resolveNode(step.node)),
397
+ afterEach: phases.afterEach.map((step)=>options.resolveNode(step.node))
398
+ };
399
+ const runId = options.createRunId?.() ?? randomUUID();
400
+ const startedAt = new Date();
401
+ const beforeEach = [];
402
+ const steps = [];
403
+ const afterEach = [];
404
+ const completedNodes = [];
405
+ const runPhase = async (phase, results)=>{
406
+ for (const [stepIndex, step] of phases[phase].entries()){
407
+ const caseContext = {
408
+ caseId: collectedCase.caseId,
409
+ runId,
410
+ name: collectedCase.definition.name,
411
+ sourcePath: collectedCase.sourcePath,
412
+ caseIndex: collectedCase.caseIndex,
413
+ phase,
414
+ stepIndex,
415
+ completedSteps: Object.freeze([
416
+ ...steps
417
+ ]),
418
+ completedNodes: Object.freeze([
419
+ ...completedNodes
420
+ ])
421
+ };
422
+ const stepInfo = {
423
+ scope: 'case',
424
+ node: step.node,
425
+ stepCount: phases[phase].length,
426
+ case: caseContext
427
+ };
428
+ await options.onStepStart?.(stepInfo);
429
+ const result = await executeStep(step, nodes[phase][stepIndex], {
430
+ scope: 'case',
431
+ case: caseContext
432
+ }, options.context);
433
+ await options.onStepResult?.(stepInfo, result);
434
+ results.push(result);
435
+ completedNodes.push(result);
436
+ if ('failed' === result.status && !result.continuedAfterError) break;
437
+ }
438
+ };
439
+ await runPhase('beforeEach', beforeEach);
440
+ if (!beforeEach.some((step)=>'failed' === step.status)) await runPhase('steps', steps);
441
+ await runPhase('afterEach', afterEach);
442
+ const endedAt = new Date();
443
+ const result = {
444
+ caseId: collectedCase.caseId,
445
+ runId,
446
+ name: collectedCase.definition.name,
447
+ sourcePath: collectedCase.sourcePath,
448
+ caseIndex: collectedCase.caseIndex,
449
+ status: completedNodes.some((step)=>'failed' === step.status) ? 'failed' : 'success',
450
+ beforeEach,
451
+ steps,
452
+ afterEach,
453
+ startedAt: startedAt.toISOString(),
454
+ endedAt: endedAt.toISOString(),
455
+ durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime())
456
+ };
457
+ await options.onResult?.(result);
458
+ return result;
459
+ }
460
+ function case_runner_define_property(obj, key, value) {
461
+ if (key in obj) Object.defineProperty(obj, key, {
462
+ value: value,
463
+ enumerable: true,
464
+ configurable: true,
465
+ writable: true
466
+ });
467
+ else obj[key] = value;
468
+ return obj;
469
+ }
470
+ class CaseRunner {
471
+ register(node) {
472
+ this.registry.register(node);
473
+ return this;
474
+ }
475
+ async run(input) {
476
+ const collectedCase = {
477
+ caseId: 'standalone-case',
478
+ projectId: 'standalone-project',
479
+ sourcePath: '',
480
+ caseIndex: 0,
481
+ definition: {
482
+ name: input.name ?? 'case',
483
+ steps: normalizeSteps(input.steps)
484
+ }
485
+ };
486
+ const result = await runCollectedCase(collectedCase, {
487
+ resolveNode: (name)=>this.registry.require(name),
488
+ context: this.context,
489
+ createRunId: ()=>'standalone-case'
490
+ });
491
+ const fatalStepError = result.steps.find((step)=>'failed' === step.status && !step.continuedAfterError)?.error;
492
+ if (fatalStepError) throw fatalStepError;
493
+ return result;
494
+ }
495
+ constructor(options = {}){
496
+ case_runner_define_property(this, "registry", void 0);
497
+ case_runner_define_property(this, "context", void 0);
498
+ this.registry = new NodeRegistry(options.nodes);
499
+ this.context = options.context;
500
+ }
501
+ }
502
+ function createCaseRunner(options = {}) {
503
+ return new CaseRunner(options);
504
+ }
505
+ function createDocumentRuntime(document, options) {
506
+ const documentRunId = options.createDocumentRunId?.() ?? randomUUID();
507
+ const startedAt = new Date();
508
+ const beforeAll = [];
509
+ const afterAll = [];
510
+ const completedNodes = [];
511
+ const nodes = {
512
+ beforeAll: document.lifecycle.beforeAll.map((step)=>options.resolveNode(step.node)),
513
+ afterAll: document.lifecycle.afterAll.map((step)=>options.resolveNode(step.node))
514
+ };
515
+ const info = {
516
+ documentId: document.documentId,
517
+ documentRunId,
518
+ projectId: document.projectId,
519
+ sourcePath: document.sourcePath,
520
+ cases: document.cases.map((caseDefinition)=>caseDefinition.definition),
521
+ lifecycle: document.lifecycle,
522
+ env: Object.freeze({
523
+ ...process.env
524
+ })
525
+ };
526
+ const teardownStack = [];
527
+ let acceptingTeardowns = void 0 !== options.setupDocument;
528
+ let context;
529
+ let setupError;
530
+ let started = false;
531
+ let finishedResult;
532
+ const createResult = (teardownErrors = [])=>{
533
+ const endedAt = new Date();
534
+ return {
535
+ documentId: document.documentId,
536
+ documentRunId,
537
+ projectId: document.projectId,
538
+ sourcePath: document.sourcePath,
539
+ status: setupError || completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
540
+ startedAt: startedAt.toISOString(),
541
+ endedAt: endedAt.toISOString(),
542
+ durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
543
+ beforeAll: [
544
+ ...beforeAll
545
+ ],
546
+ afterAll: [
547
+ ...afterAll
548
+ ],
549
+ ...setupError ? {
550
+ setupError
551
+ } : {},
552
+ ...teardownErrors.length > 0 ? {
553
+ teardownErrors
554
+ } : {}
555
+ };
556
+ };
557
+ const runPhase = async (phase, results)=>{
558
+ for (const [stepIndex, step] of document.lifecycle[phase].entries()){
559
+ const documentContext = {
560
+ documentId: document.documentId,
561
+ documentRunId,
562
+ projectId: document.projectId,
563
+ sourcePath: document.sourcePath,
564
+ phase,
565
+ stepIndex,
566
+ completedNodes: Object.freeze([
567
+ ...completedNodes
568
+ ])
569
+ };
570
+ const stepInfo = {
571
+ scope: 'document',
572
+ node: step.node,
573
+ stepCount: document.lifecycle[phase].length,
574
+ document: documentContext
575
+ };
576
+ await options.onStepStart?.(stepInfo);
577
+ const result = await executeStep(step, nodes[phase][stepIndex], {
578
+ scope: 'document',
579
+ document: documentContext
580
+ }, context);
581
+ await options.onStepResult?.(stepInfo, result);
582
+ results.push(result);
583
+ completedNodes.push(result);
584
+ if ('failed' === result.status && !result.continuedAfterError) break;
585
+ }
586
+ };
587
+ const onTeardown = (teardown)=>{
588
+ if (!acceptingTeardowns) throw new WorkflowLifecycleError('onTeardown() can only be called while setupDocument is running.', {
589
+ documentId: document.documentId,
590
+ documentRunId
591
+ });
592
+ if ('function' != typeof teardown) throw new WorkflowLifecycleError('onTeardown() requires a teardown function.', {
593
+ documentId: document.documentId,
594
+ documentRunId
595
+ });
596
+ teardownStack.push({
597
+ registrationIndex: teardownStack.length,
598
+ teardown
599
+ });
600
+ };
601
+ return {
602
+ get context () {
603
+ return context;
604
+ },
605
+ get canRunCases () {
606
+ return started && !setupError && !beforeAll.some((step)=>'failed' === step.status);
607
+ },
608
+ async start () {
609
+ if (started) throw new WorkflowLifecycleError('Workflow document runtime has already started.', {
610
+ documentId: document.documentId,
611
+ documentRunId
612
+ });
613
+ started = true;
614
+ if (options.setupDocument) try {
615
+ context = await options.setupDocument({
616
+ ...info,
617
+ onTeardown
618
+ });
619
+ } catch (error) {
620
+ setupError = new WorkflowDocumentSetupError(error, {
621
+ documentId: document.documentId,
622
+ documentRunId
623
+ });
624
+ } finally{
625
+ acceptingTeardowns = false;
626
+ }
627
+ if (!setupError) await runPhase('beforeAll', beforeAll);
628
+ return createResult();
629
+ },
630
+ async finish () {
631
+ if (finishedResult) return finishedResult;
632
+ if (!started) throw new WorkflowLifecycleError('Workflow document runtime must start before it can finish.', {
633
+ documentId: document.documentId,
634
+ documentRunId
635
+ });
636
+ if (!setupError) await runPhase('afterAll', afterAll);
637
+ const statusBeforeTeardown = createResult().status;
638
+ const teardownErrors = [];
639
+ for (const { registrationIndex, teardown } of teardownStack.reverse())try {
640
+ await teardown({
641
+ ...info,
642
+ beforeAll: Object.freeze([
643
+ ...beforeAll
644
+ ]),
645
+ afterAll: Object.freeze([
646
+ ...afterAll
647
+ ]),
648
+ status: statusBeforeTeardown,
649
+ ...setupError ? {
650
+ setupError
651
+ } : {}
652
+ });
653
+ } catch (error) {
654
+ teardownErrors.push(new WorkflowDocumentTeardownError(error, {
655
+ documentId: document.documentId,
656
+ documentRunId,
657
+ registrationIndex
658
+ }));
659
+ }
660
+ finishedResult = createResult(teardownErrors);
661
+ await options.onResult?.(finishedResult);
662
+ return finishedResult;
663
+ }
664
+ };
665
+ }
666
+ const asNotRun = (collectedCase, reason)=>({
667
+ caseId: collectedCase.caseId,
668
+ name: collectedCase.definition.name,
669
+ sourcePath: collectedCase.sourcePath,
670
+ caseIndex: collectedCase.caseIndex,
671
+ status: 'not-run',
672
+ notRunReason: reason
673
+ });
674
+ async function runWorkflowDocument(document, options) {
675
+ const createDocumentRunId = options.createDocumentRunId;
676
+ const createCaseRunId = options.createCaseRunId;
677
+ const runtime = createDocumentRuntime(document, {
678
+ resolveNode: options.resolveNode,
679
+ setupDocument: options.setupDocument,
680
+ onStepStart: options.onStepStart,
681
+ onStepResult: options.onStepResult,
682
+ onResult: options.onDocumentResult,
683
+ createDocumentRunId: createDocumentRunId ? ()=>createDocumentRunId(document) : void 0
684
+ });
685
+ const cases = [];
686
+ let runtimeStarted = false;
687
+ let documentResult;
688
+ let executionError;
689
+ try {
690
+ runtimeStarted = true;
691
+ await runtime.start();
692
+ if (runtime.canRunCases) for (const collectedCase of document.cases){
693
+ if (options.shouldStop?.()) {
694
+ cases.push(asNotRun(collectedCase, 'interrupted'));
695
+ continue;
696
+ }
697
+ await options.onCaseStart?.(collectedCase);
698
+ const run = await runCollectedCase(collectedCase, {
699
+ resolveNode: options.resolveNode,
700
+ beforeEach: document.lifecycle.beforeEach,
701
+ afterEach: document.lifecycle.afterEach,
702
+ context: runtime.context,
703
+ onStepStart: options.onStepStart,
704
+ onStepResult: options.onStepResult,
705
+ onResult: options.onCaseResult,
706
+ createRunId: createCaseRunId ? ()=>createCaseRunId(collectedCase) : void 0
707
+ });
708
+ cases.push({
709
+ caseId: collectedCase.caseId,
710
+ name: collectedCase.definition.name,
711
+ sourcePath: collectedCase.sourcePath,
712
+ caseIndex: collectedCase.caseIndex,
713
+ status: run.status,
714
+ run
715
+ });
716
+ }
717
+ else cases.push(...document.cases.map((collectedCase)=>asNotRun(collectedCase, 'document-start-failed')));
718
+ } catch (error) {
719
+ executionError = error;
720
+ } finally{
721
+ if (runtimeStarted) try {
722
+ documentResult = await runtime.finish();
723
+ } catch (error) {
724
+ executionError ??= error;
725
+ }
726
+ }
727
+ if (executionError) throw executionError;
728
+ if (!documentResult) throw new Error(`Workflow document "${document.sourcePath}" did not produce a result.`);
729
+ return {
730
+ document: documentResult,
731
+ cases
732
+ };
733
+ }
734
+ const collect_isMapping = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
735
+ const rejectUnknownKeys = (value, allowed, label)=>{
736
+ const allowedSet = new Set(allowed);
737
+ const unknown = Object.keys(value).filter((key)=>!allowedSet.has(key));
738
+ if (unknown.length) throw new WorkflowParseError(`${label} has unsupported field "${unknown[0]}".`, {
739
+ field: unknown[0]
740
+ });
741
+ };
742
+ const createCaseId = (projectId, sourcePath, caseIndex)=>createHash('sha256').update(JSON.stringify([
743
+ projectId,
744
+ sourcePath,
745
+ caseIndex
746
+ ])).digest('hex');
747
+ const createWorkflowDocumentId = (projectId, sourcePath)=>createHash('sha256').update(JSON.stringify([
748
+ projectId,
749
+ sourcePath
750
+ ])).digest('hex');
751
+ function collectWorkflowDocument(source, options) {
752
+ let parsed;
753
+ try {
754
+ parsed = load(readFileSync(source.absolutePath, 'utf8'), {
755
+ schema: JSON_SCHEMA
756
+ });
757
+ } catch (error) {
758
+ throw new WorkflowParseError(`Failed to parse workflow document "${source.sourcePath}".`, {
759
+ sourcePath: source.sourcePath
760
+ }, error);
761
+ }
762
+ if (!collect_isMapping(parsed)) throw new WorkflowParseError('Workflow document must be a mapping.');
763
+ rejectUnknownKeys(parsed, [
764
+ 'beforeAll',
765
+ 'beforeEach',
766
+ 'cases',
767
+ 'afterEach',
768
+ 'afterAll'
769
+ ], 'Workflow document');
770
+ if (!Array.isArray(parsed.cases) || 0 === parsed.cases.length) throw new WorkflowParseError('Workflow document must contain a non-empty cases array.');
771
+ const sourcePath = source.sourcePath.split(sep).join('/');
772
+ const normalizeLifecycle = (phase)=>{
773
+ const value = parsed[phase];
774
+ if (void 0 === value) return [];
775
+ if (!Array.isArray(value)) throw new WorkflowParseError(`Workflow document ${phase} must be an array.`, {
776
+ phase
777
+ });
778
+ return normalizeSteps(value).map((normalized, stepIndex)=>{
779
+ const resolved = options.resolveNode(normalized.node);
780
+ if (!resolved) throw new WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
781
+ phase,
782
+ stepIndex,
783
+ node: normalized.node
784
+ });
785
+ return normalized;
786
+ });
787
+ };
788
+ const lifecycle = {
789
+ beforeAll: normalizeLifecycle('beforeAll'),
790
+ beforeEach: normalizeLifecycle('beforeEach'),
791
+ afterEach: normalizeLifecycle('afterEach'),
792
+ afterAll: normalizeLifecycle('afterAll')
793
+ };
794
+ const ids = new Set();
795
+ const cases = parsed.cases.map((definition, caseIndex)=>{
796
+ if (!collect_isMapping(definition)) throw new WorkflowParseError(`Case ${caseIndex + 1} must be a mapping.`, {
797
+ caseIndex
798
+ });
799
+ rejectUnknownKeys(definition, [
800
+ 'name',
801
+ 'steps'
802
+ ], `Case ${caseIndex + 1}`);
803
+ if ('string' != typeof definition.name || 0 === definition.name.trim().length) throw new WorkflowParseError(`Case ${caseIndex + 1} name must be a non-empty string.`, {
804
+ caseIndex
805
+ });
806
+ if (!Array.isArray(definition.steps) || 0 === definition.steps.length) throw new WorkflowParseError(`Case ${caseIndex + 1} steps must be a non-empty array.`, {
807
+ caseIndex
808
+ });
809
+ const steps = normalizeSteps(definition.steps).map((normalized, stepIndex)=>{
810
+ if (!options.resolveNode(normalized.node)) throw new WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
811
+ caseIndex,
812
+ stepIndex,
813
+ node: normalized.node
814
+ });
815
+ return normalized;
816
+ });
817
+ const caseId = createCaseId(source.projectId, sourcePath, caseIndex);
818
+ if (ids.has(caseId)) throw new WorkflowParseError(`Case id collision: ${caseId}.`, {
819
+ caseId
820
+ });
821
+ ids.add(caseId);
822
+ return {
823
+ caseId,
824
+ projectId: source.projectId,
825
+ sourcePath,
826
+ caseIndex,
827
+ definition: {
828
+ name: definition.name,
829
+ steps
830
+ }
831
+ };
832
+ });
833
+ return {
834
+ documentId: createWorkflowDocumentId(source.projectId, sourcePath),
835
+ projectId: source.projectId,
836
+ sourcePath,
837
+ lifecycle,
838
+ cases
839
+ };
840
+ }
841
+ export { CaseExecutionError, CaseRunner, DuplicateNodeError, NodeDefinitionError, NodeExecutionError, NodeInputValidationError, NodeNotFoundError, NodeRegistry, StepTimeoutError, WorkflowDocumentExecutionError, WorkflowDocumentSetupError, WorkflowDocumentTeardownError, WorkflowError, WorkflowLifecycleError, WorkflowParseError, collectWorkflowDocument, createCaseId, createCaseRunner, createDocumentRuntime, createWorkflowDocumentId, defineNode, normalizeNodeExecutionError, normalizeStep, normalizeSteps, runWorkflowDocument, validateCommonNodeInput };
842
+
843
+ //# sourceMappingURL=index.mjs.map