@midscene/test 1.12.6-beta-20260910093036.0 → 1.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/1~891.mjs +19 -39
- package/dist/es/1~891.mjs.map +1 -1
- package/dist/es/cli/index.mjs +26 -44
- package/dist/es/cli/index.mjs.map +1 -1
- package/dist/es/index.mjs +0 -3
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/midscene/index.mjs +43 -32
- package/dist/es/midscene/index.mjs.map +1 -1
- package/dist/es/test-cli.mjs +37 -49
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/0~458.js +19 -39
- package/dist/lib/0~458.js.map +1 -1
- package/dist/lib/cli/index.js +26 -44
- package/dist/lib/cli/index.js.map +1 -1
- package/dist/lib/index.js +0 -3
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/midscene/index.js +253 -262
- package/dist/lib/midscene/index.js.map +1 -1
- package/dist/lib/test-cli.js +37 -49
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/types/cli/index.d.ts +12 -13
- package/dist/types/index.d.ts +1 -7
- package/dist/types/midscene/index.d.ts +21 -21
- package/package.json +8 -48
- package/dist/es/android/index.mjs +0 -182
- package/dist/es/android/index.mjs.map +0 -1
- package/dist/es/harmony/index.mjs +0 -182
- package/dist/es/harmony/index.mjs.map +0 -1
- package/dist/es/ios/index.mjs +0 -182
- package/dist/es/ios/index.mjs.map +0 -1
- package/dist/es/playwright/index.mjs +0 -366
- package/dist/es/playwright/index.mjs.map +0 -1
- package/dist/lib/android/index.js +0 -255
- package/dist/lib/android/index.js.map +0 -1
- package/dist/lib/harmony/index.js +0 -252
- package/dist/lib/harmony/index.js.map +0 -1
- package/dist/lib/ios/index.js +0 -255
- package/dist/lib/ios/index.js.map +0 -1
- package/dist/lib/playwright/index.js +0 -412
- package/dist/lib/playwright/index.js.map +0 -1
- package/dist/types/android/index.d.ts +0 -140
- package/dist/types/harmony/index.d.ts +0 -137
- package/dist/types/ios/index.d.ts +0 -143
- package/dist/types/playwright/index.d.ts +0 -229
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midscene/index.js","sources":["../../../src/errors.ts","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/node/define-node.ts","../../../src/midscene/index.ts"],"sourcesContent":["import type { z } from 'zod/v4';\n\nexport interface WorkflowErrorOptions {\n code?: string;\n details?: unknown;\n cause?: unknown;\n}\n\nexport class WorkflowError extends Error {\n readonly code: string;\n readonly details?: unknown;\n\n constructor(message: string, options: WorkflowErrorOptions = {}) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.code = options.code ?? 'WORKFLOW_ERROR';\n this.details = options.details;\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n ...(this.details === undefined ? {} : { details: this.details }),\n };\n }\n}\n\nexport class WorkflowParseError extends WorkflowError {\n constructor(message: string, details?: unknown, cause?: unknown) {\n super(message, { code: 'WORKFLOW_PARSE_ERROR', details, cause });\n }\n}\n\nexport class NodeDefinitionError extends WorkflowError {\n constructor(message: string, details?: unknown) {\n super(message, { code: 'NODE_DEFINITION_ERROR', details });\n }\n}\n\nexport class DuplicateNodeError extends WorkflowError {\n readonly node: string;\n\n constructor(node: string) {\n super(`Node \"${node}\" is already registered.`, {\n code: 'DUPLICATE_NODE',\n details: { node },\n });\n this.node = node;\n }\n}\n\nexport class NodeNotFoundError extends WorkflowError {\n readonly node: string;\n\n constructor(node: string) {\n super(`Node \"${node}\" is not registered.`, {\n code: 'NODE_NOT_FOUND',\n details: { node },\n });\n this.node = node;\n }\n}\n\nexport class NodeInputValidationError extends WorkflowError {\n constructor(message: string, details?: unknown) {\n super(message, { code: 'NODE_INPUT_VALIDATION_ERROR', details });\n }\n\n static fromZod(node: string, error: z.ZodError): NodeInputValidationError {\n const issues = error.issues.map((issue) => ({\n code: issue.code,\n path: issue.path.map(String).join('.'),\n message: issue.message,\n }));\n const firstIssue = issues[0];\n const path = firstIssue?.path || '<root>';\n const message = firstIssue?.message ?? 'invalid input';\n return new NodeInputValidationError(\n `Node \"${node}\" input validation failed at \"${path}\": ${message}`,\n { node, issues },\n );\n }\n}\n\nexport class StepTimeoutError extends WorkflowError {\n readonly timeoutMs: number;\n readonly node?: string;\n\n constructor(timeoutMs: number, node?: string) {\n super(\n node\n ? `Node \"${node}\" timed out after ${timeoutMs}ms.`\n : `Step timed out after ${timeoutMs}ms.`,\n {\n code: 'STEP_TIMEOUT',\n details: { timeoutMs, ...(node === undefined ? {} : { node }) },\n },\n );\n this.timeoutMs = timeoutMs;\n this.node = node;\n }\n}\n\nexport class NodeExecutionError extends WorkflowError {\n readonly node: string;\n\n constructor(node: string, cause: unknown) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(`Node \"${node}\" failed: ${causeMessage}`, {\n code: 'NODE_EXECUTION_ERROR',\n details: { node },\n cause,\n });\n this.node = node;\n }\n}\n\nexport class WorkflowLifecycleError extends WorkflowError {\n constructor(message: string, details?: unknown) {\n super(message, { code: 'WORKFLOW_LIFECYCLE_ERROR', details });\n }\n}\n\nexport class ProjectSetupError extends WorkflowError {\n constructor(cause: unknown, details: { projectName: string }) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(`Project \"${details.projectName}\" setup failed: ${causeMessage}`, {\n code: 'PROJECT_SETUP_ERROR',\n details,\n cause,\n });\n }\n}\n\nexport class ProjectTeardownError extends WorkflowError {\n constructor(\n cause: unknown,\n details: { projectName: string; registrationIndex: number },\n ) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(`Project \"${details.projectName}\" teardown failed: ${causeMessage}`, {\n code: 'PROJECT_TEARDOWN_ERROR',\n details,\n cause,\n });\n }\n}\n\nexport class NodeScopeTeardownError extends WorkflowError {\n constructor(\n cause: unknown,\n details: {\n scope: 'case' | 'document';\n scopeId: string;\n node: string;\n registrationIndex: number;\n },\n ) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(\n `${details.scope === 'case' ? 'Case attempt' : 'Workflow document'} node teardown failed for \"${details.node}\": ${causeMessage}`,\n { code: 'NODE_SCOPE_TEARDOWN_ERROR', details, cause },\n );\n }\n}\n\nexport class FatalDeviceError extends WorkflowError {\n constructor(message: string, cause?: unknown) {\n super(message, { code: 'FATAL_DEVICE_ERROR', cause });\n }\n}\n\nexport const isFatalDeviceError = (error: unknown): boolean => {\n if (error instanceof FatalDeviceError) return true;\n if (error instanceof WorkflowError && error.code === 'FATAL_DEVICE_ERROR') {\n return true;\n }\n if (\n error instanceof Error &&\n /device offline|device not found|(?:adb|bdc|device) connection (?:was )?closed/i.test(\n error.message,\n )\n ) {\n return true;\n }\n return error instanceof Error && error.cause !== undefined\n ? isFatalDeviceError(error.cause)\n : false;\n};\n\nexport class CaseExecutionError extends WorkflowError {\n readonly result: import('./engine/types').CaseRunResult;\n\n constructor(result: import('./engine/types').CaseRunResult) {\n super(`Case \"${result.name}\" failed.`, {\n code: 'CASE_EXECUTION_FAILED',\n details: { caseId: result.caseId, runId: result.runId },\n });\n this.result = result;\n }\n}\n\nexport class WorkflowDocumentExecutionError extends WorkflowError {\n readonly result: import('./engine/types').WorkflowDocumentRunResult;\n\n constructor(result: import('./engine/types').WorkflowDocumentRunResult) {\n super(`Workflow document \"${result.sourcePath}\" failed.`, {\n code: 'WORKFLOW_DOCUMENT_EXECUTION_FAILED',\n details: {\n documentId: result.documentId,\n documentRunId: result.documentRunId,\n },\n });\n this.result = result;\n }\n}\n\nexport function normalizeNodeExecutionError(\n error: unknown,\n node: string,\n): WorkflowError {\n return error instanceof WorkflowError\n ? error\n : new NodeExecutionError(node, error);\n}\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { z } from 'zod/v4';\nimport { NodeDefinitionError } from '../errors';\nimport type {\n DefineNodeOptions,\n DefineNodeWithSchemaOptions,\n NodeDefinition,\n NodeDefinitionWithSchema,\n NodeInputSchema,\n} from './types';\n\nconst validateOptionalText = (\n value: unknown,\n field: 'title' | 'description',\n node: string,\n): void => {\n if (\n value !== undefined &&\n (typeof value !== 'string' || value.trim().length === 0)\n ) {\n throw new NodeDefinitionError(\n `Node \"${node}\" ${field} must be a non-empty string.`,\n { node, field },\n );\n }\n};\n\nconst validateInputSchema = (schema: unknown, node: string): void => {\n if (schema === undefined) return;\n if (!(schema instanceof z.ZodObject)) {\n throw new NodeDefinitionError(\n `Node \"${node}\" inputSchema must be a Zod object schema.`,\n { node, field: 'inputSchema' },\n );\n }\n if ('$' in schema.shape) {\n throw new NodeDefinitionError(\n `Node \"${node}\" inputSchema must not declare \"$\" as an input property.`,\n { node, field: 'inputSchema.$' },\n );\n }\n};\n\nconst validateDefinition = (options: {\n name: string;\n title?: unknown;\n description?: unknown;\n stringInputKey?: unknown;\n inputSchema?: unknown;\n execute: unknown;\n}): void => {\n if (!options || typeof options !== 'object') {\n throw new NodeDefinitionError('Node definition must be an object.');\n }\n\n if (typeof options.name !== 'string' || options.name.trim().length === 0) {\n throw new NodeDefinitionError('Node name must be a non-empty string.');\n }\n\n validateOptionalText(options.title, 'title', options.name);\n validateOptionalText(options.description, 'description', options.name);\n if (\n options.stringInputKey !== undefined &&\n options.stringInputKey !== false &&\n (typeof options.stringInputKey !== 'string' ||\n options.stringInputKey.trim().length === 0)\n ) {\n throw new NodeDefinitionError(\n `Node \"${options.name}\" stringInputKey must be a non-empty string or false.`,\n { node: options.name, field: 'stringInputKey' },\n );\n }\n validateInputSchema(options.inputSchema, options.name);\n\n if (\n typeof options.stringInputKey === 'string' &&\n options.inputSchema instanceof z.ZodObject &&\n !(options.stringInputKey in options.inputSchema.shape)\n ) {\n throw new NodeDefinitionError(\n `Node \"${options.name}\" stringInputKey must reference a field in inputSchema.`,\n { node: options.name, field: 'stringInputKey' },\n );\n }\n\n if (typeof options.execute !== 'function') {\n throw new NodeDefinitionError(\n `Node \"${options.name}\" must provide an execute function.`,\n { node: options.name },\n );\n }\n};\n\nexport function defineNode<\n TSchema extends NodeInputSchema,\n TData = unknown,\n TContext = unknown,\n>(\n options: DefineNodeWithSchemaOptions<TSchema, TData, TContext>,\n): NodeDefinitionWithSchema<TSchema, TData, TContext>;\n\nexport function defineNode<\n TInput = unknown,\n TData = unknown,\n TContext = unknown,\n>(\n options: DefineNodeOptions<TInput, TData, TContext>,\n): NodeDefinition<TInput, TData, TContext>;\n\nexport function defineNode(\n options: DefineNodeOptions<any, any, any>,\n): NodeDefinition<any, any, any> {\n validateDefinition(options);\n return options;\n}\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-runner';\nimport { getDebug } from '@midscene/shared/logger';\nimport { z } from 'zod/v4';\nimport type { Awaitable } from '../engine/types';\nimport { NodeDefinitionError, NodeExecutionError } from '../errors';\nimport { defineNode } from '../node/define-node';\nimport type { NodeDefinition, NodeExecutionContext } from '../node/types';\n\nexport type MidsceneUIAgent = CommonAgentTestRunnerApi & {\n addDumpUpdateListener?(\n listener: (dump: string, execution?: MidsceneExecutionRef) => void,\n ): () => void;\n};\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 ctx: 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 ctx: 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 activeAgentCalls = new WeakMap<\n MidsceneUIAgent,\n { scopeId: string; node: string }\n >();\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 const scopeId =\n ctx.scope === 'case' ? ctx.case.runId : ctx.document.documentRunId;\n const activeCall = activeAgentCalls.get(agent);\n if (activeCall) {\n throw new NodeExecutionError(\n node,\n new Error(\n `The same Agent instance cannot execute overlapping Midscene Test Steps. Active scope: ${activeCall.scopeId} (${activeCall.node}); requested scope: ${scopeId} (${node}).`,\n ),\n );\n }\n\n activeAgentCalls.set(agent, { scopeId, node });\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 call();\n } finally {\n ctx.signal.removeEventListener('abort', stopListeningOnAbort);\n try {\n stopListening();\n } finally {\n activeAgentCalls.delete(agent);\n }\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 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 registeredAgentScopes = new Set<string>();\n const getExecutionId = (ctx: NodeExecutionContext<unknown, TContext>) =>\n ctx.scope === 'case' ? ctx.case.runId : ctx.document.documentRunId;\n const getAgent = async (\n ctx: NodeExecutionContext<unknown, TContext>,\n ): Promise<MidsceneUIAgent> => {\n if (!options.agentProvider) return options.getAgent!(ctx);\n const runId = getExecutionId(ctx);\n if (\n options.agentProvider.releaseAgent &&\n !registeredAgentScopes.has(runId)\n ) {\n registeredAgentScopes.add(runId);\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 registeredAgentScopes.delete(runId);\n }\n });\n }\n return options.agentProvider.getAgent(runId, ctx);\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(ctx) {\n const multiplier =\n ctx.input.unit === 'min'\n ? 60_000\n : ctx.input.unit === 's'\n ? 1_000\n : 1;\n const durationMs = ctx.input.duration * multiplier;\n await waitFor(durationMs, ctx.signal);\n return { summary: `Waited ${durationMs}ms` };\n },\n }),\n ];\n}\n"],"names":["WorkflowError","Error","undefined","message","options","NodeDefinitionError","details","NodeExecutionError","node","cause","causeMessage","String","__webpack_require__","definition","key","Object","obj","prop","Symbol","validateOptionalText","value","field","validateInputSchema","schema","z","validateDefinition","defineNode","waitInputSchema","waitFor","durationMs","signal","Promise","resolve","reject","timeout","setTimeout","abort","clearTimeout","createAgentTestRunnerNodes","definitions","getAgent","activeAgentCalls","WeakMap","runAgentCall","ctx","agent","call","captureExecutions","scopeId","activeCall","removeListener","sawExecutionWithoutId","stopListening","remove","stopListeningOnAbort","_dump","execution","warnReportTrace","getDebug","createMidsceneNodes","registeredAgentScopes","Set","getExecutionId","runId","released","agentDefinitions","multiplier"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"midscene/index.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/errors.ts","../../../src/node/define-node.ts","../../../src/midscene/index.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import type { z } from 'zod/v4';\n\nexport interface WorkflowErrorOptions {\n code?: string;\n details?: unknown;\n cause?: unknown;\n}\n\nexport class WorkflowError extends Error {\n readonly code: string;\n readonly details?: unknown;\n\n constructor(message: string, options: WorkflowErrorOptions = {}) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.code = options.code ?? 'WORKFLOW_ERROR';\n this.details = options.details;\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n ...(this.details === undefined ? {} : { details: this.details }),\n };\n }\n}\n\nexport class WorkflowParseError extends WorkflowError {\n constructor(message: string, details?: unknown, cause?: unknown) {\n super(message, { code: 'WORKFLOW_PARSE_ERROR', details, cause });\n }\n}\n\nexport class NodeDefinitionError extends WorkflowError {\n constructor(message: string, details?: unknown) {\n super(message, { code: 'NODE_DEFINITION_ERROR', details });\n }\n}\n\nexport class DuplicateNodeError extends WorkflowError {\n readonly node: string;\n\n constructor(node: string) {\n super(`Node \"${node}\" is already registered.`, {\n code: 'DUPLICATE_NODE',\n details: { node },\n });\n this.node = node;\n }\n}\n\nexport class NodeNotFoundError extends WorkflowError {\n readonly node: string;\n\n constructor(node: string) {\n super(`Node \"${node}\" is not registered.`, {\n code: 'NODE_NOT_FOUND',\n details: { node },\n });\n this.node = node;\n }\n}\n\nexport class NodeInputValidationError extends WorkflowError {\n constructor(message: string, details?: unknown) {\n super(message, { code: 'NODE_INPUT_VALIDATION_ERROR', details });\n }\n\n static fromZod(node: string, error: z.ZodError): NodeInputValidationError {\n const issues = error.issues.map((issue) => ({\n code: issue.code,\n path: issue.path.map(String).join('.'),\n message: issue.message,\n }));\n const firstIssue = issues[0];\n const path = firstIssue?.path || '<root>';\n const message = firstIssue?.message ?? 'invalid input';\n return new NodeInputValidationError(\n `Node \"${node}\" input validation failed at \"${path}\": ${message}`,\n { node, issues },\n );\n }\n}\n\nexport class StepTimeoutError extends WorkflowError {\n readonly timeoutMs: number;\n readonly node?: string;\n\n constructor(timeoutMs: number, node?: string) {\n super(\n node\n ? `Node \"${node}\" timed out after ${timeoutMs}ms.`\n : `Step timed out after ${timeoutMs}ms.`,\n {\n code: 'STEP_TIMEOUT',\n details: { timeoutMs, ...(node === undefined ? {} : { node }) },\n },\n );\n this.timeoutMs = timeoutMs;\n this.node = node;\n }\n}\n\nexport class NodeExecutionError extends WorkflowError {\n readonly node: string;\n\n constructor(node: string, cause: unknown) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(`Node \"${node}\" failed: ${causeMessage}`, {\n code: 'NODE_EXECUTION_ERROR',\n details: { node },\n cause,\n });\n this.node = node;\n }\n}\n\nexport class WorkflowLifecycleError extends WorkflowError {\n constructor(message: string, details?: unknown) {\n super(message, { code: 'WORKFLOW_LIFECYCLE_ERROR', details });\n }\n}\n\nexport class ProjectSetupError extends WorkflowError {\n constructor(cause: unknown, details: { projectName: string }) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(`Project \"${details.projectName}\" setup failed: ${causeMessage}`, {\n code: 'PROJECT_SETUP_ERROR',\n details,\n cause,\n });\n }\n}\n\nexport class ProjectTeardownError extends WorkflowError {\n constructor(\n cause: unknown,\n details: { projectName: string; registrationIndex: number },\n ) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(`Project \"${details.projectName}\" teardown failed: ${causeMessage}`, {\n code: 'PROJECT_TEARDOWN_ERROR',\n details,\n cause,\n });\n }\n}\n\nexport class NodeScopeTeardownError extends WorkflowError {\n constructor(\n cause: unknown,\n details: {\n scope: 'case' | 'document';\n scopeId: string;\n node: string;\n registrationIndex: number;\n },\n ) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(\n `${details.scope === 'case' ? 'Case attempt' : 'Workflow document'} node teardown failed for \"${details.node}\": ${causeMessage}`,\n { code: 'NODE_SCOPE_TEARDOWN_ERROR', details, cause },\n );\n }\n}\n\nexport class FatalDeviceError extends WorkflowError {\n constructor(message: string, cause?: unknown) {\n super(message, { code: 'FATAL_DEVICE_ERROR', cause });\n }\n}\n\nexport const isFatalDeviceError = (error: unknown): boolean => {\n if (error instanceof FatalDeviceError) return true;\n if (error instanceof WorkflowError && error.code === 'FATAL_DEVICE_ERROR') {\n return true;\n }\n if (\n error instanceof Error &&\n /device offline|device not found|(?:adb|bdc|device) connection (?:was )?closed/i.test(\n error.message,\n )\n ) {\n return true;\n }\n return error instanceof Error && error.cause !== undefined\n ? isFatalDeviceError(error.cause)\n : false;\n};\n\nexport class CaseExecutionError extends WorkflowError {\n readonly result: import('./engine/types').CaseRunResult;\n\n constructor(result: import('./engine/types').CaseRunResult) {\n super(`Case \"${result.name}\" failed.`, {\n code: 'CASE_EXECUTION_FAILED',\n details: { caseId: result.caseId, runId: result.runId },\n });\n this.result = result;\n }\n}\n\nexport class WorkflowDocumentExecutionError extends WorkflowError {\n readonly result: import('./engine/types').WorkflowDocumentRunResult;\n\n constructor(result: import('./engine/types').WorkflowDocumentRunResult) {\n super(`Workflow document \"${result.sourcePath}\" failed.`, {\n code: 'WORKFLOW_DOCUMENT_EXECUTION_FAILED',\n details: {\n documentId: result.documentId,\n documentRunId: result.documentRunId,\n },\n });\n this.result = result;\n }\n}\n\nexport function normalizeNodeExecutionError(\n error: unknown,\n node: string,\n): WorkflowError {\n return error instanceof WorkflowError\n ? error\n : new NodeExecutionError(node, error);\n}\n","import { z } from 'zod/v4';\nimport { NodeDefinitionError } from '../errors';\nimport type {\n DefineNodeOptions,\n DefineNodeWithSchemaOptions,\n NodeDefinition,\n NodeDefinitionWithSchema,\n NodeInputSchema,\n} from './types';\n\nconst validateOptionalText = (\n value: unknown,\n field: 'title' | 'description',\n node: string,\n): void => {\n if (\n value !== undefined &&\n (typeof value !== 'string' || value.trim().length === 0)\n ) {\n throw new NodeDefinitionError(\n `Node \"${node}\" ${field} must be a non-empty string.`,\n { node, field },\n );\n }\n};\n\nconst validateInputSchema = (schema: unknown, node: string): void => {\n if (schema === undefined) return;\n if (!(schema instanceof z.ZodObject)) {\n throw new NodeDefinitionError(\n `Node \"${node}\" inputSchema must be a Zod object schema.`,\n { node, field: 'inputSchema' },\n );\n }\n if ('$' in schema.shape) {\n throw new NodeDefinitionError(\n `Node \"${node}\" inputSchema must not declare \"$\" as an input property.`,\n { node, field: 'inputSchema.$' },\n );\n }\n};\n\nconst validateDefinition = (options: {\n name: string;\n title?: unknown;\n description?: unknown;\n stringInputKey?: unknown;\n inputSchema?: unknown;\n execute: unknown;\n}): void => {\n if (!options || typeof options !== 'object') {\n throw new NodeDefinitionError('Node definition must be an object.');\n }\n\n if (typeof options.name !== 'string' || options.name.trim().length === 0) {\n throw new NodeDefinitionError('Node name must be a non-empty string.');\n }\n\n validateOptionalText(options.title, 'title', options.name);\n validateOptionalText(options.description, 'description', options.name);\n if (\n options.stringInputKey !== undefined &&\n options.stringInputKey !== false &&\n (typeof options.stringInputKey !== 'string' ||\n options.stringInputKey.trim().length === 0)\n ) {\n throw new NodeDefinitionError(\n `Node \"${options.name}\" stringInputKey must be a non-empty string or false.`,\n { node: options.name, field: 'stringInputKey' },\n );\n }\n validateInputSchema(options.inputSchema, options.name);\n\n if (\n typeof options.stringInputKey === 'string' &&\n options.inputSchema instanceof z.ZodObject &&\n !(options.stringInputKey in options.inputSchema.shape)\n ) {\n throw new NodeDefinitionError(\n `Node \"${options.name}\" stringInputKey must reference a field in inputSchema.`,\n { node: options.name, field: 'stringInputKey' },\n );\n }\n\n if (typeof options.execute !== 'function') {\n throw new NodeDefinitionError(\n `Node \"${options.name}\" must provide an execute function.`,\n { node: options.name },\n );\n }\n};\n\nexport function defineNode<\n TSchema extends NodeInputSchema,\n TData = unknown,\n TContext = unknown,\n>(\n options: DefineNodeWithSchemaOptions<TSchema, TData, TContext>,\n): NodeDefinitionWithSchema<TSchema, TData, TContext>;\n\nexport function defineNode<\n TInput = unknown,\n TData = unknown,\n TContext = unknown,\n>(\n options: DefineNodeOptions<TInput, TData, TContext>,\n): NodeDefinition<TInput, TData, TContext>;\n\nexport function defineNode(\n options: DefineNodeOptions<any, any, any>,\n): NodeDefinition<any, any, any> {\n validateDefinition(options);\n return options;\n}\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 { getDebug } from '@midscene/shared/logger';\nimport { z } from 'zod/v4';\nimport type { Awaitable } from '../engine/types';\nimport { NodeDefinitionError, NodeExecutionError } from '../errors';\nimport { defineNode } from '../node/define-node';\nimport type { NodeDefinition, NodeExecutionContext } from '../node/types';\n\nexport type MidsceneUIAgent = CommonAgentTestRunnerApi & {\n addDumpUpdateListener?(\n listener: (dump: string, execution?: MidsceneExecutionRef) => void,\n ): () => void;\n};\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 execution: NodeExecutionContext<unknown, TContext>,\n ) => Awaitable<unknown>,\n): readonly NodeDefinition<any, any, TContext>[] => {\n const activeAgentCalls = new WeakMap<\n MidsceneUIAgent,\n { scopeId: string; node: string }\n >();\n const runAgentCall = async <T>(\n node: string,\n execution: NodeExecutionContext<unknown, TContext>,\n agent: MidsceneUIAgent,\n call: () => Promise<T>,\n captureExecutions = true,\n ): Promise<T> => {\n execution.signal.throwIfAborted();\n const scopeId =\n execution.scope === 'case'\n ? execution.case.runId\n : execution.document.documentRunId;\n const activeCall = activeAgentCalls.get(agent);\n if (activeCall) {\n throw new NodeExecutionError(\n node,\n new Error(\n `The same Agent instance cannot execute overlapping Midscene Test Steps. Active scope: ${activeCall.scopeId} (${activeCall.node}); requested scope: ${scopeId} (${node}).`,\n ),\n );\n }\n\n const currentCall = { scopeId, node };\n activeAgentCalls.set(agent, currentCall);\n const releaseCall = () => {\n // An aborted call may finish after another Step has acquired this Agent.\n if (activeAgentCalls.get(agent) === currentCall) {\n activeAgentCalls.delete(agent);\n }\n };\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 = () => {\n try {\n stopListening();\n } finally {\n releaseCall();\n }\n };\n execution.signal.addEventListener('abort', stopListeningOnAbort, {\n once: true,\n });\n try {\n if (captureExecutions && agent.addDumpUpdateListener) {\n removeListener = agent.addDumpUpdateListener((_dump, executionRef) => {\n if (!executionRef) return;\n if (!executionRef.id) {\n sawExecutionWithoutId = true;\n return;\n }\n execution.report.addTrace({\n type: 'midscene-execution',\n executionId: executionRef.id,\n });\n });\n if (execution.signal.aborted) stopListening();\n }\n return await call();\n } finally {\n execution.signal.removeEventListener('abort', stopListeningOnAbort);\n try {\n stopListening();\n } finally {\n releaseCall();\n }\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(execution) {\n const agent = await getAgent(execution);\n const call = async () =>\n definition.execute(agent, execution.input, {\n signal: execution.signal,\n });\n if (typeof agent !== 'object' || agent === null) return call();\n return runAgentCall(\n definition.name,\n execution,\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 registeredAgentScopes = new Set<string>();\n const getExecutionId = (\n execution: NodeExecutionContext<unknown, TContext>,\n ) =>\n execution.scope === 'case'\n ? execution.case.runId\n : execution.document.documentRunId;\n const getAgent = async (\n execution: NodeExecutionContext<unknown, TContext>,\n ): Promise<MidsceneUIAgent> => {\n if (!options.agentProvider) return options.getAgent!(execution);\n const runId = getExecutionId(execution);\n if (\n options.agentProvider.releaseAgent &&\n !registeredAgentScopes.has(runId)\n ) {\n registeredAgentScopes.add(runId);\n execution.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 registeredAgentScopes.delete(runId);\n }\n });\n }\n return options.agentProvider.getAgent(runId, execution);\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":["__webpack_require__","definition","key","Object","obj","prop","Symbol","WorkflowError","Error","undefined","message","options","NodeDefinitionError","details","NodeExecutionError","node","cause","causeMessage","String","validateOptionalText","value","field","validateInputSchema","schema","z","validateDefinition","defineNode","waitInputSchema","waitFor","durationMs","signal","Promise","resolve","reject","timeout","setTimeout","abort","clearTimeout","createAgentTestRunnerNodes","definitions","getAgent","activeAgentCalls","WeakMap","runAgentCall","execution","agent","call","captureExecutions","scopeId","activeCall","currentCall","releaseCall","removeListener","sawExecutionWithoutId","stopListening","remove","stopListeningOnAbort","_dump","executionRef","warnReportTrace","getDebug","createMidsceneNodes","registeredAgentScopes","Set","getExecutionId","runId","released","agentDefinitions","multiplier"],"mappings":"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"}
|
package/dist/lib/test-cli.js
CHANGED
|
@@ -104,12 +104,12 @@ var __webpack_exports__ = {};
|
|
|
104
104
|
sections.push(`**Config file:** ${location.configPath ? `\`${location.configPath}\`` : 'No config file loaded.'}`);
|
|
105
105
|
for (const project of location.projects){
|
|
106
106
|
const patterns = project.files.include.map((pattern)=>`\`${pattern}\``).join(', ');
|
|
107
|
-
const projectLabel = location.projects.length > 1 ? ` (Execution Project: ${project.name})` : '';
|
|
107
|
+
const projectLabel = location.projects.length > 1 || 'default' !== project.name ? ` (Execution Project: ${project.name})` : '';
|
|
108
108
|
const excludes = project.files.exclude?.length ? `; excludes: ${project.files.exclude.map((pattern)=>`\`${pattern}\``).join(', ')}` : '';
|
|
109
109
|
sections.push(`**Case files:** ${patterns}${projectLabel}${excludes}`);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
sections.push('Files matching `files.exclude` are not selected. Midscene Test always ignores `.git/`, `.midscene/`, `midscene_run/`, and `node_modules/` directories, including nested ones.', 'Call a Node in `cases[].steps`, or in the Workflow Document lifecycle steps `beforeAll`, `beforeEach`, `afterEach`, and `afterAll`.
|
|
112
|
+
sections.push('Files matching `files.exclude` are not selected. Midscene Test always ignores `.git/`, `.midscene/`, `midscene_run/`, and `node_modules/` directories, including nested ones.', 'Call a Node in `cases[].steps`, or in the Workflow Document lifecycle steps `beforeAll`, `beforeEach`, `afterEach`, and `afterAll`. Root Nodes are shared defaults; `projects[].nodes` adds local Nodes and overrides same-named root Nodes only within that Execution Project. Each Execution Project supplies its own runtime environment.');
|
|
113
113
|
return sections.join('\n\n');
|
|
114
114
|
};
|
|
115
115
|
const WORKFLOW_STEP_CONTRACT = `## Workflow Step Contract
|
|
@@ -569,26 +569,15 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
569
569
|
assertJsonValue(value, label, new Set());
|
|
570
570
|
return deepFreezeJson(value);
|
|
571
571
|
};
|
|
572
|
-
const
|
|
573
|
-
'web',
|
|
574
|
-
'android',
|
|
575
|
-
'ios',
|
|
576
|
-
'harmony',
|
|
577
|
-
'computer'
|
|
578
|
-
]);
|
|
579
|
-
const validatePlatform = (value, label)=>{
|
|
580
|
-
if ('string' != typeof value || !platforms.has(value)) throw new TypeError(`Midscene config ${label} must be one of web, android, ios, harmony, computer.`);
|
|
581
|
-
return value;
|
|
582
|
-
};
|
|
583
|
-
const validateProjectSetup = (value, projectPlatform, label)=>{
|
|
572
|
+
const validateProjectSetup = (value, label)=>{
|
|
584
573
|
if (void 0 === value) return;
|
|
585
574
|
if (!isRecord(value)) throw new TypeError(`Midscene config ${label} must be an object.`);
|
|
575
|
+
rejectUnknownKeys(value, [
|
|
576
|
+
'name',
|
|
577
|
+
'setup'
|
|
578
|
+
], label);
|
|
586
579
|
if ('string' != typeof value.name || 0 === value.name.trim().length) throw new TypeError(`Midscene config ${label}.name must be non-empty.`);
|
|
587
580
|
if ('function' != typeof value.setup) throw new TypeError(`Midscene config ${label}.setup must be a function.`);
|
|
588
|
-
const supported = void 0 === value.platform ? void 0 : Array.isArray(value.platform) ? value.platform.map((item, index)=>validatePlatform(item, `${label}.platform[${index}]`)) : [
|
|
589
|
-
validatePlatform(value.platform, `${label}.platform`)
|
|
590
|
-
];
|
|
591
|
-
if (supported && !supported.includes(projectPlatform)) throw new TypeError(`Midscene config ${label} does not support project platform "${projectPlatform}".`);
|
|
592
581
|
return value;
|
|
593
582
|
};
|
|
594
583
|
const validatePositiveInteger = (value, fallback, label)=>{
|
|
@@ -601,24 +590,15 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
601
590
|
if (!Number.isInteger(value) || value < 0) throw new TypeError(`Midscene config ${label} must be a non-negative integer.`);
|
|
602
591
|
return value;
|
|
603
592
|
};
|
|
604
|
-
const validateExecutionProjects = (value, defaultSetup)=>{
|
|
593
|
+
const validateExecutionProjects = (value, defaultSetup, globalNodes)=>{
|
|
605
594
|
if (void 0 === value) {
|
|
606
|
-
|
|
607
|
-
if (void 0 !== defaultSetup) {
|
|
608
|
-
if (!isRecord(defaultSetup)) throw new TypeError('Midscene config setup must be an object.');
|
|
609
|
-
if (Array.isArray(defaultSetup.platform)) {
|
|
610
|
-
if (1 !== defaultSetup.platform.length) throw new TypeError('Midscene config setup.platform must select exactly one platform when projects is omitted.');
|
|
611
|
-
platform = validatePlatform(defaultSetup.platform[0], 'setup.platform[0]');
|
|
612
|
-
} else if (void 0 !== defaultSetup.platform) platform = validatePlatform(defaultSetup.platform, 'setup.platform');
|
|
613
|
-
}
|
|
614
|
-
const setup = validateProjectSetup(defaultSetup, platform, 'setup');
|
|
595
|
+
const setup = validateProjectSetup(defaultSetup, 'setup');
|
|
615
596
|
return {
|
|
616
597
|
hasExplicitProjects: false,
|
|
617
598
|
projects: Object.freeze([
|
|
618
599
|
Object.freeze({
|
|
619
600
|
projectId: projectIdFromIndex(0),
|
|
620
601
|
name: 'default',
|
|
621
|
-
platform,
|
|
622
602
|
...setup ? {
|
|
623
603
|
setup
|
|
624
604
|
} : {},
|
|
@@ -627,7 +607,8 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
627
607
|
exclude: []
|
|
628
608
|
}),
|
|
629
609
|
retry: 0,
|
|
630
|
-
variables: Object.freeze({})
|
|
610
|
+
variables: Object.freeze({}),
|
|
611
|
+
nodes: new NodeRegistry(globalNodes.definitions())
|
|
631
612
|
})
|
|
632
613
|
])
|
|
633
614
|
};
|
|
@@ -640,8 +621,8 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
640
621
|
if (!isRecord(candidate)) throw new TypeError(`Midscene config ${label} must be an object.`);
|
|
641
622
|
rejectUnknownKeys(candidate, [
|
|
642
623
|
'name',
|
|
643
|
-
'platform',
|
|
644
624
|
'setup',
|
|
625
|
+
'nodes',
|
|
645
626
|
'files',
|
|
646
627
|
'tags',
|
|
647
628
|
'retry',
|
|
@@ -650,12 +631,17 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
650
631
|
if ('string' != typeof candidate.name || 0 === candidate.name.trim().length) throw new TypeError(`Midscene config ${label}.name must be non-empty.`);
|
|
651
632
|
if (names.has(candidate.name)) throw new TypeError(`Midscene config project name "${candidate.name}" must be unique.`);
|
|
652
633
|
names.add(candidate.name);
|
|
653
|
-
const platform = validatePlatform(candidate.platform, `${label}.platform`);
|
|
654
634
|
const files = validateTestFileSelection(candidate.files, `${label}.files`);
|
|
635
|
+
if (void 0 !== candidate.nodes && !Array.isArray(candidate.nodes)) throw new TypeError(`Midscene config ${label}.nodes must be an array.`);
|
|
636
|
+
const localNodes = new NodeRegistry(candidate.nodes);
|
|
637
|
+
const nodes = new NodeRegistry([
|
|
638
|
+
...globalNodes.definitions().filter((node)=>!localNodes.has(node.name)),
|
|
639
|
+
...localNodes.definitions()
|
|
640
|
+
]);
|
|
655
641
|
return Object.freeze({
|
|
656
642
|
projectId: projectIdFromIndex(index),
|
|
657
643
|
name: candidate.name,
|
|
658
|
-
|
|
644
|
+
nodes,
|
|
659
645
|
...files ? {
|
|
660
646
|
files
|
|
661
647
|
} : {},
|
|
@@ -663,7 +649,7 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
663
649
|
retry: validateNonNegativeInteger(candidate.retry, 0, `${label}.retry`),
|
|
664
650
|
variables: validateVariables(candidate.variables, `${label}.variables`),
|
|
665
651
|
...void 0 === candidate.setup ? {} : {
|
|
666
|
-
setup: validateProjectSetup(candidate.setup,
|
|
652
|
+
setup: validateProjectSetup(candidate.setup, `${label}.setup`)
|
|
667
653
|
}
|
|
668
654
|
});
|
|
669
655
|
});
|
|
@@ -697,7 +683,8 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
697
683
|
});
|
|
698
684
|
};
|
|
699
685
|
const validateTestProjectDefinition = (definition)=>{
|
|
700
|
-
if (!isRecord(definition)
|
|
686
|
+
if (!isRecord(definition)) throw new TypeError('Midscene config must default export an object.');
|
|
687
|
+
if (void 0 !== definition.nodes && !Array.isArray(definition.nodes)) throw new TypeError('Midscene config nodes must be an array.');
|
|
701
688
|
if ('setupWorkflow' in definition) throw new TypeError('Midscene config setupWorkflow is no longer supported. Use setup or projects[].setup instead.');
|
|
702
689
|
if ('setupDocument' in definition) throw new TypeError('Midscene config setupDocument is not supported. Use setup or projects[].setup for resources, and beforeAll/afterAll for document lifecycle steps.');
|
|
703
690
|
if ('root' in definition) throw new TypeError('Midscene config root is not supported. Pass the Test Project directory to midscene-test instead.');
|
|
@@ -710,8 +697,8 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
710
697
|
'output',
|
|
711
698
|
'nodes'
|
|
712
699
|
], 'root');
|
|
713
|
-
const resolvedProjects = validateExecutionProjects(definition.projects, definition.setup);
|
|
714
700
|
const nodes = new NodeRegistry(definition.nodes);
|
|
701
|
+
const resolvedProjects = validateExecutionProjects(definition.projects, definition.setup, nodes);
|
|
715
702
|
return {
|
|
716
703
|
...resolvedProjects,
|
|
717
704
|
test: validateTestOptions(definition.test),
|
|
@@ -779,7 +766,6 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
779
766
|
const endedAt = new Date();
|
|
780
767
|
return {
|
|
781
768
|
projectName: options.project.name,
|
|
782
|
-
platform: options.project.platform,
|
|
783
769
|
status: 'failed' === requestedStatus || setupError || teardownErrors.length > 0 ? 'failed' : 'success',
|
|
784
770
|
...setupError ? {
|
|
785
771
|
setupError
|
|
@@ -1080,7 +1066,6 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
1080
1066
|
const project = options.project ?? Object.freeze({
|
|
1081
1067
|
projectId: document.projectId,
|
|
1082
1068
|
name: document.projectId,
|
|
1083
|
-
platform: 'web',
|
|
1084
1069
|
tags: Object.freeze({
|
|
1085
1070
|
include: [],
|
|
1086
1071
|
exclude: []
|
|
@@ -2015,7 +2000,6 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2015
2000
|
return {
|
|
2016
2001
|
projectId: project.projectId,
|
|
2017
2002
|
name: project.name,
|
|
2018
|
-
platform: project.platform,
|
|
2019
2003
|
status: project.status,
|
|
2020
2004
|
retry: project.retry,
|
|
2021
2005
|
...project.lifecycle ? {
|
|
@@ -2166,7 +2150,6 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2166
2150
|
projects: result.projects.map((project)=>({
|
|
2167
2151
|
projectId: project.projectId,
|
|
2168
2152
|
name: project.name,
|
|
2169
|
-
platform: project.platform,
|
|
2170
2153
|
status: project.status,
|
|
2171
2154
|
retry: project.retry,
|
|
2172
2155
|
fileSelection: project.fileSelection,
|
|
@@ -2377,7 +2360,7 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2377
2360
|
if (unknown) throw new Error(`Unknown Midscene project: ${unknown}`);
|
|
2378
2361
|
return projects.filter((project)=>requested.has(project.name));
|
|
2379
2362
|
};
|
|
2380
|
-
const prepareProject = (project, projectRoot,
|
|
2363
|
+
const prepareProject = (project, projectRoot, runDir)=>{
|
|
2381
2364
|
const fileSelection = project.files ?? DEFAULT_TEST_FILE_SELECTION;
|
|
2382
2365
|
const files = discoverTestFiles(projectRoot, fileSelection);
|
|
2383
2366
|
const sources = files.map((absolutePath)=>({
|
|
@@ -2399,7 +2382,7 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2399
2382
|
}
|
|
2400
2383
|
for (const source of sources)try {
|
|
2401
2384
|
const collected = collectWorkflowDocument(source, {
|
|
2402
|
-
resolveNode,
|
|
2385
|
+
resolveNode: project.nodes.get.bind(project.nodes),
|
|
2403
2386
|
variables: project.variables,
|
|
2404
2387
|
env: process.env
|
|
2405
2388
|
});
|
|
@@ -2443,7 +2426,7 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2443
2426
|
});
|
|
2444
2427
|
(0, external_node_fs_.mkdirSync)(runDir);
|
|
2445
2428
|
const selectedProjects = selectProjects(definition.projects, options.projectNames);
|
|
2446
|
-
const preparedProjects = selectedProjects.map((project)=>prepareProject(project, projectRoot,
|
|
2429
|
+
const preparedProjects = selectedProjects.map((project)=>prepareProject(project, projectRoot, runDir));
|
|
2447
2430
|
const progress = options.onProgress ?? (()=>{});
|
|
2448
2431
|
const totalDocuments = preparedProjects.reduce((total, prepared)=>total + prepared.documents.length, 0);
|
|
2449
2432
|
const totalCases = preparedProjects.reduce((total, prepared)=>total + prepared.selectedCaseCount, 0);
|
|
@@ -2479,7 +2462,7 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2479
2462
|
};
|
|
2480
2463
|
const announceProject = (prepared, projectIndex)=>{
|
|
2481
2464
|
const { project } = prepared;
|
|
2482
|
-
runInfrastructureCallback(()=>progress(`[project ${projectIndex + 1}/${preparedProjects.length}] ${project.name}
|
|
2465
|
+
runInfrastructureCallback(()=>progress(`[project ${projectIndex + 1}/${preparedProjects.length}] ${project.name}`));
|
|
2483
2466
|
};
|
|
2484
2467
|
const buildProjectResult = (prepared, cases, documents, lifecycle)=>{
|
|
2485
2468
|
const { project } = prepared;
|
|
@@ -2487,7 +2470,6 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2487
2470
|
return {
|
|
2488
2471
|
projectId: project.projectId,
|
|
2489
2472
|
name: project.name,
|
|
2490
|
-
platform: project.platform,
|
|
2491
2473
|
status: projectFailed ? 'failed' : 'success',
|
|
2492
2474
|
retry: project.retry,
|
|
2493
2475
|
fileSelection: prepared.fileSelection,
|
|
@@ -2536,7 +2518,7 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2536
2518
|
}
|
|
2537
2519
|
projectProgress(` [document ${documentIndex + 1}/${prepared.documents.length}] ${document.sourcePath}`);
|
|
2538
2520
|
const execution = await runWorkflowDocument(document, {
|
|
2539
|
-
resolveNode:
|
|
2521
|
+
resolveNode: project.nodes.require.bind(project.nodes),
|
|
2540
2522
|
project,
|
|
2541
2523
|
projectContext: runtime.context,
|
|
2542
2524
|
retry: project.retry,
|
|
@@ -2692,7 +2674,7 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2692
2674
|
} else throw new Error(`Unknown option: ${arg}`);
|
|
2693
2675
|
}
|
|
2694
2676
|
if ('nodes' === command && resultDir) throw new Error('--result-dir is not supported by nodes.');
|
|
2695
|
-
if ('nodes' === command && projectNames.length >
|
|
2677
|
+
if ('nodes' === command && projectNames.length > 1) throw new Error('nodes accepts only one --project name.');
|
|
2696
2678
|
return {
|
|
2697
2679
|
...command ? {
|
|
2698
2680
|
command
|
|
@@ -2723,10 +2705,15 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2723
2705
|
const configPath = options.configPath ? (0, external_node_path_.resolve)(configSearchRoot, options.configPath) : discoverTestConfig(configSearchRoot);
|
|
2724
2706
|
if (options.configPath && (!configPath || !(0, external_node_fs_.existsSync)(configPath))) throw new Error(`Midscene config does not exist: ${configPath}`);
|
|
2725
2707
|
const project = await loadTestProject(configPath);
|
|
2726
|
-
const
|
|
2708
|
+
const projectName = options.projectNames?.[0];
|
|
2709
|
+
const selectedProjects = projectName ? project.projects.filter((candidate)=>candidate.name === projectName) : project.projects;
|
|
2710
|
+
if (0 === selectedProjects.length) throw new Error(`Unknown Midscene project: ${projectName}`);
|
|
2711
|
+
const registry = selectedProjects[0].nodes;
|
|
2712
|
+
if (selectedProjects.some((candidate)=>candidate.nodes.names().length !== registry.names().length || candidate.nodes.definitions().some((node)=>registry.get(node.name) !== node))) throw new Error('Projects have different Nodes. Use nodes --project <name> to select one.');
|
|
2713
|
+
const nodes = sortNodesForReference(registry.definitions());
|
|
2727
2714
|
const document = renderNodeReference(nodes, {
|
|
2728
2715
|
configPath: configPath ? (0, external_node_path_.relative)(configSearchRoot, configPath).split(external_node_path_.sep).join('/') : void 0,
|
|
2729
|
-
projects:
|
|
2716
|
+
projects: selectedProjects.map((executionProject)=>({
|
|
2730
2717
|
name: executionProject.name,
|
|
2731
2718
|
files: executionProject.files ?? DEFAULT_TEST_FILE_SELECTION
|
|
2732
2719
|
}))
|
|
@@ -2734,6 +2721,7 @@ A Node may map a string step value to one declared input field. Each Node sectio
|
|
|
2734
2721
|
for (const warning of document.warnings)io.error(`midscene-test nodes: ${warning}`);
|
|
2735
2722
|
const referencePath = (0, external_node_path_.resolve)(configSearchRoot, 'midscene-node-reference.md');
|
|
2736
2723
|
(0, external_node_fs_.writeFileSync)(referencePath, document.markdown);
|
|
2724
|
+
if (projectName) io.log(`Execution Project: ${projectName}`);
|
|
2737
2725
|
io.log(`Registered Nodes (${nodes.length}):`);
|
|
2738
2726
|
if (0 === nodes.length) io.log('No nodes are registered by the current Test Project.');
|
|
2739
2727
|
for (const node of nodes)io.log(`- ${node.name}: ${node.description?.trim() || "Description not declared."}`);
|