@midscene/core 1.17.3-beta-20261029072240.0 → 1.17.3

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.
Files changed (69) hide show
  1. package/dist/es/agent/task-builder.mjs +1 -2
  2. package/dist/es/agent/task-builder.mjs.map +1 -1
  3. package/dist/es/agent/utils.mjs +1 -1
  4. package/dist/es/dump/execution-recorder.mjs +70 -0
  5. package/dist/es/dump/execution-recorder.mjs.map +1 -0
  6. package/dist/es/dump/html-utils.mjs +51 -1
  7. package/dist/es/dump/html-utils.mjs.map +1 -1
  8. package/dist/es/dump/report-artifact-reader.mjs +85 -0
  9. package/dist/es/dump/report-artifact-reader.mjs.map +1 -0
  10. package/dist/es/dump/report-artifact-store.mjs +164 -0
  11. package/dist/es/dump/report-artifact-store.mjs.map +1 -0
  12. package/dist/es/dump/screenshot-store.mjs +40 -15
  13. package/dist/es/dump/screenshot-store.mjs.map +1 -1
  14. package/dist/es/dump/test-report-images.mjs +120 -0
  15. package/dist/es/dump/test-report-images.mjs.map +1 -0
  16. package/dist/es/report-cli.mjs +8 -7
  17. package/dist/es/report-cli.mjs.map +1 -1
  18. package/dist/es/report-generator.mjs +56 -47
  19. package/dist/es/report-generator.mjs.map +1 -1
  20. package/dist/es/report-html-template.mjs +1 -1
  21. package/dist/es/report.mjs +29 -55
  22. package/dist/es/report.mjs.map +1 -1
  23. package/dist/es/test-run-report.mjs.map +1 -1
  24. package/dist/es/utils.mjs +1 -1
  25. package/dist/lib/agent/task-builder.js +1 -2
  26. package/dist/lib/agent/task-builder.js.map +1 -1
  27. package/dist/lib/agent/utils.js +1 -1
  28. package/dist/lib/dump/execution-recorder.js +104 -0
  29. package/dist/lib/dump/execution-recorder.js.map +1 -0
  30. package/dist/lib/dump/html-utils.js +67 -5
  31. package/dist/lib/dump/html-utils.js.map +1 -1
  32. package/dist/lib/dump/report-artifact-reader.js +134 -0
  33. package/dist/lib/dump/report-artifact-reader.js.map +1 -0
  34. package/dist/lib/dump/report-artifact-store.js +204 -0
  35. package/dist/lib/dump/report-artifact-store.js.map +1 -0
  36. package/dist/lib/dump/screenshot-store.js +42 -14
  37. package/dist/lib/dump/screenshot-store.js.map +1 -1
  38. package/dist/lib/dump/test-report-images.js +154 -0
  39. package/dist/lib/dump/test-report-images.js.map +1 -0
  40. package/dist/lib/report-cli.js +8 -7
  41. package/dist/lib/report-cli.js.map +1 -1
  42. package/dist/lib/report-generator.js +54 -45
  43. package/dist/lib/report-generator.js.map +1 -1
  44. package/dist/lib/report-html-template.js +1 -1
  45. package/dist/lib/report.js +54 -56
  46. package/dist/lib/report.js.map +1 -1
  47. package/dist/lib/test-run-report.js.map +1 -1
  48. package/dist/lib/utils.js +1 -1
  49. package/dist/types/agent/task-builder.d.ts.map +1 -1
  50. package/dist/types/dump/execution-recorder.d.ts +23 -0
  51. package/dist/types/dump/execution-recorder.d.ts.map +1 -0
  52. package/dist/types/dump/html-utils.d.ts +9 -0
  53. package/dist/types/dump/html-utils.d.ts.map +1 -1
  54. package/dist/types/dump/report-artifact-reader.d.ts +19 -0
  55. package/dist/types/dump/report-artifact-reader.d.ts.map +1 -0
  56. package/dist/types/dump/report-artifact-store.d.ts +22 -0
  57. package/dist/types/dump/report-artifact-store.d.ts.map +1 -0
  58. package/dist/types/dump/screenshot-store.d.ts +6 -0
  59. package/dist/types/dump/screenshot-store.d.ts.map +1 -1
  60. package/dist/types/dump/test-report-images.d.ts +14 -0
  61. package/dist/types/dump/test-report-images.d.ts.map +1 -0
  62. package/dist/types/report-cli.d.ts.map +1 -1
  63. package/dist/types/report-generator.d.ts +4 -3
  64. package/dist/types/report-generator.d.ts.map +1 -1
  65. package/dist/types/report.d.ts +3 -0
  66. package/dist/types/report.d.ts.map +1 -1
  67. package/dist/types/test-run-report.d.ts +2 -0
  68. package/dist/types/test-run-report.d.ts.map +1 -1
  69. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dump/test-report-images.mjs","sources":["../../../src/dump/test-report-images.ts"],"sourcesContent":["import {\n appendFileSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n} from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { generateImageScriptTag, parseImageScripts } from './html-utils';\nimport {\n type StoredImageRef,\n imageRefFileExtension,\n normalizeStoredImageRef,\n} from './image-reference';\nimport {\n parseBase64ImageDataUrl,\n reportImageFileName,\n} from './screenshot-store';\n\n/** Final delivery format is independent of how Agents stored their source records. */\nexport class TestReportImages {\n private readonly locations = new Map<\n string,\n Pick<StoredImageRef, 'storage' | 'path'>\n >();\n private readonly files = new Set<string>();\n\n constructor(\n private readonly reportPath: string,\n private readonly external: boolean,\n ) {}\n\n private reference(ref: StoredImageRef, dataUri: string): StoredImageRef {\n const known = this.locations.get(ref.id);\n if (known) return { ...ref, ...known };\n const location: Pick<StoredImageRef, 'storage' | 'path'> = this.external\n ? {\n storage: 'file',\n path: `./screenshots/${reportImageFileName(imageRefFileExtension(ref), Buffer.from(parseBase64ImageDataUrl(dataUri).rawBase64, 'base64'))}`,\n }\n : { storage: 'inline', path: undefined };\n this.locations.set(ref.id, location);\n return { ...ref, ...location };\n }\n\n private rewrite(serialized: string): string {\n return JSON.stringify(JSON.parse(serialized), (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n return ref ? { ...ref, ...this.locations.get(ref.id) } : value;\n });\n }\n\n private collect(serialized: string): StoredImageRef[] {\n const refs = new Map<string, StoredImageRef>();\n JSON.parse(serialized, (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n if (ref && !this.locations.has(ref.id)) refs.set(ref.id, ref);\n return value;\n });\n return [...refs.values()];\n }\n\n rewriteSync(serialized: string, sourcePath: string): string {\n let inlineImages: Record<string, string> | undefined;\n for (const ref of this.collect(serialized)) {\n let data: string;\n if (ref.storage === 'file')\n data = `data:${ref.mimeType};base64,${readFileSync(resolve(dirname(sourcePath), ref.path!)).toString('base64')}`;\n else {\n inlineImages ??= parseImageScripts(readFileSync(sourcePath, 'utf8'));\n data = inlineImages[ref.id];\n if (!data)\n throw new Error(`Missing report image ${ref.id} in ${sourcePath}`);\n }\n const target = this.reference(ref, data);\n if (target.storage === 'file') {\n if (!this.files.has(target.path!)) {\n const file = join(dirname(this.reportPath), target.path!);\n mkdirSync(dirname(file), { recursive: true });\n writeFileSync(\n file,\n Buffer.from(parseBase64ImageDataUrl(data).rawBase64, 'base64'),\n );\n this.files.add(target.path!);\n }\n } else\n appendFileSync(\n this.reportPath,\n `${generateImageScriptTag(ref.id, data)}\\n`,\n );\n }\n return this.rewrite(serialized);\n }\n\n async rewriteAsync(serialized: string, sourcePath: string): Promise<string> {\n let inlineImages: Record<string, string> | undefined;\n for (const ref of this.collect(serialized)) {\n let data: string;\n if (ref.storage === 'file') {\n const bytes = await fs.readFile(\n resolve(dirname(sourcePath), ref.path!),\n );\n data = `data:${ref.mimeType};base64,${bytes.toString('base64')}`;\n } else {\n inlineImages ??= parseImageScripts(\n await fs.readFile(sourcePath, 'utf8'),\n );\n data = inlineImages[ref.id];\n if (!data)\n throw new Error(`Missing report image ${ref.id} in ${sourcePath}`);\n }\n const target = this.reference(ref, data);\n if (target.storage === 'file') {\n if (!this.files.has(target.path!)) {\n const file = join(dirname(this.reportPath), target.path!);\n await fs.mkdir(dirname(file), { recursive: true });\n await fs.writeFile(\n file,\n Buffer.from(parseBase64ImageDataUrl(data).rawBase64, 'base64'),\n );\n this.files.add(target.path!);\n }\n } else\n await fs.appendFile(\n this.reportPath,\n `${generateImageScriptTag(ref.id, data)}\\n`,\n );\n }\n return this.rewrite(serialized);\n }\n}\n"],"names":["TestReportImages","ref","dataUri","known","location","reportImageFileName","imageRefFileExtension","Buffer","parseBase64ImageDataUrl","undefined","serialized","JSON","_key","value","normalizeStoredImageRef","refs","Map","sourcePath","inlineImages","data","readFileSync","resolve","dirname","parseImageScripts","Error","target","file","join","mkdirSync","writeFileSync","appendFileSync","generateImageScriptTag","bytes","fs","reportPath","external","Set"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { copyFileSync, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, extname, join, resolve } from "node:path";
3
3
  import { z } from "zod";
4
- import { extractAllDumpScriptsSync, extractTestRunReportDumpSync } from "./dump/html-utils.mjs";
4
+ import { readReportRunnerDump, streamReportDumpScripts } from "./dump/report-artifact-reader.mjs";
5
5
  import { resolveScreenshotSource } from "./dump/screenshot-store.mjs";
6
6
  import { deriveCaseStatus } from "./dump/task-status.mjs";
7
7
  import { ReportMergingTool, collectDedupedExecutions, splitReportHtmlByExecution } from "./report.mjs";
@@ -100,21 +100,22 @@ const FAILING_TEST_STATUSES = new Set([
100
100
  ]);
101
101
  function readSourceTestStatus(htmlPath) {
102
102
  const recorded = [];
103
- for (const { openTag } of extractAllDumpScriptsSync(htmlPath)){
103
+ streamReportDumpScripts(htmlPath, ({ openTag })=>{
104
104
  const match = openTag.match(/playwright_test_status="([^"]*)"/);
105
- if (!match) continue;
105
+ if (!match) return false;
106
106
  const status = decodeURIComponent(match[1]);
107
107
  if (TEST_STATUS_VALUES.has(status)) recorded.push(status);
108
- }
108
+ return false;
109
+ });
109
110
  if (0 === recorded.length) return;
110
111
  return recorded.find((s)=>FAILING_TEST_STATUSES.has(s)) ?? recorded[0];
111
112
  }
112
113
  function deriveReportAttributesFromHtml(htmlPath, index) {
113
114
  const fallbackId = `${basename(dirname(htmlPath)) || basename(htmlPath, extname(htmlPath))}-${index + 1}`;
114
- const runnerDump = extractTestRunReportDumpSync(htmlPath);
115
+ const runnerDump = readReportRunnerDump(htmlPath);
116
+ const recordedStatus = readSourceTestStatus(htmlPath);
115
117
  try {
116
118
  const { baseDump, executions } = collectDedupedExecutions(htmlPath);
117
- const recordedStatus = readSourceTestStatus(htmlPath);
118
119
  const testStatus = recordedStatus && FAILING_TEST_STATUSES.has(recordedStatus) ? recordedStatus : runnerDump?.status === 'failed' ? 'failed' : recordedStatus ?? (runnerDump ? 'passed' : deriveCaseStatus(executions));
119
120
  return {
120
121
  testId: fallbackId,
@@ -131,7 +132,7 @@ function deriveReportAttributesFromHtml(htmlPath, index) {
131
132
  testTitle: fallbackId,
132
133
  testDescription: '',
133
134
  testDuration: 0,
134
- testStatus: runnerDump?.status === 'failed' ? 'failed' : 'passed'
135
+ testStatus: recordedStatus && FAILING_TEST_STATUSES.has(recordedStatus) ? recordedStatus : runnerDump?.status === 'failed' ? 'failed' : recordedStatus ?? 'passed'
135
136
  };
136
137
  }
137
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"report-cli.mjs","sources":["../../src/report-cli.ts"],"sourcesContent":["import {\n copyFileSync,\n existsSync,\n mkdirSync,\n statSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport {\n extractAllDumpScriptsSync,\n extractTestRunReportDumpSync,\n} from './dump/html-utils';\nimport { resolveScreenshotSource } from './dump/screenshot-store';\nimport { deriveCaseStatus } from './dump/task-status';\nimport {\n ReportMergingTool,\n collectDedupedExecutions,\n splitReportHtmlByExecution,\n} from './report';\nimport { reportToMarkdown } from './report-markdown';\nimport type { MarkdownAttachment } from './report-markdown';\nimport { collectReportSummary } from './report-stats';\nimport type { ReportFileAttributes, TestStatus } from './types';\nimport { ReportActionDump } from './types';\n\ntype ReportCliToolResult = {\n isError: boolean;\n content: Array<{\n type: 'text';\n text: string;\n }>;\n};\n\ntype ReportCliSchema = Record<string, z.ZodTypeAny>;\n\nexport interface ReportCliCommandDefinition {\n name: string;\n description: string;\n schema: ReportCliSchema;\n handler: (args: Record<string, unknown>) => Promise<ReportCliToolResult>;\n}\n\nexport interface ReportCliCommandEntry {\n name: string;\n def: ReportCliCommandDefinition;\n}\n\nexport type ConsumeReportFileAction = 'split' | 'to-markdown' | 'merge-html';\n\nexport interface ConsumeReportFileOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport type SplitReportFileOptions = ConsumeReportFileOptions;\nexport type ReportFileToMarkdownOptions = ConsumeReportFileOptions;\n\nexport interface MergeReportFilesOptions {\n htmlPaths: string[];\n outputDir?: string;\n outputName?: string;\n overwrite?: boolean;\n}\n\nexport interface MergeReportFilesResult {\n mergedReportPath: string;\n}\n\nfunction writeAttachmentFromReport(\n attachment: MarkdownAttachment,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const { suggestedFileName, id, mimeType } = attachment;\n if (opts.writtenFiles.has(suggestedFileName)) return;\n\n const absolutePath = path.join(opts.screenshotsDir, suggestedFileName);\n\n const resolved = resolveScreenshotSource(attachment.sourceRef ?? null, {\n reportPath: opts.htmlPath,\n fallbackId: id,\n fallbackMimeType: (mimeType || 'image/png') as 'image/png' | 'image/jpeg',\n });\n\n if (resolved.type === 'data-uri') {\n const rawBase64 = resolved.dataUri.replace(\n /^data:image\\/[a-zA-Z+]+;base64,/,\n '',\n );\n writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));\n opts.writtenFiles.add(suggestedFileName);\n return;\n }\n\n if (!existsSync(resolved.filePath)) {\n throw new Error(\n `Cannot resolve screenshot \"${id}\" for markdown attachment from ${opts.htmlPath}`,\n );\n }\n\n copyFileSync(resolved.filePath, absolutePath);\n opts.writtenFiles.add(suggestedFileName);\n}\n\nasync function markdownFromReport(\n htmlPath: string,\n outputDir: string,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n const mergedReport = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions,\n });\n\n const result = reportToMarkdown(mergedReport);\n\n const markdownFiles: string[] = [];\n const writtenScreenshots = new Set<string>();\n\n const mdPath = path.join(outputDir, 'report.md');\n writeFileSync(mdPath, result.markdown, 'utf-8');\n markdownFiles.push(mdPath);\n\n for (const attachment of result.attachments) {\n writeAttachmentFromReport(attachment, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshots,\n });\n }\n\n return {\n markdownFiles,\n screenshotFiles: Array.from(writtenScreenshots)\n .sort()\n .map((f) => path.join(screenshotsDir, f)),\n };\n}\n\nfunction resolveReportHtmlPath(htmlPath: string): string {\n const normalizedPath = path.resolve(htmlPath);\n\n if (!existsSync(normalizedPath)) {\n throw new Error(`report-tool: --htmlPath does not exist: ${htmlPath}`);\n }\n\n const stats = statSync(normalizedPath);\n if (!stats.isDirectory()) {\n return normalizedPath;\n }\n\n const indexHtmlPath = path.join(normalizedPath, 'index.html');\n if (!existsSync(indexHtmlPath)) {\n throw new Error(\n `report-tool: \"${htmlPath}\" is not an HTML report file, and no index.html was found under this directory.`,\n );\n }\n\n return indexHtmlPath;\n}\n\nexport function splitReportFile(options: SplitReportFileOptions): {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n} {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('splitReportFile: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('splitReportFile: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return splitReportHtmlByExecution({\n htmlPath: resolvedHtmlPath,\n outputDir,\n });\n}\n\nexport async function reportFileToMarkdown(\n options: ReportFileToMarkdownOptions,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('reportFileToMarkdown: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('reportFileToMarkdown: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return markdownFromReport(resolvedHtmlPath, outputDir);\n}\n\nconst TEST_STATUS_VALUES: ReadonlySet<TestStatus> = new Set<TestStatus>([\n 'passed',\n 'failed',\n 'timedOut',\n 'skipped',\n 'interrupted',\n]);\n\nconst FAILING_TEST_STATUSES: ReadonlySet<TestStatus> = new Set<TestStatus>([\n 'failed',\n 'timedOut',\n 'interrupted',\n]);\n\n/**\n * Reuse a `playwright_test_status` already recorded on the source report's dump\n * scripts (e.g. a Playwright-generated report carries the precise\n * timedOut/skipped/interrupted status). Returns undefined when the source has\n * no such attribute, so the caller can fall back to deriving from the dump.\n *\n * A single source report may bundle several executions (e.g. an already-merged\n * report), so surface a failing status if any execution failed — mirroring\n * `deriveCaseStatus`'s any-failure-wins semantics rather than taking the first\n * script's status and masking later failures.\n */\nfunction readSourceTestStatus(htmlPath: string): TestStatus | undefined {\n const recorded: TestStatus[] = [];\n for (const { openTag } of extractAllDumpScriptsSync(htmlPath)) {\n const match = openTag.match(/playwright_test_status=\"([^\"]*)\"/);\n if (!match) continue;\n const status = decodeURIComponent(match[1]) as TestStatus;\n if (TEST_STATUS_VALUES.has(status)) {\n recorded.push(status);\n }\n }\n if (recorded.length === 0) return undefined;\n return recorded.find((s) => FAILING_TEST_STATUSES.has(s)) ?? recorded[0];\n}\n\nfunction deriveReportAttributesFromHtml(\n htmlPath: string,\n index: number,\n): ReportFileAttributes {\n const fallbackId = `${path.basename(path.dirname(htmlPath)) || path.basename(htmlPath, path.extname(htmlPath))}-${index + 1}`;\n const runnerDump = extractTestRunReportDumpSync(htmlPath);\n try {\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n // Prefer a status the source report already recorded (keeps Playwright's\n // precise timedOut/skipped/interrupted); otherwise infer it from the dump\n // so a failing case is not silently merged in as passed.\n const recordedStatus = readSourceTestStatus(htmlPath);\n const testStatus =\n recordedStatus && FAILING_TEST_STATUSES.has(recordedStatus)\n ? recordedStatus\n : runnerDump?.status === 'failed'\n ? 'failed'\n : (recordedStatus ??\n (runnerDump ? 'passed' : deriveCaseStatus(executions)));\n return {\n testId: fallbackId,\n testTitle: baseDump.groupName || fallbackId,\n testDescription: baseDump.groupDescription ?? '',\n testDuration:\n runnerDump?.durationMs ??\n collectReportSummary({ executions }).timing.wallTimeMs ??\n 0,\n testStatus,\n };\n } catch {\n return {\n testId: fallbackId,\n testTitle: fallbackId,\n testDescription: '',\n testDuration: 0,\n testStatus: runnerDump?.status === 'failed' ? 'failed' : 'passed',\n };\n }\n}\n\nexport function mergeReportFiles(\n options: MergeReportFilesOptions,\n): MergeReportFilesResult {\n const { htmlPaths, outputDir, outputName, overwrite = false } = options;\n if (!htmlPaths || htmlPaths.length === 0) {\n throw new Error('mergeReportFiles: htmlPaths is required');\n }\n\n const resolvedPaths = htmlPaths.map((p) => resolveReportHtmlPath(p));\n\n const tool = new ReportMergingTool();\n resolvedPaths.forEach((htmlPath, index) => {\n tool.append({\n reportFilePath: htmlPath,\n reportAttributes: deriveReportAttributesFromHtml(htmlPath, index),\n });\n });\n\n const mergedReportPath = tool.mergeReports(outputName ?? 'AUTO', {\n overwrite,\n outputDir,\n });\n\n if (!mergedReportPath) {\n throw new Error('mergeReportFiles: failed to produce a merged report');\n }\n\n return { mergedReportPath };\n}\n\nfunction normalizeHtmlReportArg(raw: unknown): string[] | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (Array.isArray(raw)) {\n return raw.filter(\n (p): p is string => typeof p === 'string' && p.length > 0,\n );\n }\n if (typeof raw === 'string') {\n const trimmed = raw.trim();\n return trimmed ? [trimmed] : [];\n }\n return undefined;\n}\n\nconst reportCommandDefinition: ReportCliCommandDefinition = {\n name: 'report-tool',\n description:\n 'Transform Midscene report artifacts, including splitting executions, converting to markdown, and merging multiple reports.',\n schema: {\n action: z\n .enum(['split', 'to-markdown', 'merge-html'])\n .optional()\n .describe(\n 'Report action to run. Supports: split, to-markdown, merge-html. Defaults to split.',\n ),\n htmlPath: z\n .string()\n .optional()\n .describe(\n 'Input report HTML path (e.g. ./report/index.html). Used by split and to-markdown.',\n ),\n htmlReport: z\n .union([z.string(), z.array(z.string())])\n .optional()\n .describe(\n 'Input report HTML path for the merge action. Repeat the flag to merge multiple reports (e.g. --htmlReport ./a/index.html --htmlReport ./b.html).',\n ),\n outputDir: z\n .string()\n .optional()\n .describe(\n 'Output directory for generated report artifacts. For merge, defaults to the Midscene report directory.',\n ),\n outputName: z\n .string()\n .optional()\n .describe(\n 'Output report file/directory name (without .html) for the merge action. Defaults to an auto-generated name.',\n ),\n overwrite: z\n .union([z.boolean(), z.string()])\n .optional()\n .describe(\n 'Overwrite the existing merged report file if present (merge action only).',\n ),\n },\n handler: async (args) => {\n const {\n action = 'split',\n htmlPath,\n htmlReport,\n outputDir,\n outputName,\n overwrite,\n } = args as {\n action?: string;\n htmlPath?: string;\n htmlReport?: unknown;\n outputDir?: string;\n outputName?: string;\n overwrite?: unknown;\n };\n if (\n action !== 'split' &&\n action !== 'to-markdown' &&\n action !== 'merge-html'\n ) {\n throw new Error(\n `report-tool: unsupported --action value \"${action}\". Currently supported: split, to-markdown, merge-html`,\n );\n }\n\n if (action === 'merge-html') {\n const paths = normalizeHtmlReportArg(htmlReport);\n if (!paths || paths.length === 0) {\n throw new Error(\n 'report-tool: --htmlReport is required for action \"merge-html\". Repeat --htmlReport for each report (e.g. --htmlReport ./a/index.html --htmlReport ./b.html).',\n );\n }\n\n const overwriteFlag =\n overwrite === true || overwrite === 'true' || overwrite === '1';\n\n const result = mergeReportFiles({\n htmlPaths: paths,\n outputDir,\n outputName,\n overwrite: overwriteFlag,\n });\n\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Merged ${paths.length} report(s) into ${result.mergedReportPath}`,\n },\n ],\n };\n }\n\n if (!htmlPath) {\n throw new Error('report-tool: --htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('report-tool: --outputDir is required');\n }\n\n if (action === 'to-markdown') {\n const result = await reportFileToMarkdown({\n htmlPath,\n outputDir,\n });\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Markdown export completed. Generated ${result.markdownFiles.length} markdown file(s) and ${result.screenshotFiles.length} screenshot(s). Output path: ${outputDir}`,\n },\n ],\n };\n }\n\n const result = splitReportFile({\n htmlPath,\n outputDir,\n });\n\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Report split completed. Generated ${result.executionJsonFiles.length} execution JSON files and ${result.screenshotFiles.length} screenshots. Output path: ${outputDir}`,\n },\n ],\n };\n },\n};\n\nexport function createReportCliCommands(): ReportCliCommandEntry[] {\n return [\n {\n name: 'report-tool',\n def: reportCommandDefinition,\n },\n ];\n}\n"],"names":["writeAttachmentFromReport","attachment","opts","suggestedFileName","id","mimeType","absolutePath","path","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","existsSync","Error","copyFileSync","markdownFromReport","htmlPath","outputDir","screenshotsDir","mkdirSync","baseDump","executions","collectDedupedExecutions","mergedReport","ReportActionDump","result","reportToMarkdown","markdownFiles","writtenScreenshots","Set","mdPath","Array","f","resolveReportHtmlPath","normalizedPath","stats","statSync","indexHtmlPath","splitReportFile","options","resolvedHtmlPath","splitReportHtmlByExecution","reportFileToMarkdown","TEST_STATUS_VALUES","FAILING_TEST_STATUSES","readSourceTestStatus","recorded","openTag","extractAllDumpScriptsSync","match","status","decodeURIComponent","s","deriveReportAttributesFromHtml","index","fallbackId","runnerDump","extractTestRunReportDumpSync","recordedStatus","testStatus","deriveCaseStatus","collectReportSummary","mergeReportFiles","htmlPaths","outputName","overwrite","resolvedPaths","p","tool","ReportMergingTool","mergedReportPath","normalizeHtmlReportArg","raw","trimmed","reportCommandDefinition","z","args","action","htmlReport","paths","overwriteFlag","createReportCliCommands"],"mappings":"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"}
1
+ {"version":3,"file":"report-cli.mjs","sources":["../../src/report-cli.ts"],"sourcesContent":["import {\n copyFileSync,\n existsSync,\n mkdirSync,\n statSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport {\n readReportRunnerDump,\n streamReportDumpScripts,\n} from './dump/report-artifact-reader';\nimport { resolveScreenshotSource } from './dump/screenshot-store';\nimport { deriveCaseStatus } from './dump/task-status';\nimport {\n ReportMergingTool,\n collectDedupedExecutions,\n splitReportHtmlByExecution,\n} from './report';\nimport { reportToMarkdown } from './report-markdown';\nimport type { MarkdownAttachment } from './report-markdown';\nimport { collectReportSummary } from './report-stats';\nimport type { ReportFileAttributes, TestStatus } from './types';\nimport { ReportActionDump } from './types';\n\ntype ReportCliToolResult = {\n isError: boolean;\n content: Array<{\n type: 'text';\n text: string;\n }>;\n};\n\ntype ReportCliSchema = Record<string, z.ZodTypeAny>;\n\nexport interface ReportCliCommandDefinition {\n name: string;\n description: string;\n schema: ReportCliSchema;\n handler: (args: Record<string, unknown>) => Promise<ReportCliToolResult>;\n}\n\nexport interface ReportCliCommandEntry {\n name: string;\n def: ReportCliCommandDefinition;\n}\n\nexport type ConsumeReportFileAction = 'split' | 'to-markdown' | 'merge-html';\n\nexport interface ConsumeReportFileOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport type SplitReportFileOptions = ConsumeReportFileOptions;\nexport type ReportFileToMarkdownOptions = ConsumeReportFileOptions;\n\nexport interface MergeReportFilesOptions {\n htmlPaths: string[];\n outputDir?: string;\n outputName?: string;\n overwrite?: boolean;\n}\n\nexport interface MergeReportFilesResult {\n mergedReportPath: string;\n}\n\nfunction writeAttachmentFromReport(\n attachment: MarkdownAttachment,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const { suggestedFileName, id, mimeType } = attachment;\n if (opts.writtenFiles.has(suggestedFileName)) return;\n\n const absolutePath = path.join(opts.screenshotsDir, suggestedFileName);\n\n const resolved = resolveScreenshotSource(attachment.sourceRef ?? null, {\n reportPath: opts.htmlPath,\n fallbackId: id,\n fallbackMimeType: (mimeType || 'image/png') as 'image/png' | 'image/jpeg',\n });\n\n if (resolved.type === 'data-uri') {\n const rawBase64 = resolved.dataUri.replace(\n /^data:image\\/[a-zA-Z+]+;base64,/,\n '',\n );\n writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));\n opts.writtenFiles.add(suggestedFileName);\n return;\n }\n\n if (!existsSync(resolved.filePath)) {\n throw new Error(\n `Cannot resolve screenshot \"${id}\" for markdown attachment from ${opts.htmlPath}`,\n );\n }\n\n copyFileSync(resolved.filePath, absolutePath);\n opts.writtenFiles.add(suggestedFileName);\n}\n\nasync function markdownFromReport(\n htmlPath: string,\n outputDir: string,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n const mergedReport = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions,\n });\n\n const result = reportToMarkdown(mergedReport);\n\n const markdownFiles: string[] = [];\n const writtenScreenshots = new Set<string>();\n\n const mdPath = path.join(outputDir, 'report.md');\n writeFileSync(mdPath, result.markdown, 'utf-8');\n markdownFiles.push(mdPath);\n\n for (const attachment of result.attachments) {\n writeAttachmentFromReport(attachment, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshots,\n });\n }\n\n return {\n markdownFiles,\n screenshotFiles: Array.from(writtenScreenshots)\n .sort()\n .map((f) => path.join(screenshotsDir, f)),\n };\n}\n\nfunction resolveReportHtmlPath(htmlPath: string): string {\n const normalizedPath = path.resolve(htmlPath);\n\n if (!existsSync(normalizedPath)) {\n throw new Error(`report-tool: --htmlPath does not exist: ${htmlPath}`);\n }\n\n const stats = statSync(normalizedPath);\n if (!stats.isDirectory()) {\n return normalizedPath;\n }\n\n const indexHtmlPath = path.join(normalizedPath, 'index.html');\n if (!existsSync(indexHtmlPath)) {\n throw new Error(\n `report-tool: \"${htmlPath}\" is not an HTML report file, and no index.html was found under this directory.`,\n );\n }\n\n return indexHtmlPath;\n}\n\nexport function splitReportFile(options: SplitReportFileOptions): {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n} {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('splitReportFile: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('splitReportFile: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return splitReportHtmlByExecution({\n htmlPath: resolvedHtmlPath,\n outputDir,\n });\n}\n\nexport async function reportFileToMarkdown(\n options: ReportFileToMarkdownOptions,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('reportFileToMarkdown: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('reportFileToMarkdown: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return markdownFromReport(resolvedHtmlPath, outputDir);\n}\n\nconst TEST_STATUS_VALUES: ReadonlySet<TestStatus> = new Set<TestStatus>([\n 'passed',\n 'failed',\n 'timedOut',\n 'skipped',\n 'interrupted',\n]);\n\nconst FAILING_TEST_STATUSES: ReadonlySet<TestStatus> = new Set<TestStatus>([\n 'failed',\n 'timedOut',\n 'interrupted',\n]);\n\n/**\n * Reuse a `playwright_test_status` already recorded on the source report's dump\n * scripts (e.g. a Playwright-generated report carries the precise\n * timedOut/skipped/interrupted status). Returns undefined when the source has\n * no such attribute, so the caller can fall back to deriving from the dump.\n *\n * A single source report may bundle several executions (e.g. an already-merged\n * report), so surface a failing status if any execution failed — mirroring\n * `deriveCaseStatus`'s any-failure-wins semantics rather than taking the first\n * script's status and masking later failures.\n */\nfunction readSourceTestStatus(htmlPath: string): TestStatus | undefined {\n const recorded: TestStatus[] = [];\n streamReportDumpScripts(htmlPath, ({ openTag }) => {\n const match = openTag.match(/playwright_test_status=\"([^\"]*)\"/);\n if (!match) return false;\n const status = decodeURIComponent(match[1]) as TestStatus;\n if (TEST_STATUS_VALUES.has(status)) {\n recorded.push(status);\n }\n return false;\n });\n if (recorded.length === 0) return undefined;\n return recorded.find((s) => FAILING_TEST_STATUSES.has(s)) ?? recorded[0];\n}\n\nfunction deriveReportAttributesFromHtml(\n htmlPath: string,\n index: number,\n): ReportFileAttributes {\n const fallbackId = `${path.basename(path.dirname(htmlPath)) || path.basename(htmlPath, path.extname(htmlPath))}-${index + 1}`;\n const runnerDump = readReportRunnerDump(htmlPath);\n const recordedStatus = readSourceTestStatus(htmlPath);\n try {\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n // Prefer a status the source report already recorded (keeps Playwright's\n // precise timedOut/skipped/interrupted); otherwise infer it from the dump\n // so a failing case is not silently merged in as passed.\n const testStatus =\n recordedStatus && FAILING_TEST_STATUSES.has(recordedStatus)\n ? recordedStatus\n : runnerDump?.status === 'failed'\n ? 'failed'\n : (recordedStatus ??\n (runnerDump ? 'passed' : deriveCaseStatus(executions)));\n return {\n testId: fallbackId,\n testTitle: baseDump.groupName || fallbackId,\n testDescription: baseDump.groupDescription ?? '',\n testDuration:\n runnerDump?.durationMs ??\n collectReportSummary({ executions }).timing.wallTimeMs ??\n 0,\n testStatus,\n };\n } catch {\n return {\n testId: fallbackId,\n testTitle: fallbackId,\n testDescription: '',\n testDuration: 0,\n testStatus:\n recordedStatus && FAILING_TEST_STATUSES.has(recordedStatus)\n ? recordedStatus\n : runnerDump?.status === 'failed'\n ? 'failed'\n : (recordedStatus ?? 'passed'),\n };\n }\n}\n\nexport function mergeReportFiles(\n options: MergeReportFilesOptions,\n): MergeReportFilesResult {\n const { htmlPaths, outputDir, outputName, overwrite = false } = options;\n if (!htmlPaths || htmlPaths.length === 0) {\n throw new Error('mergeReportFiles: htmlPaths is required');\n }\n\n const resolvedPaths = htmlPaths.map((p) => resolveReportHtmlPath(p));\n\n const tool = new ReportMergingTool();\n resolvedPaths.forEach((htmlPath, index) => {\n tool.append({\n reportFilePath: htmlPath,\n reportAttributes: deriveReportAttributesFromHtml(htmlPath, index),\n });\n });\n\n const mergedReportPath = tool.mergeReports(outputName ?? 'AUTO', {\n overwrite,\n outputDir,\n });\n\n if (!mergedReportPath) {\n throw new Error('mergeReportFiles: failed to produce a merged report');\n }\n\n return { mergedReportPath };\n}\n\nfunction normalizeHtmlReportArg(raw: unknown): string[] | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (Array.isArray(raw)) {\n return raw.filter(\n (p): p is string => typeof p === 'string' && p.length > 0,\n );\n }\n if (typeof raw === 'string') {\n const trimmed = raw.trim();\n return trimmed ? [trimmed] : [];\n }\n return undefined;\n}\n\nconst reportCommandDefinition: ReportCliCommandDefinition = {\n name: 'report-tool',\n description:\n 'Transform Midscene report artifacts, including splitting executions, converting to markdown, and merging multiple reports.',\n schema: {\n action: z\n .enum(['split', 'to-markdown', 'merge-html'])\n .optional()\n .describe(\n 'Report action to run. Supports: split, to-markdown, merge-html. Defaults to split.',\n ),\n htmlPath: z\n .string()\n .optional()\n .describe(\n 'Input report HTML path (e.g. ./report/index.html). Used by split and to-markdown.',\n ),\n htmlReport: z\n .union([z.string(), z.array(z.string())])\n .optional()\n .describe(\n 'Input report HTML path for the merge action. Repeat the flag to merge multiple reports (e.g. --htmlReport ./a/index.html --htmlReport ./b.html).',\n ),\n outputDir: z\n .string()\n .optional()\n .describe(\n 'Output directory for generated report artifacts. For merge, defaults to the Midscene report directory.',\n ),\n outputName: z\n .string()\n .optional()\n .describe(\n 'Output report file/directory name (without .html) for the merge action. Defaults to an auto-generated name.',\n ),\n overwrite: z\n .union([z.boolean(), z.string()])\n .optional()\n .describe(\n 'Overwrite the existing merged report file if present (merge action only).',\n ),\n },\n handler: async (args) => {\n const {\n action = 'split',\n htmlPath,\n htmlReport,\n outputDir,\n outputName,\n overwrite,\n } = args as {\n action?: string;\n htmlPath?: string;\n htmlReport?: unknown;\n outputDir?: string;\n outputName?: string;\n overwrite?: unknown;\n };\n if (\n action !== 'split' &&\n action !== 'to-markdown' &&\n action !== 'merge-html'\n ) {\n throw new Error(\n `report-tool: unsupported --action value \"${action}\". Currently supported: split, to-markdown, merge-html`,\n );\n }\n\n if (action === 'merge-html') {\n const paths = normalizeHtmlReportArg(htmlReport);\n if (!paths || paths.length === 0) {\n throw new Error(\n 'report-tool: --htmlReport is required for action \"merge-html\". Repeat --htmlReport for each report (e.g. --htmlReport ./a/index.html --htmlReport ./b.html).',\n );\n }\n\n const overwriteFlag =\n overwrite === true || overwrite === 'true' || overwrite === '1';\n\n const result = mergeReportFiles({\n htmlPaths: paths,\n outputDir,\n outputName,\n overwrite: overwriteFlag,\n });\n\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Merged ${paths.length} report(s) into ${result.mergedReportPath}`,\n },\n ],\n };\n }\n\n if (!htmlPath) {\n throw new Error('report-tool: --htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('report-tool: --outputDir is required');\n }\n\n if (action === 'to-markdown') {\n const result = await reportFileToMarkdown({\n htmlPath,\n outputDir,\n });\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Markdown export completed. Generated ${result.markdownFiles.length} markdown file(s) and ${result.screenshotFiles.length} screenshot(s). Output path: ${outputDir}`,\n },\n ],\n };\n }\n\n const result = splitReportFile({\n htmlPath,\n outputDir,\n });\n\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Report split completed. Generated ${result.executionJsonFiles.length} execution JSON files and ${result.screenshotFiles.length} screenshots. Output path: ${outputDir}`,\n },\n ],\n };\n },\n};\n\nexport function createReportCliCommands(): ReportCliCommandEntry[] {\n return [\n {\n name: 'report-tool',\n def: reportCommandDefinition,\n },\n ];\n}\n"],"names":["writeAttachmentFromReport","attachment","opts","suggestedFileName","id","mimeType","absolutePath","path","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","existsSync","Error","copyFileSync","markdownFromReport","htmlPath","outputDir","screenshotsDir","mkdirSync","baseDump","executions","collectDedupedExecutions","mergedReport","ReportActionDump","result","reportToMarkdown","markdownFiles","writtenScreenshots","Set","mdPath","Array","f","resolveReportHtmlPath","normalizedPath","stats","statSync","indexHtmlPath","splitReportFile","options","resolvedHtmlPath","splitReportHtmlByExecution","reportFileToMarkdown","TEST_STATUS_VALUES","FAILING_TEST_STATUSES","readSourceTestStatus","recorded","streamReportDumpScripts","openTag","match","status","decodeURIComponent","s","deriveReportAttributesFromHtml","index","fallbackId","runnerDump","readReportRunnerDump","recordedStatus","testStatus","deriveCaseStatus","collectReportSummary","mergeReportFiles","htmlPaths","outputName","overwrite","resolvedPaths","p","tool","ReportMergingTool","mergedReportPath","normalizeHtmlReportArg","raw","trimmed","reportCommandDefinition","z","args","action","htmlReport","paths","overwriteFlag","createReportCliCommands"],"mappings":"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"}
@@ -5,8 +5,11 @@ import { getMidsceneRunSubDir } from "@midscene/shared/common";
5
5
  import { MIDSCENE_REPORT_QUIET, globalConfigManager } from "@midscene/shared/env";
6
6
  import { getDebug, logReport } from "@midscene/shared/logger";
7
7
  import { ifInBrowser, uuid } from "@midscene/shared/utils";
8
- import { DATA_SCREENSHOT_MODE_ATTR, generateAgentReportComment, generateDumpScriptTag, generateImageScriptTag, generateTestRunReportScriptTag, getBaseUrlFixScript } from "./dump/html-utils.mjs";
9
- import { imageRefFileExtension, normalizeStoredImageRef } from "./dump/image-reference.mjs";
8
+ import { ExecutionRecorder } from "./dump/execution-recorder.mjs";
9
+ import { DATA_SCREENSHOT_MODE_ATTR, generateAgentReportComment, generateDumpScriptTag, generateImageScriptTag, generateReportArtifactScriptTag, generateTestRunReportScriptTag, getBaseUrlFixScript } from "./dump/html-utils.mjs";
10
+ import { normalizeStoredImageRef } from "./dump/image-reference.mjs";
11
+ import { readReportArtifactManifest } from "./dump/report-artifact-reader.mjs";
12
+ import { ReportArtifactStore } from "./dump/report-artifact-store.mjs";
10
13
  import { compactReportDumps } from "./dump/report-dump-compactor.mjs";
11
14
  import { ReportImageStore } from "./dump/screenshot-store.mjs";
12
15
  import { buildAgentTestRunReportDump } from "./test-runner/reporting/agent-report.mjs";
@@ -57,7 +60,7 @@ class ReportGenerator {
57
60
  this.assertOpen();
58
61
  this.lastExecution = execution;
59
62
  this.lastReportMeta = reportMeta;
60
- this.executionsByKey.set(this.getExecutionCommentKey(execution), execution);
63
+ this.recorder.record(execution, reportMeta);
61
64
  this.mergeReportAttributes(attributes);
62
65
  this.writeQueue = this.writeQueue.then(async ()=>{
63
66
  await this.doWriteExecution(execution, reportMeta);
@@ -78,7 +81,8 @@ class ReportGenerator {
78
81
  this.flush(),
79
82
  ...this.sourcePublications
80
83
  ]);
81
- if (this.initialized) {
84
+ if (this.initialized) if (this.artifactStore) await this.artifactStore.finalize();
85
+ else {
82
86
  await this.appendAgentReportComment();
83
87
  try {
84
88
  await compactReportDumps(this.reportPath);
@@ -86,7 +90,7 @@ class ReportGenerator {
86
90
  warnReport(`Failed to compact report ${this.reportPath}; keeping the uncompressed report: ${String(error)}`);
87
91
  }
88
92
  }
89
- this.executionsByKey.clear();
93
+ this.recorder.clear();
90
94
  this.lastExecution = void 0;
91
95
  this.lastReportMeta = void 0;
92
96
  this.executionCommentKeyByObject = new WeakMap();
@@ -103,9 +107,7 @@ class ReportGenerator {
103
107
  this.assertOpen();
104
108
  this.writeQueue = this.writeQueue.then(async ()=>{
105
109
  const reportMeta = this.lastReportMeta ?? meta;
106
- const executions = [
107
- ...this.executionsByKey.values()
108
- ];
110
+ const executions = this.recorder.executions;
109
111
  const agentDump = new ReportActionDump({
110
112
  ...reportMeta,
111
113
  executions
@@ -123,6 +125,23 @@ class ReportGenerator {
123
125
  const bytes = await readFile(join(dirname(this.reportPath), ref.path));
124
126
  images.push(generateImageScriptTag(ref.id, `data:${ref.mimeType};base64,${bytes.toString('base64')}`));
125
127
  }
128
+ if (this.artifactStore) {
129
+ for (const execution of executions)await this.writeExecution(execution, this.wrapAsReportDump(execution, reportMeta));
130
+ if (!executions.length) {
131
+ await this.artifactStore.writeExecution('empty', sourceDump.serialized, this.getDumpScriptAttributes());
132
+ if (!this.initialized) {
133
+ const marker = generateReportArtifactScriptTag('report.json');
134
+ await writeFile(this.reportPath, `${getReportTpl()}${getBaseUrlFixScript()}\n${marker}`);
135
+ this.initialized = true;
136
+ }
137
+ }
138
+ await this.artifactStore.writeRunner(JSON.stringify(dump));
139
+ if (!this.firstWriteDone) {
140
+ this.firstWriteDone = true;
141
+ this.printReportPath();
142
+ }
143
+ return;
144
+ }
126
145
  const renderedDump = 'inline' === this.screenshotMode ? JSON.stringify(JSON.parse(sourceDump.serialized), (_key, value)=>{
127
146
  const ref = normalizeStoredImageRef(value);
128
147
  return ref ? {
@@ -160,30 +179,24 @@ ${generateAgentReportComment(agentDump)}`;
160
179
  enableSourceSnapshots() {
161
180
  this.screenshotStore.enableFileCopies();
162
181
  }
163
- async serializeSourceDump(frozenDump, screenshots) {
182
+ async serializeSourceDump(snapshot) {
164
183
  this.enableSourceSnapshots();
165
- const executions = frozenDump?.executions ?? [
166
- ...this.executionsByKey.values()
167
- ];
168
- const references = new Map();
169
- for (const screenshot of screenshots ?? executions.flatMap((execution)=>execution.collectScreenshots()))await this.screenshotStore.persist(screenshot);
170
- for (const execution of executions)for (const [url, ref] of (await this.persistReferenceImages(execution)))references.set(url, ref);
171
- const dump = frozenDump ?? new ReportActionDump({
172
- ...this.lastReportMeta ?? {
173
- sdkVersion: '',
174
- groupName: 'YAML execution',
175
- modelBriefs: []
176
- },
177
- executions
184
+ const captured = snapshot ?? this.recorder.capture(void 0, this.lastReportMeta ?? {
185
+ sdkVersion: '',
186
+ groupName: 'YAML execution',
187
+ modelBriefs: []
178
188
  });
189
+ for (const screenshot of captured.screenshots)await this.screenshotStore.persist(screenshot);
190
+ const references = new Map();
191
+ for (const url of captured.referenceImageUrls)references.set(url, await this.screenshotStore.persistReferenceImage(url));
179
192
  const images = new Map();
180
- const serialized = JSON.stringify(JSON.parse(dump.serializeWithReferenceImages(references)), (_key, value)=>{
193
+ const serialized = JSON.stringify(JSON.parse(captured.dump.serializeWithReferenceImages(references)), (_key, value)=>{
181
194
  const ref = normalizeStoredImageRef(value);
182
195
  if (!ref) return value;
183
196
  const stored = {
184
197
  ...ref,
185
198
  storage: 'file',
186
- path: `./screenshots/${ref.id}.${imageRefFileExtension(ref)}`
199
+ path: 'file' === ref.storage ? ref.path : this.screenshotStore.getFilePath(ref.id)
187
200
  };
188
201
  images.set(ref.id, stored);
189
202
  return stored;
@@ -196,22 +209,13 @@ ${generateAgentReportComment(agentDump)}`;
196
209
  async createSourceSnapshot(scopeId, executionIds) {
197
210
  this.assertOpen();
198
211
  if (!scopeId || basename(scopeId) !== scopeId) throw new Error('Invalid report scope ID');
199
- const capturedAt = new Date().toISOString();
200
- const selectedIds = executionIds ? new Set(executionIds) : void 0;
201
- const executions = [
202
- ...this.executionsByKey.values()
203
- ].filter((execution)=>!selectedIds || execution.id && selectedIds.has(execution.id));
204
- const screenshots = executions.flatMap((execution)=>execution.collectScreenshots());
205
- const frozen = ReportActionDump.fromSerializedString(new ReportActionDump({
206
- ...this.lastReportMeta ?? {
207
- sdkVersion: '',
208
- groupName: 'YAML execution',
209
- modelBriefs: []
210
- },
211
- executions
212
- }).serialize());
212
+ const snapshot = this.recorder.capture(executionIds, this.lastReportMeta ?? {
213
+ sdkVersion: '',
214
+ groupName: 'YAML execution',
215
+ modelBriefs: []
216
+ });
213
217
  const publication = (async ()=>{
214
- const { serialized } = await this.serializeSourceDump(frozen, screenshots);
218
+ const { serialized } = await this.serializeSourceDump(snapshot);
215
219
  const dumpPath = `${this.reportPath}.${scopeId}.${uuid()}.json`;
216
220
  mkdirSync(dirname(dumpPath), {
217
221
  recursive: true
@@ -224,7 +228,7 @@ ${generateAgentReportComment(agentDump)}`;
224
228
  dumpPath,
225
229
  collection: {
226
230
  status: 'complete',
227
- capturedAt
231
+ capturedAt: snapshot.capturedAt
228
232
  }
229
233
  };
230
234
  })();
@@ -243,7 +247,7 @@ ${generateAgentReportComment(agentDump)}`;
243
247
  async doWriteExecution(execution, reportMeta) {
244
248
  const singleDump = this.wrapAsReportDump(execution, reportMeta);
245
249
  const referenceImageRefs = await this.writeExecution(execution, singleDump);
246
- if (this.shouldPersistExecutionDump) await this.persistExecutionDumpToFile(execution, singleDump, referenceImageRefs);
250
+ if (this.shouldPersistExecutionDump && !this.artifactStore) await this.persistExecutionDumpToFile(execution, singleDump, referenceImageRefs);
247
251
  if (!this.firstWriteDone) {
248
252
  this.firstWriteDone = true;
249
253
  this.printReportPath();
@@ -255,6 +259,7 @@ ${generateAgentReportComment(agentDump)}`;
255
259
  }
256
260
  hydrateStateFromExistingReport() {
257
261
  if (!existsSync(this.reportPath)) return;
262
+ if (this.artifactStore && !readReportArtifactManifest(this.reportPath)) throw new Error('Cannot reuse an incompatible directory report. Choose a new reportFileName or regenerate the report.');
258
263
  this.initialized = true;
259
264
  if (!this.shouldPersistExecutionDump) return;
260
265
  if (!existsSync(this.executionDumpDir)) return;
@@ -287,13 +292,15 @@ ${generateAgentReportComment(agentDump)}`;
287
292
  });
288
293
  if (!this.initialized) {
289
294
  const baseUrlFix = 'directory' === this.screenshotMode ? getBaseUrlFixScript() : '';
290
- await writeFile(this.reportPath, `${getReportTpl()}${baseUrlFix}`);
295
+ const marker = this.artifactStore ? `\n${generateReportArtifactScriptTag('report.json')}` : '';
296
+ await writeFile(this.reportPath, `${getReportTpl()}${baseUrlFix}${marker}`);
291
297
  this.initialized = true;
292
298
  }
293
299
  for (const screenshot of execution.collectScreenshots())await this.screenshotStore.persist(screenshot);
294
300
  const referenceImageRefs = await this.persistReferenceImages(execution);
295
301
  const serialized = singleDump.serializeWithReferenceImages(referenceImageRefs);
296
- await appendFile(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
302
+ if (this.artifactStore) await this.artifactStore.writeExecution(this.getExecutionCommentKey(execution), serialized, this.getDumpScriptAttributes());
303
+ else await appendFile(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
297
304
  return referenceImageRefs;
298
305
  }
299
306
  async persistReferenceImages(execution) {
@@ -305,14 +312,14 @@ ${generateAgentReportComment(agentDump)}`;
305
312
  return refs;
306
313
  }
307
314
  async appendAgentReportComment() {
308
- if (this.agentCommentWritten || !this.lastReportMeta || 0 === this.executionsByKey.size) return;
315
+ if (this.agentCommentWritten || !this.lastReportMeta || 0 === this.recorder.executions.length) return;
309
316
  const reportDump = new ReportActionDump({
310
317
  sdkVersion: this.lastReportMeta.sdkVersion,
311
318
  groupName: this.lastReportMeta.groupName,
312
319
  groupDescription: this.lastReportMeta.groupDescription,
313
320
  modelBriefs: this.lastReportMeta.modelBriefs,
314
321
  deviceType: this.lastReportMeta.deviceType,
315
- executions: Array.from(this.executionsByKey.values())
322
+ executions: this.recorder.executions
316
323
  });
317
324
  await appendFile(this.reportPath, `\n${generateAgentReportComment(reportDump)}`);
318
325
  this.agentCommentWritten = true;
@@ -352,7 +359,7 @@ ${generateAgentReportComment(agentDump)}`;
352
359
  return ref ? {
353
360
  ...ref,
354
361
  storage: 'file',
355
- path: `../../screenshots/${ref.id}.${imageRefFileExtension(ref)}`
362
+ path: `../../${this.screenshotStore.getFilePath(ref.id).replace(/^\.\//, '')}`
356
363
  } : value;
357
364
  }, 2);
358
365
  await writeFile(filePath, serialized, 'utf-8');
@@ -370,7 +377,8 @@ ${generateAgentReportComment(agentDump)}`;
370
377
  _define_property(this, "initialized", false);
371
378
  _define_property(this, "lastExecution", void 0);
372
379
  _define_property(this, "lastReportMeta", void 0);
373
- _define_property(this, "executionsByKey", new Map());
380
+ _define_property(this, "recorder", new ExecutionRecorder());
381
+ _define_property(this, "artifactStore", void 0);
374
382
  _define_property(this, "executionCommentKeyByObject", new WeakMap());
375
383
  _define_property(this, "executionCommentKeyIndex", 0);
376
384
  _define_property(this, "reportAttributes", {});
@@ -383,6 +391,7 @@ ${generateAgentReportComment(agentDump)}`;
383
391
  this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;
384
392
  this.autoPrint = options.autoPrint ?? true;
385
393
  this.reportStreamId = uuid();
394
+ if ('directory' === this.screenshotMode) this.artifactStore = new ReportArtifactStore(dirname(this.reportPath), this.reportStreamId, 'agent', options.reuseExistingReport, basename(this.reportPath));
386
395
  this.screenshotStore = new ReportImageStore({
387
396
  mode: 'inline' === this.screenshotMode ? 'inline' : 'directory',
388
397
  reportPath: this.reportPath,
@@ -1 +1 @@
1
- {"version":3,"file":"report-generator.mjs","sources":["../../src/report-generator.ts"],"sourcesContent":["/*\n * PERF INVARIANT — DO NOT reintroduce sync fs APIs (writeFileSync /\n * appendFileSync) in this file's write paths. `ReportGenerator` runs on\n * the Electron main event loop during agent execution, and a single\n * progress tick appends a multi-MB ExecutionDump payload. Sync I/O here\n * blocked the loop for 20+ seconds per run, freezing IPC, scrcpy and\n * every renderer round-trip. Always use `fs/promises`. See commit\n * 6a25e05c and `report-generator-async-contract.test.ts`.\n */\nimport { existsSync, mkdirSync, readdirSync } from 'node:fs';\nimport {\n appendFile as appendFileAsync,\n readFile as readFileAsync,\n rename as renameAsync,\n unlink as unlinkAsync,\n writeFile as writeFileAsync,\n} from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n MIDSCENE_REPORT_QUIET,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { getDebug, logReport } from '@midscene/shared/logger';\nimport { ifInBrowser, uuid } from '@midscene/shared/utils';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n generateAgentReportComment,\n generateDumpScriptTag,\n generateImageScriptTag,\n generateTestRunReportScriptTag,\n getBaseUrlFixScript,\n} from './dump/html-utils';\nimport {\n imageRefFileExtension,\n normalizeStoredImageRef,\n} from './dump/image-reference';\nimport { compactReportDumps } from './dump/report-dump-compactor';\nimport { type ImageUrlRef, ReportImageStore } from './dump/screenshot-store';\nimport type { WorkflowReportSource } from './test-runner/engine/types';\nimport { buildAgentTestRunReportDump } from './test-runner/reporting/agent-report';\nimport type { RunReportInput } from './test-runner/reporting/types';\nimport {\n type ExecutionDump,\n ReportActionDump,\n type ReportAttributes,\n type ReportMeta,\n type ScreenshotMode,\n} from './types';\nimport { getReportTpl } from './utils';\n\nconst warnReport = getDebug('report-generator', { console: true });\nconst maxReportFilenameBytes = 255;\n\nexport interface IReportGenerator {\n /**\n * Write or update a single execution.\n * Each call appends a new dump script tag. The frontend deduplicates\n * executions with the same id/name, keeping only the last one.\n *\n * @param execution Current execution's full data\n * @param reportMeta Report-level metadata (groupName, sdkVersion, etc.)\n */\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void;\n\n /**\n * @deprecated Use onExecutionUpdate instead. Kept for backward compatibility.\n */\n onDumpUpdate?(dump: ReportActionDump): void;\n\n /**\n * Wait for all queued write operations to complete.\n */\n flush(): Promise<void>;\n enableSourceSnapshots?(): void;\n createSourceSnapshot?(\n scopeId: string,\n executionIds?: readonly string[],\n ): Promise<WorkflowReportSource | undefined>;\n\n /**\n * Finalize the report. Calls flush() internally.\n */\n finalize(): Promise<string | undefined>;\n\n getReportPath(): string | undefined;\n /** Publish a single root Runner snapshot into this Agent's existing report. */\n writeRunnerReport?(\n input: RunReportInput,\n scopeId: string,\n meta: ReportMeta,\n ): Promise<string | undefined>;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n onExecutionUpdate: () => {},\n flush: async () => {},\n finalize: async () => undefined,\n getReportPath: () => undefined,\n writeRunnerReport: async () => undefined,\n};\n\nexport function assertReportGenerationOptions(opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n}): void {\n if (opts.generateReport === false && opts.persistExecutionDump === true) {\n throw new Error(\n 'persistExecutionDump cannot be true when generateReport is false',\n );\n }\n}\n\nexport class ReportGenerator implements IReportGenerator {\n private reportPath: string;\n private screenshotMode: ScreenshotMode;\n private shouldPersistExecutionDump: boolean;\n private autoPrint: boolean;\n private firstWriteDone = false;\n private executionLogIndex = 0;\n private executionLogFileIndexByExecutionKey = new Map<string, number>();\n\n // Unique identifier for this report stream — used as data-group-id\n private readonly reportStreamId: string;\n\n // Tracks screenshots already written to disk (by id) to avoid duplicates\n private screenshotStore: ReportImageStore;\n private initialized = false;\n\n // Tracks the last execution + groupMeta for re-writing on finalize\n private lastExecution?: ExecutionDump;\n private lastReportMeta?: ReportMeta;\n private executionsByKey = new Map<string, ExecutionDump>();\n private executionCommentKeyByObject = new WeakMap<ExecutionDump, string>();\n private executionCommentKeyIndex = 0;\n private reportAttributes: Record<string, string> = {};\n private agentCommentWritten = false;\n\n // write queue for serial execution\n private writeQueue: Promise<void> = Promise.resolve();\n private finalization?: Promise<string | undefined>;\n private sourcePublications = new Set<Promise<unknown>>();\n\n constructor(options: {\n reportPath: string;\n screenshotMode: ScreenshotMode;\n persistExecutionDump?: boolean;\n autoPrint?: boolean;\n reuseExistingReport?: boolean;\n }) {\n this.reportPath = options.reportPath;\n this.screenshotMode = options.screenshotMode;\n this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;\n this.autoPrint = options.autoPrint ?? true;\n this.reportStreamId = uuid();\n this.screenshotStore = new ReportImageStore({\n mode: this.screenshotMode === 'inline' ? 'inline' : 'directory',\n reportPath: this.reportPath,\n screenshotsDir: join(dirname(this.reportPath), 'screenshots'),\n writeInlineImage: async (id, base64) => {\n await appendFileAsync(\n this.reportPath,\n `\\n${generateImageScriptTag(id, base64)}`,\n );\n },\n alsoWriteFileCopy: this.shouldPersistExecutionDump,\n reuseExistingReport: options.reuseExistingReport,\n });\n if (options.reuseExistingReport) {\n this.hydrateStateFromExistingReport();\n }\n }\n\n static create(\n reportFileName: string,\n opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n autoPrintReportMsg?: boolean;\n reuseExistingReport?: boolean;\n },\n ): IReportGenerator {\n assertReportGenerationOptions(opts);\n validateReportFileName(reportFileName, opts.outputFormat);\n if (opts.generateReport === false) return nullReportGenerator;\n\n // In browser environment, file system is not available\n if (ifInBrowser) return nullReportGenerator;\n\n const reportRootDir = getMidsceneRunSubDir('report');\n const outputDir = join(reportRootDir, reportFileName);\n const reportPath =\n opts.outputFormat === 'html-and-external-assets'\n ? join(outputDir, 'index.html')\n : join(reportRootDir, ensureHtmlFileName(reportFileName));\n return new ReportGenerator({\n reportPath,\n screenshotMode:\n opts.outputFormat === 'html-and-external-assets'\n ? 'directory'\n : 'inline',\n persistExecutionDump: opts.persistExecutionDump,\n autoPrint: opts.autoPrintReportMsg,\n reuseExistingReport: opts.reuseExistingReport,\n });\n }\n\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void {\n this.assertOpen();\n this.lastExecution = execution;\n this.lastReportMeta = reportMeta;\n this.executionsByKey.set(this.getExecutionCommentKey(execution), execution);\n this.mergeReportAttributes(attributes);\n this.writeQueue = this.writeQueue.then(async () => {\n await this.doWriteExecution(execution, reportMeta);\n });\n }\n\n async flush(): Promise<void> {\n await this.writeQueue;\n }\n\n async finalize(): Promise<string | undefined> {\n if (!this.finalization) {\n // Enqueue the final state before sealing the generator to new writes.\n if (this.lastExecution && this.lastReportMeta) {\n this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);\n }\n this.finalization = this.finishPublication();\n }\n return this.finalization;\n }\n\n private async finishPublication(): Promise<string | undefined> {\n // Snapshots freeze independently of the HTML queue, but both must be on\n // disk before their backing executions and diagnostic data are released.\n await Promise.all([this.flush(), ...this.sourcePublications]);\n if (this.initialized) {\n await this.appendAgentReportComment();\n try {\n await compactReportDumps(this.reportPath);\n } catch (error) {\n warnReport(\n `Failed to compact report ${this.reportPath}; keeping the uncompressed report: ${String(error)}`,\n );\n }\n }\n this.executionsByKey.clear();\n this.lastExecution = undefined;\n this.lastReportMeta = undefined;\n this.executionCommentKeyByObject = new WeakMap();\n this.executionLogFileIndexByExecutionKey.clear();\n return this.initialized ? this.reportPath : undefined;\n }\n\n private assertOpen(): void {\n if (this.finalization) throw new Error('The report has been finalized.');\n }\n\n getReportPath(): string | undefined {\n return this.reportPath;\n }\n\n async writeRunnerReport(\n input: RunReportInput,\n scopeId: string,\n meta: ReportMeta,\n ): Promise<string> {\n this.assertOpen();\n this.writeQueue = this.writeQueue.then(async () => {\n // This runs at YAML invocation completion, never on progress ticks. Keep\n // all file I/O asynchronous and publish atomically for an open viewer.\n const reportMeta = this.lastReportMeta ?? meta;\n const executions = [...this.executionsByKey.values()];\n const agentDump = new ReportActionDump({ ...reportMeta, executions });\n const reportId =\n this.reportAttributes['data-report-id'] ??\n this.reportAttributes['data-group-id'] ??\n this.reportStreamId;\n const dump = buildAgentTestRunReportDump(input, agentDump, {\n reportId,\n scopeId,\n sourcePath: this.reportPath,\n });\n for (const diagnostic of dump.diagnostics ?? [])\n warnReport(diagnostic.message);\n const sourceDump = await this.serializeSourceDump();\n const images: string[] = [];\n if (this.screenshotMode === 'inline') {\n for (const ref of sourceDump.images.values()) {\n const bytes = await readFileAsync(\n join(dirname(this.reportPath), ref.path!),\n );\n images.push(\n generateImageScriptTag(\n ref.id,\n `data:${ref.mimeType};base64,${bytes.toString('base64')}`,\n ),\n );\n }\n }\n // Rebuild from the owned dump and image index, never parse this HTML back.\n const renderedDump =\n this.screenshotMode === 'inline'\n ? JSON.stringify(JSON.parse(sourceDump.serialized), (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n return ref\n ? { ...ref, storage: 'inline', path: undefined }\n : value;\n })\n : sourceDump.serialized;\n const contents = `${getReportTpl()}${this.screenshotMode === 'directory' ? getBaseUrlFixScript() : ''}\n${generateDumpScriptTag(renderedDump, this.getDumpScriptAttributes())}\n${images.join('\\n')}\n${generateTestRunReportScriptTag(dump)}\n${generateAgentReportComment(agentDump)}`;\n mkdirSync(dirname(this.reportPath), { recursive: true });\n const temporaryPath = `${this.reportPath}.runner-${uuid()}.tmp`;\n try {\n await writeFileAsync(temporaryPath, contents);\n await renameAsync(temporaryPath, this.reportPath);\n } finally {\n await unlinkAsync(temporaryPath).catch((error) => {\n if (error.code !== 'ENOENT') throw error;\n });\n }\n this.initialized = true;\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n });\n await this.flush();\n return this.reportPath;\n }\n\n enableSourceSnapshots(): void {\n this.screenshotStore.enableFileCopies();\n }\n\n private async serializeSourceDump(\n frozenDump?: ReportActionDump,\n screenshots?: ReturnType<ExecutionDump['collectScreenshots']>,\n ) {\n this.enableSourceSnapshots();\n const executions = frozenDump?.executions ?? [\n ...this.executionsByKey.values(),\n ];\n const references = new Map<string, ImageUrlRef>();\n for (const screenshot of screenshots ??\n executions.flatMap((execution) => execution.collectScreenshots()))\n await this.screenshotStore.persist(screenshot);\n for (const execution of executions) {\n for (const [url, ref] of await this.persistReferenceImages(execution))\n references.set(url, ref);\n }\n const dump =\n frozenDump ??\n new ReportActionDump({\n ...(this.lastReportMeta ?? {\n sdkVersion: '',\n groupName: 'YAML execution',\n modelBriefs: [],\n }),\n executions,\n });\n const images = new Map<\n string,\n ReturnType<typeof normalizeStoredImageRef> & {}\n >();\n const serialized = JSON.stringify(\n JSON.parse(dump.serializeWithReferenceImages(references)),\n (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n if (!ref) return value;\n const stored = {\n ...ref,\n storage: 'file' as const,\n path: `./screenshots/${ref.id}.${imageRefFileExtension(ref)}`,\n };\n images.set(ref.id, stored);\n return stored;\n },\n );\n return { serialized, images };\n }\n\n async createSourceSnapshot(\n scopeId: string,\n executionIds?: readonly string[],\n ): Promise<WorkflowReportSource & { dumpPath: string }> {\n this.assertOpen();\n // Scope IDs originate in the kernel; never allow a filename to escape the report directory.\n if (!scopeId || basename(scopeId) !== scopeId)\n throw new Error('Invalid report scope ID');\n // Freeze before the first await. HTML writes and the live action can still\n // be pending; neither is a prerequisite for publishing this cutoff.\n const capturedAt = new Date().toISOString();\n const selectedIds = executionIds ? new Set(executionIds) : undefined;\n const executions = [...this.executionsByKey.values()].filter(\n (execution) =>\n !selectedIds || (execution.id && selectedIds.has(execution.id)),\n );\n const screenshots = executions.flatMap((execution) =>\n execution.collectScreenshots(),\n );\n const frozen = ReportActionDump.fromSerializedString(\n new ReportActionDump({\n ...(this.lastReportMeta ?? {\n sdkVersion: '',\n groupName: 'YAML execution',\n modelBriefs: [],\n }),\n executions,\n }).serialize(),\n );\n const publication = (async () => {\n const { serialized } = await this.serializeSourceDump(\n frozen,\n screenshots,\n );\n const dumpPath = `${this.reportPath}.${scopeId}.${uuid()}.json`;\n mkdirSync(dirname(dumpPath), { recursive: true });\n await writeFileAsync(dumpPath, serialized, { flag: 'wx' });\n return {\n sourcePath: this.reportPath,\n dumpPath,\n collection: { status: 'complete' as const, capturedAt },\n };\n })();\n this.sourcePublications.add(publication);\n try {\n return await publication;\n } finally {\n this.sourcePublications.delete(publication);\n }\n }\n\n private printReportPath(): void {\n if (!this.autoPrint || !this.reportPath) return;\n if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET))\n return;\n\n if (this.screenshotMode === 'directory') {\n logReport(\n `Midscene - report file updated: npx serve ${dirname(this.reportPath)}`,\n );\n } else {\n logReport(`Midscene - report file updated: ${this.reportPath}`);\n }\n }\n\n private async doWriteExecution(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): Promise<void> {\n const singleDump = this.wrapAsReportDump(execution, reportMeta);\n\n const referenceImageRefs = await this.writeExecution(execution, singleDump);\n\n if (this.shouldPersistExecutionDump) {\n await this.persistExecutionDumpToFile(\n execution,\n singleDump,\n referenceImageRefs,\n );\n }\n\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n }\n\n private mergeReportAttributes(attributes?: ReportAttributes): void {\n if (!attributes) {\n return;\n }\n\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined || value === null) {\n continue;\n }\n this.reportAttributes[key] = String(value);\n }\n }\n\n private hydrateStateFromExistingReport(): void {\n if (!existsSync(this.reportPath)) {\n return;\n }\n\n // Reuse existing report file and append new updates instead of rewriting.\n this.initialized = true;\n\n if (!this.shouldPersistExecutionDump) {\n return;\n }\n\n if (!existsSync(this.executionDumpDir)) return;\n const existingExecutionIndices = readdirSync(this.executionDumpDir)\n .map((name) => /^(\\d+)\\.execution\\.json$/.exec(name)?.[1])\n .filter((index): index is string => Boolean(index))\n .map((index) => Number.parseInt(index, 10))\n .filter((index) => Number.isFinite(index));\n\n if (existingExecutionIndices.length > 0) {\n this.executionLogIndex = Math.max(...existingExecutionIndices);\n }\n }\n\n private getDumpScriptAttributes(): Record<string, string> {\n return {\n 'data-group-id': this.reportStreamId,\n // Self-describe how this report file stores screenshots so consumers\n // (merge/delete) never have to guess the mode from the filesystem.\n [DATA_SCREENSHOT_MODE_ATTR]: this.screenshotMode,\n ...this.reportAttributes,\n };\n }\n\n /**\n * Wrap an ExecutionDump + ReportMeta into a single-execution ReportActionDump.\n */\n private wrapAsReportDump(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): ReportActionDump {\n return new ReportActionDump({\n sdkVersion: reportMeta.sdkVersion,\n groupName: reportMeta.groupName,\n groupDescription: reportMeta.groupDescription,\n modelBriefs: reportMeta.modelBriefs,\n deviceType: reportMeta.deviceType,\n executions: [execution],\n });\n }\n\n /**\n * Append assets and a dump tag without duplicating mode-specific workflows.\n * The frontend deduplicates executions with the same id/name (keeps last).\n * Duplicate dump JSON is acceptable; only screenshots are deduplicated.\n *\n * All writes go through `fs/promises` so they run on libuv's thread pool\n * rather than blocking the Node event loop. A long agent run previously\n * appended multi-MB dumps (screenshots + serialized tasks) per progress\n * tick on the main thread, starving IPC and UI for >10s per stall.\n */\n private async writeExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<Map<string, ImageUrlRef>> {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Initialize: write HTML template once\n if (!this.initialized) {\n const baseUrlFix =\n this.screenshotMode === 'directory' ? getBaseUrlFixScript() : '';\n await writeFileAsync(this.reportPath, `${getReportTpl()}${baseUrlFix}`);\n this.initialized = true;\n }\n\n // Append new screenshots (skip already-written ones)\n for (const screenshot of execution.collectScreenshots()) {\n await this.screenshotStore.persist(screenshot);\n }\n const referenceImageRefs = await this.persistReferenceImages(execution);\n\n // Append dump tag (always — frontend keeps only last per execution id)\n const serialized =\n singleDump.serializeWithReferenceImages(referenceImageRefs);\n await appendFileAsync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n return referenceImageRefs;\n }\n\n private async persistReferenceImages(\n execution: ExecutionDump,\n ): Promise<Map<string, ImageUrlRef>> {\n const refs = new Map<string, ImageUrlRef>();\n for (const imageUrl of execution.getReferenceImageUrls()) {\n const ref = await this.screenshotStore.persistReferenceImage(imageUrl);\n refs.set(imageUrl, ref);\n }\n return refs;\n }\n\n private async appendAgentReportComment(): Promise<void> {\n if (\n this.agentCommentWritten ||\n !this.lastReportMeta ||\n this.executionsByKey.size === 0\n ) {\n return;\n }\n\n const reportDump = new ReportActionDump({\n sdkVersion: this.lastReportMeta.sdkVersion,\n groupName: this.lastReportMeta.groupName,\n groupDescription: this.lastReportMeta.groupDescription,\n modelBriefs: this.lastReportMeta.modelBriefs,\n deviceType: this.lastReportMeta.deviceType,\n executions: Array.from(this.executionsByKey.values()),\n });\n await appendFileAsync(\n this.reportPath,\n `\\n${generateAgentReportComment(reportDump)}`,\n );\n this.agentCommentWritten = true;\n }\n\n private getExecutionLogKey(execution: ExecutionDump): string {\n if (!execution.id) {\n throw new Error(\n 'ReportGenerator: execution.id is required for persisting execution dumps',\n );\n }\n return `id:${execution.id}`;\n }\n\n private getExecutionCommentKey(execution: ExecutionDump): string {\n if (execution.id) {\n return `id:${execution.id}`;\n }\n\n const existingKey = this.executionCommentKeyByObject.get(execution);\n if (existingKey) {\n return existingKey;\n }\n\n const key = `no-id:${this.executionCommentKeyIndex++}`;\n this.executionCommentKeyByObject.set(execution, key);\n return key;\n }\n\n private get executionDumpDir(): string {\n // Inline HTML files share their parent directory. Each needs its own\n // numbered JSON namespace; external-asset reports already have one.\n return this.screenshotMode === 'inline'\n ? join(\n dirname(this.reportPath),\n '.execution-dumps',\n basename(this.reportPath),\n )\n : dirname(this.reportPath);\n }\n\n private async persistExecutionDumpToFile(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n referenceImageRefs: ReadonlyMap<string, ImageUrlRef>,\n ): Promise<void> {\n const dir = this.executionDumpDir;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const executionLogKey = this.getExecutionLogKey(execution);\n let fileIndex =\n this.executionLogFileIndexByExecutionKey.get(executionLogKey);\n if (!fileIndex) {\n this.executionLogIndex += 1;\n fileIndex = this.executionLogIndex;\n this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);\n }\n\n const fileName = `${fileIndex}.execution.json`;\n const filePath = join(dir, fileName);\n let serialized = singleDump.serializeWithReferenceImages(\n referenceImageRefs,\n 2,\n );\n if (this.screenshotMode === 'inline') {\n // JSON is two directories below the HTML. Point to the existing file\n // copies so it can be consumed without resolving inline HTML assets.\n serialized = JSON.stringify(\n JSON.parse(serialized),\n (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n return ref\n ? {\n ...ref,\n storage: 'file',\n path: `../../screenshots/${ref.id}.${imageRefFileExtension(ref)}`,\n }\n : value;\n },\n 2,\n );\n }\n await writeFileAsync(filePath, serialized, 'utf-8');\n }\n}\n\nfunction ensureHtmlFileName(reportFileName: string): string {\n return reportFileName.endsWith('.html')\n ? reportFileName\n : `${reportFileName}.html`;\n}\n\nfunction validateReportFileName(\n reportFileName: string,\n outputFormat?: 'single-html' | 'html-and-external-assets',\n): void {\n if (!reportFileName?.trim()) {\n throw new Error('reportFileName must be a non-empty string');\n }\n\n if (/[\\\\/]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName must not contain path separators (`/` or `\\\\\\\\`)',\n );\n }\n\n if (/[:*?\"<>|]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName contains illegal filename characters: : * ? \" < > |',\n );\n }\n\n const filenameComponent =\n outputFormat === 'html-and-external-assets'\n ? reportFileName\n : ensureHtmlFileName(reportFileName);\n const filenameBytes = new TextEncoder().encode(filenameComponent).byteLength;\n if (filenameBytes > maxReportFilenameBytes) {\n throw new Error(\n `reportFileName produces a filename component of ${filenameBytes} UTF-8 bytes; maximum is ${maxReportFilenameBytes}`,\n );\n }\n}\n"],"names":["warnReport","getDebug","maxReportFilenameBytes","nullReportGenerator","undefined","assertReportGenerationOptions","opts","Error","ReportGenerator","reportFileName","validateReportFileName","ifInBrowser","reportRootDir","getMidsceneRunSubDir","outputDir","join","reportPath","ensureHtmlFileName","execution","reportMeta","attributes","Promise","compactReportDumps","error","String","WeakMap","input","scopeId","meta","executions","agentDump","ReportActionDump","reportId","dump","buildAgentTestRunReportDump","diagnostic","sourceDump","images","ref","bytes","readFileAsync","dirname","generateImageScriptTag","renderedDump","JSON","_key","value","normalizeStoredImageRef","contents","getReportTpl","getBaseUrlFixScript","generateDumpScriptTag","generateTestRunReportScriptTag","generateAgentReportComment","mkdirSync","temporaryPath","uuid","writeFileAsync","renameAsync","unlinkAsync","frozenDump","screenshots","references","Map","screenshot","url","serialized","stored","imageRefFileExtension","executionIds","basename","capturedAt","Date","selectedIds","Set","frozen","publication","dumpPath","globalConfigManager","MIDSCENE_REPORT_QUIET","logReport","singleDump","referenceImageRefs","key","Object","existsSync","existingExecutionIndices","readdirSync","name","index","Boolean","Number","Math","DATA_SCREENSHOT_MODE_ATTR","dir","baseUrlFix","appendFileAsync","refs","imageUrl","reportDump","Array","existingKey","executionLogKey","fileIndex","fileName","filePath","options","ReportImageStore","id","base64","outputFormat","filenameComponent","filenameBytes","TextEncoder"],"mappings":"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"}
1
+ {"version":3,"file":"report-generator.mjs","sources":["../../src/report-generator.ts"],"sourcesContent":["/*\n * PERF INVARIANT — DO NOT reintroduce sync fs APIs (writeFileSync /\n * appendFileSync) in this file's write paths. `ReportGenerator` runs on\n * the Electron main event loop during agent execution, and a single\n * progress tick appends a multi-MB ExecutionDump payload. Sync I/O here\n * blocked the loop for 20+ seconds per run, freezing IPC, scrcpy and\n * every renderer round-trip. Always use `fs/promises`. See commit\n * 6a25e05c and `report-generator-async-contract.test.ts`.\n */\nimport { existsSync, mkdirSync, readdirSync } from 'node:fs';\nimport {\n appendFile as appendFileAsync,\n readFile as readFileAsync,\n rename as renameAsync,\n unlink as unlinkAsync,\n writeFile as writeFileAsync,\n} from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n MIDSCENE_REPORT_QUIET,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { getDebug, logReport } from '@midscene/shared/logger';\nimport { ifInBrowser, uuid } from '@midscene/shared/utils';\nimport {\n type ExecutionRecordSnapshot,\n ExecutionRecorder,\n} from './dump/execution-recorder';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n generateAgentReportComment,\n generateDumpScriptTag,\n generateImageScriptTag,\n generateReportArtifactScriptTag,\n generateTestRunReportScriptTag,\n getBaseUrlFixScript,\n} from './dump/html-utils';\nimport { normalizeStoredImageRef } from './dump/image-reference';\nimport { readReportArtifactManifest } from './dump/report-artifact-reader';\nimport { ReportArtifactStore } from './dump/report-artifact-store';\nimport { compactReportDumps } from './dump/report-dump-compactor';\nimport { type ImageUrlRef, ReportImageStore } from './dump/screenshot-store';\nimport type { WorkflowReportSource } from './test-runner/engine/types';\nimport { buildAgentTestRunReportDump } from './test-runner/reporting/agent-report';\nimport type { RunReportInput } from './test-runner/reporting/types';\nimport {\n type ExecutionDump,\n ReportActionDump,\n type ReportAttributes,\n type ReportMeta,\n type ScreenshotMode,\n} from './types';\nimport { getReportTpl } from './utils';\n\nconst warnReport = getDebug('report-generator', { console: true });\nconst maxReportFilenameBytes = 255;\n\nexport interface IReportGenerator {\n /**\n * Write or update a single execution.\n * Inline reports append dump tags; directory reports replace the execution's\n * data file. Both retain the latest state for each stable execution ID.\n *\n * @param execution Current execution's full data\n * @param reportMeta Report-level metadata (groupName, sdkVersion, etc.)\n */\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void;\n\n /**\n * @deprecated Use onExecutionUpdate instead. Kept for backward compatibility.\n */\n onDumpUpdate?(dump: ReportActionDump): void;\n\n /**\n * Wait for all queued write operations to complete.\n */\n flush(): Promise<void>;\n enableSourceSnapshots?(): void;\n createSourceSnapshot?(\n scopeId: string,\n executionIds?: readonly string[],\n ): Promise<WorkflowReportSource | undefined>;\n\n /**\n * Finalize the report. Calls flush() internally.\n */\n finalize(): Promise<string | undefined>;\n\n getReportPath(): string | undefined;\n /** Publish a single root Runner snapshot into this Agent's existing report. */\n writeRunnerReport?(\n input: RunReportInput,\n scopeId: string,\n meta: ReportMeta,\n ): Promise<string | undefined>;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n onExecutionUpdate: () => {},\n flush: async () => {},\n finalize: async () => undefined,\n getReportPath: () => undefined,\n writeRunnerReport: async () => undefined,\n};\n\nexport function assertReportGenerationOptions(opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n}): void {\n if (opts.generateReport === false && opts.persistExecutionDump === true) {\n throw new Error(\n 'persistExecutionDump cannot be true when generateReport is false',\n );\n }\n}\n\nexport class ReportGenerator implements IReportGenerator {\n private reportPath: string;\n private screenshotMode: ScreenshotMode;\n private shouldPersistExecutionDump: boolean;\n private autoPrint: boolean;\n private firstWriteDone = false;\n private executionLogIndex = 0;\n private executionLogFileIndexByExecutionKey = new Map<string, number>();\n\n // Unique identifier for this report stream — used as data-group-id\n private readonly reportStreamId: string;\n\n // Tracks screenshots already written to disk (by id) to avoid duplicates\n private screenshotStore: ReportImageStore;\n private initialized = false;\n\n // Tracks the last execution + groupMeta for re-writing on finalize\n private lastExecution?: ExecutionDump;\n private lastReportMeta?: ReportMeta;\n private readonly recorder = new ExecutionRecorder();\n private readonly artifactStore?: ReportArtifactStore;\n private executionCommentKeyByObject = new WeakMap<ExecutionDump, string>();\n private executionCommentKeyIndex = 0;\n private reportAttributes: Record<string, string> = {};\n private agentCommentWritten = false;\n\n // write queue for serial execution\n private writeQueue: Promise<void> = Promise.resolve();\n private finalization?: Promise<string | undefined>;\n private sourcePublications = new Set<Promise<unknown>>();\n\n constructor(options: {\n reportPath: string;\n screenshotMode: ScreenshotMode;\n persistExecutionDump?: boolean;\n autoPrint?: boolean;\n reuseExistingReport?: boolean;\n }) {\n this.reportPath = options.reportPath;\n this.screenshotMode = options.screenshotMode;\n this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;\n this.autoPrint = options.autoPrint ?? true;\n this.reportStreamId = uuid();\n if (this.screenshotMode === 'directory') {\n this.artifactStore = new ReportArtifactStore(\n dirname(this.reportPath),\n this.reportStreamId,\n 'agent',\n options.reuseExistingReport,\n basename(this.reportPath),\n );\n }\n this.screenshotStore = new ReportImageStore({\n mode: this.screenshotMode === 'inline' ? 'inline' : 'directory',\n reportPath: this.reportPath,\n screenshotsDir: join(dirname(this.reportPath), 'screenshots'),\n writeInlineImage: async (id, base64) => {\n await appendFileAsync(\n this.reportPath,\n `\\n${generateImageScriptTag(id, base64)}`,\n );\n },\n alsoWriteFileCopy: this.shouldPersistExecutionDump,\n reuseExistingReport: options.reuseExistingReport,\n });\n if (options.reuseExistingReport) {\n this.hydrateStateFromExistingReport();\n }\n }\n\n static create(\n reportFileName: string,\n opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n autoPrintReportMsg?: boolean;\n reuseExistingReport?: boolean;\n },\n ): IReportGenerator {\n assertReportGenerationOptions(opts);\n validateReportFileName(reportFileName, opts.outputFormat);\n if (opts.generateReport === false) return nullReportGenerator;\n\n // In browser environment, file system is not available\n if (ifInBrowser) return nullReportGenerator;\n\n const reportRootDir = getMidsceneRunSubDir('report');\n const outputDir = join(reportRootDir, reportFileName);\n const reportPath =\n opts.outputFormat === 'html-and-external-assets'\n ? join(outputDir, 'index.html')\n : join(reportRootDir, ensureHtmlFileName(reportFileName));\n return new ReportGenerator({\n reportPath,\n screenshotMode:\n opts.outputFormat === 'html-and-external-assets'\n ? 'directory'\n : 'inline',\n persistExecutionDump: opts.persistExecutionDump,\n autoPrint: opts.autoPrintReportMsg,\n reuseExistingReport: opts.reuseExistingReport,\n });\n }\n\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void {\n this.assertOpen();\n this.lastExecution = execution;\n this.lastReportMeta = reportMeta;\n this.recorder.record(execution, reportMeta);\n this.mergeReportAttributes(attributes);\n this.writeQueue = this.writeQueue.then(async () => {\n await this.doWriteExecution(execution, reportMeta);\n });\n }\n\n async flush(): Promise<void> {\n await this.writeQueue;\n }\n\n async finalize(): Promise<string | undefined> {\n if (!this.finalization) {\n // Enqueue the final state before sealing the generator to new writes.\n if (this.lastExecution && this.lastReportMeta) {\n this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);\n }\n this.finalization = this.finishPublication();\n }\n return this.finalization;\n }\n\n private async finishPublication(): Promise<string | undefined> {\n // Snapshots freeze independently of the HTML queue, but both must be on\n // disk before their backing executions and diagnostic data are released.\n await Promise.all([this.flush(), ...this.sourcePublications]);\n if (this.initialized) {\n if (this.artifactStore) {\n await this.artifactStore.finalize();\n } else {\n await this.appendAgentReportComment();\n try {\n await compactReportDumps(this.reportPath);\n } catch (error) {\n warnReport(\n `Failed to compact report ${this.reportPath}; keeping the uncompressed report: ${String(error)}`,\n );\n }\n }\n }\n this.recorder.clear();\n this.lastExecution = undefined;\n this.lastReportMeta = undefined;\n this.executionCommentKeyByObject = new WeakMap();\n this.executionLogFileIndexByExecutionKey.clear();\n return this.initialized ? this.reportPath : undefined;\n }\n\n private assertOpen(): void {\n if (this.finalization) throw new Error('The report has been finalized.');\n }\n\n getReportPath(): string | undefined {\n return this.reportPath;\n }\n\n async writeRunnerReport(\n input: RunReportInput,\n scopeId: string,\n meta: ReportMeta,\n ): Promise<string> {\n this.assertOpen();\n this.writeQueue = this.writeQueue.then(async () => {\n // This runs at YAML invocation completion, never on progress ticks. Keep\n // all file I/O asynchronous and publish atomically for an open viewer.\n const reportMeta = this.lastReportMeta ?? meta;\n const executions = this.recorder.executions;\n const agentDump = new ReportActionDump({ ...reportMeta, executions });\n const reportId =\n this.reportAttributes['data-report-id'] ??\n this.reportAttributes['data-group-id'] ??\n this.reportStreamId;\n const dump = buildAgentTestRunReportDump(input, agentDump, {\n reportId,\n scopeId,\n sourcePath: this.reportPath,\n });\n for (const diagnostic of dump.diagnostics ?? [])\n warnReport(diagnostic.message);\n const sourceDump = await this.serializeSourceDump();\n const images: string[] = [];\n if (this.screenshotMode === 'inline') {\n for (const ref of sourceDump.images.values()) {\n const bytes = await readFileAsync(\n join(dirname(this.reportPath), ref.path!),\n );\n images.push(\n generateImageScriptTag(\n ref.id,\n `data:${ref.mimeType};base64,${bytes.toString('base64')}`,\n ),\n );\n }\n }\n if (this.artifactStore) {\n for (const execution of executions) {\n await this.writeExecution(\n execution,\n this.wrapAsReportDump(execution, reportMeta),\n );\n }\n if (!executions.length) {\n // An empty execution group still carries setup/collection diagnostics.\n await this.artifactStore.writeExecution(\n 'empty',\n sourceDump.serialized,\n this.getDumpScriptAttributes(),\n );\n if (!this.initialized) {\n const marker = generateReportArtifactScriptTag('report.json');\n await writeFileAsync(\n this.reportPath,\n `${getReportTpl()}${getBaseUrlFixScript()}\\n${marker}`,\n );\n this.initialized = true;\n }\n }\n await this.artifactStore.writeRunner(JSON.stringify(dump));\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n return;\n }\n // Rebuild from the owned dump and image index, never parse this HTML back.\n const renderedDump =\n this.screenshotMode === 'inline'\n ? JSON.stringify(JSON.parse(sourceDump.serialized), (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n return ref\n ? { ...ref, storage: 'inline', path: undefined }\n : value;\n })\n : sourceDump.serialized;\n const contents = `${getReportTpl()}${this.screenshotMode === 'directory' ? getBaseUrlFixScript() : ''}\n${generateDumpScriptTag(renderedDump, this.getDumpScriptAttributes())}\n${images.join('\\n')}\n${generateTestRunReportScriptTag(dump)}\n${generateAgentReportComment(agentDump)}`;\n mkdirSync(dirname(this.reportPath), { recursive: true });\n const temporaryPath = `${this.reportPath}.runner-${uuid()}.tmp`;\n try {\n await writeFileAsync(temporaryPath, contents);\n await renameAsync(temporaryPath, this.reportPath);\n } finally {\n await unlinkAsync(temporaryPath).catch((error) => {\n if (error.code !== 'ENOENT') throw error;\n });\n }\n this.initialized = true;\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n });\n await this.flush();\n return this.reportPath;\n }\n\n enableSourceSnapshots(): void {\n this.screenshotStore.enableFileCopies();\n }\n\n private async serializeSourceDump(snapshot?: ExecutionRecordSnapshot) {\n this.enableSourceSnapshots();\n const captured =\n snapshot ??\n this.recorder.capture(\n undefined,\n this.lastReportMeta ?? {\n sdkVersion: '',\n groupName: 'YAML execution',\n modelBriefs: [],\n },\n );\n for (const screenshot of captured.screenshots)\n await this.screenshotStore.persist(screenshot);\n const references = new Map<string, ImageUrlRef>();\n for (const url of captured.referenceImageUrls) {\n references.set(\n url,\n await this.screenshotStore.persistReferenceImage(url),\n );\n }\n const images = new Map<\n string,\n ReturnType<typeof normalizeStoredImageRef> & {}\n >();\n const serialized = JSON.stringify(\n JSON.parse(captured.dump.serializeWithReferenceImages(references)),\n (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n if (!ref) return value;\n const stored = {\n ...ref,\n storage: 'file' as const,\n path:\n ref.storage === 'file'\n ? ref.path\n : this.screenshotStore.getFilePath(ref.id),\n };\n images.set(ref.id, stored);\n return stored;\n },\n );\n return { serialized, images };\n }\n\n async createSourceSnapshot(\n scopeId: string,\n executionIds?: readonly string[],\n ): Promise<WorkflowReportSource & { dumpPath: string }> {\n this.assertOpen();\n if (!scopeId || basename(scopeId) !== scopeId)\n throw new Error('Invalid report scope ID');\n const snapshot = this.recorder.capture(\n executionIds,\n this.lastReportMeta ?? {\n sdkVersion: '',\n groupName: 'YAML execution',\n modelBriefs: [],\n },\n );\n const publication = (async () => {\n const { serialized } = await this.serializeSourceDump(snapshot);\n const dumpPath = `${this.reportPath}.${scopeId}.${uuid()}.json`;\n mkdirSync(dirname(dumpPath), { recursive: true });\n await writeFileAsync(dumpPath, serialized, { flag: 'wx' });\n return {\n sourcePath: this.reportPath,\n dumpPath,\n collection: {\n status: 'complete' as const,\n capturedAt: snapshot.capturedAt,\n },\n };\n })();\n this.sourcePublications.add(publication);\n try {\n return await publication;\n } finally {\n this.sourcePublications.delete(publication);\n }\n }\n\n private printReportPath(): void {\n if (!this.autoPrint || !this.reportPath) return;\n if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET))\n return;\n\n if (this.screenshotMode === 'directory') {\n logReport(\n `Midscene - report file updated: npx serve ${dirname(this.reportPath)}`,\n );\n } else {\n logReport(`Midscene - report file updated: ${this.reportPath}`);\n }\n }\n\n private async doWriteExecution(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): Promise<void> {\n const singleDump = this.wrapAsReportDump(execution, reportMeta);\n\n const referenceImageRefs = await this.writeExecution(execution, singleDump);\n\n if (this.shouldPersistExecutionDump && !this.artifactStore) {\n await this.persistExecutionDumpToFile(\n execution,\n singleDump,\n referenceImageRefs,\n );\n }\n\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n }\n\n private mergeReportAttributes(attributes?: ReportAttributes): void {\n if (!attributes) {\n return;\n }\n\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined || value === null) {\n continue;\n }\n this.reportAttributes[key] = String(value);\n }\n }\n\n private hydrateStateFromExistingReport(): void {\n if (!existsSync(this.reportPath)) {\n return;\n }\n\n if (this.artifactStore && !readReportArtifactManifest(this.reportPath)) {\n throw new Error(\n 'Cannot reuse an incompatible directory report. Choose a new reportFileName or regenerate the report.',\n );\n }\n\n // Reuse existing report file and append new updates instead of rewriting.\n this.initialized = true;\n\n if (!this.shouldPersistExecutionDump) {\n return;\n }\n\n if (!existsSync(this.executionDumpDir)) return;\n const existingExecutionIndices = readdirSync(this.executionDumpDir)\n .map((name) => /^(\\d+)\\.execution\\.json$/.exec(name)?.[1])\n .filter((index): index is string => Boolean(index))\n .map((index) => Number.parseInt(index, 10))\n .filter((index) => Number.isFinite(index));\n\n if (existingExecutionIndices.length > 0) {\n this.executionLogIndex = Math.max(...existingExecutionIndices);\n }\n }\n\n private getDumpScriptAttributes(): Record<string, string> {\n return {\n 'data-group-id': this.reportStreamId,\n // Self-describe how this report file stores screenshots so consumers\n // (merge/delete) never have to guess the mode from the filesystem.\n [DATA_SCREENSHOT_MODE_ATTR]: this.screenshotMode,\n ...this.reportAttributes,\n };\n }\n\n /**\n * Wrap an ExecutionDump + ReportMeta into a single-execution ReportActionDump.\n */\n private wrapAsReportDump(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): ReportActionDump {\n return new ReportActionDump({\n sdkVersion: reportMeta.sdkVersion,\n groupName: reportMeta.groupName,\n groupDescription: reportMeta.groupDescription,\n modelBriefs: reportMeta.modelBriefs,\n deviceType: reportMeta.deviceType,\n executions: [execution],\n });\n }\n\n /**\n * Append assets and a dump tag without duplicating mode-specific workflows.\n * The frontend deduplicates executions with the same id/name (keeps last).\n * Duplicate dump JSON is acceptable; only screenshots are deduplicated.\n *\n * All writes go through `fs/promises` so they run on libuv's thread pool\n * rather than blocking the Node event loop. A long agent run previously\n * appended multi-MB dumps (screenshots + serialized tasks) per progress\n * tick on the main thread, starving IPC and UI for >10s per stall.\n */\n private async writeExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<Map<string, ImageUrlRef>> {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Initialize: write HTML template once\n if (!this.initialized) {\n const baseUrlFix =\n this.screenshotMode === 'directory' ? getBaseUrlFixScript() : '';\n const marker = this.artifactStore\n ? `\\n${generateReportArtifactScriptTag('report.json')}`\n : '';\n await writeFileAsync(\n this.reportPath,\n `${getReportTpl()}${baseUrlFix}${marker}`,\n );\n this.initialized = true;\n }\n\n // Append new screenshots (skip already-written ones)\n for (const screenshot of execution.collectScreenshots()) {\n await this.screenshotStore.persist(screenshot);\n }\n const referenceImageRefs = await this.persistReferenceImages(execution);\n\n // Publish the latest facts using the selected storage format.\n const serialized =\n singleDump.serializeWithReferenceImages(referenceImageRefs);\n if (this.artifactStore) {\n await this.artifactStore.writeExecution(\n this.getExecutionCommentKey(execution),\n serialized,\n this.getDumpScriptAttributes(),\n );\n } else {\n await appendFileAsync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n return referenceImageRefs;\n }\n\n private async persistReferenceImages(\n execution: ExecutionDump,\n ): Promise<Map<string, ImageUrlRef>> {\n const refs = new Map<string, ImageUrlRef>();\n for (const imageUrl of execution.getReferenceImageUrls()) {\n const ref = await this.screenshotStore.persistReferenceImage(imageUrl);\n refs.set(imageUrl, ref);\n }\n return refs;\n }\n\n private async appendAgentReportComment(): Promise<void> {\n if (\n this.agentCommentWritten ||\n !this.lastReportMeta ||\n this.recorder.executions.length === 0\n ) {\n return;\n }\n\n const reportDump = new ReportActionDump({\n sdkVersion: this.lastReportMeta.sdkVersion,\n groupName: this.lastReportMeta.groupName,\n groupDescription: this.lastReportMeta.groupDescription,\n modelBriefs: this.lastReportMeta.modelBriefs,\n deviceType: this.lastReportMeta.deviceType,\n executions: this.recorder.executions,\n });\n await appendFileAsync(\n this.reportPath,\n `\\n${generateAgentReportComment(reportDump)}`,\n );\n this.agentCommentWritten = true;\n }\n\n private getExecutionLogKey(execution: ExecutionDump): string {\n if (!execution.id) {\n throw new Error(\n 'ReportGenerator: execution.id is required for persisting execution dumps',\n );\n }\n return `id:${execution.id}`;\n }\n\n private getExecutionCommentKey(execution: ExecutionDump): string {\n if (execution.id) {\n return `id:${execution.id}`;\n }\n\n const existingKey = this.executionCommentKeyByObject.get(execution);\n if (existingKey) {\n return existingKey;\n }\n\n const key = `no-id:${this.executionCommentKeyIndex++}`;\n this.executionCommentKeyByObject.set(execution, key);\n return key;\n }\n\n private get executionDumpDir(): string {\n // Inline HTML files share their parent directory. Each needs its own\n // numbered JSON namespace; external-asset reports already have one.\n return this.screenshotMode === 'inline'\n ? join(\n dirname(this.reportPath),\n '.execution-dumps',\n basename(this.reportPath),\n )\n : dirname(this.reportPath);\n }\n\n private async persistExecutionDumpToFile(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n referenceImageRefs: ReadonlyMap<string, ImageUrlRef>,\n ): Promise<void> {\n const dir = this.executionDumpDir;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const executionLogKey = this.getExecutionLogKey(execution);\n let fileIndex =\n this.executionLogFileIndexByExecutionKey.get(executionLogKey);\n if (!fileIndex) {\n this.executionLogIndex += 1;\n fileIndex = this.executionLogIndex;\n this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);\n }\n\n const fileName = `${fileIndex}.execution.json`;\n const filePath = join(dir, fileName);\n let serialized = singleDump.serializeWithReferenceImages(\n referenceImageRefs,\n 2,\n );\n if (this.screenshotMode === 'inline') {\n // JSON is two directories below the HTML. Point to the existing file\n // copies so it can be consumed without resolving inline HTML assets.\n serialized = JSON.stringify(\n JSON.parse(serialized),\n (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n return ref\n ? {\n ...ref,\n storage: 'file',\n path: `../../${this.screenshotStore.getFilePath(ref.id).replace(/^\\.\\//, '')}`,\n }\n : value;\n },\n 2,\n );\n }\n await writeFileAsync(filePath, serialized, 'utf-8');\n }\n}\n\nfunction ensureHtmlFileName(reportFileName: string): string {\n return reportFileName.endsWith('.html')\n ? reportFileName\n : `${reportFileName}.html`;\n}\n\nfunction validateReportFileName(\n reportFileName: string,\n outputFormat?: 'single-html' | 'html-and-external-assets',\n): void {\n if (!reportFileName?.trim()) {\n throw new Error('reportFileName must be a non-empty string');\n }\n\n if (/[\\\\/]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName must not contain path separators (`/` or `\\\\\\\\`)',\n );\n }\n\n if (/[:*?\"<>|]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName contains illegal filename characters: : * ? \" < > |',\n );\n }\n\n const filenameComponent =\n outputFormat === 'html-and-external-assets'\n ? reportFileName\n : ensureHtmlFileName(reportFileName);\n const filenameBytes = new TextEncoder().encode(filenameComponent).byteLength;\n if (filenameBytes > maxReportFilenameBytes) {\n throw new Error(\n `reportFileName produces a filename component of ${filenameBytes} UTF-8 bytes; maximum is ${maxReportFilenameBytes}`,\n );\n }\n}\n"],"names":["warnReport","getDebug","maxReportFilenameBytes","nullReportGenerator","undefined","assertReportGenerationOptions","opts","Error","ReportGenerator","reportFileName","validateReportFileName","ifInBrowser","reportRootDir","getMidsceneRunSubDir","outputDir","join","reportPath","ensureHtmlFileName","execution","reportMeta","attributes","Promise","compactReportDumps","error","String","WeakMap","input","scopeId","meta","executions","agentDump","ReportActionDump","reportId","dump","buildAgentTestRunReportDump","diagnostic","sourceDump","images","ref","bytes","readFileAsync","dirname","generateImageScriptTag","marker","generateReportArtifactScriptTag","writeFileAsync","getReportTpl","getBaseUrlFixScript","JSON","renderedDump","_key","value","normalizeStoredImageRef","contents","generateDumpScriptTag","generateTestRunReportScriptTag","generateAgentReportComment","mkdirSync","temporaryPath","uuid","renameAsync","unlinkAsync","snapshot","captured","screenshot","references","Map","url","serialized","stored","executionIds","basename","publication","dumpPath","globalConfigManager","MIDSCENE_REPORT_QUIET","logReport","singleDump","referenceImageRefs","key","Object","existsSync","readReportArtifactManifest","existingExecutionIndices","readdirSync","name","index","Boolean","Number","Math","DATA_SCREENSHOT_MODE_ATTR","dir","baseUrlFix","appendFileAsync","refs","imageUrl","reportDump","existingKey","executionLogKey","fileIndex","fileName","filePath","options","ExecutionRecorder","Set","ReportArtifactStore","ReportImageStore","id","base64","outputFormat","filenameComponent","filenameBytes","TextEncoder"],"mappings":"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"}