@midscene/core 1.15.2-beta-20261012123219.0 → 1.15.2
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/agent/action-readiness.mjs +6 -3
- package/dist/es/agent/action-readiness.mjs.map +1 -1
- package/dist/es/agent/agent.mjs +4 -2
- package/dist/es/agent/agent.mjs.map +1 -1
- package/dist/es/agent/execution-session.mjs.map +1 -1
- package/dist/es/agent/task-builder.mjs +35 -16
- package/dist/es/agent/task-builder.mjs.map +1 -1
- package/dist/es/agent/tasks.mjs +35 -12
- package/dist/es/agent/tasks.mjs.map +1 -1
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/device/index.mjs +7 -6
- package/dist/es/device/index.mjs.map +1 -1
- package/dist/es/report-generator.mjs +30 -14
- package/dist/es/report-generator.mjs.map +1 -1
- package/dist/es/report-html-template.mjs +1 -1
- package/dist/es/report.mjs +121 -44
- package/dist/es/report.mjs.map +1 -1
- package/dist/es/service/index.mjs.map +1 -1
- package/dist/es/task-runner.mjs +22 -12
- package/dist/es/task-runner.mjs.map +1 -1
- package/dist/es/test-run-report.mjs.map +1 -1
- package/dist/es/test-runner/engine/case-runner.mjs +1 -1
- package/dist/es/test-runner/engine/case-runner.mjs.map +1 -1
- package/dist/es/test-runner/engine/document-runtime.mjs +42 -12
- package/dist/es/test-runner/engine/document-runtime.mjs.map +1 -1
- package/dist/es/test-runner/engine/execute-step.mjs +130 -12
- package/dist/es/test-runner/engine/execute-step.mjs.map +1 -1
- package/dist/es/test-runner/engine/index.mjs +1 -0
- package/dist/es/test-runner/engine/project-runtime.mjs +14 -5
- package/dist/es/test-runner/engine/project-runtime.mjs.map +1 -1
- package/dist/es/test-runner/engine/resource-operations.mjs +286 -35
- package/dist/es/test-runner/engine/resource-operations.mjs.map +1 -1
- package/dist/es/test-runner/engine/result-metadata.mjs +21 -0
- package/dist/es/test-runner/engine/result-metadata.mjs.map +1 -0
- package/dist/es/test-runner/engine/run-collected-case.mjs +30 -9
- package/dist/es/test-runner/engine/run-collected-case.mjs.map +1 -1
- package/dist/es/test-runner/engine/run-workflow-document.mjs +57 -10
- package/dist/es/test-runner/engine/run-workflow-document.mjs.map +1 -1
- package/dist/es/test-runner/errors.mjs +26 -13
- package/dist/es/test-runner/errors.mjs.map +1 -1
- package/dist/es/test-runner/execution-record.mjs +2 -0
- package/dist/es/test-runner/execution-record.mjs.map +1 -1
- package/dist/es/test-runner/index.mjs +3 -0
- package/dist/es/test-runner/reporting/agent-report.mjs +2 -7
- package/dist/es/test-runner/reporting/agent-report.mjs.map +1 -1
- package/dist/es/test-runner/reporting/merge-report.mjs +207 -6
- package/dist/es/test-runner/reporting/merge-report.mjs.map +1 -1
- package/dist/es/test-runner/reporting/metrics.mjs +77 -0
- package/dist/es/test-runner/reporting/metrics.mjs.map +1 -0
- package/dist/es/test-runner/reporting/sanitize.mjs +92 -16
- package/dist/es/test-runner/reporting/sanitize.mjs.map +1 -1
- package/dist/es/test-runner/reporting/test-plan.mjs +56 -0
- package/dist/es/test-runner/reporting/test-plan.mjs.map +1 -0
- package/dist/es/test-runner/reporting/test-run-report.mjs +208 -21
- package/dist/es/test-runner/reporting/test-run-report.mjs.map +1 -1
- package/dist/es/types.mjs.map +1 -1
- package/dist/es/utils.mjs +1 -1
- package/dist/es/yaml/legacy-yaml-runtime.mjs +3 -3
- package/dist/es/yaml/legacy-yaml-runtime.mjs.map +1 -1
- package/dist/lib/agent/action-readiness.js +6 -3
- package/dist/lib/agent/action-readiness.js.map +1 -1
- package/dist/lib/agent/agent.js +4 -2
- package/dist/lib/agent/agent.js.map +1 -1
- package/dist/lib/agent/execution-session.js.map +1 -1
- package/dist/lib/agent/task-builder.js +42 -23
- package/dist/lib/agent/task-builder.js.map +1 -1
- package/dist/lib/agent/tasks.js +35 -12
- package/dist/lib/agent/tasks.js.map +1 -1
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/device/index.js +7 -6
- package/dist/lib/device/index.js.map +1 -1
- package/dist/lib/report-generator.js +30 -14
- package/dist/lib/report-generator.js.map +1 -1
- package/dist/lib/report-html-template.js +1 -1
- package/dist/lib/report.js +121 -44
- package/dist/lib/report.js.map +1 -1
- package/dist/lib/service/index.js.map +1 -1
- package/dist/lib/task-runner.js +22 -12
- package/dist/lib/task-runner.js.map +1 -1
- package/dist/lib/test-run-report.js.map +1 -1
- package/dist/lib/test-runner/engine/case-runner.js +1 -1
- package/dist/lib/test-runner/engine/case-runner.js.map +1 -1
- package/dist/lib/test-runner/engine/document-runtime.js +41 -11
- package/dist/lib/test-runner/engine/document-runtime.js.map +1 -1
- package/dist/lib/test-runner/engine/execute-step.js +131 -10
- package/dist/lib/test-runner/engine/execute-step.js.map +1 -1
- package/dist/lib/test-runner/engine/index.js +9 -2
- package/dist/lib/test-runner/engine/index.js.map +1 -1
- package/dist/lib/test-runner/engine/project-runtime.js +13 -4
- package/dist/lib/test-runner/engine/project-runtime.js.map +1 -1
- package/dist/lib/test-runner/engine/resource-operations.js +319 -38
- package/dist/lib/test-runner/engine/resource-operations.js.map +1 -1
- package/dist/lib/test-runner/engine/result-metadata.js +58 -0
- package/dist/lib/test-runner/engine/result-metadata.js.map +1 -0
- package/dist/lib/test-runner/engine/run-collected-case.js +29 -8
- package/dist/lib/test-runner/engine/run-collected-case.js.map +1 -1
- package/dist/lib/test-runner/engine/run-workflow-document.js +57 -10
- package/dist/lib/test-runner/engine/run-workflow-document.js.map +1 -1
- package/dist/lib/test-runner/errors.js +42 -23
- package/dist/lib/test-runner/errors.js.map +1 -1
- package/dist/lib/test-runner/execution-record.js +2 -0
- package/dist/lib/test-runner/execution-record.js.map +1 -1
- package/dist/lib/test-runner/index.js +88 -16
- package/dist/lib/test-runner/index.js.map +1 -1
- package/dist/lib/test-runner/node/index.js +2 -2
- package/dist/lib/test-runner/reporting/agent-report.js +2 -7
- package/dist/lib/test-runner/reporting/agent-report.js.map +1 -1
- package/dist/lib/test-runner/reporting/merge-report.js +207 -6
- package/dist/lib/test-runner/reporting/merge-report.js.map +1 -1
- package/dist/lib/test-runner/reporting/metrics.js +114 -0
- package/dist/lib/test-runner/reporting/metrics.js.map +1 -0
- package/dist/lib/test-runner/reporting/sanitize.js +94 -15
- package/dist/lib/test-runner/reporting/sanitize.js.map +1 -1
- package/dist/lib/test-runner/reporting/test-plan.js +90 -0
- package/dist/lib/test-runner/reporting/test-plan.js.map +1 -0
- package/dist/lib/test-runner/reporting/test-run-report.js +208 -21
- package/dist/lib/test-runner/reporting/test-run-report.js.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/lib/yaml/legacy-yaml-runtime.js +2 -2
- package/dist/lib/yaml/legacy-yaml-runtime.js.map +1 -1
- package/dist/types/agent/action-readiness.d.ts +1 -1
- package/dist/types/agent/action-readiness.d.ts.map +1 -1
- package/dist/types/agent/agent.d.ts +1 -1
- package/dist/types/agent/agent.d.ts.map +1 -1
- package/dist/types/agent/execution-session.d.ts +1 -1
- package/dist/types/agent/execution-session.d.ts.map +1 -1
- package/dist/types/agent/task-builder.d.ts +4 -2
- package/dist/types/agent/task-builder.d.ts.map +1 -1
- package/dist/types/agent/tasks.d.ts +3 -0
- package/dist/types/agent/tasks.d.ts.map +1 -1
- package/dist/types/device/index.d.ts +3 -3
- package/dist/types/device/index.d.ts.map +1 -1
- package/dist/types/report-generator.d.ts +4 -2
- package/dist/types/report-generator.d.ts.map +1 -1
- package/dist/types/report.d.ts.map +1 -1
- package/dist/types/service/index.d.ts +2 -2
- package/dist/types/service/index.d.ts.map +1 -1
- package/dist/types/task-runner.d.ts +1 -1
- package/dist/types/task-runner.d.ts.map +1 -1
- package/dist/types/test-run-report.d.ts +157 -8
- package/dist/types/test-run-report.d.ts.map +1 -1
- package/dist/types/test-runner/engine/document-runtime.d.ts.map +1 -1
- package/dist/types/test-runner/engine/execute-step.d.ts +1 -0
- package/dist/types/test-runner/engine/execute-step.d.ts.map +1 -1
- package/dist/types/test-runner/engine/index.d.ts +1 -0
- package/dist/types/test-runner/engine/index.d.ts.map +1 -1
- package/dist/types/test-runner/engine/project-runtime.d.ts.map +1 -1
- package/dist/types/test-runner/engine/resource-operations.d.ts +41 -3
- package/dist/types/test-runner/engine/resource-operations.d.ts.map +1 -1
- package/dist/types/test-runner/engine/result-metadata.d.ts +18 -0
- package/dist/types/test-runner/engine/result-metadata.d.ts.map +1 -0
- package/dist/types/test-runner/engine/run-collected-case.d.ts.map +1 -1
- package/dist/types/test-runner/engine/run-workflow-document.d.ts.map +1 -1
- package/dist/types/test-runner/engine/types.d.ts +47 -8
- package/dist/types/test-runner/engine/types.d.ts.map +1 -1
- package/dist/types/test-runner/errors.d.ts +7 -0
- package/dist/types/test-runner/errors.d.ts.map +1 -1
- package/dist/types/test-runner/execution-record.d.ts.map +1 -1
- package/dist/types/test-runner/index.d.ts +2 -0
- package/dist/types/test-runner/index.d.ts.map +1 -1
- package/dist/types/test-runner/node/types.d.ts +9 -1
- package/dist/types/test-runner/node/types.d.ts.map +1 -1
- package/dist/types/test-runner/parser/types.d.ts +5 -0
- package/dist/types/test-runner/parser/types.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/agent-report.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/merge-report.d.ts +1 -1
- package/dist/types/test-runner/reporting/merge-report.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/metrics.d.ts +7 -0
- package/dist/types/test-runner/reporting/metrics.d.ts.map +1 -0
- package/dist/types/test-runner/reporting/sanitize.d.ts +1 -0
- package/dist/types/test-runner/reporting/sanitize.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/test-plan.d.ts +8 -0
- package/dist/types/test-runner/reporting/test-plan.d.ts.map +1 -0
- package/dist/types/test-runner/reporting/test-run-report.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/types.d.ts +6 -1
- package/dist/types/test-runner/reporting/types.d.ts.map +1 -1
- package/dist/types/types.d.ts +3 -1
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-runner/engine/document-runtime.mjs","sources":["../../../../src/test-runner/engine/document-runtime.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport { uuid as randomUUID } from '@midscene/shared/utils';\nimport {\n NodeScopeTeardownError,\n type WorkflowError,\n WorkflowLifecycleError,\n} from '../errors';\nimport type { CollectedWorkflowDocument } from '../parser/types';\nimport { executeStep } from './execute-step';\nimport {\n WorkflowExecutionFailure,\n asExecutionError,\n} from './execution-failure';\nimport {\n ResourceCleanupDeferredError,\n cleanupScopeResources,\n createResourceScope,\n recoverCancelledScopeResources,\n} from './resource-operations';\nimport { reportPathsFromTeardown } from './scope-teardown';\nimport type {\n CreateDocumentRuntimeOptions,\n DocumentNodePhase,\n NodeDocumentContext,\n NodeScopeTeardown,\n StepRunResult,\n WorkflowDocumentRunResult,\n WorkflowDocumentRuntime,\n WorkflowHostError,\n} from './types';\n\nexport function createDocumentRuntime<TContext = undefined>(\n document: CollectedWorkflowDocument,\n options: CreateDocumentRuntimeOptions<TContext>,\n): WorkflowDocumentRuntime<TContext> {\n const documentRunId = options.createDocumentRunId?.() ?? randomUUID();\n const startedAt = new Date();\n const beforeAll: StepRunResult[] = [];\n const afterAll: StepRunResult[] = [];\n const nodes = {\n beforeAll: document.lifecycle.beforeAll.map((step) =>\n options.resolveNode(step.node),\n ),\n afterAll: document.lifecycle.afterAll.map((step) =>\n options.resolveNode(step.node),\n ),\n };\n const project =\n options.project ??\n Object.freeze({\n projectId: document.projectId,\n name: document.projectId,\n retry: 0,\n });\n let context = options.projectContext as TContext;\n const resourceScope = createResourceScope(options.signal);\n const signal = resourceScope.signal;\n const nodeTeardownStack: Array<{\n registrationIndex: number;\n node: string;\n teardown: NodeScopeTeardown;\n host?: boolean;\n }> = [];\n let acceptingNodeTeardowns = true;\n let started = false;\n let finishedResult: WorkflowDocumentRunResult | undefined;\n let finishPromise: Promise<WorkflowDocumentRunResult> | undefined;\n const reportPaths = new Set<string>();\n const reportSources: import('./types').WorkflowReportSource[] = [];\n const executionErrors: unknown[] = [];\n const hostErrors: WorkflowHostError[] = [];\n let acceptingSetupTeardowns = false;\n\n const createResult = (\n teardownErrors: WorkflowError[] = [],\n ): WorkflowDocumentRunResult => {\n const endedAt = new Date();\n return {\n documentId: document.documentId,\n documentRunId,\n ...(options.documentAttemptIndex === undefined\n ? {}\n : { attemptIndex: options.documentAttemptIndex }),\n projectId: document.projectId,\n projectName: project.name,\n sourcePath: document.sourcePath,\n status:\n executionErrors.length > 0 ||\n hostErrors.length > 0 ||\n beforeAll.some((step) => step.status === 'failed') ||\n afterAll.some((step) => step.status === 'failed') ||\n teardownErrors.length > 0\n ? 'failed'\n : 'success',\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),\n beforeAll: [...beforeAll],\n afterAll: [...afterAll],\n ...(executionErrors.length\n ? { executionErrors: executionErrors.map(asExecutionError) }\n : {}),\n ...(hostErrors.length ? { hostErrors: [...hostErrors] } : {}),\n ...(teardownErrors.length > 0\n ? { teardownErrors: [...teardownErrors] }\n : {}),\n ...(reportPaths.size > 0 ? { reportPaths: [...reportPaths] } : {}),\n ...(reportSources.length ? { reportSources: [...reportSources] } : {}),\n };\n };\n\n const runPhase = async (\n phase: DocumentNodePhase,\n results: StepRunResult[],\n executionSignal = signal,\n ): Promise<void> => {\n for (const [stepIndex, step] of document.lifecycle[phase].entries()) {\n if (stepIndex > 0) await recoverCancelledScopeResources(executionSignal);\n const documentContext: NodeDocumentContext = {\n documentId: document.documentId,\n documentRunId,\n projectId: document.projectId,\n projectName: project.name,\n sourcePath: document.sourcePath,\n phase,\n stepIndex,\n };\n const stepInfo = {\n scope: 'document' as const,\n node: step.node,\n stepCount: document.lifecycle[phase].length,\n document: documentContext,\n };\n await options.onStepStart?.(stepInfo);\n const result = await executeStep(\n step,\n nodes[phase][stepIndex],\n { scope: 'document', document: documentContext },\n context,\n {\n signal: executionSignal,\n defaultTimeoutMs: options.defaultTimeoutMs,\n onTeardown(node, teardown) {\n if (!acceptingNodeTeardowns) {\n throw new WorkflowLifecycleError(\n 'Node teardown can only be registered while a workflow document is running.',\n { documentId: document.documentId, documentRunId, node },\n );\n }\n if (typeof teardown !== 'function') {\n throw new WorkflowLifecycleError(\n 'Node onTeardown() requires a teardown function.',\n { documentId: document.documentId, documentRunId, node },\n );\n }\n nodeTeardownStack.push({\n registrationIndex: nodeTeardownStack.length,\n node,\n teardown,\n });\n },\n },\n );\n results.push(result);\n await options.onStepResult?.(stepInfo, result);\n if (result.status === 'failed' && !result.continuedAfterError) break;\n }\n };\n\n return {\n signal,\n get context() {\n return context;\n },\n get canRunCases() {\n return (\n started &&\n executionErrors.length === 0 &&\n hostErrors.length === 0 &&\n !signal.aborted &&\n !beforeAll.some((step) => step.status === 'failed')\n );\n },\n async start() {\n if (started) {\n throw new WorkflowLifecycleError(\n 'Workflow document runtime has already started.',\n { documentId: document.documentId, documentRunId },\n );\n }\n started = true;\n if (!signal.aborted && options.documentSetup) {\n acceptingSetupTeardowns = true;\n try {\n context = await options.documentSetup.setup({\n project,\n projectContext: options.projectContext,\n document: {\n documentId: document.documentId,\n documentRunId,\n sourcePath: document.sourcePath,\n attemptIndex: options.documentAttemptIndex ?? 0,\n },\n signal,\n onTeardown(teardown) {\n if (!acceptingSetupTeardowns || typeof teardown !== 'function')\n throw new WorkflowLifecycleError(\n 'Document setup onTeardown() requires a function registered during setup.',\n { documentId: document.documentId, documentRunId },\n );\n nodeTeardownStack.push({\n registrationIndex: nodeTeardownStack.length,\n node: options.documentSetup!.name,\n teardown,\n host: true,\n });\n },\n });\n } catch (error) {\n hostErrors.push({ phase: 'setup', error: asExecutionError(error) });\n } finally {\n acceptingSetupTeardowns = false;\n }\n }\n try {\n if (!signal.aborted && !hostErrors.length)\n await runPhase('beforeAll', beforeAll);\n } catch (error) {\n executionErrors.push(error);\n throw new WorkflowExecutionFailure(createResult(), [\n ...executionErrors,\n ]);\n }\n return createResult();\n },\n async finish() {\n if (finishPromise) return finishPromise;\n if (!started) {\n throw new WorkflowLifecycleError(\n 'Workflow document runtime must start before it can finish.',\n { documentId: document.documentId, documentRunId },\n );\n }\n finishPromise = (async () => {\n const teardownErrors: WorkflowError[] = [];\n const cleanupExecutionErrors: unknown[] = [];\n let deferredCleanup = false;\n const cleanup = async () => {\n const priorTeardownErrors = teardownErrors.length;\n acceptingNodeTeardowns = false;\n for (const {\n registrationIndex,\n node,\n teardown,\n host,\n } of nodeTeardownStack.reverse()) {\n try {\n const released = await teardown();\n reportSources.push(...(released?.reportSources ?? []));\n for (const path of reportPathsFromTeardown(released)) {\n reportPaths.add(path);\n }\n } catch (error) {\n if (host) {\n hostErrors.push({\n phase: 'cleanup',\n error: asExecutionError(error),\n });\n executionErrors.push(error);\n cleanupExecutionErrors.push(error);\n continue;\n }\n teardownErrors.push(\n new NodeScopeTeardownError(error, {\n scope: 'document',\n scopeId: documentRunId,\n node,\n registrationIndex,\n }),\n );\n }\n }\n if (deferredCleanup) {\n const errors = [\n ...cleanupExecutionErrors,\n ...teardownErrors.slice(priorTeardownErrors),\n ];\n if (errors.length)\n throw new AggregateError(\n errors,\n 'Deferred document cleanup failed',\n );\n }\n };\n try {\n if (document.lifecycle.afterAll.length || nodeTeardownStack.length)\n await cleanupScopeResources(signal, cleanup, {\n beforeCleanup: async (cleanupSignal) => {\n try {\n if (!hostErrors.some((error) => error.phase === 'setup'))\n await runPhase('afterAll', afterAll, cleanupSignal);\n } catch (error) {\n executionErrors.push(error);\n cleanupExecutionErrors.push(error);\n }\n },\n onDeferredError: (error) =>\n getDebug('runner:cleanup', { console: true })(\n `Deferred document cleanup failed: ${String(error)}`,\n ),\n });\n else acceptingNodeTeardowns = false;\n } catch (error) {\n deferredCleanup = error instanceof ResourceCleanupDeferredError;\n if (!deferredCleanup) acceptingNodeTeardowns = false;\n teardownErrors.push(\n new NodeScopeTeardownError(error, {\n scope: 'document',\n scopeId: documentRunId,\n node: 'resource-drain',\n registrationIndex: -1,\n }),\n );\n }\n finishedResult = createResult(teardownErrors);\n try {\n await options.onResult?.(finishedResult);\n } catch (error) {\n executionErrors.push(error);\n finishedResult = createResult(teardownErrors);\n }\n resourceScope.dispose();\n if (executionErrors.length)\n throw new WorkflowExecutionFailure(finishedResult, [\n ...executionErrors,\n ]);\n return finishedResult;\n })();\n return finishPromise;\n },\n };\n}\n"],"names":["createDocumentRuntime","document","options","documentRunId","randomUUID","startedAt","Date","beforeAll","afterAll","nodes","step","project","Object","context","resourceScope","createResourceScope","signal","nodeTeardownStack","acceptingNodeTeardowns","started","finishedResult","finishPromise","reportPaths","Set","reportSources","executionErrors","hostErrors","acceptingSetupTeardowns","createResult","teardownErrors","endedAt","undefined","Math","asExecutionError","runPhase","phase","results","executionSignal","stepIndex","recoverCancelledScopeResources","documentContext","stepInfo","result","executeStep","node","teardown","WorkflowLifecycleError","error","WorkflowExecutionFailure","cleanupExecutionErrors","deferredCleanup","cleanup","priorTeardownErrors","registrationIndex","host","released","path","reportPathsFromTeardown","NodeScopeTeardownError","errors","AggregateError","cleanupScopeResources","cleanupSignal","getDebug","String","ResourceCleanupDeferredError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"test-runner/engine/document-runtime.mjs","sources":["../../../../src/test-runner/engine/document-runtime.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport { uuid as randomUUID } from '@midscene/shared/utils';\nimport {\n NodeScopeTeardownError,\n type WorkflowError,\n WorkflowLifecycleError,\n} from '../errors';\nimport type { CollectedWorkflowDocument } from '../parser/types';\nimport { buildTestRunReportPlan } from '../reporting/test-plan';\nimport { executeStep } from './execute-step';\nimport {\n WorkflowExecutionFailure,\n asExecutionError,\n} from './execution-failure';\nimport {\n ResourceCleanupDeferredError,\n cleanupScopeResources,\n createResourceScope,\n getPendingScopeOperations,\n getResourceCleanupSnapshot,\n recoverCancelledScopeResources,\n setResourceOperationIdentity,\n} from './resource-operations';\nimport { phaseResult } from './result-metadata';\nimport { reportPathsFromTeardown } from './scope-teardown';\nimport type {\n CreateDocumentRuntimeOptions,\n DocumentNodePhase,\n NodeDocumentContext,\n NodeScopeTeardown,\n StepRunResult,\n WorkflowDocumentRunResult,\n WorkflowDocumentRuntime,\n WorkflowHostError,\n} from './types';\n\nexport function createDocumentRuntime<TContext = undefined>(\n document: CollectedWorkflowDocument,\n options: CreateDocumentRuntimeOptions<TContext>,\n): WorkflowDocumentRuntime<TContext> {\n const documentRunId = options.createDocumentRunId?.() ?? randomUUID();\n const plan =\n options.plan ??\n buildTestRunReportPlan(document, {\n defaultTimeoutMs: options.defaultTimeoutMs,\n retry: { scope: 'case', count: options.project?.retry ?? 0 },\n });\n const startedAt = new Date();\n const beforeAll: StepRunResult[] = [];\n const afterAll: StepRunResult[] = [];\n const nodes = {\n beforeAll: document.lifecycle.beforeAll.map((step) =>\n options.resolveNode(step.node),\n ),\n afterAll: document.lifecycle.afterAll.map((step) =>\n options.resolveNode(step.node),\n ),\n };\n const project =\n options.project ??\n Object.freeze({\n projectId: document.projectId,\n name: document.projectId,\n retry: 0,\n });\n let context = options.projectContext as TContext;\n const resourceScope = createResourceScope(options.signal);\n const signal = resourceScope.signal;\n setResourceOperationIdentity(signal, { scopeId: documentRunId });\n const nodeTeardownStack: Array<{\n registrationIndex: number;\n node: string;\n teardown: NodeScopeTeardown;\n host?: boolean;\n }> = [];\n let acceptingNodeTeardowns = true;\n let started = false;\n let finishedResult: WorkflowDocumentRunResult | undefined;\n let finishPromise: Promise<WorkflowDocumentRunResult> | undefined;\n const reportPaths = new Set<string>();\n const reportSources: import('./types').WorkflowReportSource[] = [];\n const capturedSources = new Map<\n string,\n import('./types').WorkflowReportSource\n >();\n const executionErrors: unknown[] = [];\n const hostErrors: WorkflowHostError[] = [];\n let acceptingSetupTeardowns = false;\n\n const createResult = (\n teardownErrors: WorkflowError[] = [],\n ): WorkflowDocumentRunResult => {\n const endedAt = new Date();\n const cleanup = getResourceCleanupSnapshot(signal);\n const pendingOperations = getPendingScopeOperations(signal);\n const phaseResults = {\n beforeAll: phaseResult(document.lifecycle.beforeAll.length, beforeAll),\n afterAll: phaseResult(document.lifecycle.afterAll.length, afterAll),\n };\n return {\n plan,\n documentId: document.documentId,\n documentRunId,\n ...(options.documentAttemptIndex === undefined\n ? {}\n : { attemptIndex: options.documentAttemptIndex }),\n projectId: document.projectId,\n projectName: project.name,\n sourcePath: document.sourcePath,\n status:\n executionErrors.length > 0 ||\n hostErrors.length > 0 ||\n beforeAll.some((step) => step.status !== 'success') ||\n afterAll.some((step) => step.status !== 'success') ||\n cleanup.status === 'pending' ||\n cleanup.status === 'failed' ||\n pendingOperations.length > 0 ||\n teardownErrors.length > 0\n ? 'failed'\n : 'success',\n executionStatus: Object.values(phaseResults).some(\n (phase) => phase.status === 'failed',\n )\n ? 'failed'\n : Object.values(phaseResults).some(\n (phase) => phase.status === 'not-run',\n ) ||\n hostErrors.some(({ phase }) => phase === 'setup') ||\n (signal.aborted && !beforeAll.length && !afterAll.length)\n ? 'not-run'\n : 'success',\n phases: phaseResults,\n cleanup,\n ...(pendingOperations.length ? { pendingOperations } : {}),\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),\n beforeAll: [...beforeAll],\n afterAll: [...afterAll],\n ...(executionErrors.length\n ? {\n executionErrors: executionErrors\n .filter(\n (error) => !hostErrors.some((host) => host.error === error),\n )\n .map(asExecutionError),\n }\n : {}),\n ...(hostErrors.length ? { hostErrors: [...hostErrors] } : {}),\n ...(teardownErrors.length > 0\n ? { teardownErrors: [...teardownErrors] }\n : {}),\n ...(reportPaths.size > 0 ? { reportPaths: [...reportPaths] } : {}),\n ...(capturedSources.size || reportSources.length\n ? { reportSources: [...capturedSources.values(), ...reportSources] }\n : {}),\n };\n };\n\n const runPhase = async (\n phase: DocumentNodePhase,\n results: StepRunResult[],\n executionSignal = signal,\n ): Promise<void> => {\n for (const [stepIndex, step] of document.lifecycle[phase].entries()) {\n const waitForPreviousOperationMs =\n stepIndex > 0\n ? await recoverCancelledScopeResources(executionSignal)\n : 0;\n const documentContext: NodeDocumentContext = {\n documentId: document.documentId,\n documentRunId,\n projectId: document.projectId,\n projectName: project.name,\n sourcePath: document.sourcePath,\n phase,\n stepIndex,\n };\n const stepInfo = {\n scope: 'document' as const,\n node: step.node,\n stepCount: document.lifecycle[phase].length,\n document: documentContext,\n };\n await options.onStepStart?.(stepInfo);\n const result = await executeStep(\n step,\n nodes[phase][stepIndex],\n { scope: 'document', document: documentContext },\n context,\n {\n signal: executionSignal,\n defaultTimeoutMs: options.defaultTimeoutMs,\n onTeardown(node, teardown) {\n if (!acceptingNodeTeardowns) {\n throw new WorkflowLifecycleError(\n 'Node teardown can only be registered while a workflow document is running.',\n { documentId: document.documentId, documentRunId, node },\n );\n }\n if (typeof teardown !== 'function') {\n throw new WorkflowLifecycleError(\n 'Node onTeardown() requires a teardown function.',\n { documentId: document.documentId, documentRunId, node },\n );\n }\n nodeTeardownStack.push({\n registrationIndex: nodeTeardownStack.length,\n node,\n teardown,\n });\n },\n },\n );\n if (waitForPreviousOperationMs > 0)\n result.waitForPreviousOperationMs = waitForPreviousOperationMs;\n results.push(result);\n for (const source of result.report?.sources ?? [])\n capturedSources.set(\n `${source.sourcePath}\\u0000${source.dumpPath ?? ''}`,\n source,\n );\n await options.onStepResult?.(stepInfo, result);\n if (result.status !== 'success' && !result.continuedAfterError) break;\n }\n };\n\n return {\n signal,\n get context() {\n return context;\n },\n get canRunCases() {\n return (\n started &&\n executionErrors.length === 0 &&\n hostErrors.length === 0 &&\n !signal.aborted &&\n !beforeAll.some((step) => step.status !== 'success')\n );\n },\n async start() {\n if (started) {\n throw new WorkflowLifecycleError(\n 'Workflow document runtime has already started.',\n { documentId: document.documentId, documentRunId },\n );\n }\n started = true;\n if (!signal.aborted && options.documentSetup) {\n acceptingSetupTeardowns = true;\n try {\n context = await options.documentSetup.setup({\n project,\n projectContext: options.projectContext,\n document: {\n documentId: document.documentId,\n documentRunId,\n sourcePath: document.sourcePath,\n attemptIndex: options.documentAttemptIndex ?? 0,\n },\n signal,\n onTeardown(teardown) {\n if (!acceptingSetupTeardowns || typeof teardown !== 'function')\n throw new WorkflowLifecycleError(\n 'Document setup onTeardown() requires a function registered during setup.',\n { documentId: document.documentId, documentRunId },\n );\n nodeTeardownStack.push({\n registrationIndex: nodeTeardownStack.length,\n node: options.documentSetup!.name,\n teardown,\n host: true,\n });\n },\n });\n } catch (error) {\n hostErrors.push({ phase: 'setup', error: asExecutionError(error) });\n } finally {\n acceptingSetupTeardowns = false;\n }\n }\n try {\n if (!signal.aborted && !hostErrors.length)\n await runPhase('beforeAll', beforeAll);\n } catch (error) {\n executionErrors.push(error);\n throw new WorkflowExecutionFailure(createResult(), [\n ...executionErrors,\n ]);\n }\n return createResult();\n },\n async finish() {\n if (finishPromise) return finishPromise;\n if (!started) {\n throw new WorkflowLifecycleError(\n 'Workflow document runtime must start before it can finish.',\n { documentId: document.documentId, documentRunId },\n );\n }\n finishPromise = (async () => {\n const teardownErrors: WorkflowError[] = [];\n const cleanupExecutionErrors: unknown[] = [];\n let deferredCleanup = false;\n const cleanup = async () => {\n const priorTeardownErrors = teardownErrors.length;\n acceptingNodeTeardowns = false;\n for (const {\n registrationIndex,\n node,\n teardown,\n host,\n } of nodeTeardownStack.reverse()) {\n try {\n const released = await teardown();\n reportSources.push(...(released?.reportSources ?? []));\n for (const path of reportPathsFromTeardown(released)) {\n reportPaths.add(path);\n }\n } catch (error) {\n if (host) {\n const failure = asExecutionError(error);\n hostErrors.push({\n phase: 'cleanup',\n error: failure,\n });\n executionErrors.push(failure);\n cleanupExecutionErrors.push(error);\n continue;\n }\n teardownErrors.push(\n new NodeScopeTeardownError(error, {\n scope: 'document',\n scopeId: documentRunId,\n node,\n registrationIndex,\n }),\n );\n }\n }\n {\n const errors = [\n ...cleanupExecutionErrors,\n ...teardownErrors.slice(priorTeardownErrors),\n ];\n if (errors.length)\n throw new AggregateError(errors, 'Document cleanup failed');\n }\n };\n try {\n if (document.lifecycle.afterAll.length || nodeTeardownStack.length)\n await cleanupScopeResources(signal, cleanup, {\n beforeCleanup: async (cleanupSignal) => {\n try {\n if (!hostErrors.some((error) => error.phase === 'setup'))\n await runPhase('afterAll', afterAll, cleanupSignal);\n } catch (error) {\n executionErrors.push(error);\n cleanupExecutionErrors.push(error);\n }\n },\n onDeferredError: (error) =>\n getDebug('runner:cleanup', { console: true })(\n `Deferred document cleanup failed: ${String(error)}`,\n ),\n });\n else acceptingNodeTeardowns = false;\n } catch (error) {\n deferredCleanup = error instanceof ResourceCleanupDeferredError;\n if (!deferredCleanup) acceptingNodeTeardowns = false;\n if (\n !deferredCleanup &&\n !teardownErrors.length &&\n !cleanupExecutionErrors.length\n )\n teardownErrors.push(\n new NodeScopeTeardownError(error, {\n scope: 'document',\n scopeId: documentRunId,\n node: 'resource-drain',\n registrationIndex: -1,\n }),\n );\n }\n finishedResult = createResult(teardownErrors);\n try {\n await options.onResult?.(finishedResult);\n } catch (error) {\n executionErrors.push(error);\n finishedResult = createResult(teardownErrors);\n }\n resourceScope.dispose();\n if (executionErrors.length)\n throw new WorkflowExecutionFailure(finishedResult, [\n ...executionErrors,\n ]);\n return finishedResult;\n })();\n return finishPromise;\n },\n };\n}\n"],"names":["createDocumentRuntime","document","options","documentRunId","randomUUID","plan","buildTestRunReportPlan","startedAt","Date","beforeAll","afterAll","nodes","step","project","Object","context","resourceScope","createResourceScope","signal","setResourceOperationIdentity","nodeTeardownStack","acceptingNodeTeardowns","started","finishedResult","finishPromise","reportPaths","Set","reportSources","capturedSources","Map","executionErrors","hostErrors","acceptingSetupTeardowns","createResult","teardownErrors","endedAt","cleanup","getResourceCleanupSnapshot","pendingOperations","getPendingScopeOperations","phaseResults","phaseResult","undefined","phase","Math","error","host","asExecutionError","runPhase","results","executionSignal","stepIndex","waitForPreviousOperationMs","recoverCancelledScopeResources","documentContext","stepInfo","result","executeStep","node","teardown","WorkflowLifecycleError","source","WorkflowExecutionFailure","cleanupExecutionErrors","deferredCleanup","priorTeardownErrors","registrationIndex","released","path","reportPathsFromTeardown","failure","NodeScopeTeardownError","errors","AggregateError","cleanupScopeResources","cleanupSignal","getDebug","String","ResourceCleanupDeferredError"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { NodeExecutionError, NodeInputValidationError, StepTimeoutError, normalizeNodeExecutionError } from "../errors.mjs";
|
|
2
|
-
import { linkResourceSignal } from "./resource-operations.mjs";
|
|
1
|
+
import { NodeExecutionError, NodeInputValidationError, StepTimeoutError, WorkflowError, normalizeNodeExecutionError } from "../errors.mjs";
|
|
2
|
+
import { isResourceAdmissionError, linkResourceSignal, observeResourceOperations, recordOperation, setResourceOperationIdentity, trackResourceOperation } from "./resource-operations.mjs";
|
|
3
|
+
const REPORT_SNAPSHOT_TIMEOUT_MS = 5000;
|
|
3
4
|
function validateNodeOutput(output, node) {
|
|
4
5
|
if (void 0 === output) return;
|
|
5
6
|
if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
|
|
@@ -24,6 +25,8 @@ async function executeNode(step, execute, phase, stepIndex, options) {
|
|
|
24
25
|
const abortController = new AbortController();
|
|
25
26
|
const parentSignal = options.parentSignal;
|
|
26
27
|
linkResourceSignal(abortController.signal, parentSignal);
|
|
28
|
+
setResourceOperationIdentity(abortController.signal, options.identity);
|
|
29
|
+
const stopObserving = observeResourceOperations(abortController.signal, options.onEvent);
|
|
27
30
|
const abortFromParent = ()=>abortController.abort(parentSignal?.reason);
|
|
28
31
|
if (parentSignal?.aborted) abortFromParent();
|
|
29
32
|
else parentSignal?.addEventListener('abort', abortFromParent, {
|
|
@@ -36,6 +39,9 @@ async function executeNode(step, execute, phase, stepIndex, options) {
|
|
|
36
39
|
abortController.signal.throwIfAborted();
|
|
37
40
|
return execute(abortController.signal);
|
|
38
41
|
});
|
|
42
|
+
const observation = recordOperation(execution, abortController.signal, 'node');
|
|
43
|
+
trackResourceOperation({}, execution, abortController.signal, 'node', observation);
|
|
44
|
+
let returnedStatus = 'failed';
|
|
39
45
|
let settleOnAbort;
|
|
40
46
|
const abortedExecution = new Promise((_, reject)=>{
|
|
41
47
|
settleOnAbort = ()=>{
|
|
@@ -65,6 +71,7 @@ async function executeNode(step, execute, phase, stepIndex, options) {
|
|
|
65
71
|
]);
|
|
66
72
|
try {
|
|
67
73
|
const output = validateNodeOutput(await settledExecution, step.node);
|
|
74
|
+
returnedStatus = 'success';
|
|
68
75
|
return {
|
|
69
76
|
...createStepResultBase(step, startedAt, phase, stepIndex),
|
|
70
77
|
status: 'success',
|
|
@@ -74,6 +81,23 @@ async function executeNode(step, execute, phase, stepIndex, options) {
|
|
|
74
81
|
}
|
|
75
82
|
};
|
|
76
83
|
} catch (error) {
|
|
84
|
+
if (isResourceAdmissionError(error)) {
|
|
85
|
+
returnedStatus = 'not-run';
|
|
86
|
+
return {
|
|
87
|
+
node: step.node,
|
|
88
|
+
phase,
|
|
89
|
+
stepIndex,
|
|
90
|
+
input: step.input,
|
|
91
|
+
meta: step.meta,
|
|
92
|
+
status: 'not-run',
|
|
93
|
+
notRunReason: error.message,
|
|
94
|
+
...error.blockedBy ? {
|
|
95
|
+
blockedBy: error.blockedBy
|
|
96
|
+
} : {},
|
|
97
|
+
continuedAfterError: step.meta.continueOnError,
|
|
98
|
+
error
|
|
99
|
+
};
|
|
100
|
+
}
|
|
77
101
|
let output;
|
|
78
102
|
let failure = error;
|
|
79
103
|
if (error instanceof NodeExecutionError && void 0 !== error.output) try {
|
|
@@ -91,6 +115,8 @@ async function executeNode(step, execute, phase, stepIndex, options) {
|
|
|
91
115
|
}
|
|
92
116
|
};
|
|
93
117
|
} finally{
|
|
118
|
+
observation.runnerReturned(returnedStatus);
|
|
119
|
+
stopObserving();
|
|
94
120
|
if (void 0 !== timeout) clearTimeout(timeout);
|
|
95
121
|
if (void 0 !== abortGraceTimeout) clearTimeout(abortGraceTimeout);
|
|
96
122
|
if (void 0 !== settleOnAbort) abortController.signal.removeEventListener('abort', settleOnAbort);
|
|
@@ -99,25 +125,102 @@ async function executeNode(step, execute, phase, stepIndex, options) {
|
|
|
99
125
|
}
|
|
100
126
|
function createNodeReportCollector(node) {
|
|
101
127
|
const traces = new Map();
|
|
128
|
+
const captures = [];
|
|
102
129
|
let accepting = true;
|
|
103
130
|
return {
|
|
104
131
|
collector: Object.freeze({
|
|
132
|
+
onSnapshot (capture) {
|
|
133
|
+
if (!accepting || 'function' != typeof capture) throw new WorkflowError('A report snapshot callback must be registered while the Step is running.', {
|
|
134
|
+
code: 'REPORT_SNAPSHOT_REGISTRATION'
|
|
135
|
+
});
|
|
136
|
+
captures.push(capture);
|
|
137
|
+
},
|
|
105
138
|
addTrace (trace) {
|
|
106
139
|
if (!accepting) throw new NodeExecutionError(node, new Error('Report traces can only be added while the current Step is running.'));
|
|
107
140
|
if (!trace || 'object' != typeof trace || 'midscene-execution' !== trace.type) throw new NodeExecutionError(node, new TypeError('Report trace type must be "midscene-execution".'));
|
|
108
141
|
if ('string' != typeof trace.executionId || 0 === trace.executionId.trim().length) throw new NodeExecutionError(node, new TypeError('Report trace executionId must be non-empty.'));
|
|
109
142
|
const normalized = Object.freeze({
|
|
110
143
|
type: 'midscene-execution',
|
|
111
|
-
executionId: trace.executionId
|
|
144
|
+
executionId: trace.executionId,
|
|
145
|
+
...trace.metrics ? {
|
|
146
|
+
metrics: structuredClone(trace.metrics)
|
|
147
|
+
} : {}
|
|
112
148
|
});
|
|
113
149
|
traces.set(`${normalized.type}:${normalized.executionId}`, normalized);
|
|
114
150
|
}
|
|
115
151
|
}),
|
|
116
|
-
close () {
|
|
152
|
+
async close () {
|
|
117
153
|
accepting = false;
|
|
118
|
-
|
|
154
|
+
const closedTraces = Object.freeze([
|
|
119
155
|
...traces.values()
|
|
120
156
|
]);
|
|
157
|
+
if (!captures.length) return Object.freeze({
|
|
158
|
+
traces: closedTraces
|
|
159
|
+
});
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
let timer;
|
|
162
|
+
const expired = new Promise((_, reject)=>{
|
|
163
|
+
timer = setTimeout(()=>{
|
|
164
|
+
const error = new WorkflowError('Agent report snapshot collection timed out.', {
|
|
165
|
+
code: 'REPORT_SNAPSHOT_TIMEOUT',
|
|
166
|
+
details: {
|
|
167
|
+
timeoutMs: REPORT_SNAPSHOT_TIMEOUT_MS
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
controller.abort(error);
|
|
171
|
+
reject(error);
|
|
172
|
+
}, REPORT_SNAPSHOT_TIMEOUT_MS);
|
|
173
|
+
});
|
|
174
|
+
const outcomes = await Promise.all(captures.map(async (capture)=>{
|
|
175
|
+
try {
|
|
176
|
+
const source = await Promise.race([
|
|
177
|
+
Promise.resolve().then(()=>capture(controller.signal, closedTraces)),
|
|
178
|
+
expired
|
|
179
|
+
]);
|
|
180
|
+
if (!source || 'string' != typeof source.sourcePath || !source.sourcePath.trim() || void 0 !== source.dumpPath && ('string' != typeof source.dumpPath || !source.dumpPath.trim())) throw new Error('Agent report capture must return sourcePath and a valid optional dumpPath.');
|
|
181
|
+
const captured = source.dumpPath ? source : {
|
|
182
|
+
...source,
|
|
183
|
+
collection: {
|
|
184
|
+
status: 'partial',
|
|
185
|
+
capturedAt: source.collection?.capturedAt ?? new Date().toISOString(),
|
|
186
|
+
reason: source.collection?.reason ?? 'HTML report captures do not guarantee an immutable cutoff.'
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
return {
|
|
190
|
+
source: Object.freeze({
|
|
191
|
+
...captured,
|
|
192
|
+
...captured.collection ? {
|
|
193
|
+
collection: Object.freeze({
|
|
194
|
+
...captured.collection
|
|
195
|
+
})
|
|
196
|
+
} : {}
|
|
197
|
+
})
|
|
198
|
+
};
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return {
|
|
201
|
+
error: error instanceof WorkflowError ? error : new WorkflowError(`Agent report snapshot collection failed: ${String(error)}`, {
|
|
202
|
+
code: 'REPORT_SNAPSHOT_FAILED',
|
|
203
|
+
cause: error
|
|
204
|
+
})
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}));
|
|
208
|
+
if (void 0 !== timer) clearTimeout(timer);
|
|
209
|
+
const sources = outcomes.flatMap((outcome)=>outcome.source ? [
|
|
210
|
+
outcome.source
|
|
211
|
+
] : []);
|
|
212
|
+
const errors = outcomes.flatMap((outcome)=>outcome.error ? [
|
|
213
|
+
outcome.error
|
|
214
|
+
] : []);
|
|
215
|
+
return Object.freeze({
|
|
216
|
+
traces: closedTraces,
|
|
217
|
+
...sources.length ? {
|
|
218
|
+
sources: Object.freeze(sources)
|
|
219
|
+
} : {},
|
|
220
|
+
...errors.length ? {
|
|
221
|
+
collectionErrors: Object.freeze(errors)
|
|
222
|
+
} : {}
|
|
223
|
+
});
|
|
121
224
|
}
|
|
122
225
|
};
|
|
123
226
|
}
|
|
@@ -131,6 +234,8 @@ async function executeStep(step, node, target, context, execution = {}) {
|
|
|
131
234
|
const phase = 'case' === target.scope ? target.case.phase : target.document.phase;
|
|
132
235
|
const stepIndex = 'case' === target.scope ? target.case.stepIndex : target.document.stepIndex;
|
|
133
236
|
const reportCollector = createNodeReportCollector(step.node);
|
|
237
|
+
const events = [];
|
|
238
|
+
const scopeId = 'case' === target.scope ? target.case.runId : target.document.documentRunId;
|
|
134
239
|
let result;
|
|
135
240
|
try {
|
|
136
241
|
result = await executeNode(step, async (signal)=>{
|
|
@@ -158,20 +263,33 @@ async function executeStep(step, node, target, context, execution = {}) {
|
|
|
158
263
|
});
|
|
159
264
|
}, phase, stepIndex, {
|
|
160
265
|
parentSignal: execution.signal,
|
|
161
|
-
defaultTimeoutMs: execution.defaultTimeoutMs
|
|
266
|
+
defaultTimeoutMs: execution.defaultTimeoutMs,
|
|
267
|
+
identity: {
|
|
268
|
+
scopeId,
|
|
269
|
+
stepId: `${scopeId}:${phase}:${stepIndex}`,
|
|
270
|
+
node: step.node
|
|
271
|
+
},
|
|
272
|
+
onEvent: (event)=>events.push(event)
|
|
162
273
|
});
|
|
163
274
|
} finally{
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
275
|
+
const report = await reportCollector.close();
|
|
276
|
+
if (result) result = {
|
|
166
277
|
...result,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
278
|
+
operations: Object.freeze({
|
|
279
|
+
capturedAt: new Date().toISOString(),
|
|
280
|
+
events: Object.freeze([
|
|
281
|
+
...events
|
|
282
|
+
])
|
|
283
|
+
})
|
|
284
|
+
};
|
|
285
|
+
if ((report.traces.length || report.sources?.length || report.collectionErrors?.length) && result) result = {
|
|
286
|
+
...result,
|
|
287
|
+
report
|
|
170
288
|
};
|
|
171
289
|
}
|
|
172
290
|
if (!result) throw new NodeExecutionError(step.node, new Error('Step execution completed without a result.'));
|
|
173
291
|
return result;
|
|
174
292
|
}
|
|
175
|
-
export { executeStep };
|
|
293
|
+
export { REPORT_SNAPSHOT_TIMEOUT_MS, executeStep };
|
|
176
294
|
|
|
177
295
|
//# sourceMappingURL=execute-step.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-runner/engine/execute-step.mjs","sources":["../../../../src/test-runner/engine/execute-step.ts"],"sourcesContent":["import {\n NodeExecutionError,\n NodeInputValidationError,\n StepTimeoutError,\n normalizeNodeExecutionError,\n} from '../errors';\nimport type {\n NodeDefinition,\n NodeReportCollector,\n NodeReportTrace,\n NodeResult,\n} from '../node/types';\nimport type { NormalizedStep } from '../parser/types';\nimport { linkResourceSignal } from './resource-operations';\nimport type {\n NodeCaseContext,\n NodeDocumentContext,\n NodeExecutionPhase,\n NodeScopeTeardown,\n StepRunResult,\n} from './types';\n\nfunction validateNodeOutput<TData>(\n output: unknown,\n node: string,\n): NodeResult<TData> | undefined {\n if (output === undefined) {\n return undefined;\n }\n\n if (typeof output !== 'object' || output === null || Array.isArray(output)) {\n throw new NodeExecutionError(\n node,\n new TypeError('Node output must be an object or undefined.'),\n );\n }\n\n if (\n 'summary' in output &&\n output.summary !== undefined &&\n typeof output.summary !== 'string'\n ) {\n throw new NodeExecutionError(\n node,\n new TypeError('Node output summary must be a string.'),\n );\n }\n\n // Node data is an API value. JSON serialization belongs to the publisher;\n // serialization failure must not turn completed actions into retryable work.\n\n return output as NodeResult<TData>;\n}\n\nfunction createStepResultBase(\n step: NormalizedStep,\n startedAt: Date,\n phase: NodeExecutionPhase,\n stepIndex: number,\n) {\n const endedAt = new Date();\n return {\n node: step.node,\n phase,\n stepIndex,\n input: step.input,\n meta: step.meta,\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),\n };\n}\n\nasync function executeNode<TOutputData>(\n step: NormalizedStep,\n execute: (signal: AbortSignal) => unknown,\n phase: NodeExecutionPhase,\n stepIndex: number,\n options: { parentSignal?: AbortSignal; defaultTimeoutMs?: number },\n): Promise<StepRunResult<TOutputData>> {\n const startedAt = new Date();\n const abortController = new AbortController();\n const parentSignal = options.parentSignal;\n linkResourceSignal(abortController.signal, parentSignal);\n const abortFromParent = () => abortController.abort(parentSignal?.reason);\n if (parentSignal?.aborted) abortFromParent();\n else parentSignal?.addEventListener('abort', abortFromParent, { once: true });\n const timeoutMs = step.meta.timeoutMs ?? options.defaultTimeoutMs;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortGraceTimeout: ReturnType<typeof setTimeout> | undefined;\n const execution = Promise.resolve().then(() => {\n abortController.signal.throwIfAborted();\n return execute(abortController.signal);\n });\n let settleOnAbort: (() => void) | undefined;\n const abortedExecution = new Promise<never>((_, reject) => {\n settleOnAbort = () => {\n // Abort listeners run synchronously. Wait one task before rejecting so a\n // cooperative node can settle in response to the forwarded abort first.\n abortGraceTimeout = setTimeout(() => {\n reject(abortController.signal.reason);\n }, 0);\n };\n if (abortController.signal.aborted) settleOnAbort();\n else\n abortController.signal.addEventListener('abort', settleOnAbort, {\n once: true,\n });\n });\n const timeoutExecution =\n timeoutMs === undefined\n ? undefined\n : new Promise<never>((_, reject) => {\n timeout = setTimeout(() => {\n if (abortController.signal.aborted) {\n reject(abortController.signal.reason);\n return;\n }\n const timeoutError = new StepTimeoutError(timeoutMs, step.node);\n abortController.abort(timeoutError);\n reject(timeoutError);\n }, timeoutMs);\n });\n const settledExecution = Promise.race([\n execution,\n abortedExecution,\n ...(timeoutExecution === undefined ? [] : [timeoutExecution]),\n ]);\n\n try {\n const output = validateNodeOutput<TOutputData>(\n await settledExecution,\n step.node,\n );\n return {\n ...createStepResultBase(step, startedAt, phase, stepIndex),\n status: 'success',\n continuedAfterError: false,\n ...(output === undefined ? {} : { output }),\n };\n } catch (error) {\n let output: NodeResult<TOutputData> | undefined;\n let failure = error;\n if (error instanceof NodeExecutionError && error.output !== undefined) {\n try {\n output = validateNodeOutput<TOutputData>(error.output, step.node);\n } catch (validationError) {\n failure = validationError;\n }\n }\n return {\n ...createStepResultBase(step, startedAt, phase, stepIndex),\n status: 'failed',\n continuedAfterError: step.meta.continueOnError,\n error: normalizeNodeExecutionError(failure, step.node),\n ...(output === undefined ? {} : { output }),\n };\n } finally {\n if (timeout !== undefined) clearTimeout(timeout);\n if (abortGraceTimeout !== undefined) clearTimeout(abortGraceTimeout);\n if (settleOnAbort !== undefined) {\n abortController.signal.removeEventListener('abort', settleOnAbort);\n }\n parentSignal?.removeEventListener('abort', abortFromParent);\n }\n}\n\ntype StepExecutionTarget =\n | { scope: 'case'; case: NodeCaseContext }\n | { scope: 'document'; document: NodeDocumentContext };\n\nfunction createNodeReportCollector(node: string): {\n collector: NodeReportCollector;\n close(): readonly NodeReportTrace[];\n} {\n const traces = new Map<string, NodeReportTrace>();\n let accepting = true;\n\n return {\n collector: Object.freeze({\n addTrace(trace: NodeReportTrace): void {\n if (!accepting) {\n throw new NodeExecutionError(\n node,\n new Error(\n 'Report traces can only be added while the current Step is running.',\n ),\n );\n }\n if (\n !trace ||\n typeof trace !== 'object' ||\n (trace as { type?: unknown }).type !== 'midscene-execution'\n ) {\n throw new NodeExecutionError(\n node,\n new TypeError('Report trace type must be \"midscene-execution\".'),\n );\n }\n if (\n typeof trace.executionId !== 'string' ||\n trace.executionId.trim().length === 0\n ) {\n throw new NodeExecutionError(\n node,\n new TypeError('Report trace executionId must be non-empty.'),\n );\n }\n const normalized = Object.freeze({\n type: 'midscene-execution' as const,\n executionId: trace.executionId,\n });\n traces.set(`${normalized.type}:${normalized.executionId}`, normalized);\n },\n }),\n close(): readonly NodeReportTrace[] {\n accepting = false;\n return Object.freeze([...traces.values()]);\n },\n };\n}\n\nasync function parseNodeInput<TInput, TData, TContext>(\n node: NodeDefinition<TInput, TData, TContext>,\n input: Record<string, unknown>,\n): Promise<TInput> {\n if (!node.inputSchema) {\n return input as TInput;\n }\n\n const parsed = await node.inputSchema.safeParseAsync(input);\n if (!parsed.success) {\n throw NodeInputValidationError.fromZod(node.name, parsed.error);\n }\n return parsed.data as TInput;\n}\n\nexport async function executeStep<\n TInput = unknown,\n TOutputData = unknown,\n TContext = unknown,\n>(\n step: NormalizedStep,\n node: NodeDefinition<TInput, TOutputData, TContext>,\n target: StepExecutionTarget,\n context: TContext,\n execution: {\n signal?: AbortSignal;\n defaultTimeoutMs?: number;\n onTeardown?(node: string, teardown: NodeScopeTeardown): void;\n } = {},\n): Promise<StepRunResult<TOutputData>> {\n const phase =\n target.scope === 'case' ? target.case.phase : target.document.phase;\n const stepIndex =\n target.scope === 'case' ? target.case.stepIndex : target.document.stepIndex;\n\n const reportCollector = createNodeReportCollector(step.node);\n let result: StepRunResult<TOutputData> | undefined;\n\n try {\n result = await executeNode<TOutputData>(\n step,\n async (signal) => {\n const input = await parseNodeInput(node, step.input);\n signal.throwIfAborted();\n const common = {\n input,\n $: step.meta,\n signal,\n context,\n report: reportCollector.collector,\n onTeardown: (teardown: NodeScopeTeardown) => {\n if (!execution.onTeardown) {\n throw new NodeExecutionError(\n step.node,\n new Error(\n 'The current execution scope cannot register teardown.',\n ),\n );\n }\n execution.onTeardown(step.node, teardown);\n },\n };\n return target.scope === 'case'\n ? node.execute({ ...common, scope: 'case', case: target.case })\n : node.execute({\n ...common,\n scope: 'document',\n document: target.document,\n });\n },\n phase,\n stepIndex,\n {\n parentSignal: execution.signal,\n defaultTimeoutMs: execution.defaultTimeoutMs,\n },\n );\n } finally {\n const traces = reportCollector.close();\n if (traces.length > 0 && result) {\n result = { ...result, report: { traces } };\n }\n }\n\n if (!result) {\n throw new NodeExecutionError(\n step.node,\n new Error('Step execution completed without a result.'),\n );\n }\n return result;\n}\n"],"names":["validateNodeOutput","output","node","undefined","Array","NodeExecutionError","TypeError","createStepResultBase","step","startedAt","phase","stepIndex","endedAt","Date","Math","executeNode","execute","options","abortController","AbortController","parentSignal","linkResourceSignal","abortFromParent","timeoutMs","timeout","abortGraceTimeout","execution","Promise","settleOnAbort","abortedExecution","_","reject","setTimeout","timeoutExecution","timeoutError","StepTimeoutError","settledExecution","error","failure","validationError","normalizeNodeExecutionError","clearTimeout","createNodeReportCollector","traces","Map","accepting","Object","trace","Error","normalized","parseNodeInput","input","parsed","NodeInputValidationError","executeStep","target","context","reportCollector","result","signal","common","teardown"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"test-runner/engine/execute-step.mjs","sources":["../../../../src/test-runner/engine/execute-step.ts"],"sourcesContent":["import type { TestRunReportOperationEvent } from '../../test-run-report';\nimport {\n NodeExecutionError,\n NodeInputValidationError,\n StepTimeoutError,\n WorkflowError,\n normalizeNodeExecutionError,\n} from '../errors';\nimport type {\n NodeDefinition,\n NodeReportCollector,\n NodeReportTrace,\n NodeResult,\n} from '../node/types';\nimport type { NormalizedStep } from '../parser/types';\nimport {\n isResourceAdmissionError,\n linkResourceSignal,\n observeResourceOperations,\n recordOperation,\n setResourceOperationIdentity,\n trackResourceOperation,\n} from './resource-operations';\nimport type {\n NodeCaseContext,\n NodeDocumentContext,\n NodeExecutionPhase,\n NodeScopeTeardown,\n StepRunResult,\n WorkflowReportSource,\n} from './types';\n\nexport const REPORT_SNAPSHOT_TIMEOUT_MS = 5000;\n\nfunction validateNodeOutput<TData>(\n output: unknown,\n node: string,\n): NodeResult<TData> | undefined {\n if (output === undefined) {\n return undefined;\n }\n\n if (typeof output !== 'object' || output === null || Array.isArray(output)) {\n throw new NodeExecutionError(\n node,\n new TypeError('Node output must be an object or undefined.'),\n );\n }\n\n if (\n 'summary' in output &&\n output.summary !== undefined &&\n typeof output.summary !== 'string'\n ) {\n throw new NodeExecutionError(\n node,\n new TypeError('Node output summary must be a string.'),\n );\n }\n\n // Node data is an API value. JSON serialization belongs to the publisher;\n // serialization failure must not turn completed actions into retryable work.\n\n return output as NodeResult<TData>;\n}\n\nfunction createStepResultBase(\n step: NormalizedStep,\n startedAt: Date,\n phase: NodeExecutionPhase,\n stepIndex: number,\n) {\n const endedAt = new Date();\n return {\n node: step.node,\n phase,\n stepIndex,\n input: step.input,\n meta: step.meta,\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),\n };\n}\n\nasync function executeNode<TOutputData>(\n step: NormalizedStep,\n execute: (signal: AbortSignal) => unknown,\n phase: NodeExecutionPhase,\n stepIndex: number,\n options: {\n parentSignal?: AbortSignal;\n defaultTimeoutMs?: number;\n identity: { scopeId: string; stepId: string; node: string };\n onEvent(event: TestRunReportOperationEvent): void;\n },\n): Promise<StepRunResult<TOutputData>> {\n const startedAt = new Date();\n const abortController = new AbortController();\n const parentSignal = options.parentSignal;\n linkResourceSignal(abortController.signal, parentSignal);\n setResourceOperationIdentity(abortController.signal, options.identity);\n const stopObserving = observeResourceOperations(\n abortController.signal,\n options.onEvent,\n );\n const abortFromParent = () => abortController.abort(parentSignal?.reason);\n if (parentSignal?.aborted) abortFromParent();\n else parentSignal?.addEventListener('abort', abortFromParent, { once: true });\n const timeoutMs = step.meta.timeoutMs ?? options.defaultTimeoutMs;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortGraceTimeout: ReturnType<typeof setTimeout> | undefined;\n const execution = Promise.resolve().then(() => {\n abortController.signal.throwIfAborted();\n return execute(abortController.signal);\n });\n const observation = recordOperation(\n execution,\n abortController.signal,\n 'node',\n );\n // A custom node's teardown cannot dispose what its actual execution still uses.\n trackResourceOperation(\n {},\n execution,\n abortController.signal,\n 'node',\n observation,\n );\n let returnedStatus: 'success' | 'failed' | 'not-run' = 'failed';\n let settleOnAbort: (() => void) | undefined;\n const abortedExecution = new Promise<never>((_, reject) => {\n settleOnAbort = () => {\n // Abort listeners run synchronously. Wait one task before rejecting so a\n // cooperative node can settle in response to the forwarded abort first.\n abortGraceTimeout = setTimeout(() => {\n reject(abortController.signal.reason);\n }, 0);\n };\n if (abortController.signal.aborted) settleOnAbort();\n else\n abortController.signal.addEventListener('abort', settleOnAbort, {\n once: true,\n });\n });\n const timeoutExecution =\n timeoutMs === undefined\n ? undefined\n : new Promise<never>((_, reject) => {\n timeout = setTimeout(() => {\n if (abortController.signal.aborted) {\n reject(abortController.signal.reason);\n return;\n }\n const timeoutError = new StepTimeoutError(timeoutMs, step.node);\n abortController.abort(timeoutError);\n reject(timeoutError);\n }, timeoutMs);\n });\n const settledExecution = Promise.race([\n execution,\n abortedExecution,\n ...(timeoutExecution === undefined ? [] : [timeoutExecution]),\n ]);\n\n try {\n const output = validateNodeOutput<TOutputData>(\n await settledExecution,\n step.node,\n );\n returnedStatus = 'success';\n return {\n ...createStepResultBase(step, startedAt, phase, stepIndex),\n status: 'success',\n continuedAfterError: false,\n ...(output === undefined ? {} : { output }),\n };\n } catch (error) {\n if (isResourceAdmissionError(error)) {\n returnedStatus = 'not-run';\n return {\n node: step.node,\n phase,\n stepIndex,\n input: step.input,\n meta: step.meta,\n status: 'not-run',\n notRunReason: error.message,\n ...(error.blockedBy ? { blockedBy: error.blockedBy } : {}),\n continuedAfterError: step.meta.continueOnError,\n error,\n };\n }\n let output: NodeResult<TOutputData> | undefined;\n let failure = error;\n if (error instanceof NodeExecutionError && error.output !== undefined) {\n try {\n output = validateNodeOutput<TOutputData>(error.output, step.node);\n } catch (validationError) {\n failure = validationError;\n }\n }\n return {\n ...createStepResultBase(step, startedAt, phase, stepIndex),\n status: 'failed',\n continuedAfterError: step.meta.continueOnError,\n error: normalizeNodeExecutionError(failure, step.node),\n ...(output === undefined ? {} : { output }),\n };\n } finally {\n observation.runnerReturned(returnedStatus);\n stopObserving();\n if (timeout !== undefined) clearTimeout(timeout);\n if (abortGraceTimeout !== undefined) clearTimeout(abortGraceTimeout);\n if (settleOnAbort !== undefined) {\n abortController.signal.removeEventListener('abort', settleOnAbort);\n }\n parentSignal?.removeEventListener('abort', abortFromParent);\n }\n}\n\ntype StepExecutionTarget =\n | { scope: 'case'; case: NodeCaseContext }\n | { scope: 'document'; document: NodeDocumentContext };\n\nfunction createNodeReportCollector(node: string): {\n collector: NodeReportCollector;\n close(): Promise<NonNullable<StepRunResult['report']>>;\n} {\n const traces = new Map<string, NodeReportTrace>();\n const captures: Array<Parameters<NodeReportCollector['onSnapshot']>[0]> = [];\n let accepting = true;\n\n return {\n collector: Object.freeze({\n onSnapshot(capture: Parameters<NodeReportCollector['onSnapshot']>[0]) {\n if (!accepting || typeof capture !== 'function')\n throw new WorkflowError(\n 'A report snapshot callback must be registered while the Step is running.',\n { code: 'REPORT_SNAPSHOT_REGISTRATION' },\n );\n captures.push(capture);\n },\n addTrace(trace: NodeReportTrace): void {\n if (!accepting) {\n throw new NodeExecutionError(\n node,\n new Error(\n 'Report traces can only be added while the current Step is running.',\n ),\n );\n }\n if (\n !trace ||\n typeof trace !== 'object' ||\n (trace as { type?: unknown }).type !== 'midscene-execution'\n ) {\n throw new NodeExecutionError(\n node,\n new TypeError('Report trace type must be \"midscene-execution\".'),\n );\n }\n if (\n typeof trace.executionId !== 'string' ||\n trace.executionId.trim().length === 0\n ) {\n throw new NodeExecutionError(\n node,\n new TypeError('Report trace executionId must be non-empty.'),\n );\n }\n const normalized = Object.freeze({\n type: 'midscene-execution' as const,\n executionId: trace.executionId,\n ...(trace.metrics ? { metrics: structuredClone(trace.metrics) } : {}),\n });\n traces.set(`${normalized.type}:${normalized.executionId}`, normalized);\n },\n }),\n async close(): Promise<NonNullable<StepRunResult['report']>> {\n accepting = false;\n const closedTraces = Object.freeze([...traces.values()]);\n if (!captures.length) return Object.freeze({ traces: closedTraces });\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const expired = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n const error = new WorkflowError(\n 'Agent report snapshot collection timed out.',\n {\n code: 'REPORT_SNAPSHOT_TIMEOUT',\n details: { timeoutMs: REPORT_SNAPSHOT_TIMEOUT_MS },\n },\n );\n controller.abort(error);\n reject(error);\n }, REPORT_SNAPSHOT_TIMEOUT_MS);\n });\n const outcomes = await Promise.all(\n captures.map(async (capture) => {\n try {\n const source = await Promise.race([\n Promise.resolve().then(() =>\n capture(controller.signal, closedTraces),\n ),\n expired,\n ]);\n if (\n !source ||\n typeof source.sourcePath !== 'string' ||\n !source.sourcePath.trim() ||\n (source.dumpPath !== undefined &&\n (typeof source.dumpPath !== 'string' ||\n !source.dumpPath.trim()))\n )\n throw new Error(\n 'Agent report capture must return sourcePath and a valid optional dumpPath.',\n );\n const captured = source.dumpPath\n ? source\n : {\n ...source,\n collection: {\n status: 'partial' as const,\n capturedAt:\n source.collection?.capturedAt ?? new Date().toISOString(),\n reason:\n source.collection?.reason ??\n 'HTML report captures do not guarantee an immutable cutoff.',\n },\n };\n return {\n source: Object.freeze({\n ...captured,\n ...(captured.collection\n ? { collection: Object.freeze({ ...captured.collection }) }\n : {}),\n }),\n };\n } catch (error) {\n return {\n error:\n error instanceof WorkflowError\n ? error\n : new WorkflowError(\n `Agent report snapshot collection failed: ${String(error)}`,\n { code: 'REPORT_SNAPSHOT_FAILED', cause: error },\n ),\n };\n }\n }),\n );\n if (timer !== undefined) clearTimeout(timer);\n const sources = outcomes.flatMap((outcome) =>\n outcome.source ? [outcome.source] : [],\n );\n const errors = outcomes.flatMap((outcome) =>\n outcome.error ? [outcome.error] : [],\n );\n return Object.freeze({\n traces: closedTraces,\n ...(sources.length ? { sources: Object.freeze(sources) } : {}),\n ...(errors.length ? { collectionErrors: Object.freeze(errors) } : {}),\n });\n },\n };\n}\n\nasync function parseNodeInput<TInput, TData, TContext>(\n node: NodeDefinition<TInput, TData, TContext>,\n input: Record<string, unknown>,\n): Promise<TInput> {\n if (!node.inputSchema) {\n return input as TInput;\n }\n\n const parsed = await node.inputSchema.safeParseAsync(input);\n if (!parsed.success) {\n throw NodeInputValidationError.fromZod(node.name, parsed.error);\n }\n return parsed.data as TInput;\n}\n\nexport async function executeStep<\n TInput = unknown,\n TOutputData = unknown,\n TContext = unknown,\n>(\n step: NormalizedStep,\n node: NodeDefinition<TInput, TOutputData, TContext>,\n target: StepExecutionTarget,\n context: TContext,\n execution: {\n signal?: AbortSignal;\n defaultTimeoutMs?: number;\n onTeardown?(node: string, teardown: NodeScopeTeardown): void;\n } = {},\n): Promise<StepRunResult<TOutputData>> {\n const phase =\n target.scope === 'case' ? target.case.phase : target.document.phase;\n const stepIndex =\n target.scope === 'case' ? target.case.stepIndex : target.document.stepIndex;\n\n const reportCollector = createNodeReportCollector(step.node);\n const events: TestRunReportOperationEvent[] = [];\n const scopeId =\n target.scope === 'case' ? target.case.runId : target.document.documentRunId;\n let result: StepRunResult<TOutputData> | undefined;\n\n try {\n result = await executeNode<TOutputData>(\n step,\n async (signal) => {\n const input = await parseNodeInput(node, step.input);\n signal.throwIfAborted();\n const common = {\n input,\n $: step.meta,\n signal,\n context,\n report: reportCollector.collector,\n onTeardown: (teardown: NodeScopeTeardown) => {\n if (!execution.onTeardown) {\n throw new NodeExecutionError(\n step.node,\n new Error(\n 'The current execution scope cannot register teardown.',\n ),\n );\n }\n execution.onTeardown(step.node, teardown);\n },\n };\n return target.scope === 'case'\n ? node.execute({ ...common, scope: 'case', case: target.case })\n : node.execute({\n ...common,\n scope: 'document',\n document: target.document,\n });\n },\n phase,\n stepIndex,\n {\n parentSignal: execution.signal,\n defaultTimeoutMs: execution.defaultTimeoutMs,\n identity: {\n scopeId,\n stepId: `${scopeId}:${phase}:${stepIndex}`,\n node: step.node,\n },\n onEvent: (event) => events.push(event),\n },\n );\n } finally {\n const report = await reportCollector.close();\n if (result)\n result = {\n ...result,\n operations: Object.freeze({\n capturedAt: new Date().toISOString(),\n events: Object.freeze([...events]),\n }),\n };\n if (\n (report.traces.length ||\n report.sources?.length ||\n report.collectionErrors?.length) &&\n result\n ) {\n result = { ...result, report };\n }\n }\n\n if (!result) {\n throw new NodeExecutionError(\n step.node,\n new Error('Step execution completed without a result.'),\n );\n }\n return result;\n}\n"],"names":["REPORT_SNAPSHOT_TIMEOUT_MS","validateNodeOutput","output","node","undefined","Array","NodeExecutionError","TypeError","createStepResultBase","step","startedAt","phase","stepIndex","endedAt","Date","Math","executeNode","execute","options","abortController","AbortController","parentSignal","linkResourceSignal","setResourceOperationIdentity","stopObserving","observeResourceOperations","abortFromParent","timeoutMs","timeout","abortGraceTimeout","execution","Promise","observation","recordOperation","trackResourceOperation","returnedStatus","settleOnAbort","abortedExecution","_","reject","setTimeout","timeoutExecution","timeoutError","StepTimeoutError","settledExecution","error","isResourceAdmissionError","failure","validationError","normalizeNodeExecutionError","clearTimeout","createNodeReportCollector","traces","Map","captures","accepting","Object","capture","WorkflowError","trace","Error","normalized","structuredClone","closedTraces","controller","timer","expired","outcomes","source","captured","String","sources","outcome","errors","parseNodeInput","input","parsed","NodeInputValidationError","executeStep","target","context","reportCollector","events","scopeId","result","signal","common","teardown","event","report"],"mappings":"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"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { getDebug } from "@midscene/shared/logger";
|
|
2
2
|
import { ProjectSetupError, ProjectTeardownError, WorkflowLifecycleError } from "../errors.mjs";
|
|
3
|
-
import { ResourceCleanupDeferredError, cleanupScopeResources, linkResourceSignal } from "./resource-operations.mjs";
|
|
3
|
+
import { ResourceCleanupDeferredError, cleanupScopeResources, getPendingScopeOperations, getResourceCleanupSnapshot, linkResourceSignal, setResourceOperationIdentity } from "./resource-operations.mjs";
|
|
4
4
|
const createProjectRuntime = (options)=>{
|
|
5
5
|
const startedAt = new Date();
|
|
6
6
|
const controller = new AbortController();
|
|
7
7
|
const parentSignal = options.signal;
|
|
8
8
|
linkResourceSignal(controller.signal, parentSignal);
|
|
9
|
+
setResourceOperationIdentity(controller.signal, {
|
|
10
|
+
scopeId: options.project.projectId
|
|
11
|
+
});
|
|
9
12
|
const abortFromParent = ()=>controller.abort(parentSignal?.reason);
|
|
10
13
|
if (parentSignal?.aborted) abortFromParent();
|
|
11
14
|
else parentSignal?.addEventListener('abort', abortFromParent, {
|
|
@@ -20,9 +23,11 @@ const createProjectRuntime = (options)=>{
|
|
|
20
23
|
let finishPromise;
|
|
21
24
|
const createResult = (requestedStatus, teardownErrors = [])=>{
|
|
22
25
|
const endedAt = new Date();
|
|
26
|
+
const cleanup = getResourceCleanupSnapshot(controller.signal);
|
|
27
|
+
const pendingOperations = getPendingScopeOperations(controller.signal);
|
|
23
28
|
return {
|
|
24
29
|
projectName: options.project.name,
|
|
25
|
-
status: 'failed' === requestedStatus || setupError || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
30
|
+
status: 'failed' === requestedStatus || setupError || teardownErrors.length > 0 || 'pending' === cleanup.status || 'failed' === cleanup.status || pendingOperations.length > 0 ? 'failed' : 'success',
|
|
26
31
|
...setupError ? {
|
|
27
32
|
setupError
|
|
28
33
|
} : {},
|
|
@@ -33,7 +38,11 @@ const createProjectRuntime = (options)=>{
|
|
|
33
38
|
} : {},
|
|
34
39
|
startedAt: startedAt.toISOString(),
|
|
35
40
|
endedAt: endedAt.toISOString(),
|
|
36
|
-
durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime())
|
|
41
|
+
durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
|
|
42
|
+
cleanup,
|
|
43
|
+
...pendingOperations.length ? {
|
|
44
|
+
pendingOperations
|
|
45
|
+
} : {}
|
|
37
46
|
};
|
|
38
47
|
};
|
|
39
48
|
const onTeardown = (teardown)=>{
|
|
@@ -111,7 +120,7 @@ const createProjectRuntime = (options)=>{
|
|
|
111
120
|
registrationIndex
|
|
112
121
|
}));
|
|
113
122
|
}
|
|
114
|
-
if (
|
|
123
|
+
if (teardownErrors.length > priorErrors) throw new AggregateError(teardownErrors.slice(priorErrors), 'Project cleanup failed');
|
|
115
124
|
};
|
|
116
125
|
try {
|
|
117
126
|
if (teardownStack.length) await cleanupScopeResources(controller.signal, cleanup, {
|
|
@@ -121,7 +130,7 @@ const createProjectRuntime = (options)=>{
|
|
|
121
130
|
});
|
|
122
131
|
} catch (error) {
|
|
123
132
|
deferredCleanup = error instanceof ResourceCleanupDeferredError;
|
|
124
|
-
teardownErrors.push(new ProjectTeardownError(error, {
|
|
133
|
+
if (!deferredCleanup && !teardownErrors.length) teardownErrors.push(new ProjectTeardownError(error, {
|
|
125
134
|
projectName: options.project.name,
|
|
126
135
|
registrationIndex: -1
|
|
127
136
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-runner/engine/project-runtime.mjs","sources":["../../../../src/test-runner/engine/project-runtime.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport {\n ProjectSetupError,\n ProjectTeardownError,\n WorkflowLifecycleError,\n} from '../errors';\nimport {\n ResourceCleanupDeferredError,\n cleanupScopeResources,\n linkResourceSignal,\n} from './resource-operations';\nimport type {\n ProjectRuntime,\n ProjectRuntimeOptions,\n ProjectRuntimeResult,\n} from './types';\n\nexport const createProjectRuntime = <TProjectContext = unknown>(\n options: ProjectRuntimeOptions<TProjectContext>,\n): ProjectRuntime<TProjectContext> => {\n const startedAt = new Date();\n const controller = new AbortController();\n const parentSignal = options.signal;\n linkResourceSignal(controller.signal, parentSignal);\n const abortFromParent = () => controller.abort(parentSignal?.reason);\n if (parentSignal?.aborted) abortFromParent();\n else parentSignal?.addEventListener('abort', abortFromParent, { once: true });\n\n const teardownStack: Array<{\n registrationIndex: number;\n teardown: Parameters<\n Parameters<NonNullable<typeof options.setup>['setup']>[0]['onTeardown']\n >[0];\n }> = [];\n let context: TProjectContext | undefined;\n let setupError: ProjectSetupError | undefined;\n let started = false;\n let acceptingTeardowns = options.setup !== undefined;\n let finishedResult: ProjectRuntimeResult<TProjectContext> | undefined;\n let finishPromise: Promise<ProjectRuntimeResult<TProjectContext>> | undefined;\n\n const createResult = (\n requestedStatus: 'success' | 'failed',\n teardownErrors: readonly ProjectTeardownError[] = [],\n ): ProjectRuntimeResult<TProjectContext> => {\n const endedAt = new Date();\n return {\n projectName: options.project.name,\n status:\n requestedStatus === 'failed' || setupError || teardownErrors.length > 0\n ? 'failed'\n : 'success',\n ...(setupError ? { setupError } : {}),\n ...(teardownErrors.length > 0\n ? { teardownErrors: [...teardownErrors] }\n : {}),\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),\n };\n };\n\n const onTeardown = (\n teardown: (typeof teardownStack)[number]['teardown'],\n ): void => {\n if (!acceptingTeardowns) {\n throw new WorkflowLifecycleError(\n 'onTeardown() can only be called while Project setup is running.',\n { projectName: options.project.name },\n );\n }\n if (typeof teardown !== 'function') {\n throw new WorkflowLifecycleError(\n 'Project onTeardown() requires a teardown function.',\n { projectName: options.project.name },\n );\n }\n teardownStack.push({\n registrationIndex: teardownStack.length,\n teardown,\n });\n };\n\n return {\n get context() {\n return context;\n },\n get signal() {\n return controller.signal;\n },\n get canRun() {\n return started && !setupError && !controller.signal.aborted;\n },\n async start() {\n if (started) {\n throw new WorkflowLifecycleError(\n 'Project runtime has already started.',\n {\n projectName: options.project.name,\n },\n );\n }\n started = true;\n if (controller.signal.aborted) {\n acceptingTeardowns = false;\n return createResult('failed');\n }\n if (options.setup) {\n try {\n context = await options.setup.setup({\n project: options.project,\n env: Object.freeze({ ...process.env }),\n signal: controller.signal,\n onTeardown,\n });\n } catch (error) {\n setupError = new ProjectSetupError(error, {\n projectName: options.project.name,\n });\n } finally {\n acceptingTeardowns = false;\n }\n }\n return createResult(setupError ? 'failed' : 'success');\n },\n async finish(requestedStatus = 'success') {\n if (finishPromise) return finishPromise;\n if (!started) {\n throw new WorkflowLifecycleError(\n 'Project runtime must start before it can finish.',\n { projectName: options.project.name },\n );\n }\n finishPromise = (async () => {\n const statusBeforeTeardown = createResult(requestedStatus).status;\n const teardownErrors: ProjectTeardownError[] = [];\n let deferredCleanup = false;\n const cleanup = async () => {\n const priorErrors = teardownErrors.length;\n for (const {\n registrationIndex,\n teardown,\n } of teardownStack.reverse()) {\n try {\n await teardown({\n project: options.project,\n context,\n status: statusBeforeTeardown,\n ...(setupError ? { setupError } : {}),\n });\n } catch (error) {\n teardownErrors.push(\n new ProjectTeardownError(error, {\n projectName: options.project.name,\n registrationIndex,\n }),\n );\n }\n }\n if (deferredCleanup && teardownErrors.length > priorErrors)\n throw new AggregateError(\n teardownErrors.slice(priorErrors),\n 'Deferred Project cleanup failed',\n );\n };\n try {\n if (teardownStack.length)\n await cleanupScopeResources(controller.signal, cleanup, {\n onDeferredError: (error) =>\n getDebug('runner:cleanup', { console: true })(\n `Deferred Project cleanup failed: ${String(error)}`,\n ),\n });\n } catch (error) {\n deferredCleanup = error instanceof ResourceCleanupDeferredError;\n teardownErrors.push(\n new ProjectTeardownError(error, {\n projectName: options.project.name,\n registrationIndex: -1,\n }),\n );\n }\n parentSignal?.removeEventListener('abort', abortFromParent);\n finishedResult = createResult(requestedStatus, teardownErrors);\n return finishedResult;\n })();\n return finishPromise;\n },\n abort(reason) {\n controller.abort(reason);\n },\n };\n};\n"],"names":["createProjectRuntime","options","startedAt","Date","controller","AbortController","parentSignal","linkResourceSignal","abortFromParent","teardownStack","context","setupError","started","acceptingTeardowns","undefined","finishedResult","finishPromise","createResult","requestedStatus","teardownErrors","endedAt","Math","onTeardown","teardown","WorkflowLifecycleError","Object","process","error","ProjectSetupError","statusBeforeTeardown","deferredCleanup","cleanup","priorErrors","registrationIndex","ProjectTeardownError","AggregateError","cleanupScopeResources","getDebug","String","ResourceCleanupDeferredError","reason"],"mappings":";;;AAiBO,MAAMA,uBAAuB,CAClCC;IAEA,MAAMC,YAAY,IAAIC;IACtB,MAAMC,aAAa,IAAIC;IACvB,MAAMC,eAAeL,QAAQ,MAAM;IACnCM,mBAAmBH,WAAW,MAAM,EAAEE;IACtC,MAAME,kBAAkB,IAAMJ,WAAW,KAAK,CAACE,cAAc;IAC7D,IAAIA,cAAc,SAASE;SACtBF,cAAc,iBAAiB,SAASE,iBAAiB;QAAE,MAAM;IAAK;IAE3E,MAAMC,gBAKD,EAAE;IACP,IAAIC;IACJ,IAAIC;IACJ,IAAIC,UAAU;IACd,IAAIC,qBAAqBZ,AAAkBa,WAAlBb,QAAQ,KAAK;IACtC,IAAIc;IACJ,IAAIC;IAEJ,MAAMC,eAAe,CACnBC,iBACAC,iBAAkD,EAAE;QAEpD,MAAMC,UAAU,IAAIjB;QACpB,OAAO;YACL,aAAaF,QAAQ,OAAO,CAAC,IAAI;YACjC,QACEiB,AAAoB,aAApBA,mBAAgCP,cAAcQ,eAAe,MAAM,GAAG,IAClE,WACA;YACN,GAAIR,aAAa;gBAAEA;YAAW,IAAI,CAAC,CAAC;YACpC,GAAIQ,eAAe,MAAM,GAAG,IACxB;gBAAE,gBAAgB;uBAAIA;iBAAe;YAAC,IACtC,CAAC,CAAC;YACN,WAAWjB,UAAU,WAAW;YAChC,SAASkB,QAAQ,WAAW;YAC5B,YAAYC,KAAK,GAAG,CAAC,GAAGD,QAAQ,OAAO,KAAKlB,UAAU,OAAO;QAC/D;IACF;IAEA,MAAMoB,aAAa,CACjBC;QAEA,IAAI,CAACV,oBACH,MAAM,IAAIW,uBACR,mEACA;YAAE,aAAavB,QAAQ,OAAO,CAAC,IAAI;QAAC;QAGxC,IAAI,AAAoB,cAApB,OAAOsB,UACT,MAAM,IAAIC,uBACR,sDACA;YAAE,aAAavB,QAAQ,OAAO,CAAC,IAAI;QAAC;QAGxCQ,cAAc,IAAI,CAAC;YACjB,mBAAmBA,cAAc,MAAM;YACvCc;QACF;IACF;IAEA,OAAO;QACL,IAAI,WAAU;YACZ,OAAOb;QACT;QACA,IAAI,UAAS;YACX,OAAON,WAAW,MAAM;QAC1B;QACA,IAAI,UAAS;YACX,OAAOQ,WAAW,CAACD,cAAc,CAACP,WAAW,MAAM,CAAC,OAAO;QAC7D;QACA,MAAM;YACJ,IAAIQ,SACF,MAAM,IAAIY,uBACR,wCACA;gBACE,aAAavB,QAAQ,OAAO,CAAC,IAAI;YACnC;YAGJW,UAAU;YACV,IAAIR,WAAW,MAAM,CAAC,OAAO,EAAE;gBAC7BS,qBAAqB;gBACrB,OAAOI,aAAa;YACtB;YACA,IAAIhB,QAAQ,KAAK,EACf,IAAI;gBACFS,UAAU,MAAMT,QAAQ,KAAK,CAAC,KAAK,CAAC;oBAClC,SAASA,QAAQ,OAAO;oBACxB,KAAKwB,OAAO,MAAM,CAAC;wBAAE,GAAGC,QAAQ,GAAG;oBAAC;oBACpC,QAAQtB,WAAW,MAAM;oBACzBkB;gBACF;YACF,EAAE,OAAOK,OAAO;gBACdhB,aAAa,IAAIiB,kBAAkBD,OAAO;oBACxC,aAAa1B,QAAQ,OAAO,CAAC,IAAI;gBACnC;YACF,SAAU;gBACRY,qBAAqB;YACvB;YAEF,OAAOI,aAAaN,aAAa,WAAW;QAC9C;QACA,MAAM,QAAOO,kBAAkB,SAAS;YACtC,IAAIF,eAAe,OAAOA;YAC1B,IAAI,CAACJ,SACH,MAAM,IAAIY,uBACR,oDACA;gBAAE,aAAavB,QAAQ,OAAO,CAAC,IAAI;YAAC;YAGxCe,gBAAiB;gBACf,MAAMa,uBAAuBZ,aAAaC,iBAAiB,MAAM;gBACjE,MAAMC,iBAAyC,EAAE;gBACjD,IAAIW,kBAAkB;gBACtB,MAAMC,UAAU;oBACd,MAAMC,cAAcb,eAAe,MAAM;oBACzC,KAAK,MAAM,EACTc,iBAAiB,EACjBV,QAAQ,EACT,IAAId,cAAc,OAAO,GACxB,IAAI;wBACF,MAAMc,SAAS;4BACb,SAAStB,QAAQ,OAAO;4BACxBS;4BACA,QAAQmB;4BACR,GAAIlB,aAAa;gCAAEA;4BAAW,IAAI,CAAC,CAAC;wBACtC;oBACF,EAAE,OAAOgB,OAAO;wBACdR,eAAe,IAAI,CACjB,IAAIe,qBAAqBP,OAAO;4BAC9B,aAAa1B,QAAQ,OAAO,CAAC,IAAI;4BACjCgC;wBACF;oBAEJ;oBAEF,IAAIH,mBAAmBX,eAAe,MAAM,GAAGa,aAC7C,MAAM,IAAIG,eACRhB,eAAe,KAAK,CAACa,cACrB;gBAEN;gBACA,IAAI;oBACF,IAAIvB,cAAc,MAAM,EACtB,MAAM2B,sBAAsBhC,WAAW,MAAM,EAAE2B,SAAS;wBACtD,iBAAiB,CAACJ,QAChBU,SAAS,kBAAkB;gCAAE,SAAS;4BAAK,GACzC,CAAC,iCAAiC,EAAEC,OAAOX,QAAQ;oBAEzD;gBACJ,EAAE,OAAOA,OAAO;oBACdG,kBAAkBH,iBAAiBY;oBACnCpB,eAAe,IAAI,CACjB,IAAIe,qBAAqBP,OAAO;wBAC9B,aAAa1B,QAAQ,OAAO,CAAC,IAAI;wBACjC,mBAAmB;oBACrB;gBAEJ;gBACAK,cAAc,oBAAoB,SAASE;gBAC3CO,iBAAiBE,aAAaC,iBAAiBC;gBAC/C,OAAOJ;YACT;YACA,OAAOC;QACT;QACA,OAAMwB,MAAM;YACVpC,WAAW,KAAK,CAACoC;QACnB;IACF;AACF"}
|
|
1
|
+
{"version":3,"file":"test-runner/engine/project-runtime.mjs","sources":["../../../../src/test-runner/engine/project-runtime.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport {\n ProjectSetupError,\n ProjectTeardownError,\n WorkflowLifecycleError,\n} from '../errors';\nimport {\n ResourceCleanupDeferredError,\n cleanupScopeResources,\n getPendingScopeOperations,\n getResourceCleanupSnapshot,\n linkResourceSignal,\n setResourceOperationIdentity,\n} from './resource-operations';\nimport type {\n ProjectRuntime,\n ProjectRuntimeOptions,\n ProjectRuntimeResult,\n} from './types';\n\nexport const createProjectRuntime = <TProjectContext = unknown>(\n options: ProjectRuntimeOptions<TProjectContext>,\n): ProjectRuntime<TProjectContext> => {\n const startedAt = new Date();\n const controller = new AbortController();\n const parentSignal = options.signal;\n linkResourceSignal(controller.signal, parentSignal);\n setResourceOperationIdentity(controller.signal, {\n scopeId: options.project.projectId,\n });\n const abortFromParent = () => controller.abort(parentSignal?.reason);\n if (parentSignal?.aborted) abortFromParent();\n else parentSignal?.addEventListener('abort', abortFromParent, { once: true });\n\n const teardownStack: Array<{\n registrationIndex: number;\n teardown: Parameters<\n Parameters<NonNullable<typeof options.setup>['setup']>[0]['onTeardown']\n >[0];\n }> = [];\n let context: TProjectContext | undefined;\n let setupError: ProjectSetupError | undefined;\n let started = false;\n let acceptingTeardowns = options.setup !== undefined;\n let finishedResult: ProjectRuntimeResult<TProjectContext> | undefined;\n let finishPromise: Promise<ProjectRuntimeResult<TProjectContext>> | undefined;\n\n const createResult = (\n requestedStatus: 'success' | 'failed',\n teardownErrors: readonly ProjectTeardownError[] = [],\n ): ProjectRuntimeResult<TProjectContext> => {\n const endedAt = new Date();\n const cleanup = getResourceCleanupSnapshot(controller.signal);\n const pendingOperations = getPendingScopeOperations(controller.signal);\n return {\n projectName: options.project.name,\n status:\n requestedStatus === 'failed' ||\n setupError ||\n teardownErrors.length > 0 ||\n cleanup.status === 'pending' ||\n cleanup.status === 'failed' ||\n pendingOperations.length > 0\n ? 'failed'\n : 'success',\n ...(setupError ? { setupError } : {}),\n ...(teardownErrors.length > 0\n ? { teardownErrors: [...teardownErrors] }\n : {}),\n startedAt: startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),\n cleanup,\n ...(pendingOperations.length ? { pendingOperations } : {}),\n };\n };\n\n const onTeardown = (\n teardown: (typeof teardownStack)[number]['teardown'],\n ): void => {\n if (!acceptingTeardowns) {\n throw new WorkflowLifecycleError(\n 'onTeardown() can only be called while Project setup is running.',\n { projectName: options.project.name },\n );\n }\n if (typeof teardown !== 'function') {\n throw new WorkflowLifecycleError(\n 'Project onTeardown() requires a teardown function.',\n { projectName: options.project.name },\n );\n }\n teardownStack.push({\n registrationIndex: teardownStack.length,\n teardown,\n });\n };\n\n return {\n get context() {\n return context;\n },\n get signal() {\n return controller.signal;\n },\n get canRun() {\n return started && !setupError && !controller.signal.aborted;\n },\n async start() {\n if (started) {\n throw new WorkflowLifecycleError(\n 'Project runtime has already started.',\n {\n projectName: options.project.name,\n },\n );\n }\n started = true;\n if (controller.signal.aborted) {\n acceptingTeardowns = false;\n return createResult('failed');\n }\n if (options.setup) {\n try {\n context = await options.setup.setup({\n project: options.project,\n env: Object.freeze({ ...process.env }),\n signal: controller.signal,\n onTeardown,\n });\n } catch (error) {\n setupError = new ProjectSetupError(error, {\n projectName: options.project.name,\n });\n } finally {\n acceptingTeardowns = false;\n }\n }\n return createResult(setupError ? 'failed' : 'success');\n },\n async finish(requestedStatus = 'success') {\n if (finishPromise) return finishPromise;\n if (!started) {\n throw new WorkflowLifecycleError(\n 'Project runtime must start before it can finish.',\n { projectName: options.project.name },\n );\n }\n finishPromise = (async () => {\n const statusBeforeTeardown = createResult(requestedStatus).status;\n const teardownErrors: ProjectTeardownError[] = [];\n let deferredCleanup = false;\n const cleanup = async () => {\n const priorErrors = teardownErrors.length;\n for (const {\n registrationIndex,\n teardown,\n } of teardownStack.reverse()) {\n try {\n await teardown({\n project: options.project,\n context,\n status: statusBeforeTeardown,\n ...(setupError ? { setupError } : {}),\n });\n } catch (error) {\n teardownErrors.push(\n new ProjectTeardownError(error, {\n projectName: options.project.name,\n registrationIndex,\n }),\n );\n }\n }\n if (teardownErrors.length > priorErrors)\n throw new AggregateError(\n teardownErrors.slice(priorErrors),\n 'Project cleanup failed',\n );\n };\n try {\n if (teardownStack.length)\n await cleanupScopeResources(controller.signal, cleanup, {\n onDeferredError: (error) =>\n getDebug('runner:cleanup', { console: true })(\n `Deferred Project cleanup failed: ${String(error)}`,\n ),\n });\n } catch (error) {\n deferredCleanup = error instanceof ResourceCleanupDeferredError;\n if (!deferredCleanup && !teardownErrors.length)\n teardownErrors.push(\n new ProjectTeardownError(error, {\n projectName: options.project.name,\n registrationIndex: -1,\n }),\n );\n }\n parentSignal?.removeEventListener('abort', abortFromParent);\n finishedResult = createResult(requestedStatus, teardownErrors);\n return finishedResult;\n })();\n return finishPromise;\n },\n abort(reason) {\n controller.abort(reason);\n },\n };\n};\n"],"names":["createProjectRuntime","options","startedAt","Date","controller","AbortController","parentSignal","linkResourceSignal","setResourceOperationIdentity","abortFromParent","teardownStack","context","setupError","started","acceptingTeardowns","undefined","finishedResult","finishPromise","createResult","requestedStatus","teardownErrors","endedAt","cleanup","getResourceCleanupSnapshot","pendingOperations","getPendingScopeOperations","Math","onTeardown","teardown","WorkflowLifecycleError","Object","process","error","ProjectSetupError","statusBeforeTeardown","deferredCleanup","priorErrors","registrationIndex","ProjectTeardownError","AggregateError","cleanupScopeResources","getDebug","String","ResourceCleanupDeferredError","reason"],"mappings":"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"}
|