@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.
- package/dist/es/agent/task-builder.mjs +1 -2
- package/dist/es/agent/task-builder.mjs.map +1 -1
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/dump/execution-recorder.mjs +70 -0
- package/dist/es/dump/execution-recorder.mjs.map +1 -0
- package/dist/es/dump/html-utils.mjs +51 -1
- package/dist/es/dump/html-utils.mjs.map +1 -1
- package/dist/es/dump/report-artifact-reader.mjs +85 -0
- package/dist/es/dump/report-artifact-reader.mjs.map +1 -0
- package/dist/es/dump/report-artifact-store.mjs +164 -0
- package/dist/es/dump/report-artifact-store.mjs.map +1 -0
- package/dist/es/dump/screenshot-store.mjs +40 -15
- package/dist/es/dump/screenshot-store.mjs.map +1 -1
- package/dist/es/dump/test-report-images.mjs +120 -0
- package/dist/es/dump/test-report-images.mjs.map +1 -0
- package/dist/es/report-cli.mjs +8 -7
- package/dist/es/report-cli.mjs.map +1 -1
- package/dist/es/report-generator.mjs +56 -47
- package/dist/es/report-generator.mjs.map +1 -1
- package/dist/es/report-html-template.mjs +1 -1
- package/dist/es/report.mjs +29 -55
- package/dist/es/report.mjs.map +1 -1
- package/dist/es/test-run-report.mjs.map +1 -1
- package/dist/es/utils.mjs +1 -1
- package/dist/lib/agent/task-builder.js +1 -2
- package/dist/lib/agent/task-builder.js.map +1 -1
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/dump/execution-recorder.js +104 -0
- package/dist/lib/dump/execution-recorder.js.map +1 -0
- package/dist/lib/dump/html-utils.js +67 -5
- package/dist/lib/dump/html-utils.js.map +1 -1
- package/dist/lib/dump/report-artifact-reader.js +134 -0
- package/dist/lib/dump/report-artifact-reader.js.map +1 -0
- package/dist/lib/dump/report-artifact-store.js +204 -0
- package/dist/lib/dump/report-artifact-store.js.map +1 -0
- package/dist/lib/dump/screenshot-store.js +42 -14
- package/dist/lib/dump/screenshot-store.js.map +1 -1
- package/dist/lib/dump/test-report-images.js +154 -0
- package/dist/lib/dump/test-report-images.js.map +1 -0
- package/dist/lib/report-cli.js +8 -7
- package/dist/lib/report-cli.js.map +1 -1
- package/dist/lib/report-generator.js +54 -45
- package/dist/lib/report-generator.js.map +1 -1
- package/dist/lib/report-html-template.js +1 -1
- package/dist/lib/report.js +54 -56
- package/dist/lib/report.js.map +1 -1
- package/dist/lib/test-run-report.js.map +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/types/agent/task-builder.d.ts.map +1 -1
- package/dist/types/dump/execution-recorder.d.ts +23 -0
- package/dist/types/dump/execution-recorder.d.ts.map +1 -0
- package/dist/types/dump/html-utils.d.ts +9 -0
- package/dist/types/dump/html-utils.d.ts.map +1 -1
- package/dist/types/dump/report-artifact-reader.d.ts +19 -0
- package/dist/types/dump/report-artifact-reader.d.ts.map +1 -0
- package/dist/types/dump/report-artifact-store.d.ts +22 -0
- package/dist/types/dump/report-artifact-store.d.ts.map +1 -0
- package/dist/types/dump/screenshot-store.d.ts +6 -0
- package/dist/types/dump/screenshot-store.d.ts.map +1 -1
- package/dist/types/dump/test-report-images.d.ts +14 -0
- package/dist/types/dump/test-report-images.d.ts.map +1 -0
- package/dist/types/report-cli.d.ts.map +1 -1
- package/dist/types/report-generator.d.ts +4 -3
- package/dist/types/report-generator.d.ts.map +1 -1
- package/dist/types/report.d.ts +3 -0
- package/dist/types/report.d.ts.map +1 -1
- package/dist/types/test-run-report.d.ts +2 -0
- package/dist/types/test-run-report.d.ts.map +1 -1
- 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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"}
|
package/dist/es/report-cli.mjs
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
103
|
+
streamReportDumpScripts(htmlPath, ({ openTag })=>{
|
|
104
104
|
const match = openTag.match(/playwright_test_status="([^"]*)"/);
|
|
105
|
-
if (!match)
|
|
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 =
|
|
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":";;;;;;;;;;AAqEA,SAASA,0BACPC,UAA8B,EAC9BC,IAIC;IAED,MAAM,EAAEC,iBAAiB,EAAEC,EAAE,EAAEC,QAAQ,EAAE,GAAGJ;IAC5C,IAAIC,KAAK,YAAY,CAAC,GAAG,CAACC,oBAAoB;IAE9C,MAAMG,eAAeC,KAAUL,KAAK,cAAc,EAAEC;IAEpD,MAAMK,WAAWC,wBAAwBR,WAAW,SAAS,IAAI,MAAM;QACrE,YAAYC,KAAK,QAAQ;QACzB,YAAYE;QACZ,kBAAmBC,YAAY;IACjC;IAEA,IAAIG,AAAkB,eAAlBA,SAAS,IAAI,EAAiB;QAChC,MAAME,YAAYF,SAAS,OAAO,CAAC,OAAO,CACxC,mCACA;QAEFG,cAAcL,cAAcM,OAAO,IAAI,CAACF,WAAW;QACnDR,KAAK,YAAY,CAAC,GAAG,CAACC;QACtB;IACF;IAEA,IAAI,CAACU,WAAWL,SAAS,QAAQ,GAC/B,MAAM,IAAIM,MACR,CAAC,2BAA2B,EAAEV,GAAG,+BAA+B,EAAEF,KAAK,QAAQ,EAAE;IAIrFa,aAAaP,SAAS,QAAQ,EAAEF;IAChCJ,KAAK,YAAY,CAAC,GAAG,CAACC;AACxB;AAEA,eAAea,mBACbC,QAAgB,EAChBC,SAAiB;IAEjB,MAAMC,iBAAiBZ,KAAUW,WAAW;IAE5CE,UAAUF,WAAW;QAAE,WAAW;IAAK;IACvCE,UAAUD,gBAAgB;QAAE,WAAW;IAAK;IAE5C,MAAM,EAAEE,QAAQ,EAAEC,UAAU,EAAE,GAAGC,yBAAyBN;IAE1D,MAAMO,eAAe,IAAIC,iBAAiB;QACxC,YAAYJ,SAAS,UAAU;QAC/B,WAAWA,SAAS,SAAS;QAC7B,kBAAkBA,SAAS,gBAAgB;QAC3C,aAAaA,SAAS,WAAW;QACjC,YAAYA,SAAS,UAAU;QAC/BC;IACF;IAEA,MAAMI,SAASC,iBAAiBH;IAEhC,MAAMI,gBAA0B,EAAE;IAClC,MAAMC,qBAAqB,IAAIC;IAE/B,MAAMC,SAASxB,KAAUW,WAAW;IACpCP,cAAcoB,QAAQL,OAAO,QAAQ,EAAE;IACvCE,cAAc,IAAI,CAACG;IAEnB,KAAK,MAAM9B,cAAcyB,OAAO,WAAW,CACzC1B,0BAA0BC,YAAY;QACpCgB;QACAE;QACA,cAAcU;IAChB;IAGF,OAAO;QACLD;QACA,iBAAiBI,MAAM,IAAI,CAACH,oBACzB,IAAI,GACJ,GAAG,CAAC,CAACI,IAAM1B,KAAUY,gBAAgBc;IAC1C;AACF;AAEA,SAASC,sBAAsBjB,QAAgB;IAC7C,MAAMkB,iBAAiB5B,QAAaU;IAEpC,IAAI,CAACJ,WAAWsB,iBACd,MAAM,IAAIrB,MAAM,CAAC,wCAAwC,EAAEG,UAAU;IAGvE,MAAMmB,QAAQC,SAASF;IACvB,IAAI,CAACC,MAAM,WAAW,IACpB,OAAOD;IAGT,MAAMG,gBAAgB/B,KAAU4B,gBAAgB;IAChD,IAAI,CAACtB,WAAWyB,gBACd,MAAM,IAAIxB,MACR,CAAC,cAAc,EAAEG,SAAS,+EAA+E,CAAC;IAI9G,OAAOqB;AACT;AAEO,SAASC,gBAAgBC,OAA+B;IAI7D,MAAM,EAAEvB,QAAQ,EAAEC,SAAS,EAAE,GAAGsB;IAChC,IAAI,CAACvB,UACH,MAAM,IAAIH,MAAM;IAGlB,IAAI,CAACI,WACH,MAAM,IAAIJ,MAAM;IAGlB,MAAM2B,mBAAmBP,sBAAsBjB;IAC/C,OAAOyB,2BAA2B;QAChC,UAAUD;QACVvB;IACF;AACF;AAEO,eAAeyB,qBACpBH,OAAoC;IAEpC,MAAM,EAAEvB,QAAQ,EAAEC,SAAS,EAAE,GAAGsB;IAChC,IAAI,CAACvB,UACH,MAAM,IAAIH,MAAM;IAGlB,IAAI,CAACI,WACH,MAAM,IAAIJ,MAAM;IAGlB,MAAM2B,mBAAmBP,sBAAsBjB;IAC/C,OAAOD,mBAAmByB,kBAAkBvB;AAC9C;AAEA,MAAM0B,qBAA8C,IAAId,IAAgB;IACtE;IACA;IACA;IACA;IACA;CACD;AAED,MAAMe,wBAAiD,IAAIf,IAAgB;IACzE;IACA;IACA;CACD;AAaD,SAASgB,qBAAqB7B,QAAgB;IAC5C,MAAM8B,WAAyB,EAAE;IACjCC,wBAAwB/B,UAAU,CAAC,EAAEgC,OAAO,EAAE;QAC5C,MAAMC,QAAQD,QAAQ,KAAK,CAAC;QAC5B,IAAI,CAACC,OAAO,OAAO;QACnB,MAAMC,SAASC,mBAAmBF,KAAK,CAAC,EAAE;QAC1C,IAAIN,mBAAmB,GAAG,CAACO,SACzBJ,SAAS,IAAI,CAACI;QAEhB,OAAO;IACT;IACA,IAAIJ,AAAoB,MAApBA,SAAS,MAAM,EAAQ;IAC3B,OAAOA,SAAS,IAAI,CAAC,CAACM,IAAMR,sBAAsB,GAAG,CAACQ,OAAON,QAAQ,CAAC,EAAE;AAC1E;AAEA,SAASO,+BACPrC,QAAgB,EAChBsC,KAAa;IAEb,MAAMC,aAAa,GAAGjD,SAAcA,QAAaU,cAAcV,SAAcU,UAAUV,QAAaU,WAAW,CAAC,EAAEsC,QAAQ,GAAG;IAC7H,MAAME,aAAaC,qBAAqBzC;IACxC,MAAM0C,iBAAiBb,qBAAqB7B;IAC5C,IAAI;QACF,MAAM,EAAEI,QAAQ,EAAEC,UAAU,EAAE,GAAGC,yBAAyBN;QAI1D,MAAM2C,aACJD,kBAAkBd,sBAAsB,GAAG,CAACc,kBACxCA,iBACAF,YAAY,WAAW,WACrB,WACCE,kBACAF,CAAAA,aAAa,WAAWI,iBAAiBvC,WAAU;QAC5D,OAAO;YACL,QAAQkC;YACR,WAAWnC,SAAS,SAAS,IAAImC;YACjC,iBAAiBnC,SAAS,gBAAgB,IAAI;YAC9C,cACEoC,YAAY,cACZK,qBAAqB;gBAAExC;YAAW,GAAG,MAAM,CAAC,UAAU,IACtD;YACFsC;QACF;IACF,EAAE,OAAM;QACN,OAAO;YACL,QAAQJ;YACR,WAAWA;YACX,iBAAiB;YACjB,cAAc;YACd,YACEG,kBAAkBd,sBAAsB,GAAG,CAACc,kBACxCA,iBACAF,YAAY,WAAW,WACrB,WACCE,kBAAkB;QAC7B;IACF;AACF;AAEO,SAASI,iBACdvB,OAAgC;IAEhC,MAAM,EAAEwB,SAAS,EAAE9C,SAAS,EAAE+C,UAAU,EAAEC,YAAY,KAAK,EAAE,GAAG1B;IAChE,IAAI,CAACwB,aAAaA,AAAqB,MAArBA,UAAU,MAAM,EAChC,MAAM,IAAIlD,MAAM;IAGlB,MAAMqD,gBAAgBH,UAAU,GAAG,CAAC,CAACI,IAAMlC,sBAAsBkC;IAEjE,MAAMC,OAAO,IAAIC;IACjBH,cAAc,OAAO,CAAC,CAAClD,UAAUsC;QAC/Bc,KAAK,MAAM,CAAC;YACV,gBAAgBpD;YAChB,kBAAkBqC,+BAA+BrC,UAAUsC;QAC7D;IACF;IAEA,MAAMgB,mBAAmBF,KAAK,YAAY,CAACJ,cAAc,QAAQ;QAC/DC;QACAhD;IACF;IAEA,IAAI,CAACqD,kBACH,MAAM,IAAIzD,MAAM;IAGlB,OAAO;QAAEyD;IAAiB;AAC5B;AAEA,SAASC,uBAAuBC,GAAY;IAC1C,IAAIA,QAAAA,KAAmC;IACvC,IAAIzC,MAAM,OAAO,CAACyC,MAChB,OAAOA,IAAI,MAAM,CACf,CAACL,IAAmB,AAAa,YAAb,OAAOA,KAAkBA,EAAE,MAAM,GAAG;IAG5D,IAAI,AAAe,YAAf,OAAOK,KAAkB;QAC3B,MAAMC,UAAUD,IAAI,IAAI;QACxB,OAAOC,UAAU;YAACA;SAAQ,GAAG,EAAE;IACjC;AAEF;AAEA,MAAMC,0BAAsD;IAC1D,MAAM;IACN,aACE;IACF,QAAQ;QACN,QAAQC,CAAC,CAADA,OACD,CAAC;YAAC;YAAS;YAAe;SAAa,EAC3C,QAAQ,GACR,QAAQ,CACP;QAEJ,UAAUA,EAAAA,MACD,GACN,QAAQ,GACR,QAAQ,CACP;QAEJ,YAAYA,EAAAA,KACJ,CAAC;YAACA,EAAE,MAAM;YAAIA,EAAE,KAAK,CAACA,EAAE,MAAM;SAAI,EACvC,QAAQ,GACR,QAAQ,CACP;QAEJ,WAAWA,EAAAA,MACF,GACN,QAAQ,GACR,QAAQ,CACP;QAEJ,YAAYA,EAAAA,MACH,GACN,QAAQ,GACR,QAAQ,CACP;QAEJ,WAAWA,EAAAA,KACH,CAAC;YAACA,EAAE,OAAO;YAAIA,EAAE,MAAM;SAAG,EAC/B,QAAQ,GACR,QAAQ,CACP;IAEN;IACA,SAAS,OAAOC;QACd,MAAM,EACJC,SAAS,OAAO,EAChB7D,QAAQ,EACR8D,UAAU,EACV7D,SAAS,EACT+C,UAAU,EACVC,SAAS,EACV,GAAGW;QAQJ,IACEC,AAAW,YAAXA,UACAA,AAAW,kBAAXA,UACAA,AAAW,iBAAXA,QAEA,MAAM,IAAIhE,MACR,CAAC,yCAAyC,EAAEgE,OAAO,sDAAsD,CAAC;QAI9G,IAAIA,AAAW,iBAAXA,QAAyB;YAC3B,MAAME,QAAQR,uBAAuBO;YACrC,IAAI,CAACC,SAASA,AAAiB,MAAjBA,MAAM,MAAM,EACxB,MAAM,IAAIlE,MACR;YAIJ,MAAMmE,gBACJf,AAAc,SAAdA,aAAsBA,AAAc,WAAdA,aAAwBA,AAAc,QAAdA;YAEhD,MAAMxC,SAASqC,iBAAiB;gBAC9B,WAAWiB;gBACX9D;gBACA+C;gBACA,WAAWgB;YACb;YAEA,OAAO;gBACL,SAAS;gBACT,SAAS;oBACP;wBACE,MAAM;wBACN,MAAM,CAAC,OAAO,EAAED,MAAM,MAAM,CAAC,gBAAgB,EAAEtD,OAAO,gBAAgB,EAAE;oBAC1E;iBACD;YACH;QACF;QAEA,IAAI,CAACT,UACH,MAAM,IAAIH,MAAM;QAGlB,IAAI,CAACI,WACH,MAAM,IAAIJ,MAAM;QAGlB,IAAIgE,AAAW,kBAAXA,QAA0B;YAC5B,MAAMpD,SAAS,MAAMiB,qBAAqB;gBACxC1B;gBACAC;YACF;YACA,OAAO;gBACL,SAAS;gBACT,SAAS;oBACP;wBACE,MAAM;wBACN,MAAM,CAAC,qCAAqC,EAAEQ,OAAO,aAAa,CAAC,MAAM,CAAC,sBAAsB,EAAEA,OAAO,eAAe,CAAC,MAAM,CAAC,6BAA6B,EAAER,WAAW;oBAC5K;iBACD;YACH;QACF;QAEA,MAAMQ,SAASa,gBAAgB;YAC7BtB;YACAC;QACF;QAEA,OAAO;YACL,SAAS;YACT,SAAS;gBACP;oBACE,MAAM;oBACN,MAAM,CAAC,kCAAkC,EAAEQ,OAAO,kBAAkB,CAAC,MAAM,CAAC,0BAA0B,EAAEA,OAAO,eAAe,CAAC,MAAM,CAAC,2BAA2B,EAAER,WAAW;gBAChL;aACD;QACH;IACF;AACF;AAEO,SAASgE;IACd,OAAO;QACL;YACE,MAAM;YACN,KAAKP;QACP;KACD;AACH"}
|
|
@@ -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 {
|
|
9
|
-
import {
|
|
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.
|
|
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.
|
|
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(
|
|
182
|
+
async serializeSourceDump(snapshot) {
|
|
164
183
|
this.enableSourceSnapshots();
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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:
|
|
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
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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:
|
|
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, "
|
|
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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"}
|