@midscene/testing-framework 0.0.0 → 1.8.8-beta-20260602033804.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,14 +57,40 @@ const main = async ()=>{
57
57
  cwd: input.cwd,
58
58
  inlineConfig
59
59
  });
60
- const unhandled = result?.unhandledErrors ?? [];
61
- writeOutput({
62
- ok: Boolean(result?.ok),
63
- unhandledErrors: unhandled.map((error)=>({
60
+ const rstestResult = result ?? {};
61
+ const unhandled = rstestResult.unhandledErrors ?? [];
62
+ const testErrors = [];
63
+ const mapErrors = (errors)=>(errors ?? []).map((error)=>({
64
64
  name: error.name,
65
65
  message: error.message,
66
66
  stack: error.stack
67
- }))
67
+ }));
68
+ for (const file of rstestResult.files ?? []){
69
+ const filePath = file.testPath ?? '';
70
+ if (file.errors && file.errors.length > 0) testErrors.push({
71
+ kind: 'suite',
72
+ file: filePath,
73
+ testName: file.name ?? filePath,
74
+ errors: mapErrors(file.errors)
75
+ });
76
+ for (const entry of file.results ?? []){
77
+ if (!entry.errors || 0 === entry.errors.length) continue;
78
+ const trail = [
79
+ ...entry.parentNames ?? [],
80
+ entry.name ?? ''
81
+ ].filter((segment)=>segment && segment.length > 0).join(' > ');
82
+ testErrors.push({
83
+ kind: 'case',
84
+ file: entry.testPath ?? filePath,
85
+ testName: trail || entry.name || filePath,
86
+ errors: mapErrors(entry.errors)
87
+ });
88
+ }
89
+ }
90
+ writeOutput({
91
+ ok: Boolean(rstestResult.ok),
92
+ unhandledErrors: mapErrors(unhandled),
93
+ testErrors
68
94
  });
69
95
  };
70
96
  main().catch((error)=>{
@@ -78,7 +104,8 @@ main().catch((error)=>{
78
104
  message: error instanceof Error ? error.message : String(error),
79
105
  stack: error instanceof Error ? error.stack : void 0
80
106
  }
81
- ]
107
+ ],
108
+ testErrors: []
82
109
  });
83
110
  process.exit(1);
84
111
  });
@@ -1 +1 @@
1
- {"version":3,"file":"runner-worker.mjs","sources":["../../src/runner-worker.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\ninterface WorkerInput {\n cwd: string;\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\ninterface WorkerOutput {\n ok: boolean;\n unhandledErrors: Array<{\n name?: string;\n message?: string;\n stack?: string;\n }>;\n}\n\nconst readStdin = (): Promise<string> =>\n new Promise((resolveStdin, reject) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk) => {\n data += chunk;\n });\n process.stdin.on('end', () => resolveStdin(data));\n process.stdin.on('error', reject);\n });\n\nconst writeOutput = (output: WorkerOutput): void => {\n process.stdout.write(\n `__MIDSCENE_RUNNER_WORKER_RESULT__${JSON.stringify(output)}\\n`,\n );\n};\n\nconst main = async (): Promise<void> => {\n const raw = await readStdin();\n const input = JSON.parse(raw) as WorkerInput;\n\n const projectRequire = createRequire(resolve(input.root, 'package.json'));\n const rstestPkgJson = projectRequire.resolve('@rstest/core/package.json');\n const rsbuildEntry = createRequire(rstestPkgJson).resolve('@rsbuild/core');\n\n const [{ runRstest }, { rspack }] = await Promise.all([\n import('@rstest/core/api'),\n import(pathToFileURL(rsbuildEntry).href),\n ]);\n\n const maxConcurrency =\n input.maxConcurrency !== undefined\n ? Math.max(1, input.maxConcurrency)\n : undefined;\n\n const inlineConfig: Record<string, unknown> = {\n root: input.root,\n include: input.include,\n testEnvironment: 'node',\n reporters: [],\n ...(input.testTimeout !== undefined\n ? { testTimeout: input.testTimeout }\n : {}),\n ...(maxConcurrency !== undefined ? { maxConcurrency } : {}),\n ...(maxConcurrency !== undefined\n ? { pool: { maxWorkers: maxConcurrency, minWorkers: maxConcurrency } }\n : {}),\n ...(input.bail !== undefined ? { bail: input.bail } : {}),\n ...(input.retry !== undefined ? { retry: input.retry } : {}),\n tools: {\n rspack: (\n _config: unknown,\n { appendPlugins }: { appendPlugins: (plugin: unknown) => void },\n ) => {\n appendPlugins(\n new rspack.experiments.VirtualModulesPlugin(input.virtualModules),\n );\n },\n },\n };\n\n const result = await runRstest({ cwd: input.cwd, inlineConfig });\n\n type UnhandledError = { name?: string; message?: string; stack?: string };\n const unhandled =\n (result as { unhandledErrors?: UnhandledError[] } | undefined)\n ?.unhandledErrors ?? [];\n\n writeOutput({\n ok: Boolean(result?.ok),\n unhandledErrors: unhandled.map((error) => ({\n name: error.name,\n message: error.message,\n stack: error.stack,\n })),\n });\n};\n\nmain().catch((error) => {\n const message =\n error instanceof Error ? error.stack || error.message : String(error);\n process.stderr.write(`runner-worker fatal: ${message}\\n`);\n writeOutput({\n ok: false,\n unhandledErrors: [\n {\n name: error instanceof Error ? error.name : 'WorkerError',\n message: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n },\n ],\n });\n process.exit(1);\n});\n"],"names":["readStdin","Promise","resolveStdin","reject","data","process","chunk","writeOutput","output","JSON","main","raw","input","projectRequire","createRequire","resolve","rstestPkgJson","rsbuildEntry","runRstest","rspack","pathToFileURL","maxConcurrency","undefined","Math","inlineConfig","_config","appendPlugins","result","unhandled","Boolean","error","message","Error","String"],"mappings":"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"}
1
+ {"version":3,"file":"runner-worker.mjs","sources":["../../src/runner-worker.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\ninterface WorkerInput {\n cwd: string;\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\ninterface WorkerError {\n name?: string;\n message?: string;\n stack?: string;\n}\n\ninterface WorkerOutput {\n ok: boolean;\n unhandledErrors: WorkerError[];\n /**\n * Failures captured from Rstest's result graph. We forward both case-level\n * errors and file/suite-level errors (e.g. a `beforeAll` hook crash) so the\n * framework can surface a meaningful message when a case never produced its\n * own result file.\n */\n testErrors: Array<{\n kind: 'case' | 'suite';\n file: string;\n testName: string;\n errors: WorkerError[];\n }>;\n}\n\nconst readStdin = (): Promise<string> =>\n new Promise((resolveStdin, reject) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk) => {\n data += chunk;\n });\n process.stdin.on('end', () => resolveStdin(data));\n process.stdin.on('error', reject);\n });\n\nconst writeOutput = (output: WorkerOutput): void => {\n process.stdout.write(\n `__MIDSCENE_RUNNER_WORKER_RESULT__${JSON.stringify(output)}\\n`,\n );\n};\n\nconst main = async (): Promise<void> => {\n const raw = await readStdin();\n const input = JSON.parse(raw) as WorkerInput;\n\n const projectRequire = createRequire(resolve(input.root, 'package.json'));\n const rstestPkgJson = projectRequire.resolve('@rstest/core/package.json');\n const rsbuildEntry = createRequire(rstestPkgJson).resolve('@rsbuild/core');\n\n const [{ runRstest }, { rspack }] = await Promise.all([\n import('@rstest/core/api'),\n import(pathToFileURL(rsbuildEntry).href),\n ]);\n\n const maxConcurrency =\n input.maxConcurrency !== undefined\n ? Math.max(1, input.maxConcurrency)\n : undefined;\n\n const inlineConfig: Record<string, unknown> = {\n root: input.root,\n include: input.include,\n testEnvironment: 'node',\n reporters: [],\n ...(input.testTimeout !== undefined\n ? { testTimeout: input.testTimeout }\n : {}),\n ...(maxConcurrency !== undefined ? { maxConcurrency } : {}),\n ...(maxConcurrency !== undefined\n ? { pool: { maxWorkers: maxConcurrency, minWorkers: maxConcurrency } }\n : {}),\n ...(input.bail !== undefined ? { bail: input.bail } : {}),\n ...(input.retry !== undefined ? { retry: input.retry } : {}),\n tools: {\n rspack: (\n _config: unknown,\n { appendPlugins }: { appendPlugins: (plugin: unknown) => void },\n ) => {\n appendPlugins(\n new rspack.experiments.VirtualModulesPlugin(input.virtualModules),\n );\n },\n },\n };\n\n const result = await runRstest({ cwd: input.cwd, inlineConfig });\n\n type FormattedError = {\n name?: string;\n message?: string;\n stack?: string;\n };\n type RstestTestResult = {\n name?: string;\n testPath?: string;\n parentNames?: string[];\n status?: string;\n errors?: FormattedError[];\n };\n type RstestFileResult = RstestTestResult & {\n results?: RstestTestResult[];\n };\n type RstestRunResult = {\n ok?: boolean;\n files?: RstestFileResult[];\n unhandledErrors?: FormattedError[];\n };\n\n const rstestResult = (result ?? {}) as RstestRunResult;\n const unhandled = rstestResult.unhandledErrors ?? [];\n const testErrors: WorkerOutput['testErrors'] = [];\n\n const mapErrors = (errors: FormattedError[] | undefined): WorkerError[] =>\n (errors ?? []).map((error) => ({\n name: error.name,\n message: error.message,\n stack: error.stack,\n }));\n\n for (const file of rstestResult.files ?? []) {\n const filePath = file.testPath ?? '';\n // Errors that belong to the file itself (suite hooks, top-level throws).\n if (file.errors && file.errors.length > 0) {\n testErrors.push({\n kind: 'suite',\n file: filePath,\n testName: file.name ?? filePath,\n errors: mapErrors(file.errors),\n });\n }\n // Per-test results (rstest flattens cases and nested suites here).\n for (const entry of file.results ?? []) {\n if (!entry.errors || entry.errors.length === 0) continue;\n const trail = [...(entry.parentNames ?? []), entry.name ?? '']\n .filter((segment) => segment && segment.length > 0)\n .join(' > ');\n testErrors.push({\n kind: 'case',\n file: entry.testPath ?? filePath,\n testName: trail || entry.name || filePath,\n errors: mapErrors(entry.errors),\n });\n }\n }\n\n writeOutput({\n ok: Boolean(rstestResult.ok),\n unhandledErrors: mapErrors(unhandled),\n testErrors,\n });\n};\n\nmain().catch((error) => {\n const message =\n error instanceof Error ? error.stack || error.message : String(error);\n process.stderr.write(`runner-worker fatal: ${message}\\n`);\n writeOutput({\n ok: false,\n unhandledErrors: [\n {\n name: error instanceof Error ? error.name : 'WorkerError',\n message: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n },\n ],\n testErrors: [],\n });\n process.exit(1);\n});\n"],"names":["readStdin","Promise","resolveStdin","reject","data","process","chunk","writeOutput","output","JSON","main","raw","input","projectRequire","createRequire","resolve","rstestPkgJson","rsbuildEntry","runRstest","rspack","pathToFileURL","maxConcurrency","undefined","Math","inlineConfig","_config","appendPlugins","result","rstestResult","unhandled","testErrors","mapErrors","errors","error","file","filePath","entry","trail","segment","Boolean","message","Error","String"],"mappings":"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"}
@@ -66,22 +66,29 @@ const defaultRstestRunner = async (project)=>{
66
66
  if (stdoutBuffer.length > 0 && !stdoutBuffer.startsWith(RUNNER_WORKER_RESULT_PREFIX)) process.stdout.write(stdoutBuffer);
67
67
  if (!parsedResult) return void rejectRunner(new Error(`runner-worker exited (code=${code ?? 'null'}, signal=${signal ?? 'null'}) without producing a result`));
68
68
  if (parsedResult.unhandledErrors.length > 0) for (const error of parsedResult.unhandledErrors)console.error(error.stack || `${error.name}: ${error.message}`);
69
- resolveRunner({
70
- ok: parsedResult.ok
71
- });
69
+ resolveRunner(parsedResult);
72
70
  });
73
71
  child.stdin?.write(JSON.stringify(input));
74
72
  child.stdin?.end();
75
73
  });
76
74
  };
77
- const readCaseResult = (item)=>{
75
+ const formatWorkerError = (error)=>{
76
+ if (error.stack) return error.stack;
77
+ if (error.message) return error.name ? `${error.name}: ${error.message}` : error.message;
78
+ return error.name ?? 'Unknown error';
79
+ };
80
+ const readCaseResult = (item, context)=>{
78
81
  if (existsSync(item.resultFile)) return JSON.parse(readFileSync(item.resultFile, 'utf8'));
82
+ const caseErrors = context.testErrors.filter((entry)=>'case' === entry.kind && entry.testName.includes(item.testName)).flatMap((entry)=>entry.errors);
83
+ const suiteErrors = context.testErrors.filter((entry)=>'suite' === entry.kind).flatMap((entry)=>entry.errors);
84
+ const errorSource = caseErrors.length > 0 ? caseErrors : suiteErrors.length > 0 ? suiteErrors : context.unhandledErrors;
85
+ const errorText = errorSource.length > 0 ? errorSource.map(formatWorkerError).join('\n\n') : 'Not executed';
79
86
  return {
80
87
  file: item.filePath,
81
88
  testName: item.testName,
82
89
  success: false,
83
90
  duration: 0,
84
- error: 'Not executed'
91
+ error: errorText
85
92
  };
86
93
  };
87
94
  const writeSummaryFile = (loaded, summary)=>{
@@ -147,7 +154,10 @@ async function runMidsceneSuite(options = {}) {
147
154
  bail: loaded.config.testRunner?.bail,
148
155
  retry: loaded.config.testRunner?.retry
149
156
  });
150
- const results = yamlCases.map(readCaseResult);
157
+ const results = yamlCases.map((item)=>readCaseResult(item, {
158
+ testErrors: runResult.testErrors ?? [],
159
+ unhandledErrors: runResult.unhandledErrors ?? []
160
+ }));
151
161
  const summary = {
152
162
  total: results.length,
153
163
  passed: results.filter((result)=>result.success).length,
@@ -1 +1 @@
1
- {"version":3,"file":"runner.mjs","sources":["../../src/runner.ts"],"sourcesContent":["import { fork } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { collectFrameworkTestFiles, loadMidsceneConfig } from './config';\nimport { loadFrameworkDotenv } from './dotenv';\nimport { createYamlFrameworkSuiteSource } from './runtime/source';\nimport type {\n FrameworkCaseResult,\n FrameworkSuiteSummary,\n LoadedMidsceneConfig,\n} from './types';\n\nconst RUNNER_WORKER_RESULT_PREFIX = '__MIDSCENE_RUNNER_WORKER_RESULT__';\n\nconst SUITE_MODULE_ID = 'virtual:midscene-framework/suite.test.ts';\n\nexport interface FrameworkRstestProject {\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\nexport type FrameworkRstestRunner = (\n project: FrameworkRstestProject,\n) => Promise<{ ok: boolean }>;\n\nexport interface RunMidsceneSuiteOptions {\n /** Path to `midscene.config.ts`. Defaults to the one in `cwd`. */\n configPath?: string;\n /** Directory for generated wiring and per-case result files. */\n outputDir?: string;\n /** Override the Rstest runner. Mainly for tests. */\n rstestRunner?: FrameworkRstestRunner;\n stdio?: 'inherit' | 'pipe';\n}\n\ninterface SuiteCase {\n filePath: string;\n testName: string;\n resultFile: string;\n}\n\nconst safeStem = (relativePath: string, index: number): string => {\n const base = relativePath\n .replace(/\\.[^.]+$/, '')\n .replace(/[^a-zA-Z0-9._-]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return `${String(index + 1).padStart(3, '0')}-${base || 'case'}`;\n};\n\ninterface WorkerInput {\n cwd: string;\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\ninterface WorkerOutput {\n ok: boolean;\n unhandledErrors: Array<{\n name?: string;\n message?: string;\n stack?: string;\n }>;\n}\n\nconst resolveWorkerEntry = (): string => {\n // `runner.js` and `runner-worker.js` ship side-by-side in both the CJS and\n // ESM bundles, so resolving relative to the current module works in both\n // formats. Use `__filename` for CJS and `import.meta.url` for ESM. The ESM\n // build emits `.mjs` files; pick the matching extension.\n const moduleUrl =\n typeof __filename === 'string'\n ? null\n : (import.meta as { url?: string } | undefined)?.url;\n const here = moduleUrl ? fileURLToPath(moduleUrl) : __filename;\n const ext = here.endsWith('.mjs') ? '.mjs' : '.js';\n return resolve(dirname(here), `runner-worker${ext}`);\n};\n\n/**\n * Default Rstest driver. We deliberately spawn `runRstest` in a child process:\n * the user's `midscene.config.ts` typically imports playwright (or\n * `@midscene/web/playwright`), which transitively initializes a bundled\n * `@vitest/expect` copy and defines `Symbol.for('$$jest-matchers-object')` on\n * `globalThis` non-configurably. Calling `runRstest` in the same process then\n * fails with `TypeError: Cannot redefine property: Symbol($$jest-matchers-object)`\n * because Rstest's own `@vitest/expect` copy tries to redefine the same global.\n * Forking sidesteps the collision: the child has never imported playwright.\n */\nconst defaultRstestRunner: FrameworkRstestRunner = async (project) => {\n const workerEntry = resolveWorkerEntry();\n\n const input: WorkerInput = {\n cwd: project.root,\n root: project.root,\n include: project.include,\n virtualModules: project.virtualModules,\n maxConcurrency: project.maxConcurrency,\n testTimeout: project.testTimeout,\n bail: project.bail,\n retry: project.retry,\n };\n\n return await new Promise<{ ok: boolean }>((resolveRunner, rejectRunner) => {\n const child = fork(workerEntry, [], {\n cwd: project.root,\n env: process.env,\n stdio: ['pipe', 'pipe', 'inherit', 'ipc'],\n });\n\n let stdoutBuffer = '';\n let parsedResult: WorkerOutput | undefined;\n\n child.stdout?.setEncoding('utf8');\n child.stdout?.on('data', (chunk: string) => {\n stdoutBuffer += chunk;\n let newlineIndex = stdoutBuffer.indexOf('\\n');\n while (newlineIndex !== -1) {\n const line = stdoutBuffer.slice(0, newlineIndex);\n stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);\n if (line.startsWith(RUNNER_WORKER_RESULT_PREFIX)) {\n try {\n parsedResult = JSON.parse(\n line.slice(RUNNER_WORKER_RESULT_PREFIX.length),\n ) as WorkerOutput;\n } catch (error) {\n rejectRunner(\n new Error(\n `Failed to parse runner-worker result: ${\n error instanceof Error ? error.message : String(error)\n }`,\n ),\n );\n return;\n }\n } else if (line.length > 0) {\n process.stdout.write(`${line}\\n`);\n }\n newlineIndex = stdoutBuffer.indexOf('\\n');\n }\n });\n\n child.on('error', (error) => {\n rejectRunner(error);\n });\n\n child.on('exit', (code, signal) => {\n if (\n stdoutBuffer.length > 0 &&\n !stdoutBuffer.startsWith(RUNNER_WORKER_RESULT_PREFIX)\n ) {\n process.stdout.write(stdoutBuffer);\n }\n\n if (!parsedResult) {\n rejectRunner(\n new Error(\n `runner-worker exited (code=${code ?? 'null'}, signal=${\n signal ?? 'null'\n }) without producing a result`,\n ),\n );\n return;\n }\n\n if (parsedResult.unhandledErrors.length > 0) {\n for (const error of parsedResult.unhandledErrors) {\n console.error(error.stack || `${error.name}: ${error.message}`);\n }\n }\n\n resolveRunner({ ok: parsedResult.ok });\n });\n\n child.stdin?.write(JSON.stringify(input));\n child.stdin?.end();\n });\n};\n\nconst readCaseResult = (item: SuiteCase): FrameworkCaseResult => {\n if (existsSync(item.resultFile)) {\n return JSON.parse(\n readFileSync(item.resultFile, 'utf8'),\n ) as FrameworkCaseResult;\n }\n\n return {\n file: item.filePath,\n testName: item.testName,\n success: false,\n duration: 0,\n error: 'Not executed',\n };\n};\n\nconst writeSummaryFile = (\n loaded: LoadedMidsceneConfig,\n summary: FrameworkSuiteSummary,\n): string | undefined => {\n const target = loaded.config.output?.summary;\n if (!target) {\n return undefined;\n }\n const summaryPath = resolve(loaded.root, target);\n mkdirSync(dirname(summaryPath), { recursive: true });\n writeFileSync(summaryPath, JSON.stringify(summary, null, 2));\n return summaryPath;\n};\n\nconst printSummary = (summary: FrameworkSuiteSummary, summaryPath?: string) => {\n console.log('\\nšŸ“Š Midscene suite summary');\n console.log(` Total: ${summary.total}`);\n console.log(` Passed: ${summary.passed}`);\n console.log(` Failed: ${summary.failed}`);\n console.log(` Duration: ${(summary.durationMs / 1000).toFixed(2)}s`);\n if (summaryPath) {\n console.log(` Summary: ${summaryPath}`);\n }\n for (const result of summary.results) {\n if (!result.success) {\n console.log(` āŒ ${result.testName}: ${result.error ?? 'failed'}`);\n }\n }\n};\n\n/**\n * Load `midscene.config.ts`, discover cases, run them through Rstest, and write\n * the suite summary. Intended to be the entire body of a project's\n * `run-suite.ts`:\n *\n * ```ts\n * import { runMidsceneSuite } from '@midscene/testing-framework';\n * await runMidsceneSuite();\n * ```\n */\nexport async function runMidsceneSuite(\n options: RunMidsceneSuiteOptions = {},\n): Promise<FrameworkSuiteSummary> {\n const loaded = await loadMidsceneConfig(options.configPath);\n\n const dotenvFiles = loadFrameworkDotenv({\n cwd: process.cwd(),\n configDir: loaded.root,\n envConfig: loaded.config.env,\n });\n for (const entry of dotenvFiles) {\n if (entry.loaded) {\n console.log(` Env file: ${entry.path}`);\n }\n }\n\n const files = await collectFrameworkTestFiles({\n root: loaded.root,\n config: loaded.config,\n });\n\n if (files.length === 0) {\n throw new Error(\n `No test files found in ${resolve(loaded.root, loaded.config.testDir || './e2e')}`,\n );\n }\n\n const outputDir =\n options.outputDir ||\n join(loaded.root, 'midscene_run', 'tmp', `framework-${Date.now()}`);\n const resultDir = join(outputDir, 'results');\n mkdirSync(resultDir, { recursive: true });\n\n const yamlCases: SuiteCase[] = files\n .filter((file) => file.type === 'yaml')\n .map((file, index) => ({\n filePath: file.filePath,\n testName: file.relativePath,\n resultFile: join(resultDir, `${safeStem(file.relativePath, index)}.json`),\n }));\n\n const virtualModules: Record<string, string> = {};\n const include: string[] = [];\n\n if (yamlCases.length > 0) {\n virtualModules[SUITE_MODULE_ID] = createYamlFrameworkSuiteSource({\n configPath: loaded.path,\n projectDir: loaded.root,\n cases: yamlCases,\n });\n include.push(SUITE_MODULE_ID);\n }\n\n for (const file of files) {\n if (file.type === 'test') {\n include.push(file.filePath);\n }\n }\n\n const runner = options.rstestRunner || defaultRstestRunner;\n const runResult = await runner({\n root: loaded.root,\n include,\n virtualModules,\n maxConcurrency: loaded.config.testRunner?.maxConcurrency,\n testTimeout: loaded.config.testRunner?.testTimeout,\n bail: loaded.config.testRunner?.bail,\n retry: loaded.config.testRunner?.retry,\n });\n\n const results = yamlCases.map(readCaseResult);\n const summary: FrameworkSuiteSummary = {\n total: results.length,\n passed: results.filter((result) => result.success).length,\n failed: results.filter((result) => !result.success).length,\n durationMs: results.reduce(\n (sum, result) => sum + (result.duration || 0),\n 0,\n ),\n results,\n };\n\n const summaryPath = writeSummaryFile(loaded, summary);\n printSummary(summary, summaryPath);\n\n if (!runResult.ok || summary.failed > 0) {\n process.exitCode = 1;\n }\n\n return summary;\n}\n"],"names":["RUNNER_WORKER_RESULT_PREFIX","SUITE_MODULE_ID","safeStem","relativePath","index","base","String","resolveWorkerEntry","moduleUrl","__filename","here","fileURLToPath","ext","resolve","dirname","defaultRstestRunner","project","workerEntry","input","Promise","resolveRunner","rejectRunner","child","fork","process","stdoutBuffer","parsedResult","chunk","newlineIndex","line","JSON","error","Error","code","signal","console","readCaseResult","item","existsSync","readFileSync","writeSummaryFile","loaded","summary","target","summaryPath","mkdirSync","writeFileSync","printSummary","result","runMidsceneSuite","options","loadMidsceneConfig","dotenvFiles","loadFrameworkDotenv","entry","files","collectFrameworkTestFiles","outputDir","join","Date","resultDir","yamlCases","file","virtualModules","include","createYamlFrameworkSuiteSource","runner","runResult","results","sum"],"mappings":";;;;;;;AAaA,MAAMA,8BAA8B;AAEpC,MAAMC,kBAAkB;AAgCxB,MAAMC,WAAW,CAACC,cAAsBC;IACtC,MAAMC,OAAOF,aACV,OAAO,CAAC,YAAY,IACpB,OAAO,CAAC,qBAAqB,KAC7B,OAAO,CAAC,YAAY;IACvB,OAAO,GAAGG,OAAOF,QAAQ,GAAG,QAAQ,CAAC,GAAG,KAAK,CAAC,EAAEC,QAAQ,QAAQ;AAClE;AAsBA,MAAME,qBAAqB;IAKzB,MAAMC,YACJ,AAAsB,YAAtB,OAAOC,aACH,OACC,aAA8C;IACrD,MAAMC,OAAOF,YAAYG,cAAcH,aAAaC;IACpD,MAAMG,MAAMF,KAAK,QAAQ,CAAC,UAAU,SAAS;IAC7C,OAAOG,QAAQC,QAAQJ,OAAO,CAAC,aAAa,EAAEE,KAAK;AACrD;AAYA,MAAMG,sBAA6C,OAAOC;IACxD,MAAMC,cAAcV;IAEpB,MAAMW,QAAqB;QACzB,KAAKF,QAAQ,IAAI;QACjB,MAAMA,QAAQ,IAAI;QAClB,SAASA,QAAQ,OAAO;QACxB,gBAAgBA,QAAQ,cAAc;QACtC,gBAAgBA,QAAQ,cAAc;QACtC,aAAaA,QAAQ,WAAW;QAChC,MAAMA,QAAQ,IAAI;QAClB,OAAOA,QAAQ,KAAK;IACtB;IAEA,OAAO,MAAM,IAAIG,QAAyB,CAACC,eAAeC;QACxD,MAAMC,QAAQC,KAAKN,aAAa,EAAE,EAAE;YAClC,KAAKD,QAAQ,IAAI;YACjB,KAAKQ,QAAQ,GAAG;YAChB,OAAO;gBAAC;gBAAQ;gBAAQ;gBAAW;aAAM;QAC3C;QAEA,IAAIC,eAAe;QACnB,IAAIC;QAEJJ,MAAM,MAAM,EAAE,YAAY;QAC1BA,MAAM,MAAM,EAAE,GAAG,QAAQ,CAACK;YACxBF,gBAAgBE;YAChB,IAAIC,eAAeH,aAAa,OAAO,CAAC;YACxC,MAAOG,AAAiB,OAAjBA,aAAqB;gBAC1B,MAAMC,OAAOJ,aAAa,KAAK,CAAC,GAAGG;gBACnCH,eAAeA,aAAa,KAAK,CAACG,eAAe;gBACjD,IAAIC,KAAK,UAAU,CAAC7B,8BAClB,IAAI;oBACF0B,eAAeI,KAAK,KAAK,CACvBD,KAAK,KAAK,CAAC7B,4BAA4B,MAAM;gBAEjD,EAAE,OAAO+B,OAAO;oBACdV,aACE,IAAIW,MACF,CAAC,sCAAsC,EACrCD,iBAAiBC,QAAQD,MAAM,OAAO,GAAGzB,OAAOyB,QAChD;oBAGN;gBACF;qBACK,IAAIF,KAAK,MAAM,GAAG,GACvBL,QAAQ,MAAM,CAAC,KAAK,CAAC,GAAGK,KAAK,EAAE,CAAC;gBAElCD,eAAeH,aAAa,OAAO,CAAC;YACtC;QACF;QAEAH,MAAM,EAAE,CAAC,SAAS,CAACS;YACjBV,aAAaU;QACf;QAEAT,MAAM,EAAE,CAAC,QAAQ,CAACW,MAAMC;YACtB,IACET,aAAa,MAAM,GAAG,KACtB,CAACA,aAAa,UAAU,CAACzB,8BAEzBwB,QAAQ,MAAM,CAAC,KAAK,CAACC;YAGvB,IAAI,CAACC,cAAc,YACjBL,aACE,IAAIW,MACF,CAAC,2BAA2B,EAAEC,QAAQ,OAAO,SAAS,EACpDC,UAAU,OACX,4BAA4B,CAAC;YAMpC,IAAIR,aAAa,eAAe,CAAC,MAAM,GAAG,GACxC,KAAK,MAAMK,SAASL,aAAa,eAAe,CAC9CS,QAAQ,KAAK,CAACJ,MAAM,KAAK,IAAI,GAAGA,MAAM,IAAI,CAAC,EAAE,EAAEA,MAAM,OAAO,EAAE;YAIlEX,cAAc;gBAAE,IAAIM,aAAa,EAAE;YAAC;QACtC;QAEAJ,MAAM,KAAK,EAAE,MAAMQ,KAAK,SAAS,CAACZ;QAClCI,MAAM,KAAK,EAAE;IACf;AACF;AAEA,MAAMc,iBAAiB,CAACC;IACtB,IAAIC,WAAWD,KAAK,UAAU,GAC5B,OAAOP,KAAK,KAAK,CACfS,aAAaF,KAAK,UAAU,EAAE;IAIlC,OAAO;QACL,MAAMA,KAAK,QAAQ;QACnB,UAAUA,KAAK,QAAQ;QACvB,SAAS;QACT,UAAU;QACV,OAAO;IACT;AACF;AAEA,MAAMG,mBAAmB,CACvBC,QACAC;IAEA,MAAMC,SAASF,OAAO,MAAM,CAAC,MAAM,EAAE;IACrC,IAAI,CAACE,QACH;IAEF,MAAMC,cAAc/B,QAAQ4B,OAAO,IAAI,EAAEE;IACzCE,UAAU/B,QAAQ8B,cAAc;QAAE,WAAW;IAAK;IAClDE,cAAcF,aAAad,KAAK,SAAS,CAACY,SAAS,MAAM;IACzD,OAAOE;AACT;AAEA,MAAMG,eAAe,CAACL,SAAgCE;IACpDT,QAAQ,GAAG,CAAC;IACZA,QAAQ,GAAG,CAAC,CAAC,UAAU,EAAEO,QAAQ,KAAK,EAAE;IACxCP,QAAQ,GAAG,CAAC,CAAC,WAAW,EAAEO,QAAQ,MAAM,EAAE;IAC1CP,QAAQ,GAAG,CAAC,CAAC,WAAW,EAAEO,QAAQ,MAAM,EAAE;IAC1CP,QAAQ,GAAG,CAAC,CAAC,aAAa,EAAGO,AAAAA,CAAAA,QAAQ,UAAU,GAAG,IAAG,EAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACrE,IAAIE,aACFT,QAAQ,GAAG,CAAC,CAAC,YAAY,EAAES,aAAa;IAE1C,KAAK,MAAMI,UAAUN,QAAQ,OAAO,CAClC,IAAI,CAACM,OAAO,OAAO,EACjBb,QAAQ,GAAG,CAAC,CAAC,KAAK,EAAEa,OAAO,QAAQ,CAAC,EAAE,EAAEA,OAAO,KAAK,IAAI,UAAU;AAGxE;AAYO,eAAeC,iBACpBC,UAAmC,CAAC,CAAC;IAErC,MAAMT,SAAS,MAAMU,mBAAmBD,QAAQ,UAAU;IAE1D,MAAME,cAAcC,oBAAoB;QACtC,KAAK7B,QAAQ,GAAG;QAChB,WAAWiB,OAAO,IAAI;QACtB,WAAWA,OAAO,MAAM,CAAC,GAAG;IAC9B;IACA,KAAK,MAAMa,SAASF,YAClB,IAAIE,MAAM,MAAM,EACdnB,QAAQ,GAAG,CAAC,CAAC,aAAa,EAAEmB,MAAM,IAAI,EAAE;IAI5C,MAAMC,QAAQ,MAAMC,0BAA0B;QAC5C,MAAMf,OAAO,IAAI;QACjB,QAAQA,OAAO,MAAM;IACvB;IAEA,IAAIc,AAAiB,MAAjBA,MAAM,MAAM,EACd,MAAM,IAAIvB,MACR,CAAC,uBAAuB,EAAEnB,QAAQ4B,OAAO,IAAI,EAAEA,OAAO,MAAM,CAAC,OAAO,IAAI,UAAU;IAItF,MAAMgB,YACJP,QAAQ,SAAS,IACjBQ,KAAKjB,OAAO,IAAI,EAAE,gBAAgB,OAAO,CAAC,UAAU,EAAEkB,KAAK,GAAG,IAAI;IACpE,MAAMC,YAAYF,KAAKD,WAAW;IAClCZ,UAAUe,WAAW;QAAE,WAAW;IAAK;IAEvC,MAAMC,YAAyBN,MAC5B,MAAM,CAAC,CAACO,OAASA,AAAc,WAAdA,KAAK,IAAI,EAC1B,GAAG,CAAC,CAACA,MAAM1D,QAAW;YACrB,UAAU0D,KAAK,QAAQ;YACvB,UAAUA,KAAK,YAAY;YAC3B,YAAYJ,KAAKE,WAAW,GAAG1D,SAAS4D,KAAK,YAAY,EAAE1D,OAAO,KAAK,CAAC;QAC1E;IAEF,MAAM2D,iBAAyC,CAAC;IAChD,MAAMC,UAAoB,EAAE;IAE5B,IAAIH,UAAU,MAAM,GAAG,GAAG;QACxBE,cAAc,CAAC9D,gBAAgB,GAAGgE,+BAA+B;YAC/D,YAAYxB,OAAO,IAAI;YACvB,YAAYA,OAAO,IAAI;YACvB,OAAOoB;QACT;QACAG,QAAQ,IAAI,CAAC/D;IACf;IAEA,KAAK,MAAM6D,QAAQP,MACjB,IAAIO,AAAc,WAAdA,KAAK,IAAI,EACXE,QAAQ,IAAI,CAACF,KAAK,QAAQ;IAI9B,MAAMI,SAAShB,QAAQ,YAAY,IAAInC;IACvC,MAAMoD,YAAY,MAAMD,OAAO;QAC7B,MAAMzB,OAAO,IAAI;QACjBuB;QACAD;QACA,gBAAgBtB,OAAO,MAAM,CAAC,UAAU,EAAE;QAC1C,aAAaA,OAAO,MAAM,CAAC,UAAU,EAAE;QACvC,MAAMA,OAAO,MAAM,CAAC,UAAU,EAAE;QAChC,OAAOA,OAAO,MAAM,CAAC,UAAU,EAAE;IACnC;IAEA,MAAM2B,UAAUP,UAAU,GAAG,CAACzB;IAC9B,MAAMM,UAAiC;QACrC,OAAO0B,QAAQ,MAAM;QACrB,QAAQA,QAAQ,MAAM,CAAC,CAACpB,SAAWA,OAAO,OAAO,EAAE,MAAM;QACzD,QAAQoB,QAAQ,MAAM,CAAC,CAACpB,SAAW,CAACA,OAAO,OAAO,EAAE,MAAM;QAC1D,YAAYoB,QAAQ,MAAM,CACxB,CAACC,KAAKrB,SAAWqB,MAAOrB,CAAAA,OAAO,QAAQ,IAAI,IAC3C;QAEFoB;IACF;IAEA,MAAMxB,cAAcJ,iBAAiBC,QAAQC;IAC7CK,aAAaL,SAASE;IAEtB,IAAI,CAACuB,UAAU,EAAE,IAAIzB,QAAQ,MAAM,GAAG,GACpClB,QAAQ,QAAQ,GAAG;IAGrB,OAAOkB;AACT"}
1
+ {"version":3,"file":"runner.mjs","sources":["../../src/runner.ts"],"sourcesContent":["import { fork } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { collectFrameworkTestFiles, loadMidsceneConfig } from './config';\nimport { loadFrameworkDotenv } from './dotenv';\nimport { createYamlFrameworkSuiteSource } from './runtime/source';\nimport type {\n FrameworkCaseResult,\n FrameworkSuiteSummary,\n LoadedMidsceneConfig,\n} from './types';\n\nconst RUNNER_WORKER_RESULT_PREFIX = '__MIDSCENE_RUNNER_WORKER_RESULT__';\n\nconst SUITE_MODULE_ID = 'virtual:midscene-framework/suite.test.ts';\n\nexport interface FrameworkRstestProject {\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\nexport type FrameworkRstestRunner = (\n project: FrameworkRstestProject,\n) => Promise<{\n ok: boolean;\n unhandledErrors?: WorkerError[];\n testErrors?: Array<{\n kind: 'case' | 'suite';\n file: string;\n testName: string;\n errors: WorkerError[];\n }>;\n}>;\n\nexport interface RunMidsceneSuiteOptions {\n /** Path to `midscene.config.ts`. Defaults to the one in `cwd`. */\n configPath?: string;\n /** Directory for generated wiring and per-case result files. */\n outputDir?: string;\n /** Override the Rstest runner. Mainly for tests. */\n rstestRunner?: FrameworkRstestRunner;\n stdio?: 'inherit' | 'pipe';\n}\n\ninterface SuiteCase {\n filePath: string;\n testName: string;\n resultFile: string;\n}\n\nconst safeStem = (relativePath: string, index: number): string => {\n const base = relativePath\n .replace(/\\.[^.]+$/, '')\n .replace(/[^a-zA-Z0-9._-]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return `${String(index + 1).padStart(3, '0')}-${base || 'case'}`;\n};\n\ninterface WorkerInput {\n cwd: string;\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\ninterface WorkerError {\n name?: string;\n message?: string;\n stack?: string;\n}\n\ninterface WorkerOutput {\n ok: boolean;\n unhandledErrors: WorkerError[];\n testErrors: Array<{\n kind: 'case' | 'suite';\n file: string;\n testName: string;\n errors: WorkerError[];\n }>;\n}\n\nconst resolveWorkerEntry = (): string => {\n // `runner.js` and `runner-worker.js` ship side-by-side in both the CJS and\n // ESM bundles, so resolving relative to the current module works in both\n // formats. Use `__filename` for CJS and `import.meta.url` for ESM. The ESM\n // build emits `.mjs` files; pick the matching extension.\n const moduleUrl =\n typeof __filename === 'string'\n ? null\n : (import.meta as { url?: string } | undefined)?.url;\n const here = moduleUrl ? fileURLToPath(moduleUrl) : __filename;\n const ext = here.endsWith('.mjs') ? '.mjs' : '.js';\n return resolve(dirname(here), `runner-worker${ext}`);\n};\n\n/**\n * Default Rstest driver. We deliberately spawn `runRstest` in a child process:\n * the user's `midscene.config.ts` typically imports playwright (or\n * `@midscene/web/playwright`), which transitively initializes a bundled\n * `@vitest/expect` copy and defines `Symbol.for('$$jest-matchers-object')` on\n * `globalThis` non-configurably. Calling `runRstest` in the same process then\n * fails with `TypeError: Cannot redefine property: Symbol($$jest-matchers-object)`\n * because Rstest's own `@vitest/expect` copy tries to redefine the same global.\n * Forking sidesteps the collision: the child has never imported playwright.\n */\nconst defaultRstestRunner: FrameworkRstestRunner = async (project) => {\n const workerEntry = resolveWorkerEntry();\n\n const input: WorkerInput = {\n cwd: project.root,\n root: project.root,\n include: project.include,\n virtualModules: project.virtualModules,\n maxConcurrency: project.maxConcurrency,\n testTimeout: project.testTimeout,\n bail: project.bail,\n retry: project.retry,\n };\n\n return await new Promise<WorkerOutput>((resolveRunner, rejectRunner) => {\n const child = fork(workerEntry, [], {\n cwd: project.root,\n env: process.env,\n stdio: ['pipe', 'pipe', 'inherit', 'ipc'],\n });\n\n let stdoutBuffer = '';\n let parsedResult: WorkerOutput | undefined;\n\n child.stdout?.setEncoding('utf8');\n child.stdout?.on('data', (chunk: string) => {\n stdoutBuffer += chunk;\n let newlineIndex = stdoutBuffer.indexOf('\\n');\n while (newlineIndex !== -1) {\n const line = stdoutBuffer.slice(0, newlineIndex);\n stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);\n if (line.startsWith(RUNNER_WORKER_RESULT_PREFIX)) {\n try {\n parsedResult = JSON.parse(\n line.slice(RUNNER_WORKER_RESULT_PREFIX.length),\n ) as WorkerOutput;\n } catch (error) {\n rejectRunner(\n new Error(\n `Failed to parse runner-worker result: ${\n error instanceof Error ? error.message : String(error)\n }`,\n ),\n );\n return;\n }\n } else if (line.length > 0) {\n process.stdout.write(`${line}\\n`);\n }\n newlineIndex = stdoutBuffer.indexOf('\\n');\n }\n });\n\n child.on('error', (error) => {\n rejectRunner(error);\n });\n\n child.on('exit', (code, signal) => {\n if (\n stdoutBuffer.length > 0 &&\n !stdoutBuffer.startsWith(RUNNER_WORKER_RESULT_PREFIX)\n ) {\n process.stdout.write(stdoutBuffer);\n }\n\n if (!parsedResult) {\n rejectRunner(\n new Error(\n `runner-worker exited (code=${code ?? 'null'}, signal=${\n signal ?? 'null'\n }) without producing a result`,\n ),\n );\n return;\n }\n\n if (parsedResult.unhandledErrors.length > 0) {\n for (const error of parsedResult.unhandledErrors) {\n console.error(error.stack || `${error.name}: ${error.message}`);\n }\n }\n\n resolveRunner(parsedResult);\n });\n\n child.stdin?.write(JSON.stringify(input));\n child.stdin?.end();\n });\n};\n\nconst formatWorkerError = (error: WorkerError): string => {\n if (error.stack) return error.stack;\n if (error.message) {\n return error.name ? `${error.name}: ${error.message}` : error.message;\n }\n return error.name ?? 'Unknown error';\n};\n\nconst readCaseResult = (\n item: SuiteCase,\n context: {\n testErrors: WorkerOutput['testErrors'];\n unhandledErrors: WorkerError[];\n },\n): FrameworkCaseResult => {\n if (existsSync(item.resultFile)) {\n return JSON.parse(\n readFileSync(item.resultFile, 'utf8'),\n ) as FrameworkCaseResult;\n }\n\n // Case-specific errors win over suite-level / hook errors.\n const caseErrors = context.testErrors\n .filter(\n (entry) =>\n entry.kind === 'case' && entry.testName.includes(item.testName),\n )\n .flatMap((entry) => entry.errors);\n const suiteErrors = context.testErrors\n .filter((entry) => entry.kind === 'suite')\n .flatMap((entry) => entry.errors);\n\n const errorSource =\n caseErrors.length > 0\n ? caseErrors\n : suiteErrors.length > 0\n ? suiteErrors\n : context.unhandledErrors;\n const errorText =\n errorSource.length > 0\n ? errorSource.map(formatWorkerError).join('\\n\\n')\n : 'Not executed';\n\n return {\n file: item.filePath,\n testName: item.testName,\n success: false,\n duration: 0,\n error: errorText,\n };\n};\n\nconst writeSummaryFile = (\n loaded: LoadedMidsceneConfig,\n summary: FrameworkSuiteSummary,\n): string | undefined => {\n const target = loaded.config.output?.summary;\n if (!target) {\n return undefined;\n }\n const summaryPath = resolve(loaded.root, target);\n mkdirSync(dirname(summaryPath), { recursive: true });\n writeFileSync(summaryPath, JSON.stringify(summary, null, 2));\n return summaryPath;\n};\n\nconst printSummary = (summary: FrameworkSuiteSummary, summaryPath?: string) => {\n console.log('\\nšŸ“Š Midscene suite summary');\n console.log(` Total: ${summary.total}`);\n console.log(` Passed: ${summary.passed}`);\n console.log(` Failed: ${summary.failed}`);\n console.log(` Duration: ${(summary.durationMs / 1000).toFixed(2)}s`);\n if (summaryPath) {\n console.log(` Summary: ${summaryPath}`);\n }\n for (const result of summary.results) {\n if (!result.success) {\n console.log(` āŒ ${result.testName}: ${result.error ?? 'failed'}`);\n }\n }\n};\n\n/**\n * Load `midscene.config.ts`, discover cases, run them through Rstest, and write\n * the suite summary. Intended to be the entire body of a project's\n * `run-suite.ts`:\n *\n * ```ts\n * import { runMidsceneSuite } from '@midscene/testing-framework';\n * await runMidsceneSuite();\n * ```\n */\nexport async function runMidsceneSuite(\n options: RunMidsceneSuiteOptions = {},\n): Promise<FrameworkSuiteSummary> {\n const loaded = await loadMidsceneConfig(options.configPath);\n\n const dotenvFiles = loadFrameworkDotenv({\n cwd: process.cwd(),\n configDir: loaded.root,\n envConfig: loaded.config.env,\n });\n for (const entry of dotenvFiles) {\n if (entry.loaded) {\n console.log(` Env file: ${entry.path}`);\n }\n }\n\n const files = await collectFrameworkTestFiles({\n root: loaded.root,\n config: loaded.config,\n });\n\n if (files.length === 0) {\n throw new Error(\n `No test files found in ${resolve(loaded.root, loaded.config.testDir || './e2e')}`,\n );\n }\n\n const outputDir =\n options.outputDir ||\n join(loaded.root, 'midscene_run', 'tmp', `framework-${Date.now()}`);\n const resultDir = join(outputDir, 'results');\n mkdirSync(resultDir, { recursive: true });\n\n const yamlCases: SuiteCase[] = files\n .filter((file) => file.type === 'yaml')\n .map((file, index) => ({\n filePath: file.filePath,\n testName: file.relativePath,\n resultFile: join(resultDir, `${safeStem(file.relativePath, index)}.json`),\n }));\n\n const virtualModules: Record<string, string> = {};\n const include: string[] = [];\n\n if (yamlCases.length > 0) {\n virtualModules[SUITE_MODULE_ID] = createYamlFrameworkSuiteSource({\n configPath: loaded.path,\n projectDir: loaded.root,\n cases: yamlCases,\n });\n include.push(SUITE_MODULE_ID);\n }\n\n for (const file of files) {\n if (file.type === 'test') {\n include.push(file.filePath);\n }\n }\n\n const runner = options.rstestRunner || defaultRstestRunner;\n const runResult = await runner({\n root: loaded.root,\n include,\n virtualModules,\n maxConcurrency: loaded.config.testRunner?.maxConcurrency,\n testTimeout: loaded.config.testRunner?.testTimeout,\n bail: loaded.config.testRunner?.bail,\n retry: loaded.config.testRunner?.retry,\n });\n\n const results = yamlCases.map((item) =>\n readCaseResult(item, {\n testErrors: runResult.testErrors ?? [],\n unhandledErrors: runResult.unhandledErrors ?? [],\n }),\n );\n const summary: FrameworkSuiteSummary = {\n total: results.length,\n passed: results.filter((result) => result.success).length,\n failed: results.filter((result) => !result.success).length,\n durationMs: results.reduce(\n (sum, result) => sum + (result.duration || 0),\n 0,\n ),\n results,\n };\n\n const summaryPath = writeSummaryFile(loaded, summary);\n printSummary(summary, summaryPath);\n\n if (!runResult.ok || summary.failed > 0) {\n process.exitCode = 1;\n }\n\n return summary;\n}\n"],"names":["RUNNER_WORKER_RESULT_PREFIX","SUITE_MODULE_ID","safeStem","relativePath","index","base","String","resolveWorkerEntry","moduleUrl","__filename","here","fileURLToPath","ext","resolve","dirname","defaultRstestRunner","project","workerEntry","input","Promise","resolveRunner","rejectRunner","child","fork","process","stdoutBuffer","parsedResult","chunk","newlineIndex","line","JSON","error","Error","code","signal","console","formatWorkerError","readCaseResult","item","context","existsSync","readFileSync","caseErrors","entry","suiteErrors","errorSource","errorText","writeSummaryFile","loaded","summary","target","summaryPath","mkdirSync","writeFileSync","printSummary","result","runMidsceneSuite","options","loadMidsceneConfig","dotenvFiles","loadFrameworkDotenv","files","collectFrameworkTestFiles","outputDir","join","Date","resultDir","yamlCases","file","virtualModules","include","createYamlFrameworkSuiteSource","runner","runResult","results","sum"],"mappings":"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"}
@@ -59,14 +59,40 @@ const main = async ()=>{
59
59
  cwd: input.cwd,
60
60
  inlineConfig
61
61
  });
62
- const unhandled = result?.unhandledErrors ?? [];
63
- writeOutput({
64
- ok: Boolean(result?.ok),
65
- unhandledErrors: unhandled.map((error)=>({
62
+ const rstestResult = result ?? {};
63
+ const unhandled = rstestResult.unhandledErrors ?? [];
64
+ const testErrors = [];
65
+ const mapErrors = (errors)=>(errors ?? []).map((error)=>({
66
66
  name: error.name,
67
67
  message: error.message,
68
68
  stack: error.stack
69
- }))
69
+ }));
70
+ for (const file of rstestResult.files ?? []){
71
+ const filePath = file.testPath ?? '';
72
+ if (file.errors && file.errors.length > 0) testErrors.push({
73
+ kind: 'suite',
74
+ file: filePath,
75
+ testName: file.name ?? filePath,
76
+ errors: mapErrors(file.errors)
77
+ });
78
+ for (const entry of file.results ?? []){
79
+ if (!entry.errors || 0 === entry.errors.length) continue;
80
+ const trail = [
81
+ ...entry.parentNames ?? [],
82
+ entry.name ?? ''
83
+ ].filter((segment)=>segment && segment.length > 0).join(' > ');
84
+ testErrors.push({
85
+ kind: 'case',
86
+ file: entry.testPath ?? filePath,
87
+ testName: trail || entry.name || filePath,
88
+ errors: mapErrors(entry.errors)
89
+ });
90
+ }
91
+ }
92
+ writeOutput({
93
+ ok: Boolean(rstestResult.ok),
94
+ unhandledErrors: mapErrors(unhandled),
95
+ testErrors
70
96
  });
71
97
  };
72
98
  main().catch((error)=>{
@@ -80,7 +106,8 @@ main().catch((error)=>{
80
106
  message: error instanceof Error ? error.message : String(error),
81
107
  stack: error instanceof Error ? error.stack : void 0
82
108
  }
83
- ]
109
+ ],
110
+ testErrors: []
84
111
  });
85
112
  process.exit(1);
86
113
  });
@@ -1 +1 @@
1
- {"version":3,"file":"runner-worker.js","sources":["../../src/runner-worker.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\ninterface WorkerInput {\n cwd: string;\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\ninterface WorkerOutput {\n ok: boolean;\n unhandledErrors: Array<{\n name?: string;\n message?: string;\n stack?: string;\n }>;\n}\n\nconst readStdin = (): Promise<string> =>\n new Promise((resolveStdin, reject) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk) => {\n data += chunk;\n });\n process.stdin.on('end', () => resolveStdin(data));\n process.stdin.on('error', reject);\n });\n\nconst writeOutput = (output: WorkerOutput): void => {\n process.stdout.write(\n `__MIDSCENE_RUNNER_WORKER_RESULT__${JSON.stringify(output)}\\n`,\n );\n};\n\nconst main = async (): Promise<void> => {\n const raw = await readStdin();\n const input = JSON.parse(raw) as WorkerInput;\n\n const projectRequire = createRequire(resolve(input.root, 'package.json'));\n const rstestPkgJson = projectRequire.resolve('@rstest/core/package.json');\n const rsbuildEntry = createRequire(rstestPkgJson).resolve('@rsbuild/core');\n\n const [{ runRstest }, { rspack }] = await Promise.all([\n import('@rstest/core/api'),\n import(pathToFileURL(rsbuildEntry).href),\n ]);\n\n const maxConcurrency =\n input.maxConcurrency !== undefined\n ? Math.max(1, input.maxConcurrency)\n : undefined;\n\n const inlineConfig: Record<string, unknown> = {\n root: input.root,\n include: input.include,\n testEnvironment: 'node',\n reporters: [],\n ...(input.testTimeout !== undefined\n ? { testTimeout: input.testTimeout }\n : {}),\n ...(maxConcurrency !== undefined ? { maxConcurrency } : {}),\n ...(maxConcurrency !== undefined\n ? { pool: { maxWorkers: maxConcurrency, minWorkers: maxConcurrency } }\n : {}),\n ...(input.bail !== undefined ? { bail: input.bail } : {}),\n ...(input.retry !== undefined ? { retry: input.retry } : {}),\n tools: {\n rspack: (\n _config: unknown,\n { appendPlugins }: { appendPlugins: (plugin: unknown) => void },\n ) => {\n appendPlugins(\n new rspack.experiments.VirtualModulesPlugin(input.virtualModules),\n );\n },\n },\n };\n\n const result = await runRstest({ cwd: input.cwd, inlineConfig });\n\n type UnhandledError = { name?: string; message?: string; stack?: string };\n const unhandled =\n (result as { unhandledErrors?: UnhandledError[] } | undefined)\n ?.unhandledErrors ?? [];\n\n writeOutput({\n ok: Boolean(result?.ok),\n unhandledErrors: unhandled.map((error) => ({\n name: error.name,\n message: error.message,\n stack: error.stack,\n })),\n });\n};\n\nmain().catch((error) => {\n const message =\n error instanceof Error ? error.stack || error.message : String(error);\n process.stderr.write(`runner-worker fatal: ${message}\\n`);\n writeOutput({\n ok: false,\n unhandledErrors: [\n {\n name: error instanceof Error ? error.name : 'WorkerError',\n message: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n },\n ],\n });\n process.exit(1);\n});\n"],"names":["readStdin","Promise","resolveStdin","reject","data","process","chunk","writeOutput","output","JSON","main","raw","input","projectRequire","createRequire","resolve","rstestPkgJson","rsbuildEntry","runRstest","rspack","pathToFileURL","maxConcurrency","undefined","Math","inlineConfig","_config","appendPlugins","result","unhandled","Boolean","error","message","Error","String"],"mappings":"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"}
1
+ {"version":3,"file":"runner-worker.js","sources":["../../src/runner-worker.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\ninterface WorkerInput {\n cwd: string;\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\ninterface WorkerError {\n name?: string;\n message?: string;\n stack?: string;\n}\n\ninterface WorkerOutput {\n ok: boolean;\n unhandledErrors: WorkerError[];\n /**\n * Failures captured from Rstest's result graph. We forward both case-level\n * errors and file/suite-level errors (e.g. a `beforeAll` hook crash) so the\n * framework can surface a meaningful message when a case never produced its\n * own result file.\n */\n testErrors: Array<{\n kind: 'case' | 'suite';\n file: string;\n testName: string;\n errors: WorkerError[];\n }>;\n}\n\nconst readStdin = (): Promise<string> =>\n new Promise((resolveStdin, reject) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk) => {\n data += chunk;\n });\n process.stdin.on('end', () => resolveStdin(data));\n process.stdin.on('error', reject);\n });\n\nconst writeOutput = (output: WorkerOutput): void => {\n process.stdout.write(\n `__MIDSCENE_RUNNER_WORKER_RESULT__${JSON.stringify(output)}\\n`,\n );\n};\n\nconst main = async (): Promise<void> => {\n const raw = await readStdin();\n const input = JSON.parse(raw) as WorkerInput;\n\n const projectRequire = createRequire(resolve(input.root, 'package.json'));\n const rstestPkgJson = projectRequire.resolve('@rstest/core/package.json');\n const rsbuildEntry = createRequire(rstestPkgJson).resolve('@rsbuild/core');\n\n const [{ runRstest }, { rspack }] = await Promise.all([\n import('@rstest/core/api'),\n import(pathToFileURL(rsbuildEntry).href),\n ]);\n\n const maxConcurrency =\n input.maxConcurrency !== undefined\n ? Math.max(1, input.maxConcurrency)\n : undefined;\n\n const inlineConfig: Record<string, unknown> = {\n root: input.root,\n include: input.include,\n testEnvironment: 'node',\n reporters: [],\n ...(input.testTimeout !== undefined\n ? { testTimeout: input.testTimeout }\n : {}),\n ...(maxConcurrency !== undefined ? { maxConcurrency } : {}),\n ...(maxConcurrency !== undefined\n ? { pool: { maxWorkers: maxConcurrency, minWorkers: maxConcurrency } }\n : {}),\n ...(input.bail !== undefined ? { bail: input.bail } : {}),\n ...(input.retry !== undefined ? { retry: input.retry } : {}),\n tools: {\n rspack: (\n _config: unknown,\n { appendPlugins }: { appendPlugins: (plugin: unknown) => void },\n ) => {\n appendPlugins(\n new rspack.experiments.VirtualModulesPlugin(input.virtualModules),\n );\n },\n },\n };\n\n const result = await runRstest({ cwd: input.cwd, inlineConfig });\n\n type FormattedError = {\n name?: string;\n message?: string;\n stack?: string;\n };\n type RstestTestResult = {\n name?: string;\n testPath?: string;\n parentNames?: string[];\n status?: string;\n errors?: FormattedError[];\n };\n type RstestFileResult = RstestTestResult & {\n results?: RstestTestResult[];\n };\n type RstestRunResult = {\n ok?: boolean;\n files?: RstestFileResult[];\n unhandledErrors?: FormattedError[];\n };\n\n const rstestResult = (result ?? {}) as RstestRunResult;\n const unhandled = rstestResult.unhandledErrors ?? [];\n const testErrors: WorkerOutput['testErrors'] = [];\n\n const mapErrors = (errors: FormattedError[] | undefined): WorkerError[] =>\n (errors ?? []).map((error) => ({\n name: error.name,\n message: error.message,\n stack: error.stack,\n }));\n\n for (const file of rstestResult.files ?? []) {\n const filePath = file.testPath ?? '';\n // Errors that belong to the file itself (suite hooks, top-level throws).\n if (file.errors && file.errors.length > 0) {\n testErrors.push({\n kind: 'suite',\n file: filePath,\n testName: file.name ?? filePath,\n errors: mapErrors(file.errors),\n });\n }\n // Per-test results (rstest flattens cases and nested suites here).\n for (const entry of file.results ?? []) {\n if (!entry.errors || entry.errors.length === 0) continue;\n const trail = [...(entry.parentNames ?? []), entry.name ?? '']\n .filter((segment) => segment && segment.length > 0)\n .join(' > ');\n testErrors.push({\n kind: 'case',\n file: entry.testPath ?? filePath,\n testName: trail || entry.name || filePath,\n errors: mapErrors(entry.errors),\n });\n }\n }\n\n writeOutput({\n ok: Boolean(rstestResult.ok),\n unhandledErrors: mapErrors(unhandled),\n testErrors,\n });\n};\n\nmain().catch((error) => {\n const message =\n error instanceof Error ? error.stack || error.message : String(error);\n process.stderr.write(`runner-worker fatal: ${message}\\n`);\n writeOutput({\n ok: false,\n unhandledErrors: [\n {\n name: error instanceof Error ? error.name : 'WorkerError',\n message: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n },\n ],\n testErrors: [],\n });\n process.exit(1);\n});\n"],"names":["readStdin","Promise","resolveStdin","reject","data","process","chunk","writeOutput","output","JSON","main","raw","input","projectRequire","createRequire","resolve","rstestPkgJson","rsbuildEntry","runRstest","rspack","pathToFileURL","maxConcurrency","undefined","Math","inlineConfig","_config","appendPlugins","result","rstestResult","unhandled","testErrors","mapErrors","errors","error","file","filePath","entry","trail","segment","Boolean","message","Error","String"],"mappings":"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"}
@@ -94,22 +94,29 @@ const defaultRstestRunner = async (project)=>{
94
94
  if (stdoutBuffer.length > 0 && !stdoutBuffer.startsWith(RUNNER_WORKER_RESULT_PREFIX)) process.stdout.write(stdoutBuffer);
95
95
  if (!parsedResult) return void rejectRunner(new Error(`runner-worker exited (code=${code ?? 'null'}, signal=${signal ?? 'null'}) without producing a result`));
96
96
  if (parsedResult.unhandledErrors.length > 0) for (const error of parsedResult.unhandledErrors)console.error(error.stack || `${error.name}: ${error.message}`);
97
- resolveRunner({
98
- ok: parsedResult.ok
99
- });
97
+ resolveRunner(parsedResult);
100
98
  });
101
99
  child.stdin?.write(JSON.stringify(input));
102
100
  child.stdin?.end();
103
101
  });
104
102
  };
105
- const readCaseResult = (item)=>{
103
+ const formatWorkerError = (error)=>{
104
+ if (error.stack) return error.stack;
105
+ if (error.message) return error.name ? `${error.name}: ${error.message}` : error.message;
106
+ return error.name ?? 'Unknown error';
107
+ };
108
+ const readCaseResult = (item, context)=>{
106
109
  if ((0, external_node_fs_namespaceObject.existsSync)(item.resultFile)) return JSON.parse((0, external_node_fs_namespaceObject.readFileSync)(item.resultFile, 'utf8'));
110
+ const caseErrors = context.testErrors.filter((entry)=>'case' === entry.kind && entry.testName.includes(item.testName)).flatMap((entry)=>entry.errors);
111
+ const suiteErrors = context.testErrors.filter((entry)=>'suite' === entry.kind).flatMap((entry)=>entry.errors);
112
+ const errorSource = caseErrors.length > 0 ? caseErrors : suiteErrors.length > 0 ? suiteErrors : context.unhandledErrors;
113
+ const errorText = errorSource.length > 0 ? errorSource.map(formatWorkerError).join('\n\n') : 'Not executed';
107
114
  return {
108
115
  file: item.filePath,
109
116
  testName: item.testName,
110
117
  success: false,
111
118
  duration: 0,
112
- error: 'Not executed'
119
+ error: errorText
113
120
  };
114
121
  };
115
122
  const writeSummaryFile = (loaded, summary)=>{
@@ -175,7 +182,10 @@ async function runMidsceneSuite(options = {}) {
175
182
  bail: loaded.config.testRunner?.bail,
176
183
  retry: loaded.config.testRunner?.retry
177
184
  });
178
- const results = yamlCases.map(readCaseResult);
185
+ const results = yamlCases.map((item)=>readCaseResult(item, {
186
+ testErrors: runResult.testErrors ?? [],
187
+ unhandledErrors: runResult.unhandledErrors ?? []
188
+ }));
179
189
  const summary = {
180
190
  total: results.length,
181
191
  passed: results.filter((result)=>result.success).length,
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/runner.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 { fork } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { collectFrameworkTestFiles, loadMidsceneConfig } from './config';\nimport { loadFrameworkDotenv } from './dotenv';\nimport { createYamlFrameworkSuiteSource } from './runtime/source';\nimport type {\n FrameworkCaseResult,\n FrameworkSuiteSummary,\n LoadedMidsceneConfig,\n} from './types';\n\nconst RUNNER_WORKER_RESULT_PREFIX = '__MIDSCENE_RUNNER_WORKER_RESULT__';\n\nconst SUITE_MODULE_ID = 'virtual:midscene-framework/suite.test.ts';\n\nexport interface FrameworkRstestProject {\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\nexport type FrameworkRstestRunner = (\n project: FrameworkRstestProject,\n) => Promise<{ ok: boolean }>;\n\nexport interface RunMidsceneSuiteOptions {\n /** Path to `midscene.config.ts`. Defaults to the one in `cwd`. */\n configPath?: string;\n /** Directory for generated wiring and per-case result files. */\n outputDir?: string;\n /** Override the Rstest runner. Mainly for tests. */\n rstestRunner?: FrameworkRstestRunner;\n stdio?: 'inherit' | 'pipe';\n}\n\ninterface SuiteCase {\n filePath: string;\n testName: string;\n resultFile: string;\n}\n\nconst safeStem = (relativePath: string, index: number): string => {\n const base = relativePath\n .replace(/\\.[^.]+$/, '')\n .replace(/[^a-zA-Z0-9._-]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return `${String(index + 1).padStart(3, '0')}-${base || 'case'}`;\n};\n\ninterface WorkerInput {\n cwd: string;\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\ninterface WorkerOutput {\n ok: boolean;\n unhandledErrors: Array<{\n name?: string;\n message?: string;\n stack?: string;\n }>;\n}\n\nconst resolveWorkerEntry = (): string => {\n // `runner.js` and `runner-worker.js` ship side-by-side in both the CJS and\n // ESM bundles, so resolving relative to the current module works in both\n // formats. Use `__filename` for CJS and `import.meta.url` for ESM. The ESM\n // build emits `.mjs` files; pick the matching extension.\n const moduleUrl =\n typeof __filename === 'string'\n ? null\n : (import.meta as { url?: string } | undefined)?.url;\n const here = moduleUrl ? fileURLToPath(moduleUrl) : __filename;\n const ext = here.endsWith('.mjs') ? '.mjs' : '.js';\n return resolve(dirname(here), `runner-worker${ext}`);\n};\n\n/**\n * Default Rstest driver. We deliberately spawn `runRstest` in a child process:\n * the user's `midscene.config.ts` typically imports playwright (or\n * `@midscene/web/playwright`), which transitively initializes a bundled\n * `@vitest/expect` copy and defines `Symbol.for('$$jest-matchers-object')` on\n * `globalThis` non-configurably. Calling `runRstest` in the same process then\n * fails with `TypeError: Cannot redefine property: Symbol($$jest-matchers-object)`\n * because Rstest's own `@vitest/expect` copy tries to redefine the same global.\n * Forking sidesteps the collision: the child has never imported playwright.\n */\nconst defaultRstestRunner: FrameworkRstestRunner = async (project) => {\n const workerEntry = resolveWorkerEntry();\n\n const input: WorkerInput = {\n cwd: project.root,\n root: project.root,\n include: project.include,\n virtualModules: project.virtualModules,\n maxConcurrency: project.maxConcurrency,\n testTimeout: project.testTimeout,\n bail: project.bail,\n retry: project.retry,\n };\n\n return await new Promise<{ ok: boolean }>((resolveRunner, rejectRunner) => {\n const child = fork(workerEntry, [], {\n cwd: project.root,\n env: process.env,\n stdio: ['pipe', 'pipe', 'inherit', 'ipc'],\n });\n\n let stdoutBuffer = '';\n let parsedResult: WorkerOutput | undefined;\n\n child.stdout?.setEncoding('utf8');\n child.stdout?.on('data', (chunk: string) => {\n stdoutBuffer += chunk;\n let newlineIndex = stdoutBuffer.indexOf('\\n');\n while (newlineIndex !== -1) {\n const line = stdoutBuffer.slice(0, newlineIndex);\n stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);\n if (line.startsWith(RUNNER_WORKER_RESULT_PREFIX)) {\n try {\n parsedResult = JSON.parse(\n line.slice(RUNNER_WORKER_RESULT_PREFIX.length),\n ) as WorkerOutput;\n } catch (error) {\n rejectRunner(\n new Error(\n `Failed to parse runner-worker result: ${\n error instanceof Error ? error.message : String(error)\n }`,\n ),\n );\n return;\n }\n } else if (line.length > 0) {\n process.stdout.write(`${line}\\n`);\n }\n newlineIndex = stdoutBuffer.indexOf('\\n');\n }\n });\n\n child.on('error', (error) => {\n rejectRunner(error);\n });\n\n child.on('exit', (code, signal) => {\n if (\n stdoutBuffer.length > 0 &&\n !stdoutBuffer.startsWith(RUNNER_WORKER_RESULT_PREFIX)\n ) {\n process.stdout.write(stdoutBuffer);\n }\n\n if (!parsedResult) {\n rejectRunner(\n new Error(\n `runner-worker exited (code=${code ?? 'null'}, signal=${\n signal ?? 'null'\n }) without producing a result`,\n ),\n );\n return;\n }\n\n if (parsedResult.unhandledErrors.length > 0) {\n for (const error of parsedResult.unhandledErrors) {\n console.error(error.stack || `${error.name}: ${error.message}`);\n }\n }\n\n resolveRunner({ ok: parsedResult.ok });\n });\n\n child.stdin?.write(JSON.stringify(input));\n child.stdin?.end();\n });\n};\n\nconst readCaseResult = (item: SuiteCase): FrameworkCaseResult => {\n if (existsSync(item.resultFile)) {\n return JSON.parse(\n readFileSync(item.resultFile, 'utf8'),\n ) as FrameworkCaseResult;\n }\n\n return {\n file: item.filePath,\n testName: item.testName,\n success: false,\n duration: 0,\n error: 'Not executed',\n };\n};\n\nconst writeSummaryFile = (\n loaded: LoadedMidsceneConfig,\n summary: FrameworkSuiteSummary,\n): string | undefined => {\n const target = loaded.config.output?.summary;\n if (!target) {\n return undefined;\n }\n const summaryPath = resolve(loaded.root, target);\n mkdirSync(dirname(summaryPath), { recursive: true });\n writeFileSync(summaryPath, JSON.stringify(summary, null, 2));\n return summaryPath;\n};\n\nconst printSummary = (summary: FrameworkSuiteSummary, summaryPath?: string) => {\n console.log('\\nšŸ“Š Midscene suite summary');\n console.log(` Total: ${summary.total}`);\n console.log(` Passed: ${summary.passed}`);\n console.log(` Failed: ${summary.failed}`);\n console.log(` Duration: ${(summary.durationMs / 1000).toFixed(2)}s`);\n if (summaryPath) {\n console.log(` Summary: ${summaryPath}`);\n }\n for (const result of summary.results) {\n if (!result.success) {\n console.log(` āŒ ${result.testName}: ${result.error ?? 'failed'}`);\n }\n }\n};\n\n/**\n * Load `midscene.config.ts`, discover cases, run them through Rstest, and write\n * the suite summary. Intended to be the entire body of a project's\n * `run-suite.ts`:\n *\n * ```ts\n * import { runMidsceneSuite } from '@midscene/testing-framework';\n * await runMidsceneSuite();\n * ```\n */\nexport async function runMidsceneSuite(\n options: RunMidsceneSuiteOptions = {},\n): Promise<FrameworkSuiteSummary> {\n const loaded = await loadMidsceneConfig(options.configPath);\n\n const dotenvFiles = loadFrameworkDotenv({\n cwd: process.cwd(),\n configDir: loaded.root,\n envConfig: loaded.config.env,\n });\n for (const entry of dotenvFiles) {\n if (entry.loaded) {\n console.log(` Env file: ${entry.path}`);\n }\n }\n\n const files = await collectFrameworkTestFiles({\n root: loaded.root,\n config: loaded.config,\n });\n\n if (files.length === 0) {\n throw new Error(\n `No test files found in ${resolve(loaded.root, loaded.config.testDir || './e2e')}`,\n );\n }\n\n const outputDir =\n options.outputDir ||\n join(loaded.root, 'midscene_run', 'tmp', `framework-${Date.now()}`);\n const resultDir = join(outputDir, 'results');\n mkdirSync(resultDir, { recursive: true });\n\n const yamlCases: SuiteCase[] = files\n .filter((file) => file.type === 'yaml')\n .map((file, index) => ({\n filePath: file.filePath,\n testName: file.relativePath,\n resultFile: join(resultDir, `${safeStem(file.relativePath, index)}.json`),\n }));\n\n const virtualModules: Record<string, string> = {};\n const include: string[] = [];\n\n if (yamlCases.length > 0) {\n virtualModules[SUITE_MODULE_ID] = createYamlFrameworkSuiteSource({\n configPath: loaded.path,\n projectDir: loaded.root,\n cases: yamlCases,\n });\n include.push(SUITE_MODULE_ID);\n }\n\n for (const file of files) {\n if (file.type === 'test') {\n include.push(file.filePath);\n }\n }\n\n const runner = options.rstestRunner || defaultRstestRunner;\n const runResult = await runner({\n root: loaded.root,\n include,\n virtualModules,\n maxConcurrency: loaded.config.testRunner?.maxConcurrency,\n testTimeout: loaded.config.testRunner?.testTimeout,\n bail: loaded.config.testRunner?.bail,\n retry: loaded.config.testRunner?.retry,\n });\n\n const results = yamlCases.map(readCaseResult);\n const summary: FrameworkSuiteSummary = {\n total: results.length,\n passed: results.filter((result) => result.success).length,\n failed: results.filter((result) => !result.success).length,\n durationMs: results.reduce(\n (sum, result) => sum + (result.duration || 0),\n 0,\n ),\n results,\n };\n\n const summaryPath = writeSummaryFile(loaded, summary);\n printSummary(summary, summaryPath);\n\n if (!runResult.ok || summary.failed > 0) {\n process.exitCode = 1;\n }\n\n return summary;\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","RUNNER_WORKER_RESULT_PREFIX","SUITE_MODULE_ID","safeStem","relativePath","index","base","String","resolveWorkerEntry","moduleUrl","here","fileURLToPath","__filename","ext","resolve","dirname","defaultRstestRunner","project","workerEntry","input","Promise","resolveRunner","rejectRunner","child","fork","process","stdoutBuffer","parsedResult","chunk","newlineIndex","line","JSON","error","Error","code","signal","console","readCaseResult","item","existsSync","readFileSync","writeSummaryFile","loaded","summary","target","summaryPath","mkdirSync","writeFileSync","printSummary","result","runMidsceneSuite","options","loadMidsceneConfig","dotenvFiles","loadFrameworkDotenv","entry","files","collectFrameworkTestFiles","outputDir","join","Date","resultDir","yamlCases","file","virtualModules","include","createYamlFrameworkSuiteSource","runner","runResult","results","sum"],"mappings":"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"}
1
+ {"version":3,"file":"runner.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/runner.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 { fork } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { collectFrameworkTestFiles, loadMidsceneConfig } from './config';\nimport { loadFrameworkDotenv } from './dotenv';\nimport { createYamlFrameworkSuiteSource } from './runtime/source';\nimport type {\n FrameworkCaseResult,\n FrameworkSuiteSummary,\n LoadedMidsceneConfig,\n} from './types';\n\nconst RUNNER_WORKER_RESULT_PREFIX = '__MIDSCENE_RUNNER_WORKER_RESULT__';\n\nconst SUITE_MODULE_ID = 'virtual:midscene-framework/suite.test.ts';\n\nexport interface FrameworkRstestProject {\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\nexport type FrameworkRstestRunner = (\n project: FrameworkRstestProject,\n) => Promise<{\n ok: boolean;\n unhandledErrors?: WorkerError[];\n testErrors?: Array<{\n kind: 'case' | 'suite';\n file: string;\n testName: string;\n errors: WorkerError[];\n }>;\n}>;\n\nexport interface RunMidsceneSuiteOptions {\n /** Path to `midscene.config.ts`. Defaults to the one in `cwd`. */\n configPath?: string;\n /** Directory for generated wiring and per-case result files. */\n outputDir?: string;\n /** Override the Rstest runner. Mainly for tests. */\n rstestRunner?: FrameworkRstestRunner;\n stdio?: 'inherit' | 'pipe';\n}\n\ninterface SuiteCase {\n filePath: string;\n testName: string;\n resultFile: string;\n}\n\nconst safeStem = (relativePath: string, index: number): string => {\n const base = relativePath\n .replace(/\\.[^.]+$/, '')\n .replace(/[^a-zA-Z0-9._-]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return `${String(index + 1).padStart(3, '0')}-${base || 'case'}`;\n};\n\ninterface WorkerInput {\n cwd: string;\n root: string;\n include: string[];\n virtualModules: Record<string, string>;\n maxConcurrency?: number;\n testTimeout?: number;\n bail?: number;\n retry?: number;\n}\n\ninterface WorkerError {\n name?: string;\n message?: string;\n stack?: string;\n}\n\ninterface WorkerOutput {\n ok: boolean;\n unhandledErrors: WorkerError[];\n testErrors: Array<{\n kind: 'case' | 'suite';\n file: string;\n testName: string;\n errors: WorkerError[];\n }>;\n}\n\nconst resolveWorkerEntry = (): string => {\n // `runner.js` and `runner-worker.js` ship side-by-side in both the CJS and\n // ESM bundles, so resolving relative to the current module works in both\n // formats. Use `__filename` for CJS and `import.meta.url` for ESM. The ESM\n // build emits `.mjs` files; pick the matching extension.\n const moduleUrl =\n typeof __filename === 'string'\n ? null\n : (import.meta as { url?: string } | undefined)?.url;\n const here = moduleUrl ? fileURLToPath(moduleUrl) : __filename;\n const ext = here.endsWith('.mjs') ? '.mjs' : '.js';\n return resolve(dirname(here), `runner-worker${ext}`);\n};\n\n/**\n * Default Rstest driver. We deliberately spawn `runRstest` in a child process:\n * the user's `midscene.config.ts` typically imports playwright (or\n * `@midscene/web/playwright`), which transitively initializes a bundled\n * `@vitest/expect` copy and defines `Symbol.for('$$jest-matchers-object')` on\n * `globalThis` non-configurably. Calling `runRstest` in the same process then\n * fails with `TypeError: Cannot redefine property: Symbol($$jest-matchers-object)`\n * because Rstest's own `@vitest/expect` copy tries to redefine the same global.\n * Forking sidesteps the collision: the child has never imported playwright.\n */\nconst defaultRstestRunner: FrameworkRstestRunner = async (project) => {\n const workerEntry = resolveWorkerEntry();\n\n const input: WorkerInput = {\n cwd: project.root,\n root: project.root,\n include: project.include,\n virtualModules: project.virtualModules,\n maxConcurrency: project.maxConcurrency,\n testTimeout: project.testTimeout,\n bail: project.bail,\n retry: project.retry,\n };\n\n return await new Promise<WorkerOutput>((resolveRunner, rejectRunner) => {\n const child = fork(workerEntry, [], {\n cwd: project.root,\n env: process.env,\n stdio: ['pipe', 'pipe', 'inherit', 'ipc'],\n });\n\n let stdoutBuffer = '';\n let parsedResult: WorkerOutput | undefined;\n\n child.stdout?.setEncoding('utf8');\n child.stdout?.on('data', (chunk: string) => {\n stdoutBuffer += chunk;\n let newlineIndex = stdoutBuffer.indexOf('\\n');\n while (newlineIndex !== -1) {\n const line = stdoutBuffer.slice(0, newlineIndex);\n stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);\n if (line.startsWith(RUNNER_WORKER_RESULT_PREFIX)) {\n try {\n parsedResult = JSON.parse(\n line.slice(RUNNER_WORKER_RESULT_PREFIX.length),\n ) as WorkerOutput;\n } catch (error) {\n rejectRunner(\n new Error(\n `Failed to parse runner-worker result: ${\n error instanceof Error ? error.message : String(error)\n }`,\n ),\n );\n return;\n }\n } else if (line.length > 0) {\n process.stdout.write(`${line}\\n`);\n }\n newlineIndex = stdoutBuffer.indexOf('\\n');\n }\n });\n\n child.on('error', (error) => {\n rejectRunner(error);\n });\n\n child.on('exit', (code, signal) => {\n if (\n stdoutBuffer.length > 0 &&\n !stdoutBuffer.startsWith(RUNNER_WORKER_RESULT_PREFIX)\n ) {\n process.stdout.write(stdoutBuffer);\n }\n\n if (!parsedResult) {\n rejectRunner(\n new Error(\n `runner-worker exited (code=${code ?? 'null'}, signal=${\n signal ?? 'null'\n }) without producing a result`,\n ),\n );\n return;\n }\n\n if (parsedResult.unhandledErrors.length > 0) {\n for (const error of parsedResult.unhandledErrors) {\n console.error(error.stack || `${error.name}: ${error.message}`);\n }\n }\n\n resolveRunner(parsedResult);\n });\n\n child.stdin?.write(JSON.stringify(input));\n child.stdin?.end();\n });\n};\n\nconst formatWorkerError = (error: WorkerError): string => {\n if (error.stack) return error.stack;\n if (error.message) {\n return error.name ? `${error.name}: ${error.message}` : error.message;\n }\n return error.name ?? 'Unknown error';\n};\n\nconst readCaseResult = (\n item: SuiteCase,\n context: {\n testErrors: WorkerOutput['testErrors'];\n unhandledErrors: WorkerError[];\n },\n): FrameworkCaseResult => {\n if (existsSync(item.resultFile)) {\n return JSON.parse(\n readFileSync(item.resultFile, 'utf8'),\n ) as FrameworkCaseResult;\n }\n\n // Case-specific errors win over suite-level / hook errors.\n const caseErrors = context.testErrors\n .filter(\n (entry) =>\n entry.kind === 'case' && entry.testName.includes(item.testName),\n )\n .flatMap((entry) => entry.errors);\n const suiteErrors = context.testErrors\n .filter((entry) => entry.kind === 'suite')\n .flatMap((entry) => entry.errors);\n\n const errorSource =\n caseErrors.length > 0\n ? caseErrors\n : suiteErrors.length > 0\n ? suiteErrors\n : context.unhandledErrors;\n const errorText =\n errorSource.length > 0\n ? errorSource.map(formatWorkerError).join('\\n\\n')\n : 'Not executed';\n\n return {\n file: item.filePath,\n testName: item.testName,\n success: false,\n duration: 0,\n error: errorText,\n };\n};\n\nconst writeSummaryFile = (\n loaded: LoadedMidsceneConfig,\n summary: FrameworkSuiteSummary,\n): string | undefined => {\n const target = loaded.config.output?.summary;\n if (!target) {\n return undefined;\n }\n const summaryPath = resolve(loaded.root, target);\n mkdirSync(dirname(summaryPath), { recursive: true });\n writeFileSync(summaryPath, JSON.stringify(summary, null, 2));\n return summaryPath;\n};\n\nconst printSummary = (summary: FrameworkSuiteSummary, summaryPath?: string) => {\n console.log('\\nšŸ“Š Midscene suite summary');\n console.log(` Total: ${summary.total}`);\n console.log(` Passed: ${summary.passed}`);\n console.log(` Failed: ${summary.failed}`);\n console.log(` Duration: ${(summary.durationMs / 1000).toFixed(2)}s`);\n if (summaryPath) {\n console.log(` Summary: ${summaryPath}`);\n }\n for (const result of summary.results) {\n if (!result.success) {\n console.log(` āŒ ${result.testName}: ${result.error ?? 'failed'}`);\n }\n }\n};\n\n/**\n * Load `midscene.config.ts`, discover cases, run them through Rstest, and write\n * the suite summary. Intended to be the entire body of a project's\n * `run-suite.ts`:\n *\n * ```ts\n * import { runMidsceneSuite } from '@midscene/testing-framework';\n * await runMidsceneSuite();\n * ```\n */\nexport async function runMidsceneSuite(\n options: RunMidsceneSuiteOptions = {},\n): Promise<FrameworkSuiteSummary> {\n const loaded = await loadMidsceneConfig(options.configPath);\n\n const dotenvFiles = loadFrameworkDotenv({\n cwd: process.cwd(),\n configDir: loaded.root,\n envConfig: loaded.config.env,\n });\n for (const entry of dotenvFiles) {\n if (entry.loaded) {\n console.log(` Env file: ${entry.path}`);\n }\n }\n\n const files = await collectFrameworkTestFiles({\n root: loaded.root,\n config: loaded.config,\n });\n\n if (files.length === 0) {\n throw new Error(\n `No test files found in ${resolve(loaded.root, loaded.config.testDir || './e2e')}`,\n );\n }\n\n const outputDir =\n options.outputDir ||\n join(loaded.root, 'midscene_run', 'tmp', `framework-${Date.now()}`);\n const resultDir = join(outputDir, 'results');\n mkdirSync(resultDir, { recursive: true });\n\n const yamlCases: SuiteCase[] = files\n .filter((file) => file.type === 'yaml')\n .map((file, index) => ({\n filePath: file.filePath,\n testName: file.relativePath,\n resultFile: join(resultDir, `${safeStem(file.relativePath, index)}.json`),\n }));\n\n const virtualModules: Record<string, string> = {};\n const include: string[] = [];\n\n if (yamlCases.length > 0) {\n virtualModules[SUITE_MODULE_ID] = createYamlFrameworkSuiteSource({\n configPath: loaded.path,\n projectDir: loaded.root,\n cases: yamlCases,\n });\n include.push(SUITE_MODULE_ID);\n }\n\n for (const file of files) {\n if (file.type === 'test') {\n include.push(file.filePath);\n }\n }\n\n const runner = options.rstestRunner || defaultRstestRunner;\n const runResult = await runner({\n root: loaded.root,\n include,\n virtualModules,\n maxConcurrency: loaded.config.testRunner?.maxConcurrency,\n testTimeout: loaded.config.testRunner?.testTimeout,\n bail: loaded.config.testRunner?.bail,\n retry: loaded.config.testRunner?.retry,\n });\n\n const results = yamlCases.map((item) =>\n readCaseResult(item, {\n testErrors: runResult.testErrors ?? [],\n unhandledErrors: runResult.unhandledErrors ?? [],\n }),\n );\n const summary: FrameworkSuiteSummary = {\n total: results.length,\n passed: results.filter((result) => result.success).length,\n failed: results.filter((result) => !result.success).length,\n durationMs: results.reduce(\n (sum, result) => sum + (result.duration || 0),\n 0,\n ),\n results,\n };\n\n const summaryPath = writeSummaryFile(loaded, summary);\n printSummary(summary, summaryPath);\n\n if (!runResult.ok || summary.failed > 0) {\n process.exitCode = 1;\n }\n\n return summary;\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","RUNNER_WORKER_RESULT_PREFIX","SUITE_MODULE_ID","safeStem","relativePath","index","base","String","resolveWorkerEntry","moduleUrl","here","fileURLToPath","__filename","ext","resolve","dirname","defaultRstestRunner","project","workerEntry","input","Promise","resolveRunner","rejectRunner","child","fork","process","stdoutBuffer","parsedResult","chunk","newlineIndex","line","JSON","error","Error","code","signal","console","formatWorkerError","readCaseResult","item","context","existsSync","readFileSync","caseErrors","entry","suiteErrors","errorSource","errorText","writeSummaryFile","loaded","summary","target","summaryPath","mkdirSync","writeFileSync","printSummary","result","runMidsceneSuite","options","loadMidsceneConfig","dotenvFiles","loadFrameworkDotenv","files","collectFrameworkTestFiles","outputDir","join","Date","resultDir","yamlCases","file","virtualModules","include","createYamlFrameworkSuiteSource","runner","runResult","results","sum"],"mappings":"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"}
@@ -10,6 +10,13 @@ export interface FrameworkRstestProject {
10
10
  }
11
11
  export type FrameworkRstestRunner = (project: FrameworkRstestProject) => Promise<{
12
12
  ok: boolean;
13
+ unhandledErrors?: WorkerError[];
14
+ testErrors?: Array<{
15
+ kind: 'case' | 'suite';
16
+ file: string;
17
+ testName: string;
18
+ errors: WorkerError[];
19
+ }>;
13
20
  }>;
14
21
  export interface RunMidsceneSuiteOptions {
15
22
  /** Path to `midscene.config.ts`. Defaults to the one in `cwd`. */
@@ -20,6 +27,11 @@ export interface RunMidsceneSuiteOptions {
20
27
  rstestRunner?: FrameworkRstestRunner;
21
28
  stdio?: 'inherit' | 'pipe';
22
29
  }
30
+ interface WorkerError {
31
+ name?: string;
32
+ message?: string;
33
+ stack?: string;
34
+ }
23
35
  /**
24
36
  * Load `midscene.config.ts`, discover cases, run them through Rstest, and write
25
37
  * the suite summary. Intended to be the entire body of a project's
@@ -31,3 +43,4 @@ export interface RunMidsceneSuiteOptions {
31
43
  * ```
32
44
  */
33
45
  export declare function runMidsceneSuite(options?: RunMidsceneSuiteOptions): Promise<FrameworkSuiteSummary>;
46
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@midscene/testing-framework",
3
3
  "description": "Midscene AI-native UI testing framework runtime built on Rstest.",
4
- "version": "0.0.0",
4
+ "version": "1.8.8-beta-20260602033804.0",
5
5
  "repository": "https://github.com/web-infra-dev/midscene",
6
6
  "homepage": "https://midscenejs.com/",
7
7
  "main": "./dist/lib/index.js",
@@ -24,18 +24,18 @@
24
24
  "README.md"
25
25
  ],
26
26
  "dependencies": {
27
- "@midscene/core": "1.8.7-beta-20260601040030.0",
28
- "@midscene/shared": "1.8.7-beta-20260601040030.0",
29
27
  "dotenv": "^16.4.5",
30
28
  "glob": "11.0.0",
31
29
  "jiti": "2.6.1",
32
- "js-yaml": "4.1.0"
30
+ "js-yaml": "4.1.0",
31
+ "@midscene/core": "1.8.8-beta-20260602033804.0",
32
+ "@midscene/shared": "1.8.8-beta-20260602033804.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@midscene/android": "1.8.7-beta-20260601040030.0",
36
- "@midscene/web": "1.8.7-beta-20260601040030.0",
37
35
  "@rstest/core": "*",
38
- "playwright": "^1.45.0"
36
+ "playwright": "^1.45.0",
37
+ "@midscene/android": "1.8.8-beta-20260602033804.0",
38
+ "@midscene/web": "1.8.8-beta-20260602033804.0"
39
39
  },
40
40
  "peerDependenciesMeta": {
41
41
  "@midscene/android": {
@@ -56,8 +56,8 @@
56
56
  "playwright": "^1.45.0",
57
57
  "typescript": "^5.8.3",
58
58
  "vitest": "3.0.5",
59
- "@midscene/android": "1.8.6",
60
- "@midscene/web": "1.8.6"
59
+ "@midscene/android": "1.8.8-beta-20260602033804.0",
60
+ "@midscene/web": "1.8.8-beta-20260602033804.0"
61
61
  },
62
62
  "engines": {
63
63
  "node": ">=18.19.0"