@midscene/test 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/index.mjs CHANGED
@@ -1,9 +1,8 @@
1
- import { CaseExecutionError, CaseRunner, DuplicateNodeError, FatalDeviceError, NodeDefinitionError, NodeExecutionError, NodeInputValidationError, NodeNotFoundError, NodeRegistry, NodeScopeTeardownError, ProjectSetupError, ProjectTeardownError, StepTimeoutError, WorkflowDocumentExecutionError, WorkflowError, WorkflowLifecycleError, WorkflowParseError, buildTestRunReportDump, classifyWorkflowDocumentFormat, collectTestRunReportSources, createCaseRunner, createDocumentRuntime, createProjectRuntime, defineNode, isFatalDeviceError, normalizeNodeExecutionError, normalizeStep, normalizeSteps, runWorkflowDocument, sanitizeReportError, sanitizeReportText, sanitizeReportValue } from "@midscene/core/internal/test-runner";
1
+ import { CaseExecutionError, CaseRunner, DuplicateNodeError, FatalDeviceError, NodeDefinitionError, NodeExecutionError, NodeInputValidationError, NodeNotFoundError, NodeRegistry, NodeScopeTeardownError, ProjectSetupError, ProjectTeardownError, StepTimeoutError, WorkflowDocumentExecutionError, WorkflowError, WorkflowLifecycleError, WorkflowParseError, buildTestRunReportDump, classifyWorkflowDocumentFormat, collectTestRunReportSources, createCaseRunner, createDocumentRuntime, createProjectRuntime, defineNode, isFatalDeviceError, isReportSecretKey, normalizeNodeExecutionError, normalizeStep, normalizeSteps, runWorkflowDocument, sanitizeReportError, sanitizeReportText, sanitizeReportValue } from "@midscene/core/internal/test-runner";
2
2
  import { readFileSync } from "node:fs";
3
- import { dirname, join, relative, sep } from "node:path";
4
- import { JSON_SCHEMA, load } from "js-yaml";
3
+ import { sep } from "node:path";
5
4
  import { createHash } from "node:crypto";
6
- import { writeFile } from "node:fs/promises";
5
+ import { JSON_SCHEMA, load } from "js-yaml";
7
6
  import { z } from "zod/v4";
8
7
  const case_runner_CaseRunner = CaseRunner;
9
8
  const case_runner_createCaseRunner = createCaseRunner;
@@ -28,6 +27,69 @@ const createDocumentInvocationId = (projectId, sourcePath, invocationIndex = 0)=
28
27
  ])).digest('hex');
29
28
  const normalize_normalizeStep = normalizeStep;
30
29
  const normalize_normalizeSteps = normalizeSteps;
30
+ function parseWorkflowSource(source) {
31
+ const stack = [];
32
+ const nodes = [];
33
+ const sensitiveNodes = new Set();
34
+ const sensitiveValues = new Set();
35
+ const redactedPaths = new Set();
36
+ const sourceOffset = 0xfeff === source.charCodeAt(0) ? 1 : 0;
37
+ const parsed = load(source, {
38
+ schema: JSON_SCHEMA,
39
+ listener (event, state) {
40
+ if ('open' === event) return void stack.push({
41
+ start: state.position + sourceOffset,
42
+ end: 0,
43
+ value: null,
44
+ children: []
45
+ });
46
+ const node = stack.pop();
47
+ node.end = state.position + sourceOffset;
48
+ node.value = state.result;
49
+ stack.at(-1)?.children.push(node);
50
+ nodes.push(node);
51
+ if ('mapping' !== state.kind) return;
52
+ for(let index = 0; index < node.children.length; index += 2){
53
+ const key = node.children[index].value;
54
+ const value = node.children[index + 1];
55
+ if ('string' == typeof key && isReportSecretKey(key) && value) {
56
+ sensitiveNodes.add(value);
57
+ if (null !== value.value) sensitiveValues.add(value.value);
58
+ redactedPaths.add(`$..[${JSON.stringify(key)}]`);
59
+ }
60
+ }
61
+ }
62
+ });
63
+ const ranges = nodes.filter((node)=>sensitiveNodes.has(node) || sensitiveValues.has(node.value)).sort((left, right)=>left.start - right.start || right.end - left.end);
64
+ let offset = 0;
65
+ let redacted = '';
66
+ for (const range of ranges){
67
+ if (range.start < offset) continue;
68
+ const text = source.slice(range.start, range.end);
69
+ const leading = text.match(/^\s*/)?.[0] ?? '';
70
+ const trailing = text.slice(leading.length).match(/\s*$/)?.[0] ?? '';
71
+ redacted += `${source.slice(offset, range.start)}${leading}"[REDACTED]"${trailing}`;
72
+ offset = range.end;
73
+ }
74
+ redacted += source.slice(offset);
75
+ const value = sanitizeReportText(redacted, 65536);
76
+ return {
77
+ parsed,
78
+ sourceYaml: {
79
+ value,
80
+ ...redactedPaths.size ? {
81
+ redactedPaths: [
82
+ ...redactedPaths
83
+ ]
84
+ } : {},
85
+ ...value !== redacted ? {
86
+ truncatedPaths: [
87
+ '$'
88
+ ]
89
+ } : {}
90
+ }
91
+ };
92
+ }
31
93
  const interpolationExpression = /\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}|\$\{([^{}]+)\}/g;
32
94
  const exactEnvExpression = /^\$\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}$/;
33
95
  const exactProjectExpression = /^\$\{([^{}]+)\}$/;
@@ -102,16 +164,15 @@ const rejectUnknownKeys = (value, allowed, label)=>{
102
164
  const createCaseId = (projectId, sourcePath, caseIndex)=>createCaseInvocationId(projectId, sourcePath, caseIndex);
103
165
  const createWorkflowDocumentId = (projectId, sourcePath)=>createDocumentInvocationId(projectId, sourcePath);
104
166
  function collectWorkflowDocument(source, options) {
105
- let parsed;
167
+ let snapshot;
106
168
  try {
107
- parsed = load(readFileSync(source.absolutePath, 'utf8'), {
108
- schema: JSON_SCHEMA
109
- });
169
+ snapshot = parseWorkflowSource(readFileSync(source.absolutePath, 'utf8'));
110
170
  } catch (error) {
111
171
  throw new WorkflowParseError(`Failed to parse workflow document "${source.sourcePath}".`, {
112
172
  sourcePath: source.sourcePath
113
173
  }, error);
114
174
  }
175
+ const { parsed, sourceYaml } = snapshot;
115
176
  if (!isMapping(parsed)) throw new WorkflowParseError('Workflow document must be a mapping.');
116
177
  const format = classifyWorkflowDocumentFormat(parsed);
117
178
  if ('legacy' === format || 'legacy-batch' === format || 'mixed' === format) throw new NativeWorkflowFormatError(format, source.sourcePath);
@@ -126,6 +187,7 @@ function collectWorkflowDocument(source, options) {
126
187
  const sourcePath = source.sourcePath.split(sep).join('/');
127
188
  const resolveStepVariables = (step, phase, stepIndex, caseIndex)=>({
128
189
  ...step,
190
+ templateInput: step.input,
129
191
  input: resolveWorkflowVariables(step.input, {
130
192
  variables: options.variables,
131
193
  env: options.env ?? process.env,
@@ -211,107 +273,11 @@ function collectWorkflowDocument(source, options) {
211
273
  documentId: createWorkflowDocumentId(source.projectId, sourcePath),
212
274
  projectId: source.projectId,
213
275
  sourcePath,
276
+ sourceYaml,
214
277
  lifecycle,
215
278
  cases
216
279
  };
217
280
  }
218
- const result_markdown_text = (value)=>value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll('|', '&#124;').replaceAll('[', '&#91;').replaceAll(']', '&#93;').replaceAll('*', '&#42;').replaceAll('_', '&#95;').replaceAll('`', '&#96;').replaceAll('\\', '&#92;').replace(/[\r\n]+/g, ' ');
219
- const duration = (ms)=>{
220
- const seconds = Math.round(ms / 1000);
221
- return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
222
- };
223
- const href = (from, target)=>relative(dirname(from), target).split(sep).map(encodeURIComponent).join('/');
224
- function renderTestResultMarkdown(dump, options = {}) {
225
- const { summary } = dump;
226
- const outcome = 'success' === dump.status ? `✅ **${summary.passed}/${summary.total} passed**` : `❌ **${summary.failed} failed** · ${summary.passed} passed · ${summary.notRun} not run`;
227
- const lines = [
228
- '## Midscene Test',
229
- '',
230
- `${outcome} · ${duration(dump.durationMs)}`,
231
- ''
232
- ];
233
- if (summary.filtered) lines.push(`**Filtered:** ${summary.filtered}`, '');
234
- const infrastructure = [
235
- summary.collectionErrors ? `${summary.collectionErrors} collection errors` : '',
236
- summary.documentFailures ? `${summary.documentFailures} document failures` : '',
237
- summary.projectFailures ? `${summary.projectFailures} project failures` : ''
238
- ].filter(Boolean);
239
- if (infrastructure.length) lines.push(`**Infrastructure:** ${infrastructure.join(' · ')}`, '');
240
- const groups = [];
241
- const errors = [];
242
- const recordSteps = (label, steps)=>{
243
- for (const step of steps)if ('failed' === step.status) errors.push(`${label} / ${step.node}: ${step.error?.message ?? 'Step failed'}`);
244
- };
245
- for (const project of dump.projects){
246
- if (project.lifecycle?.setupError) errors.push(`${project.name}: ${project.lifecycle.setupError.message}`);
247
- for (const error of project.lifecycle?.teardownErrors ?? [])errors.push(`${project.name}: ${error.message}`);
248
- for (const error of project.collectionErrors)errors.push(`${project.name} / ${error.sourcePath}: ${error.error.message}`);
249
- for (const document of project.documents){
250
- const name = document.sourcePath.replace(/\\/g, '/').replace(/\.(?:yaml|yml)$/, '');
251
- const group = {
252
- name: dump.projects.length > 1 ? `${project.name} / ${name}` : name,
253
- rows: []
254
- };
255
- groups.push(group);
256
- recordSteps(`${project.name} / ${document.sourcePath}`, [
257
- ...document.beforeAll,
258
- ...document.afterAll
259
- ]);
260
- for (const error of document.teardownErrors ?? [])errors.push(`${document.sourcePath}: ${error.message}`);
261
- for (const failure of document.hostErrors ?? [])errors.push(`${document.sourcePath} / ${failure.phase}: ${failure.error.message}`);
262
- for (const testCase of document.cases){
263
- const attempt = testCase.attempts.at(-1);
264
- const steps = attempt ? [
265
- ...attempt.beforeEach,
266
- ...attempt.steps,
267
- ...attempt.afterEach
268
- ] : [];
269
- const failedSteps = steps.filter((step)=>'failed' === step.status);
270
- const step = failedSteps.at(-1) ?? [
271
- ...steps
272
- ].reverse().find((item)=>item.agentDetails?.length) ?? steps.at(-1);
273
- const route = new URLSearchParams({
274
- 'runner-page': 'case',
275
- 'runner-project': project.projectId,
276
- 'runner-case': testCase.caseId,
277
- ...step ? {
278
- 'runner-step': step.id
279
- } : {}
280
- });
281
- const target = options.reportHref ? `${options.reportHref}#${route}` : void 0;
282
- const name = target ? `[${result_markdown_text(testCase.name)}](<${target}>)` : result_markdown_text(testCase.name);
283
- group.rows.push(` - ${'success' === testCase.status ? '✅' : 'not-run' === testCase.status ? '⏭️' : '❌'} ${name}`);
284
- }
285
- }
286
- }
287
- for (const diagnostic of dump.diagnostics ?? [])if ('error' === diagnostic.level) errors.push(diagnostic.message);
288
- if (errors.length) lines.push('### Execution errors', '', ...errors.map((error)=>`- ${result_markdown_text(error)}`), '');
289
- for (const group of groups)if (group.rows.length) lines.push(`- ${result_markdown_text(group.name)}`, ...group.rows);
290
- if (!groups.some((group)=>group.rows.length)) lines.push('No cases were reported.');
291
- if (options.reportHref) lines.push('', `[Open the Midscene Test report ↗](<${options.reportHref}>)`);
292
- return `${lines.join('\n')}\n`;
293
- }
294
- const emptyIndex = ()=>({
295
- sources: [],
296
- metrics: {
297
- modelCallCount: 0,
298
- modelTimeMs: 0,
299
- promptTokens: 0,
300
- cachedInputTokens: 0,
301
- completionTokens: 0,
302
- totalTokens: 0
303
- }
304
- });
305
- async function writeTestResultMarkdown(result, index = emptyIndex()) {
306
- const output = join(dirname(result.summaryPath), 'result.md');
307
- const dump = buildTestRunReportDump(result, index);
308
- await writeFile(output, renderTestResultMarkdown(dump, {
309
- ...result.reportPath ? {
310
- reportHref: href(output, result.reportPath)
311
- } : {}
312
- }));
313
- return output;
314
- }
315
- export { CaseExecutionError, case_runner_CaseRunner as CaseRunner, DuplicateNodeError, FatalDeviceError, NativeWorkflowFormatError, NodeDefinitionError, NodeExecutionError, NodeInputValidationError, NodeNotFoundError, registry_NodeRegistry as NodeRegistry, NodeScopeTeardownError, ProjectSetupError, ProjectTeardownError, StepTimeoutError, WorkflowDocumentExecutionError, WorkflowError, WorkflowLifecycleError, WorkflowParseError, buildTestRunReportDump, collectTestRunReportSources, collectWorkflowDocument, createCaseId, case_runner_createCaseRunner as createCaseRunner, document_runtime_createDocumentRuntime as createDocumentRuntime, createProjectRuntime, createWorkflowDocumentId, define_node_defineNode as defineNode, isFatalDeviceError, normalizeNodeExecutionError, normalize_normalizeStep as normalizeStep, normalize_normalizeSteps as normalizeSteps, renderTestResultMarkdown, resolveWorkflowVariables, run_workflow_document_runWorkflowDocument as runWorkflowDocument, sanitizeReportError, sanitizeReportText, sanitizeReportValue, writeTestResultMarkdown, z };
281
+ export { CaseExecutionError, case_runner_CaseRunner as CaseRunner, DuplicateNodeError, FatalDeviceError, NativeWorkflowFormatError, NodeDefinitionError, NodeExecutionError, NodeInputValidationError, NodeNotFoundError, registry_NodeRegistry as NodeRegistry, NodeScopeTeardownError, ProjectSetupError, ProjectTeardownError, StepTimeoutError, WorkflowDocumentExecutionError, WorkflowError, WorkflowLifecycleError, WorkflowParseError, buildTestRunReportDump, collectTestRunReportSources, collectWorkflowDocument, createCaseId, case_runner_createCaseRunner as createCaseRunner, document_runtime_createDocumentRuntime as createDocumentRuntime, createProjectRuntime, createWorkflowDocumentId, define_node_defineNode as defineNode, isFatalDeviceError, normalizeNodeExecutionError, normalize_normalizeStep as normalizeStep, normalize_normalizeSteps as normalizeSteps, resolveWorkflowVariables, run_workflow_document_runWorkflowDocument as runWorkflowDocument, sanitizeReportError, sanitizeReportText, sanitizeReportValue, z };
316
282
 
317
283
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/engine/case-runner.ts","../../src/engine/document-runtime.ts","../../src/engine/registry.ts","../../src/engine/run-workflow-document.ts","../../src/node/define-node.ts","../../src/parser/identifiers.ts","../../src/parser/normalize.ts","../../src/parser/variables.ts","../../src/parser/collect.ts","../../src/report/result-markdown.ts"],"sourcesContent":["import {\n CaseRunner as CoreCaseRunner,\n createCaseRunner as coreCreateCaseRunner,\n} from '@midscene/core/internal/test-runner';\nimport type { NodeDefinition } from '../node/types';\nimport type { CaseInput } from '../parser/types';\nimport type { NodeRegistry } from './registry';\nimport type { CaseRunResult, CaseRunnerOptions } from './types';\n\nexport type CaseRunner<TContext = undefined> = Omit<\n CoreCaseRunner<TContext>,\n 'run' | 'register' | 'registry'\n> & {\n readonly registry: NodeRegistry;\n register<TInput, TData>(\n node: NodeDefinition<TInput, TData, TContext>,\n ): CaseRunner<TContext>;\n run(input: CaseInput): Promise<CaseRunResult>;\n};\nexport const CaseRunner: {\n new <TContext = undefined>(\n options?: CaseRunnerOptions<TContext>,\n ): CaseRunner<TContext>;\n} = CoreCaseRunner;\nexport const createCaseRunner: <TContext = undefined>(\n options?: CaseRunnerOptions<TContext>,\n) => CaseRunner<TContext> = coreCreateCaseRunner;\n","import { createDocumentRuntime as coreCreateDocumentRuntime } from '@midscene/core/internal/test-runner';\nimport type { CollectedWorkflowDocument } from '../parser/types';\nimport type {\n CreateDocumentRuntimeOptions,\n WorkflowDocumentRuntime,\n} from './types';\n\nexport const createDocumentRuntime: <TContext = undefined>(\n document: CollectedWorkflowDocument,\n options: CreateDocumentRuntimeOptions<TContext>,\n) => WorkflowDocumentRuntime<TContext> = coreCreateDocumentRuntime;\n","import { NodeRegistry as CoreNodeRegistry } from '@midscene/core/internal/test-runner';\nimport type { NodeDefinition } from '../node/types';\n\nexport type NodeRegistry = Omit<\n CoreNodeRegistry,\n 'get' | 'require' | 'register' | 'definitions'\n> & {\n get(name: string): NodeDefinition<any, any> | undefined;\n require(name: string): NodeDefinition<any, any>;\n register<TInput, TData>(node: NodeDefinition<TInput, TData>): NodeRegistry;\n definitions(): readonly NodeDefinition<any, any, any>[];\n};\nexport const NodeRegistry: {\n new (nodes?: readonly NodeDefinition<any, any>[]): NodeRegistry;\n} = CoreNodeRegistry;\n","import { runWorkflowDocument as coreRunWorkflowDocument } from '@midscene/core/internal/test-runner';\nimport type { CollectedWorkflowDocument } from '../parser/types';\nimport type {\n RunWorkflowDocumentOptions,\n WorkflowDocumentExecutionResult,\n} from './types';\n\nexport const runWorkflowDocument: <TContext = undefined>(\n document: CollectedWorkflowDocument,\n options: RunWorkflowDocumentOptions<TContext>,\n) => Promise<WorkflowDocumentExecutionResult> = coreRunWorkflowDocument;\n","import { defineNode as coreDefineNode } from '@midscene/core/internal/test-runner';\nimport type {\n DefineNodeOptions,\n DefineNodeWithSchemaOptions,\n NodeDefinition,\n NodeDefinitionWithSchema,\n NodeInputSchema,\n} from './types';\n\n// A type-only facade: validation and execution still belong to the shared kernel.\ninterface DefineNativeNode {\n <TSchema extends NodeInputSchema, TData = unknown, TContext = unknown>(\n options: DefineNodeWithSchemaOptions<TSchema, TData, TContext>,\n ): NodeDefinitionWithSchema<TSchema, TData, TContext>;\n <TInput = unknown, TData = unknown, TContext = unknown>(\n options: DefineNodeOptions<TInput, TData, TContext>,\n ): NodeDefinition<TInput, TData, TContext>;\n}\nexport const defineNode: DefineNativeNode = coreDefineNode;\n","import { createHash } from 'node:crypto';\n\n/** Internal invocation identity keeps repeated legacy files distinct. */\nexport const createCaseInvocationId = (\n projectId: string,\n sourcePath: string,\n caseIndex: number,\n invocationIndex = 0,\n): string =>\n createHash('sha256')\n .update(\n JSON.stringify([\n projectId,\n sourcePath,\n caseIndex,\n ...(invocationIndex ? [invocationIndex] : []),\n ]),\n )\n .digest('hex');\n\nexport const createDocumentInvocationId = (\n projectId: string,\n sourcePath: string,\n invocationIndex = 0,\n): string =>\n createHash('sha256')\n .update(\n JSON.stringify([\n projectId,\n sourcePath,\n ...(invocationIndex ? [invocationIndex] : []),\n ]),\n )\n .digest('hex');\n","import {\n type ResolveNodeForNormalization,\n normalizeStep as coreNormalizeStep,\n normalizeSteps as coreNormalizeSteps,\n} from '@midscene/core/internal/test-runner';\nimport type { NormalizedStep } from './types';\nexport type { ResolveNodeForNormalization } from '@midscene/core/internal/test-runner';\n\nexport const normalizeStep: (\n value: unknown,\n index?: number,\n resolveNode?: ResolveNodeForNormalization,\n) => NormalizedStep = coreNormalizeStep;\nexport const normalizeSteps: (\n values: unknown,\n resolveNode?: ResolveNodeForNormalization,\n) => NormalizedStep[] = coreNormalizeSteps;\n","import type { JsonValue } from '../cli/test-project';\nimport { WorkflowParseError } from '../errors';\n\nexport interface VariableResolutionLocation {\n projectName: string;\n sourcePath: string;\n phase: 'beforeAll' | 'beforeEach' | 'steps' | 'afterEach' | 'afterAll';\n stepIndex: number;\n caseIndex?: number;\n}\n\nexport interface ResolveWorkflowVariablesOptions {\n variables?: Readonly<Record<string, JsonValue>>;\n env?: Readonly<NodeJS.ProcessEnv>;\n location: VariableResolutionLocation;\n}\n\nconst interpolationExpression =\n /\\$\\{\\{([A-Za-z_][A-Za-z0-9_]*)\\}\\}|\\$\\{([^{}]+)\\}/g;\nconst exactEnvExpression = /^\\$\\{\\{([A-Za-z_][A-Za-z0-9_]*)\\}\\}$/;\nconst exactProjectExpression = /^\\$\\{([^{}]+)\\}$/;\n\nconst missingVariable = (\n kind: 'environment' | 'project',\n name: string,\n options: ResolveWorkflowVariablesOptions,\n): never => {\n throw new WorkflowParseError(\n `Undefined ${kind} variable \"${name}\" in ${options.location.sourcePath}.`,\n { ...options.location, variable: name, variableKind: kind },\n );\n};\n\nconst projectVariable = (\n name: string,\n options: ResolveWorkflowVariablesOptions,\n): JsonValue => {\n if (!Object.hasOwn(options.variables ?? {}, name)) {\n return missingVariable('project', name, options);\n }\n return options.variables![name];\n};\n\nconst environmentVariable = (\n name: string,\n options: ResolveWorkflowVariablesOptions,\n): string => {\n const value = options.env?.[name];\n if (value === undefined) return missingVariable('environment', name, options);\n return value;\n};\n\nconst interpolationValue = (\n name: string,\n value: JsonValue,\n options: ResolveWorkflowVariablesOptions,\n): string => {\n if (typeof value === 'object' && value !== null) {\n throw new WorkflowParseError(\n `Project variable \"${name}\" must be a primitive when embedded in a string.`,\n {\n ...options.location,\n variable: name,\n variableKind: 'project',\n },\n );\n }\n return String(value);\n};\n\nconst resolveString = (\n value: string,\n options: ResolveWorkflowVariablesOptions,\n): JsonValue => {\n const exactEnv = exactEnvExpression.exec(value);\n if (exactEnv) return environmentVariable(exactEnv[1], options);\n const exactProject = exactProjectExpression.exec(value);\n if (exactProject) return projectVariable(exactProject[1], options);\n\n return value.replace(\n interpolationExpression,\n (_match, environmentName: string | undefined, projectName: string) =>\n environmentName\n ? environmentVariable(environmentName, options)\n : interpolationValue(\n projectName,\n projectVariable(projectName, options),\n options,\n ),\n );\n};\n\nexport const resolveWorkflowVariables = (\n value: unknown,\n options: ResolveWorkflowVariablesOptions,\n): unknown => {\n if (typeof value === 'string') return resolveString(value, options);\n if (Array.isArray(value)) {\n return value.map((child) => resolveWorkflowVariables(child, options));\n }\n if (typeof value === 'object' && value !== null) {\n return Object.fromEntries(\n Object.entries(value).map(([key, child]) => [\n key,\n resolveWorkflowVariables(child, options),\n ]),\n );\n }\n return value;\n};\n","import { readFileSync } from 'node:fs';\nimport { sep } from 'node:path';\nimport { classifyWorkflowDocumentFormat } from '@midscene/core/internal/test-runner';\nimport { JSON_SCHEMA, load as loadYaml } from 'js-yaml';\nimport type { JsonValue } from '../cli/test-project';\nimport { WorkflowParseError } from '../errors';\nimport type { NodeDefinition } from '../node/types';\nimport {\n createCaseInvocationId,\n createDocumentInvocationId,\n} from './identifiers';\nimport { normalizeSteps } from './normalize';\nimport type {\n CollectedCase,\n CollectedWorkflowDocument,\n WorkflowDocumentSource,\n} from './types';\nimport { resolveWorkflowVariables } from './variables';\n\nexport interface CollectWorkflowDocumentOptions {\n resolveNode(name: string): NodeDefinition<any, any> | undefined;\n variables?: Readonly<Record<string, JsonValue>>;\n env?: Readonly<NodeJS.ProcessEnv>;\n}\n\nconst isMapping = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport class NativeWorkflowFormatError extends WorkflowParseError {\n readonly actualFormat: 'legacy' | 'legacy-batch' | 'mixed';\n\n constructor(\n actualFormat: 'legacy' | 'legacy-batch' | 'mixed',\n sourcePath: string,\n ) {\n super(\n actualFormat === 'legacy'\n ? `Legacy tasks/flow YAML is not supported by midscene-test: ${sourcePath}. Run it with \\`midscene ${sourcePath}\\`, or migrate the file to cases/steps.`\n : actualFormat === 'legacy-batch'\n ? `Legacy YAML batch configuration is not a midscene-test workflow document: ${sourcePath}. Run it with \\`midscene --config ${sourcePath}\\`, or migrate it to a native project config.`\n : `A workflow file cannot mix legacy tasks/flow with native cases/steps or lifecycle hooks: ${sourcePath}. Keep the formats in separate files and run each file with its matching command.`,\n { sourcePath, actualFormat, expectedFormat: 'native' },\n );\n this.actualFormat = actualFormat;\n }\n}\n\nconst rejectUnknownKeys = (\n value: Record<string, unknown>,\n allowed: readonly string[],\n label: string,\n) => {\n const allowedSet = new Set(allowed);\n const unknown = Object.keys(value).filter((key) => !allowedSet.has(key));\n if (unknown.length) {\n throw new WorkflowParseError(\n `${label} has unsupported field \"${unknown[0]}\".`,\n { field: unknown[0] },\n );\n }\n};\n\nexport const createCaseId = (\n projectId: string,\n sourcePath: string,\n caseIndex: number,\n): string => createCaseInvocationId(projectId, sourcePath, caseIndex);\n\nexport const createWorkflowDocumentId = (\n projectId: string,\n sourcePath: string,\n): string => createDocumentInvocationId(projectId, sourcePath);\n\nexport function collectWorkflowDocument(\n source: WorkflowDocumentSource,\n options: CollectWorkflowDocumentOptions,\n): CollectedWorkflowDocument {\n let parsed: unknown;\n try {\n parsed = loadYaml(readFileSync(source.absolutePath, 'utf8'), {\n schema: JSON_SCHEMA,\n });\n } catch (error) {\n throw new WorkflowParseError(\n `Failed to parse workflow document \"${source.sourcePath}\".`,\n { sourcePath: source.sourcePath },\n error,\n );\n }\n\n if (!isMapping(parsed)) {\n throw new WorkflowParseError('Workflow document must be a mapping.');\n }\n const format = classifyWorkflowDocumentFormat(parsed);\n if (format === 'legacy' || format === 'legacy-batch' || format === 'mixed') {\n throw new NativeWorkflowFormatError(format, source.sourcePath);\n }\n rejectUnknownKeys(\n parsed,\n ['beforeAll', 'beforeEach', 'cases', 'afterEach', 'afterAll'],\n 'Workflow document',\n );\n if (!Array.isArray(parsed.cases) || parsed.cases.length === 0) {\n throw new WorkflowParseError(\n 'Workflow document must contain a non-empty cases array.',\n );\n }\n\n const sourcePath = source.sourcePath.split(sep).join('/');\n const resolveStepVariables = (\n step: ReturnType<typeof normalizeSteps>[number],\n phase: 'beforeAll' | 'beforeEach' | 'steps' | 'afterEach' | 'afterAll',\n stepIndex: number,\n caseIndex?: number,\n ) => ({\n ...step,\n input: resolveWorkflowVariables(step.input, {\n variables: options.variables,\n env: options.env ?? process.env,\n location: {\n projectName: source.projectName ?? source.projectId,\n sourcePath,\n phase,\n stepIndex,\n ...(caseIndex === undefined ? {} : { caseIndex }),\n },\n }) as typeof step.input,\n });\n const normalizeLifecycle = (\n phase: 'beforeAll' | 'beforeEach' | 'afterEach' | 'afterAll',\n ) => {\n const value = parsed[phase];\n if (value === undefined) return [];\n if (!Array.isArray(value)) {\n throw new WorkflowParseError(\n `Workflow document ${phase} must be an array.`,\n { phase },\n );\n }\n return normalizeSteps(value, options.resolveNode).map(\n (normalized, stepIndex) => {\n const resolved = options.resolveNode(normalized.node);\n if (!resolved) {\n throw new WorkflowParseError(\n `Workflow document ${phase} step ${stepIndex + 1} references unknown node \"${normalized.node}\".`,\n { phase, stepIndex, node: normalized.node },\n );\n }\n return resolveStepVariables(normalized, phase, stepIndex);\n },\n );\n };\n const lifecycle = {\n beforeAll: normalizeLifecycle('beforeAll'),\n beforeEach: normalizeLifecycle('beforeEach'),\n afterEach: normalizeLifecycle('afterEach'),\n afterAll: normalizeLifecycle('afterAll'),\n };\n const ids = new Set<string>();\n const cases: CollectedCase[] = parsed.cases.map((definition, caseIndex) => {\n if (!isMapping(definition)) {\n throw new WorkflowParseError(`Case ${caseIndex + 1} must be a mapping.`, {\n caseIndex,\n });\n }\n rejectUnknownKeys(\n definition,\n ['name', 'tags', 'steps'],\n `Case ${caseIndex + 1}`,\n );\n if (\n typeof definition.name !== 'string' ||\n definition.name.trim().length === 0\n ) {\n throw new WorkflowParseError(\n `Case ${caseIndex + 1} name must be a non-empty string.`,\n { caseIndex },\n );\n }\n if (!Array.isArray(definition.steps) || definition.steps.length === 0) {\n throw new WorkflowParseError(\n `Case ${caseIndex + 1} steps must be a non-empty array.`,\n { caseIndex },\n );\n }\n const tags = definition.tags ?? [];\n if (\n !Array.isArray(tags) ||\n tags.some((tag) => typeof tag !== 'string' || tag.trim().length === 0)\n ) {\n throw new WorkflowParseError(\n `Case ${caseIndex + 1} tags must be an array of non-empty strings.`,\n { caseIndex },\n );\n }\n\n const steps = normalizeSteps(definition.steps, options.resolveNode).map(\n (normalized, stepIndex) => {\n if (!options.resolveNode(normalized.node)) {\n throw new WorkflowParseError(\n `Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node \"${normalized.node}\".`,\n { caseIndex, stepIndex, node: normalized.node },\n );\n }\n return resolveStepVariables(normalized, 'steps', stepIndex, caseIndex);\n },\n );\n const caseId = createCaseId(source.projectId, sourcePath, caseIndex);\n if (ids.has(caseId)) {\n throw new WorkflowParseError(`Case id collision: ${caseId}.`, {\n caseId,\n });\n }\n ids.add(caseId);\n\n return {\n caseId,\n projectId: source.projectId,\n sourcePath,\n caseIndex,\n definition: {\n name: definition.name,\n tags: tags as string[],\n steps,\n },\n };\n });\n\n return {\n documentId: createWorkflowDocumentId(source.projectId, sourcePath),\n projectId: source.projectId,\n sourcePath,\n lifecycle,\n cases,\n } satisfies CollectedWorkflowDocument;\n}\n","import { writeFile } from 'node:fs/promises';\nimport { dirname, join, relative, sep } from 'node:path';\nimport type {\n TestRunReportDump,\n TestRunReportSourceIndex,\n TestRunReportStep,\n} from '@midscene/core';\nimport type { TestProjectRunResult } from '../cli/types';\nimport { buildTestRunReportDump } from './test-run-report';\n\nconst text = (value: string): string =>\n value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll('|', '&#124;')\n .replaceAll('[', '&#91;')\n .replaceAll(']', '&#93;')\n .replaceAll('*', '&#42;')\n .replaceAll('_', '&#95;')\n .replaceAll('`', '&#96;')\n .replaceAll('\\\\', '&#92;')\n .replace(/[\\r\\n]+/g, ' ');\n\nconst duration = (ms: number): string => {\n const seconds = Math.round(ms / 1000);\n return seconds < 60\n ? `${seconds}s`\n : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;\n};\nconst href = (from: string, target: string): string =>\n relative(dirname(from), target).split(sep).map(encodeURIComponent).join('/');\n\nexport interface TestResultMarkdownOptions {\n /** Relative or hosted URL of the HTML report. */\n reportHref?: string;\n}\n\n/** Render the same sanitized execution facts used by the HTML report. */\nexport function renderTestResultMarkdown(\n dump: TestRunReportDump,\n options: TestResultMarkdownOptions = {},\n): string {\n const { summary } = dump;\n const outcome =\n dump.status === 'success'\n ? `✅ **${summary.passed}/${summary.total} passed**`\n : `❌ **${summary.failed} failed** · ${summary.passed} passed · ${summary.notRun} not run`;\n const lines = [\n '## Midscene Test',\n '',\n `${outcome} · ${duration(dump.durationMs)}`,\n '',\n ];\n if (summary.filtered) lines.push(`**Filtered:** ${summary.filtered}`, '');\n const infrastructure = [\n summary.collectionErrors\n ? `${summary.collectionErrors} collection errors`\n : '',\n summary.documentFailures\n ? `${summary.documentFailures} document failures`\n : '',\n summary.projectFailures\n ? `${summary.projectFailures} project failures`\n : '',\n ].filter(Boolean);\n if (infrastructure.length)\n lines.push(`**Infrastructure:** ${infrastructure.join(' · ')}`, '');\n const groups: { name: string; rows: string[] }[] = [];\n const errors: string[] = [];\n const recordSteps = (label: string, steps: readonly TestRunReportStep[]) => {\n for (const step of steps) {\n if (step.status === 'failed')\n errors.push(\n `${label} / ${step.node}: ${step.error?.message ?? 'Step failed'}`,\n );\n }\n };\n for (const project of dump.projects) {\n if (project.lifecycle?.setupError)\n errors.push(`${project.name}: ${project.lifecycle.setupError.message}`);\n for (const error of project.lifecycle?.teardownErrors ?? [])\n errors.push(`${project.name}: ${error.message}`);\n for (const error of project.collectionErrors)\n errors.push(\n `${project.name} / ${error.sourcePath}: ${error.error.message}`,\n );\n for (const document of project.documents) {\n const name = document.sourcePath\n .replace(/\\\\/g, '/')\n .replace(/\\.(?:yaml|yml)$/, '');\n const group = {\n name: dump.projects.length > 1 ? `${project.name} / ${name}` : name,\n rows: [] as string[],\n };\n groups.push(group);\n recordSteps(`${project.name} / ${document.sourcePath}`, [\n ...document.beforeAll,\n ...document.afterAll,\n ]);\n for (const error of document.teardownErrors ?? [])\n errors.push(`${document.sourcePath}: ${error.message}`);\n for (const failure of document.hostErrors ?? [])\n errors.push(\n `${document.sourcePath} / ${failure.phase}: ${failure.error.message}`,\n );\n for (const testCase of document.cases) {\n const attempt = testCase.attempts.at(-1);\n const steps = attempt\n ? [...attempt.beforeEach, ...attempt.steps, ...attempt.afterEach]\n : [];\n const failedSteps = steps.filter((step) => step.status === 'failed');\n const step =\n failedSteps.at(-1) ??\n [...steps].reverse().find((item) => item.agentDetails?.length) ??\n steps.at(-1);\n const route = new URLSearchParams({\n 'runner-page': 'case',\n 'runner-project': project.projectId,\n 'runner-case': testCase.caseId,\n ...(step ? { 'runner-step': step.id } : {}),\n });\n const target = options.reportHref\n ? `${options.reportHref}#${route}`\n : undefined;\n const name = target\n ? `[${text(testCase.name)}](<${target}>)`\n : text(testCase.name);\n group.rows.push(\n ` - ${testCase.status === 'success' ? '✅' : testCase.status === 'not-run' ? '⏭️' : '❌'} ${name}`,\n );\n }\n }\n }\n for (const diagnostic of dump.diagnostics ?? []) {\n if (diagnostic.level === 'error') errors.push(diagnostic.message);\n }\n if (errors.length)\n lines.push(\n '### Execution errors',\n '',\n ...errors.map((error) => `- ${text(error)}`),\n '',\n );\n for (const group of groups) {\n if (group.rows.length) lines.push(`- ${text(group.name)}`, ...group.rows);\n }\n if (!groups.some((group) => group.rows.length))\n lines.push('No cases were reported.');\n if (options.reportHref)\n lines.push(\n '',\n `[Open the Midscene Test report ↗](<${options.reportHref}>)`,\n );\n return `${lines.join('\\n')}\\n`;\n}\n\nconst emptyIndex = (): TestRunReportSourceIndex => ({\n sources: [],\n metrics: {\n modelCallCount: 0,\n modelTimeMs: 0,\n promptTokens: 0,\n cachedInputTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n },\n});\n\n/** Write a per-run Markdown artifact even when HTML publication fails or is disabled. */\nexport async function writeTestResultMarkdown(\n result: TestProjectRunResult,\n index: TestRunReportSourceIndex = emptyIndex(),\n): Promise<string> {\n const output = join(dirname(result.summaryPath), 'result.md');\n const dump = buildTestRunReportDump(result, index);\n await writeFile(\n output,\n renderTestResultMarkdown(dump, {\n ...(result.reportPath\n ? { reportHref: href(output, result.reportPath) }\n : {}),\n }),\n );\n return output;\n}\n"],"names":["CaseRunner","CoreCaseRunner","createCaseRunner","coreCreateCaseRunner","createDocumentRuntime","coreCreateDocumentRuntime","NodeRegistry","CoreNodeRegistry","runWorkflowDocument","coreRunWorkflowDocument","defineNode","coreDefineNode","createCaseInvocationId","projectId","sourcePath","caseIndex","invocationIndex","createHash","JSON","createDocumentInvocationId","normalizeStep","coreNormalizeStep","normalizeSteps","coreNormalizeSteps","interpolationExpression","exactEnvExpression","exactProjectExpression","missingVariable","kind","name","options","WorkflowParseError","projectVariable","Object","environmentVariable","value","undefined","interpolationValue","String","resolveString","exactEnv","exactProject","_match","environmentName","projectName","resolveWorkflowVariables","Array","child","key","isMapping","NativeWorkflowFormatError","actualFormat","rejectUnknownKeys","allowed","label","allowedSet","Set","unknown","createCaseId","createWorkflowDocumentId","collectWorkflowDocument","source","parsed","loadYaml","readFileSync","JSON_SCHEMA","error","format","classifyWorkflowDocumentFormat","sep","resolveStepVariables","step","phase","stepIndex","process","normalizeLifecycle","normalized","resolved","lifecycle","ids","cases","definition","tags","tag","steps","caseId","text","duration","ms","seconds","Math","href","from","target","relative","dirname","encodeURIComponent","renderTestResultMarkdown","dump","summary","outcome","lines","infrastructure","Boolean","groups","errors","recordSteps","project","document","group","failure","testCase","attempt","failedSteps","item","route","URLSearchParams","diagnostic","emptyIndex","writeTestResultMarkdown","result","index","output","join","buildTestRunReportDump","writeFile"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../../src/engine/case-runner.ts","../../src/engine/document-runtime.ts","../../src/engine/registry.ts","../../src/engine/run-workflow-document.ts","../../src/node/define-node.ts","../../src/parser/identifiers.ts","../../src/parser/normalize.ts","../../src/parser/source.ts","../../src/parser/variables.ts","../../src/parser/collect.ts"],"sourcesContent":["import {\n CaseRunner as CoreCaseRunner,\n createCaseRunner as coreCreateCaseRunner,\n} from '@midscene/core/internal/test-runner';\nimport type { NodeDefinition } from '../node/types';\nimport type { CaseInput } from '../parser/types';\nimport type { NodeRegistry } from './registry';\nimport type { CaseRunResult, CaseRunnerOptions } from './types';\n\nexport type CaseRunner<TContext = undefined> = Omit<\n CoreCaseRunner<TContext>,\n 'run' | 'register' | 'registry'\n> & {\n readonly registry: NodeRegistry;\n register<TInput, TData>(\n node: NodeDefinition<TInput, TData, TContext>,\n ): CaseRunner<TContext>;\n run(input: CaseInput): Promise<CaseRunResult>;\n};\nexport const CaseRunner: {\n new <TContext = undefined>(\n options?: CaseRunnerOptions<TContext>,\n ): CaseRunner<TContext>;\n} = CoreCaseRunner;\nexport const createCaseRunner: <TContext = undefined>(\n options?: CaseRunnerOptions<TContext>,\n) => CaseRunner<TContext> = coreCreateCaseRunner;\n","import { createDocumentRuntime as coreCreateDocumentRuntime } from '@midscene/core/internal/test-runner';\nimport type { CollectedWorkflowDocument } from '../parser/types';\nimport type {\n CreateDocumentRuntimeOptions,\n WorkflowDocumentRuntime,\n} from './types';\n\nexport const createDocumentRuntime: <TContext = undefined>(\n document: CollectedWorkflowDocument,\n options: CreateDocumentRuntimeOptions<TContext>,\n) => WorkflowDocumentRuntime<TContext> = coreCreateDocumentRuntime;\n","import { NodeRegistry as CoreNodeRegistry } from '@midscene/core/internal/test-runner';\nimport type { NodeDefinition } from '../node/types';\n\nexport type NodeRegistry = Omit<\n CoreNodeRegistry,\n 'get' | 'require' | 'register' | 'definitions'\n> & {\n get(name: string): NodeDefinition<any, any> | undefined;\n require(name: string): NodeDefinition<any, any>;\n register<TInput, TData>(node: NodeDefinition<TInput, TData>): NodeRegistry;\n definitions(): readonly NodeDefinition<any, any, any>[];\n};\nexport const NodeRegistry: {\n new (nodes?: readonly NodeDefinition<any, any>[]): NodeRegistry;\n} = CoreNodeRegistry;\n","import { runWorkflowDocument as coreRunWorkflowDocument } from '@midscene/core/internal/test-runner';\nimport type { CollectedWorkflowDocument } from '../parser/types';\nimport type {\n RunWorkflowDocumentOptions,\n WorkflowDocumentExecutionResult,\n} from './types';\n\nexport const runWorkflowDocument: <TContext = undefined>(\n document: CollectedWorkflowDocument,\n options: RunWorkflowDocumentOptions<TContext>,\n) => Promise<WorkflowDocumentExecutionResult> = coreRunWorkflowDocument;\n","import { defineNode as coreDefineNode } from '@midscene/core/internal/test-runner';\nimport type {\n DefineNodeOptions,\n DefineNodeWithSchemaOptions,\n NodeDefinition,\n NodeDefinitionWithSchema,\n NodeInputSchema,\n} from './types';\n\n// A type-only facade: validation and execution still belong to the shared kernel.\ninterface DefineNativeNode {\n <TSchema extends NodeInputSchema, TData = unknown, TContext = unknown>(\n options: DefineNodeWithSchemaOptions<TSchema, TData, TContext>,\n ): NodeDefinitionWithSchema<TSchema, TData, TContext>;\n <TInput = unknown, TData = unknown, TContext = unknown>(\n options: DefineNodeOptions<TInput, TData, TContext>,\n ): NodeDefinition<TInput, TData, TContext>;\n}\nexport const defineNode: DefineNativeNode = coreDefineNode;\n","import { createHash } from 'node:crypto';\n\n/** Internal invocation identity keeps repeated legacy files distinct. */\nexport const createCaseInvocationId = (\n projectId: string,\n sourcePath: string,\n caseIndex: number,\n invocationIndex = 0,\n): string =>\n createHash('sha256')\n .update(\n JSON.stringify([\n projectId,\n sourcePath,\n caseIndex,\n ...(invocationIndex ? [invocationIndex] : []),\n ]),\n )\n .digest('hex');\n\nexport const createDocumentInvocationId = (\n projectId: string,\n sourcePath: string,\n invocationIndex = 0,\n): string =>\n createHash('sha256')\n .update(\n JSON.stringify([\n projectId,\n sourcePath,\n ...(invocationIndex ? [invocationIndex] : []),\n ]),\n )\n .digest('hex');\n","import {\n type ResolveNodeForNormalization,\n normalizeStep as coreNormalizeStep,\n normalizeSteps as coreNormalizeSteps,\n} from '@midscene/core/internal/test-runner';\nimport type { NormalizedStep } from './types';\nexport type { ResolveNodeForNormalization } from '@midscene/core/internal/test-runner';\n\nexport const normalizeStep: (\n value: unknown,\n index?: number,\n resolveNode?: ResolveNodeForNormalization,\n) => NormalizedStep = coreNormalizeStep;\nexport const normalizeSteps: (\n values: unknown,\n resolveNode?: ResolveNodeForNormalization,\n) => NormalizedStep[] = coreNormalizeSteps;\n","import type { TestRunReportValue } from '@midscene/core';\nimport {\n isReportSecretKey,\n sanitizeReportText,\n} from '@midscene/core/internal/test-runner';\nimport { JSON_SCHEMA, load } from 'js-yaml';\n\ninterface SourceNode {\n start: number;\n end: number;\n value: unknown;\n children: SourceNode[];\n}\n\n/** Keep author formatting while redacting values by parsed YAML keys, including aliases. */\nexport function parseWorkflowSource(source: string): {\n parsed: unknown;\n sourceYaml: TestRunReportValue;\n} {\n const stack: SourceNode[] = [];\n const nodes: SourceNode[] = [];\n const sensitiveNodes = new Set<SourceNode>();\n const sensitiveValues = new Set<unknown>();\n const redactedPaths = new Set<string>();\n // js-yaml removes the BOM before reporting source positions.\n const sourceOffset = source.charCodeAt(0) === 0xfeff ? 1 : 0;\n const parsed = load(source, {\n schema: JSON_SCHEMA,\n listener(event, state) {\n if (event === 'open') {\n stack.push({\n start: state.position + sourceOffset,\n end: 0,\n value: null,\n children: [],\n });\n return;\n }\n const node = stack.pop()!;\n node.end = state.position + sourceOffset;\n node.value = state.result;\n stack.at(-1)?.children.push(node);\n nodes.push(node);\n if (state.kind !== 'mapping') return;\n for (let index = 0; index < node.children.length; index += 2) {\n const key = node.children[index].value;\n const value = node.children[index + 1];\n if (typeof key !== 'string' || !isReportSecretKey(key) || !value)\n continue;\n sensitiveNodes.add(value);\n if (value.value !== null) sensitiveValues.add(value.value);\n redactedPaths.add(`$..[${JSON.stringify(key)}]`);\n }\n },\n });\n // Redact the definition as well when a secret value refers to a YAML anchor.\n const ranges = nodes\n .filter(\n (node) => sensitiveNodes.has(node) || sensitiveValues.has(node.value),\n )\n .sort((left, right) => left.start - right.start || right.end - left.end);\n let offset = 0;\n let redacted = '';\n for (const range of ranges) {\n if (range.start < offset) continue;\n const text = source.slice(range.start, range.end);\n const leading = text.match(/^\\s*/)?.[0] ?? '';\n const trailing = text.slice(leading.length).match(/\\s*$/)?.[0] ?? '';\n redacted += `${source.slice(offset, range.start)}${leading}\"[REDACTED]\"${trailing}`;\n offset = range.end;\n }\n redacted += source.slice(offset);\n const value = sanitizeReportText(redacted, 64 * 1024);\n return {\n parsed,\n sourceYaml: {\n value,\n ...(redactedPaths.size ? { redactedPaths: [...redactedPaths] } : {}),\n ...(value !== redacted ? { truncatedPaths: ['$'] } : {}),\n },\n };\n}\n","import type { JsonValue } from '../cli/test-project';\nimport { WorkflowParseError } from '../errors';\n\nexport interface VariableResolutionLocation {\n projectName: string;\n sourcePath: string;\n phase: 'beforeAll' | 'beforeEach' | 'steps' | 'afterEach' | 'afterAll';\n stepIndex: number;\n caseIndex?: number;\n}\n\nexport interface ResolveWorkflowVariablesOptions {\n variables?: Readonly<Record<string, JsonValue>>;\n env?: Readonly<NodeJS.ProcessEnv>;\n location: VariableResolutionLocation;\n}\n\nconst interpolationExpression =\n /\\$\\{\\{([A-Za-z_][A-Za-z0-9_]*)\\}\\}|\\$\\{([^{}]+)\\}/g;\nconst exactEnvExpression = /^\\$\\{\\{([A-Za-z_][A-Za-z0-9_]*)\\}\\}$/;\nconst exactProjectExpression = /^\\$\\{([^{}]+)\\}$/;\n\nconst missingVariable = (\n kind: 'environment' | 'project',\n name: string,\n options: ResolveWorkflowVariablesOptions,\n): never => {\n throw new WorkflowParseError(\n `Undefined ${kind} variable \"${name}\" in ${options.location.sourcePath}.`,\n { ...options.location, variable: name, variableKind: kind },\n );\n};\n\nconst projectVariable = (\n name: string,\n options: ResolveWorkflowVariablesOptions,\n): JsonValue => {\n if (!Object.hasOwn(options.variables ?? {}, name)) {\n return missingVariable('project', name, options);\n }\n return options.variables![name];\n};\n\nconst environmentVariable = (\n name: string,\n options: ResolveWorkflowVariablesOptions,\n): string => {\n const value = options.env?.[name];\n if (value === undefined) return missingVariable('environment', name, options);\n return value;\n};\n\nconst interpolationValue = (\n name: string,\n value: JsonValue,\n options: ResolveWorkflowVariablesOptions,\n): string => {\n if (typeof value === 'object' && value !== null) {\n throw new WorkflowParseError(\n `Project variable \"${name}\" must be a primitive when embedded in a string.`,\n {\n ...options.location,\n variable: name,\n variableKind: 'project',\n },\n );\n }\n return String(value);\n};\n\nconst resolveString = (\n value: string,\n options: ResolveWorkflowVariablesOptions,\n): JsonValue => {\n const exactEnv = exactEnvExpression.exec(value);\n if (exactEnv) return environmentVariable(exactEnv[1], options);\n const exactProject = exactProjectExpression.exec(value);\n if (exactProject) return projectVariable(exactProject[1], options);\n\n return value.replace(\n interpolationExpression,\n (_match, environmentName: string | undefined, projectName: string) =>\n environmentName\n ? environmentVariable(environmentName, options)\n : interpolationValue(\n projectName,\n projectVariable(projectName, options),\n options,\n ),\n );\n};\n\nexport const resolveWorkflowVariables = (\n value: unknown,\n options: ResolveWorkflowVariablesOptions,\n): unknown => {\n if (typeof value === 'string') return resolveString(value, options);\n if (Array.isArray(value)) {\n return value.map((child) => resolveWorkflowVariables(child, options));\n }\n if (typeof value === 'object' && value !== null) {\n return Object.fromEntries(\n Object.entries(value).map(([key, child]) => [\n key,\n resolveWorkflowVariables(child, options),\n ]),\n );\n }\n return value;\n};\n","import { readFileSync } from 'node:fs';\nimport { sep } from 'node:path';\nimport { classifyWorkflowDocumentFormat } from '@midscene/core/internal/test-runner';\nimport type { JsonValue } from '../cli/test-project';\nimport { WorkflowParseError } from '../errors';\nimport type { NodeDefinition } from '../node/types';\nimport {\n createCaseInvocationId,\n createDocumentInvocationId,\n} from './identifiers';\nimport { normalizeSteps } from './normalize';\nimport { parseWorkflowSource } from './source';\nimport type {\n CollectedCase,\n CollectedWorkflowDocument,\n WorkflowDocumentSource,\n} from './types';\nimport { resolveWorkflowVariables } from './variables';\n\nexport interface CollectWorkflowDocumentOptions {\n resolveNode(name: string): NodeDefinition<any, any> | undefined;\n variables?: Readonly<Record<string, JsonValue>>;\n env?: Readonly<NodeJS.ProcessEnv>;\n}\n\nconst isMapping = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport class NativeWorkflowFormatError extends WorkflowParseError {\n readonly actualFormat: 'legacy' | 'legacy-batch' | 'mixed';\n\n constructor(\n actualFormat: 'legacy' | 'legacy-batch' | 'mixed',\n sourcePath: string,\n ) {\n super(\n actualFormat === 'legacy'\n ? `Legacy tasks/flow YAML is not supported by midscene-test: ${sourcePath}. Run it with \\`midscene ${sourcePath}\\`, or migrate the file to cases/steps.`\n : actualFormat === 'legacy-batch'\n ? `Legacy YAML batch configuration is not a midscene-test workflow document: ${sourcePath}. Run it with \\`midscene --config ${sourcePath}\\`, or migrate it to a native project config.`\n : `A workflow file cannot mix legacy tasks/flow with native cases/steps or lifecycle hooks: ${sourcePath}. Keep the formats in separate files and run each file with its matching command.`,\n { sourcePath, actualFormat, expectedFormat: 'native' },\n );\n this.actualFormat = actualFormat;\n }\n}\n\nconst rejectUnknownKeys = (\n value: Record<string, unknown>,\n allowed: readonly string[],\n label: string,\n) => {\n const allowedSet = new Set(allowed);\n const unknown = Object.keys(value).filter((key) => !allowedSet.has(key));\n if (unknown.length) {\n throw new WorkflowParseError(\n `${label} has unsupported field \"${unknown[0]}\".`,\n { field: unknown[0] },\n );\n }\n};\n\nexport const createCaseId = (\n projectId: string,\n sourcePath: string,\n caseIndex: number,\n): string => createCaseInvocationId(projectId, sourcePath, caseIndex);\n\nexport const createWorkflowDocumentId = (\n projectId: string,\n sourcePath: string,\n): string => createDocumentInvocationId(projectId, sourcePath);\n\nexport function collectWorkflowDocument(\n source: WorkflowDocumentSource,\n options: CollectWorkflowDocumentOptions,\n): CollectedWorkflowDocument {\n let snapshot: ReturnType<typeof parseWorkflowSource>;\n try {\n snapshot = parseWorkflowSource(readFileSync(source.absolutePath, 'utf8'));\n } catch (error) {\n throw new WorkflowParseError(\n `Failed to parse workflow document \"${source.sourcePath}\".`,\n { sourcePath: source.sourcePath },\n error,\n );\n }\n const { parsed, sourceYaml } = snapshot;\n\n if (!isMapping(parsed)) {\n throw new WorkflowParseError('Workflow document must be a mapping.');\n }\n const format = classifyWorkflowDocumentFormat(parsed);\n if (format === 'legacy' || format === 'legacy-batch' || format === 'mixed') {\n throw new NativeWorkflowFormatError(format, source.sourcePath);\n }\n rejectUnknownKeys(\n parsed,\n ['beforeAll', 'beforeEach', 'cases', 'afterEach', 'afterAll'],\n 'Workflow document',\n );\n if (!Array.isArray(parsed.cases) || parsed.cases.length === 0) {\n throw new WorkflowParseError(\n 'Workflow document must contain a non-empty cases array.',\n );\n }\n\n const sourcePath = source.sourcePath.split(sep).join('/');\n const resolveStepVariables = (\n step: ReturnType<typeof normalizeSteps>[number],\n phase: 'beforeAll' | 'beforeEach' | 'steps' | 'afterEach' | 'afterAll',\n stepIndex: number,\n caseIndex?: number,\n ) => ({\n ...step,\n templateInput: step.input,\n input: resolveWorkflowVariables(step.input, {\n variables: options.variables,\n env: options.env ?? process.env,\n location: {\n projectName: source.projectName ?? source.projectId,\n sourcePath,\n phase,\n stepIndex,\n ...(caseIndex === undefined ? {} : { caseIndex }),\n },\n }) as typeof step.input,\n });\n const normalizeLifecycle = (\n phase: 'beforeAll' | 'beforeEach' | 'afterEach' | 'afterAll',\n ) => {\n const value = parsed[phase];\n if (value === undefined) return [];\n if (!Array.isArray(value)) {\n throw new WorkflowParseError(\n `Workflow document ${phase} must be an array.`,\n { phase },\n );\n }\n return normalizeSteps(value, options.resolveNode).map(\n (normalized, stepIndex) => {\n const resolved = options.resolveNode(normalized.node);\n if (!resolved) {\n throw new WorkflowParseError(\n `Workflow document ${phase} step ${stepIndex + 1} references unknown node \"${normalized.node}\".`,\n { phase, stepIndex, node: normalized.node },\n );\n }\n return resolveStepVariables(normalized, phase, stepIndex);\n },\n );\n };\n const lifecycle = {\n beforeAll: normalizeLifecycle('beforeAll'),\n beforeEach: normalizeLifecycle('beforeEach'),\n afterEach: normalizeLifecycle('afterEach'),\n afterAll: normalizeLifecycle('afterAll'),\n };\n const ids = new Set<string>();\n const cases: CollectedCase[] = parsed.cases.map((definition, caseIndex) => {\n if (!isMapping(definition)) {\n throw new WorkflowParseError(`Case ${caseIndex + 1} must be a mapping.`, {\n caseIndex,\n });\n }\n rejectUnknownKeys(\n definition,\n ['name', 'tags', 'steps'],\n `Case ${caseIndex + 1}`,\n );\n if (\n typeof definition.name !== 'string' ||\n definition.name.trim().length === 0\n ) {\n throw new WorkflowParseError(\n `Case ${caseIndex + 1} name must be a non-empty string.`,\n { caseIndex },\n );\n }\n if (!Array.isArray(definition.steps) || definition.steps.length === 0) {\n throw new WorkflowParseError(\n `Case ${caseIndex + 1} steps must be a non-empty array.`,\n { caseIndex },\n );\n }\n const tags = definition.tags ?? [];\n if (\n !Array.isArray(tags) ||\n tags.some((tag) => typeof tag !== 'string' || tag.trim().length === 0)\n ) {\n throw new WorkflowParseError(\n `Case ${caseIndex + 1} tags must be an array of non-empty strings.`,\n { caseIndex },\n );\n }\n\n const steps = normalizeSteps(definition.steps, options.resolveNode).map(\n (normalized, stepIndex) => {\n if (!options.resolveNode(normalized.node)) {\n throw new WorkflowParseError(\n `Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node \"${normalized.node}\".`,\n { caseIndex, stepIndex, node: normalized.node },\n );\n }\n return resolveStepVariables(normalized, 'steps', stepIndex, caseIndex);\n },\n );\n const caseId = createCaseId(source.projectId, sourcePath, caseIndex);\n if (ids.has(caseId)) {\n throw new WorkflowParseError(`Case id collision: ${caseId}.`, {\n caseId,\n });\n }\n ids.add(caseId);\n\n return {\n caseId,\n projectId: source.projectId,\n sourcePath,\n caseIndex,\n definition: {\n name: definition.name,\n tags: tags as string[],\n steps,\n },\n };\n });\n\n return {\n documentId: createWorkflowDocumentId(source.projectId, sourcePath),\n projectId: source.projectId,\n sourcePath,\n sourceYaml,\n lifecycle,\n cases,\n } satisfies CollectedWorkflowDocument;\n}\n"],"names":["CaseRunner","CoreCaseRunner","createCaseRunner","coreCreateCaseRunner","createDocumentRuntime","coreCreateDocumentRuntime","NodeRegistry","CoreNodeRegistry","runWorkflowDocument","coreRunWorkflowDocument","defineNode","coreDefineNode","createCaseInvocationId","projectId","sourcePath","caseIndex","invocationIndex","createHash","JSON","createDocumentInvocationId","normalizeStep","coreNormalizeStep","normalizeSteps","coreNormalizeSteps","parseWorkflowSource","source","stack","nodes","sensitiveNodes","Set","sensitiveValues","redactedPaths","sourceOffset","parsed","load","JSON_SCHEMA","event","state","node","index","key","value","isReportSecretKey","ranges","left","right","offset","redacted","range","text","leading","trailing","sanitizeReportText","interpolationExpression","exactEnvExpression","exactProjectExpression","missingVariable","kind","name","options","WorkflowParseError","projectVariable","Object","environmentVariable","undefined","interpolationValue","String","resolveString","exactEnv","exactProject","_match","environmentName","projectName","resolveWorkflowVariables","Array","child","isMapping","NativeWorkflowFormatError","actualFormat","rejectUnknownKeys","allowed","label","allowedSet","unknown","createCaseId","createWorkflowDocumentId","collectWorkflowDocument","snapshot","readFileSync","error","sourceYaml","format","classifyWorkflowDocumentFormat","sep","resolveStepVariables","step","phase","stepIndex","process","normalizeLifecycle","normalized","resolved","lifecycle","ids","cases","definition","tags","tag","steps","caseId"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { actionInputSchema, aiActInputSchema, aiActOptionsInputSchema, aiAssertInputSchema, aiAssertOptionsInputSchema, aiDragAndDropInputSchema, aiInputInputSchema, aiKeyboardPressInputSchema, aiLongPressInputSchema, aiPinchInputSchema, aiQueryInputSchema, aiScrollInputSchema, aiTapInputSchema, aiWaitForInputSchema, insightInputSchema, insightOptionsInputSchema, javascriptInputSchema, locateOptionsInputSchema, promptImageInputSchema, recordToReportInputSchema, recordToReportOptionsInputSchema, reportScreenshotInputSchema, runGherkinScenarioInputSchema, setAIContextInputSchema, structuredUserPromptInputSchema, userPromptInputSchema } from "@midscene/core/agent/test";
2
- import { NodeDefinitionError, assertResourceAvailable, defineNode, trackResourceOperation } from "@midscene/core/internal/test-runner";
2
+ import { NodeDefinitionError, admitResourceOperation, collectTestRunReportMetrics, defineNode, hasPendingResourceOperation, retainResourceForScope, trackResourceOperation } from "@midscene/core/internal/test-runner";
3
3
  import { enterYamlAction, enterYamlExecution, runInYamlExecutionContext } from "@midscene/core/internal/yaml-runtime";
4
4
  import { getDebug } from "@midscene/shared/logger";
5
5
  import { z } from "zod/v4";
@@ -31,7 +31,38 @@ const waitFor = async (durationMs, signal)=>{
31
31
  const createAgentTestRunnerNodes = (definitions, getAgent)=>{
32
32
  const runAgentCall = async (node, ctx, agent, call, captureExecutions = true)=>runInYamlExecutionContext(async ()=>{
33
33
  const scopeId = 'case' === ctx.scope ? ctx.case.runId : ctx.document.documentRunId;
34
- const isCleanupOwner = assertResourceAvailable(agent, true, ctx.signal);
34
+ const isCleanupOwner = admitResourceOperation(agent, ctx.signal);
35
+ if (captureExecutions) ctx.report.onSnapshot(async (signal, traces)=>{
36
+ signal.throwIfAborted();
37
+ const partial = hasPendingResourceOperation(agent);
38
+ const capturedAt = new Date().toISOString();
39
+ let source = await agent._createReportSource?.(scopeId, traces.map((trace)=>trace.executionId));
40
+ if (!source) {
41
+ const path = await agent.flushReport?.();
42
+ if (path) source = {
43
+ sourcePath: path,
44
+ collection: {
45
+ status: 'partial',
46
+ capturedAt,
47
+ reason: 'This provider exposes live HTML without immutable snapshot support.'
48
+ }
49
+ };
50
+ }
51
+ signal.throwIfAborted();
52
+ if (!source) throw new Error('Agent did not return a report snapshot.');
53
+ return {
54
+ ...source,
55
+ collection: {
56
+ status: partial || source.collection?.status === 'partial' ? 'partial' : 'complete',
57
+ capturedAt: source.collection?.capturedAt ?? capturedAt,
58
+ ...source.collection?.reason ? {
59
+ reason: source.collection.reason
60
+ } : partial ? {
61
+ reason: 'An Agent operation was still running at the snapshot cutoff.'
62
+ } : {}
63
+ }
64
+ };
65
+ });
35
66
  const yamlBoundary = enterYamlExecution(agent, {
36
67
  allowIndependentOverlap: isCleanupOwner
37
68
  });
@@ -54,7 +85,19 @@ const createAgentTestRunnerNodes = (definitions, getAgent)=>{
54
85
  }
55
86
  ctx.report.addTrace({
56
87
  type: 'midscene-execution',
57
- executionId: execution.id
88
+ executionId: execution.id,
89
+ ...execution.tasks ? {
90
+ metrics: collectTestRunReportMetrics({
91
+ executions: [
92
+ {
93
+ id: execution.id,
94
+ name: node,
95
+ logTime: 0,
96
+ tasks: execution.tasks
97
+ }
98
+ ]
99
+ })
100
+ } : {}
58
101
  });
59
102
  });
60
103
  if (ctx.signal.aborted) stopListening();
@@ -108,34 +151,14 @@ function createMidsceneNodes(options) {
108
151
  if (!agents) {
109
152
  agents = new Set();
110
153
  agentsByScope.set(runId, agents);
111
- const ownedScope = agents;
112
154
  ctx.onTeardown(async ()=>{
113
- const errors = [];
114
- const reportPaths = [];
115
- const reportSources = [];
116
155
  try {
117
- for (const agent of ownedScope)try {
118
- const path = await agent.flushReport?.();
119
- if (path) reportPaths.push(path);
120
- const source = await agent._createReportSource?.(runId);
121
- if (source) reportSources.push(source);
122
- } catch (error) {
123
- errors.push(error);
124
- }
125
- try {
126
- const released = await options.agentProvider?.releaseAgent?.(runId);
127
- if (released?.reportPath && !reportPaths.includes(released.reportPath)) reportPaths.push(released.reportPath);
128
- } catch (error) {
129
- errors.push(error);
130
- }
131
- if (1 === errors.length) throw errors[0];
132
- if (errors.length) throw new AggregateError(errors, 'Agent report publication and release failed');
133
- return {
134
- reportPaths,
135
- ...reportSources.length ? {
136
- reportSources
137
- } : {}
138
- };
156
+ const released = await options.agentProvider?.releaseAgent?.(runId);
157
+ return released?.reportPath ? {
158
+ reportPaths: [
159
+ released.reportPath
160
+ ]
161
+ } : void 0;
139
162
  } finally{
140
163
  agentsByScope.delete(runId);
141
164
  }
@@ -144,10 +167,11 @@ function createMidsceneNodes(options) {
144
167
  const scopeAgents = agents;
145
168
  return trackResourceOperation({}, Promise.resolve().then(async ()=>{
146
169
  const agent = options.agentProvider ? await options.agentProvider.getAgent(runId, ctx) : await options.getAgent(ctx);
170
+ retainResourceForScope(agent, ctx.signal);
147
171
  agent._prepareForTestRunner?.();
148
172
  scopeAgents.add(agent);
149
173
  return agent;
150
- }), ctx.signal);
174
+ }), ctx.signal, 'acquisition');
151
175
  };
152
176
  const agentDefinitions = options.agentClass.getTestRunnerNodeDefinitions();
153
177
  return [
@@ -1 +1 @@
1
- {"version":3,"file":"midscene/index.mjs","sources":["../../../src/node/define-node.ts","../../../src/midscene/index.ts"],"sourcesContent":["import { defineNode as coreDefineNode } from '@midscene/core/internal/test-runner';\nimport type {\n DefineNodeOptions,\n DefineNodeWithSchemaOptions,\n NodeDefinition,\n NodeDefinitionWithSchema,\n NodeInputSchema,\n} from './types';\n\n// A type-only facade: validation and execution still belong to the shared kernel.\ninterface DefineNativeNode {\n <TSchema extends NodeInputSchema, TData = unknown, TContext = unknown>(\n options: DefineNodeWithSchemaOptions<TSchema, TData, TContext>,\n ): NodeDefinitionWithSchema<TSchema, TData, TContext>;\n <TInput = unknown, TData = unknown, TContext = unknown>(\n options: DefineNodeOptions<TInput, TData, TContext>,\n ): NodeDefinition<TInput, TData, TContext>;\n}\nexport const defineNode: DefineNativeNode = coreDefineNode;\n","import {\n type AgentTestRunnerNodeDefinition,\n type AgentTestRunnerNodeProvider,\n type CommonAgentTestRunnerApi,\n aiActInputSchema,\n aiActOptionsInputSchema,\n aiAssertInputSchema,\n aiAssertOptionsInputSchema,\n aiTapInputSchema,\n insightInputSchema,\n insightOptionsInputSchema,\n locateOptionsInputSchema,\n promptImageInputSchema,\n recordToReportInputSchema,\n recordToReportOptionsInputSchema,\n reportScreenshotInputSchema,\n structuredUserPromptInputSchema,\n userPromptInputSchema,\n} from '@midscene/core/agent/test';\nimport {\n type WorkflowReportSource,\n assertResourceAvailable,\n trackResourceOperation,\n} from '@midscene/core/internal/test-runner';\nimport {\n enterYamlAction,\n enterYamlExecution,\n runInYamlExecutionContext,\n} from '@midscene/core/internal/yaml-runtime';\nimport { getDebug } from '@midscene/shared/logger';\nimport { z } from 'zod/v4';\nimport type { Awaitable } from '../engine/types';\nimport { NodeDefinitionError } from '../errors';\nimport { defineNode } from '../node/define-node';\nimport type { NodeDefinition, NodeExecutionContext } from '../node/types';\n\nexport type MidsceneUIAgent = CommonAgentTestRunnerApi & {\n _prepareForTestRunner?(): void;\n _createReportSource?(\n scopeId: string,\n ): Promise<WorkflowReportSource | undefined>;\n flushReport?(): Promise<string | undefined>;\n addDumpUpdateListener?(\n listener: (dump: string, execution?: MidsceneExecutionRef) => void,\n ): () => void;\n};\n\nexport {\n actionInputSchema,\n aiDragAndDropInputSchema,\n aiInputInputSchema,\n aiKeyboardPressInputSchema,\n aiLongPressInputSchema,\n aiPinchInputSchema,\n aiQueryInputSchema,\n aiScrollInputSchema,\n aiWaitForInputSchema,\n javascriptInputSchema,\n runGherkinScenarioInputSchema,\n setAIContextInputSchema,\n} from '@midscene/core/agent/test';\nexport type {\n ActionNodeInput,\n AiDragAndDropNodeInput,\n AiInputNodeInput,\n AiKeyboardPressNodeInput,\n AiLongPressNodeInput,\n AiPinchNodeInput,\n AiQueryNodeInput,\n AiScrollNodeInput,\n AiWaitForNodeInput,\n JavascriptNodeInput,\n RunGherkinScenarioNodeInput,\n SetAIContextNodeInput,\n} from '@midscene/core/agent/test';\n\nexport {\n aiActInputSchema,\n aiActOptionsInputSchema,\n aiAssertInputSchema,\n aiAssertOptionsInputSchema,\n aiTapInputSchema,\n insightInputSchema,\n insightOptionsInputSchema,\n locateOptionsInputSchema,\n promptImageInputSchema,\n recordToReportInputSchema,\n recordToReportOptionsInputSchema,\n reportScreenshotInputSchema,\n structuredUserPromptInputSchema,\n userPromptInputSchema,\n};\n\nexport interface MidsceneExecutionRef {\n id?: string;\n}\n\nexport interface AgentProvider<TContext> {\n getAgent(\n runId: string,\n execution: NodeExecutionContext<unknown, TContext>,\n ): Awaitable<MidsceneUIAgent>;\n // biome-ignore lint/suspicious/noConfusingVoidType: providers without a report intentionally return void.\n releaseAgent?(runId: string): Awaitable<AgentReleaseResult | void>;\n dispose?(): Awaitable<void>;\n}\n\nexport interface AgentReleaseResult {\n /** Absolute path to the finalized report for this Agent scope. */\n reportPath?: string;\n}\n\nexport const waitInputSchema = z.strictObject({\n duration: z.number().positive().describe('How long to wait.'),\n unit: z\n .enum(['ms', 's', 'min'])\n .default('ms')\n .describe('Duration unit: milliseconds, seconds, or minutes.'),\n});\n\nexport type AiActNodeInput = z.infer<typeof aiActInputSchema>;\nexport type AiAssertNodeInput = z.infer<typeof aiAssertInputSchema>;\nexport type AiTapNodeInput = z.infer<typeof aiTapInputSchema>;\nexport type InsightNodeInput = z.infer<typeof insightInputSchema>;\nexport type RecordToReportNodeInput = z.infer<typeof recordToReportInputSchema>;\nexport type WaitNodeInput = z.infer<typeof waitInputSchema>;\n\nexport interface CreateMidsceneNodesOptions<TContext> {\n getAgent?(\n execution: NodeExecutionContext<unknown, TContext>,\n ): Awaitable<MidsceneUIAgent>;\n agentProvider?: AgentProvider<TContext>;\n /** Agent class that declares the Agent-backed Nodes to register. */\n agentClass: AgentTestRunnerNodeProvider;\n}\n\nconst waitFor = async (durationMs: number, signal: AbortSignal) => {\n if (signal.aborted) {\n throw signal.reason ?? new Error('Wait aborted.');\n }\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n signal.removeEventListener('abort', abort);\n resolve();\n }, durationMs);\n const abort = () => {\n clearTimeout(timeout);\n reject(signal.reason ?? new Error('Wait aborted.'));\n };\n signal.addEventListener('abort', abort, { once: true });\n });\n};\n\n/** Adapt Agent-owned descriptions to executable Midscene Test Nodes. */\nexport const createAgentTestRunnerNodes = <TContext>(\n definitions: readonly AgentTestRunnerNodeDefinition[],\n getAgent: (\n ctx: NodeExecutionContext<unknown, TContext>,\n ) => Awaitable<unknown>,\n): readonly NodeDefinition<any, any, TContext>[] => {\n const runAgentCall = async <T>(\n node: string,\n ctx: NodeExecutionContext<unknown, TContext>,\n agent: MidsceneUIAgent,\n call: () => Promise<T>,\n captureExecutions = true,\n ): Promise<T> =>\n runInYamlExecutionContext(async () => {\n const scopeId =\n ctx.scope === 'case' ? ctx.case.runId : ctx.document.documentRunId;\n const isCleanupOwner = assertResourceAvailable(agent, true, ctx.signal);\n // Native Steps own the root report. Any Agent.runYaml invoked underneath\n // contributes to that Step instead of publishing another root snapshot.\n const yamlBoundary = enterYamlExecution(agent, {\n allowIndependentOverlap: isCleanupOwner,\n });\n const leaveYamlAction = enterYamlAction(agent, ctx.signal);\n let removeListener: (() => void) | undefined;\n let sawExecutionWithoutId = false;\n const stopListening = () => {\n const remove = removeListener;\n removeListener = undefined;\n remove?.();\n };\n const stopListeningOnAbort = () => stopListening();\n try {\n if (captureExecutions && agent.addDumpUpdateListener) {\n removeListener = agent.addDumpUpdateListener((_dump, execution) => {\n if (!execution) return;\n if (!execution.id) {\n sawExecutionWithoutId = true;\n return;\n }\n ctx.report.addTrace({\n type: 'midscene-execution',\n executionId: execution.id,\n });\n });\n if (ctx.signal.aborted) stopListening();\n else {\n ctx.signal.addEventListener('abort', stopListeningOnAbort, {\n once: true,\n });\n }\n }\n return await trackResourceOperation(\n agent,\n Promise.resolve().then(call),\n ctx.signal,\n );\n } finally {\n leaveYamlAction();\n yamlBoundary.finish();\n ctx.signal.removeEventListener('abort', stopListeningOnAbort);\n stopListening();\n if (sawExecutionWithoutId) {\n warnReportTrace(\n `Skipped a report trace without a stable execution id for ${node} in scope ${scopeId}.`,\n );\n }\n }\n });\n\n return definitions.map((definition) =>\n defineNode({\n name: definition.name,\n ...(definition.title === undefined ? {} : { title: definition.title }),\n ...(definition.description === undefined\n ? {}\n : { description: definition.description }),\n ...(definition.stringInputKey === undefined\n ? {}\n : { stringInputKey: definition.stringInputKey }),\n inputSchema: definition.inputSchema,\n async execute(ctx) {\n const agent = await getAgent(ctx);\n ctx.signal.throwIfAborted();\n const call = async () =>\n definition.execute(agent, ctx.input, { signal: ctx.signal });\n if (typeof agent !== 'object' || agent === null) return call();\n return runAgentCall(\n definition.name,\n ctx,\n agent as MidsceneUIAgent,\n call,\n );\n },\n }),\n );\n};\n\nconst warnReportTrace = getDebug('test-runner:report-trace', {\n console: true,\n});\n\nexport function createMidsceneNodes<TContext>(\n options: CreateMidsceneNodesOptions<TContext>,\n): readonly NodeDefinition<any, any, TContext>[] {\n if (!options || typeof options !== 'object') {\n throw new NodeDefinitionError(\n 'createMidsceneNodes() options must be an object.',\n );\n }\n if (\n typeof options.agentProvider?.getAgent !== 'function' &&\n typeof options.getAgent !== 'function'\n ) {\n throw new NodeDefinitionError(\n 'createMidsceneNodes() requires getAgent or agentProvider.getAgent.',\n );\n }\n if (\n !options.agentClass ||\n typeof options.agentClass.getTestRunnerNodeDefinitions !== 'function'\n ) {\n throw new NodeDefinitionError(\n 'createMidsceneNodes() requires agentClass.getTestRunnerNodeDefinitions().',\n );\n }\n\n const agentsByScope = new Map<string, Set<MidsceneUIAgent>>();\n const getAgent = async (\n ctx: NodeExecutionContext<unknown, TContext>,\n ): Promise<MidsceneUIAgent> => {\n const runId =\n ctx.scope === 'case' ? ctx.case.runId : ctx.document.documentRunId;\n let agents = agentsByScope.get(runId);\n if (!agents) {\n agents = new Set();\n agentsByScope.set(runId, agents);\n const ownedScope = agents;\n ctx.onTeardown(async () => {\n const errors: unknown[] = [];\n const reportPaths: string[] = [];\n const reportSources: WorkflowReportSource[] = [];\n try {\n for (const agent of ownedScope) {\n try {\n const path = await agent.flushReport?.();\n if (path) reportPaths.push(path);\n const source = await agent._createReportSource?.(runId);\n if (source) reportSources.push(source);\n } catch (error) {\n errors.push(error);\n }\n }\n // Publication failure never prevents the owner from disposing its Agent.\n try {\n const released = await options.agentProvider?.releaseAgent?.(runId);\n if (\n released?.reportPath &&\n !reportPaths.includes(released.reportPath)\n )\n reportPaths.push(released.reportPath);\n } catch (error) {\n errors.push(error);\n }\n if (errors.length === 1) throw errors[0];\n if (errors.length)\n throw new AggregateError(\n errors,\n 'Agent report publication and release failed',\n );\n return {\n reportPaths,\n ...(reportSources.length ? { reportSources } : {}),\n };\n } finally {\n agentsByScope.delete(runId);\n }\n });\n }\n const scopeAgents = agents;\n // Acquisition can outlive cancellation. Teardown waits for actual settlement.\n return trackResourceOperation(\n {},\n Promise.resolve().then(async () => {\n const agent = options.agentProvider\n ? await options.agentProvider.getAgent(runId, ctx)\n : await options.getAgent!(ctx);\n agent._prepareForTestRunner?.();\n scopeAgents.add(agent);\n return agent;\n }),\n ctx.signal,\n );\n };\n\n const agentDefinitions = options.agentClass.getTestRunnerNodeDefinitions();\n\n return [\n ...createAgentTestRunnerNodes(agentDefinitions, getAgent),\n defineNode<typeof waitInputSchema, unknown, TContext>({\n name: 'wait',\n description: 'Wait for a fixed duration while honoring cancellation.',\n stringInputKey: false,\n inputSchema: waitInputSchema,\n async execute(execution) {\n const multiplier =\n execution.input.unit === 'min'\n ? 60_000\n : execution.input.unit === 's'\n ? 1_000\n : 1;\n const durationMs = execution.input.duration * multiplier;\n await waitFor(durationMs, execution.signal);\n return { summary: `Waited ${durationMs}ms` };\n },\n }),\n ];\n}\n"],"names":["defineNode","coreDefineNode","waitInputSchema","z","waitFor","durationMs","signal","Error","Promise","resolve","reject","timeout","setTimeout","abort","clearTimeout","createAgentTestRunnerNodes","definitions","getAgent","runAgentCall","node","ctx","agent","call","captureExecutions","runInYamlExecutionContext","scopeId","isCleanupOwner","assertResourceAvailable","yamlBoundary","enterYamlExecution","leaveYamlAction","enterYamlAction","removeListener","sawExecutionWithoutId","stopListening","remove","undefined","stopListeningOnAbort","_dump","execution","trackResourceOperation","warnReportTrace","definition","getDebug","createMidsceneNodes","options","NodeDefinitionError","agentsByScope","Map","runId","agents","Set","ownedScope","errors","reportPaths","reportSources","path","source","error","released","AggregateError","scopeAgents","agentDefinitions","multiplier"],"mappings":"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"}
1
+ {"version":3,"file":"midscene/index.mjs","sources":["../../../src/node/define-node.ts","../../../src/midscene/index.ts"],"sourcesContent":["import { defineNode as coreDefineNode } from '@midscene/core/internal/test-runner';\nimport type {\n DefineNodeOptions,\n DefineNodeWithSchemaOptions,\n NodeDefinition,\n NodeDefinitionWithSchema,\n NodeInputSchema,\n} from './types';\n\n// A type-only facade: validation and execution still belong to the shared kernel.\ninterface DefineNativeNode {\n <TSchema extends NodeInputSchema, TData = unknown, TContext = unknown>(\n options: DefineNodeWithSchemaOptions<TSchema, TData, TContext>,\n ): NodeDefinitionWithSchema<TSchema, TData, TContext>;\n <TInput = unknown, TData = unknown, TContext = unknown>(\n options: DefineNodeOptions<TInput, TData, TContext>,\n ): NodeDefinition<TInput, TData, TContext>;\n}\nexport const defineNode: DefineNativeNode = coreDefineNode;\n","import type { IExecutionDump } from '@midscene/core';\nimport {\n type AgentTestRunnerNodeDefinition,\n type AgentTestRunnerNodeProvider,\n type CommonAgentTestRunnerApi,\n aiActInputSchema,\n aiActOptionsInputSchema,\n aiAssertInputSchema,\n aiAssertOptionsInputSchema,\n aiTapInputSchema,\n insightInputSchema,\n insightOptionsInputSchema,\n locateOptionsInputSchema,\n promptImageInputSchema,\n recordToReportInputSchema,\n recordToReportOptionsInputSchema,\n reportScreenshotInputSchema,\n structuredUserPromptInputSchema,\n userPromptInputSchema,\n} from '@midscene/core/agent/test';\nimport {\n type WorkflowReportSource,\n admitResourceOperation,\n collectTestRunReportMetrics,\n hasPendingResourceOperation,\n retainResourceForScope,\n trackResourceOperation,\n} from '@midscene/core/internal/test-runner';\nimport {\n enterYamlAction,\n enterYamlExecution,\n runInYamlExecutionContext,\n} from '@midscene/core/internal/yaml-runtime';\nimport { getDebug } from '@midscene/shared/logger';\nimport { z } from 'zod/v4';\nimport type { Awaitable } from '../engine/types';\nimport { NodeDefinitionError } from '../errors';\nimport { defineNode } from '../node/define-node';\nimport type { NodeDefinition, NodeExecutionContext } from '../node/types';\n\nexport type MidsceneUIAgent = CommonAgentTestRunnerApi & {\n _prepareForTestRunner?(): void;\n _createReportSource?(\n scopeId: string,\n executionIds?: readonly string[],\n ): Promise<WorkflowReportSource | undefined>;\n flushReport?(): Promise<string | undefined>;\n addDumpUpdateListener?(\n listener: (dump: string, execution?: MidsceneExecutionRef) => void,\n ): () => void;\n};\n\nexport {\n actionInputSchema,\n aiDragAndDropInputSchema,\n aiInputInputSchema,\n aiKeyboardPressInputSchema,\n aiLongPressInputSchema,\n aiPinchInputSchema,\n aiQueryInputSchema,\n aiScrollInputSchema,\n aiWaitForInputSchema,\n javascriptInputSchema,\n runGherkinScenarioInputSchema,\n setAIContextInputSchema,\n} from '@midscene/core/agent/test';\nexport type {\n ActionNodeInput,\n AiDragAndDropNodeInput,\n AiInputNodeInput,\n AiKeyboardPressNodeInput,\n AiLongPressNodeInput,\n AiPinchNodeInput,\n AiQueryNodeInput,\n AiScrollNodeInput,\n AiWaitForNodeInput,\n JavascriptNodeInput,\n RunGherkinScenarioNodeInput,\n SetAIContextNodeInput,\n} from '@midscene/core/agent/test';\n\nexport {\n aiActInputSchema,\n aiActOptionsInputSchema,\n aiAssertInputSchema,\n aiAssertOptionsInputSchema,\n aiTapInputSchema,\n insightInputSchema,\n insightOptionsInputSchema,\n locateOptionsInputSchema,\n promptImageInputSchema,\n recordToReportInputSchema,\n recordToReportOptionsInputSchema,\n reportScreenshotInputSchema,\n structuredUserPromptInputSchema,\n userPromptInputSchema,\n};\n\nexport interface MidsceneExecutionRef {\n id?: string;\n tasks?: IExecutionDump['tasks'];\n}\n\nexport interface AgentProvider<TContext> {\n getAgent(\n runId: string,\n execution: NodeExecutionContext<unknown, TContext>,\n ): Awaitable<MidsceneUIAgent>;\n // biome-ignore lint/suspicious/noConfusingVoidType: providers without a report intentionally return void.\n releaseAgent?(runId: string): Awaitable<AgentReleaseResult | void>;\n dispose?(): Awaitable<void>;\n}\n\nexport interface AgentReleaseResult {\n /** Absolute path to the finalized report for this Agent scope. */\n reportPath?: string;\n}\n\nexport const waitInputSchema = z.strictObject({\n duration: z.number().positive().describe('How long to wait.'),\n unit: z\n .enum(['ms', 's', 'min'])\n .default('ms')\n .describe('Duration unit: milliseconds, seconds, or minutes.'),\n});\n\nexport type AiActNodeInput = z.infer<typeof aiActInputSchema>;\nexport type AiAssertNodeInput = z.infer<typeof aiAssertInputSchema>;\nexport type AiTapNodeInput = z.infer<typeof aiTapInputSchema>;\nexport type InsightNodeInput = z.infer<typeof insightInputSchema>;\nexport type RecordToReportNodeInput = z.infer<typeof recordToReportInputSchema>;\nexport type WaitNodeInput = z.infer<typeof waitInputSchema>;\n\nexport interface CreateMidsceneNodesOptions<TContext> {\n getAgent?(\n execution: NodeExecutionContext<unknown, TContext>,\n ): Awaitable<MidsceneUIAgent>;\n agentProvider?: AgentProvider<TContext>;\n /** Agent class that declares the Agent-backed Nodes to register. */\n agentClass: AgentTestRunnerNodeProvider;\n}\n\nconst waitFor = async (durationMs: number, signal: AbortSignal) => {\n if (signal.aborted) {\n throw signal.reason ?? new Error('Wait aborted.');\n }\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n signal.removeEventListener('abort', abort);\n resolve();\n }, durationMs);\n const abort = () => {\n clearTimeout(timeout);\n reject(signal.reason ?? new Error('Wait aborted.'));\n };\n signal.addEventListener('abort', abort, { once: true });\n });\n};\n\n/** Adapt Agent-owned descriptions to executable Midscene Test Nodes. */\nexport const createAgentTestRunnerNodes = <TContext>(\n definitions: readonly AgentTestRunnerNodeDefinition[],\n getAgent: (\n ctx: NodeExecutionContext<unknown, TContext>,\n ) => Awaitable<unknown>,\n): readonly NodeDefinition<any, any, TContext>[] => {\n const runAgentCall = async <T>(\n node: string,\n ctx: NodeExecutionContext<unknown, TContext>,\n agent: MidsceneUIAgent,\n call: () => Promise<T>,\n captureExecutions = true,\n ): Promise<T> =>\n runInYamlExecutionContext(async () => {\n const scopeId =\n ctx.scope === 'case' ? ctx.case.runId : ctx.document.documentRunId;\n const isCleanupOwner = admitResourceOperation(agent, ctx.signal);\n if (captureExecutions) {\n ctx.report.onSnapshot(async (signal, traces) => {\n signal.throwIfAborted();\n const partial = hasPendingResourceOperation(agent);\n const capturedAt = new Date().toISOString();\n let source = await agent._createReportSource?.(\n scopeId,\n traces.map((trace) => trace.executionId),\n );\n // External adapters may only expose HTML. Capture it independently\n // of cleanup, while marking its mutable source as partial.\n if (!source) {\n const path = await agent.flushReport?.();\n if (path)\n source = {\n sourcePath: path,\n collection: {\n status: 'partial',\n capturedAt,\n reason:\n 'This provider exposes live HTML without immutable snapshot support.',\n },\n };\n }\n signal.throwIfAborted();\n if (!source)\n throw new Error('Agent did not return a report snapshot.');\n return {\n ...source,\n collection: {\n status:\n partial || source.collection?.status === 'partial'\n ? ('partial' as const)\n : ('complete' as const),\n capturedAt: source.collection?.capturedAt ?? capturedAt,\n ...(source.collection?.reason\n ? { reason: source.collection.reason }\n : partial\n ? {\n reason:\n 'An Agent operation was still running at the snapshot cutoff.',\n }\n : {}),\n },\n };\n });\n }\n // Native Steps own the root report. Any Agent.runYaml invoked underneath\n // contributes to that Step instead of publishing another root snapshot.\n const yamlBoundary = enterYamlExecution(agent, {\n allowIndependentOverlap: isCleanupOwner,\n });\n const leaveYamlAction = enterYamlAction(agent, ctx.signal);\n let removeListener: (() => void) | undefined;\n let sawExecutionWithoutId = false;\n const stopListening = () => {\n const remove = removeListener;\n removeListener = undefined;\n remove?.();\n };\n const stopListeningOnAbort = () => stopListening();\n try {\n if (captureExecutions && agent.addDumpUpdateListener) {\n removeListener = agent.addDumpUpdateListener((_dump, execution) => {\n if (!execution) return;\n if (!execution.id) {\n sawExecutionWithoutId = true;\n return;\n }\n ctx.report.addTrace({\n type: 'midscene-execution',\n executionId: execution.id,\n ...(execution.tasks\n ? {\n metrics: collectTestRunReportMetrics({\n executions: [\n {\n id: execution.id,\n name: node,\n logTime: 0,\n tasks: execution.tasks,\n },\n ],\n }),\n }\n : {}),\n });\n });\n if (ctx.signal.aborted) stopListening();\n else {\n ctx.signal.addEventListener('abort', stopListeningOnAbort, {\n once: true,\n });\n }\n }\n return await trackResourceOperation(\n agent,\n Promise.resolve().then(call),\n ctx.signal,\n );\n } finally {\n leaveYamlAction();\n yamlBoundary.finish();\n ctx.signal.removeEventListener('abort', stopListeningOnAbort);\n stopListening();\n if (sawExecutionWithoutId) {\n warnReportTrace(\n `Skipped a report trace without a stable execution id for ${node} in scope ${scopeId}.`,\n );\n }\n }\n });\n\n return definitions.map((definition) =>\n defineNode({\n name: definition.name,\n ...(definition.title === undefined ? {} : { title: definition.title }),\n ...(definition.description === undefined\n ? {}\n : { description: definition.description }),\n ...(definition.stringInputKey === undefined\n ? {}\n : { stringInputKey: definition.stringInputKey }),\n inputSchema: definition.inputSchema,\n async execute(ctx) {\n const agent = await getAgent(ctx);\n ctx.signal.throwIfAborted();\n const call = async () =>\n definition.execute(agent, ctx.input, { signal: ctx.signal });\n if (typeof agent !== 'object' || agent === null) return call();\n return runAgentCall(\n definition.name,\n ctx,\n agent as MidsceneUIAgent,\n call,\n );\n },\n }),\n );\n};\n\nconst warnReportTrace = getDebug('test-runner:report-trace', {\n console: true,\n});\n\nexport function createMidsceneNodes<TContext>(\n options: CreateMidsceneNodesOptions<TContext>,\n): readonly NodeDefinition<any, any, TContext>[] {\n if (!options || typeof options !== 'object') {\n throw new NodeDefinitionError(\n 'createMidsceneNodes() options must be an object.',\n );\n }\n if (\n typeof options.agentProvider?.getAgent !== 'function' &&\n typeof options.getAgent !== 'function'\n ) {\n throw new NodeDefinitionError(\n 'createMidsceneNodes() requires getAgent or agentProvider.getAgent.',\n );\n }\n if (\n !options.agentClass ||\n typeof options.agentClass.getTestRunnerNodeDefinitions !== 'function'\n ) {\n throw new NodeDefinitionError(\n 'createMidsceneNodes() requires agentClass.getTestRunnerNodeDefinitions().',\n );\n }\n\n const agentsByScope = new Map<string, Set<MidsceneUIAgent>>();\n const getAgent = async (\n ctx: NodeExecutionContext<unknown, TContext>,\n ): Promise<MidsceneUIAgent> => {\n const runId =\n ctx.scope === 'case' ? ctx.case.runId : ctx.document.documentRunId;\n let agents = agentsByScope.get(runId);\n if (!agents) {\n agents = new Set();\n agentsByScope.set(runId, agents);\n ctx.onTeardown(async () => {\n try {\n const released = await options.agentProvider?.releaseAgent?.(runId);\n return released?.reportPath\n ? { reportPaths: [released.reportPath] }\n : undefined;\n } finally {\n agentsByScope.delete(runId);\n }\n });\n }\n const scopeAgents = agents;\n // Acquisition can outlive cancellation. Teardown waits for actual settlement.\n return trackResourceOperation(\n {},\n Promise.resolve().then(async () => {\n const agent = options.agentProvider\n ? await options.agentProvider.getAgent(runId, ctx)\n : await options.getAgent!(ctx);\n retainResourceForScope(agent, ctx.signal);\n agent._prepareForTestRunner?.();\n scopeAgents.add(agent);\n return agent;\n }),\n ctx.signal,\n 'acquisition',\n );\n };\n\n const agentDefinitions = options.agentClass.getTestRunnerNodeDefinitions();\n\n return [\n ...createAgentTestRunnerNodes(agentDefinitions, getAgent),\n defineNode<typeof waitInputSchema, unknown, TContext>({\n name: 'wait',\n description: 'Wait for a fixed duration while honoring cancellation.',\n stringInputKey: false,\n inputSchema: waitInputSchema,\n async execute(execution) {\n const multiplier =\n execution.input.unit === 'min'\n ? 60_000\n : execution.input.unit === 's'\n ? 1_000\n : 1;\n const durationMs = execution.input.duration * multiplier;\n await waitFor(durationMs, execution.signal);\n return { summary: `Waited ${durationMs}ms` };\n },\n }),\n ];\n}\n"],"names":["defineNode","coreDefineNode","waitInputSchema","z","waitFor","durationMs","signal","Error","Promise","resolve","reject","timeout","setTimeout","abort","clearTimeout","createAgentTestRunnerNodes","definitions","getAgent","runAgentCall","node","ctx","agent","call","captureExecutions","runInYamlExecutionContext","scopeId","isCleanupOwner","admitResourceOperation","traces","partial","hasPendingResourceOperation","capturedAt","Date","source","trace","path","yamlBoundary","enterYamlExecution","leaveYamlAction","enterYamlAction","removeListener","sawExecutionWithoutId","stopListening","remove","undefined","stopListeningOnAbort","_dump","execution","collectTestRunReportMetrics","trackResourceOperation","warnReportTrace","definition","getDebug","createMidsceneNodes","options","NodeDefinitionError","agentsByScope","Map","runId","agents","Set","released","scopeAgents","retainResourceForScope","agentDefinitions","multiplier"],"mappings":"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"}