@midscene/core 1.7.3 → 1.7.4
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/utils.mjs +1 -1
- package/dist/es/report.mjs +20 -1
- package/dist/es/report.mjs.map +1 -1
- package/dist/es/utils.mjs +2 -2
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/report.js +19 -0
- package/dist/lib/report.js.map +1 -1
- package/dist/lib/utils.js +2 -2
- package/package.json +2 -2
package/dist/es/agent/utils.mjs
CHANGED
|
@@ -123,7 +123,7 @@ async function matchElementFromCache(context, cacheEntry, cachePrompt, cacheable
|
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
const getMidsceneVersion = ()=>"1.7.
|
|
126
|
+
const getMidsceneVersion = ()=>"1.7.4";
|
|
127
127
|
const parsePrompt = (prompt)=>{
|
|
128
128
|
if ('string' == typeof prompt) return {
|
|
129
129
|
textPrompt: prompt,
|
package/dist/es/report.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { getReportFileName } from "./agent/index.mjs";
|
|
|
6
6
|
import { extractAllDumpScriptsSync, extractLastDumpScriptSync, getBaseUrlFixScript, streamDumpScriptsSync, streamImageScriptsToFile } from "./dump/html-utils.mjs";
|
|
7
7
|
import { normalizeScreenshotRef, resolveScreenshotSource } from "./dump/screenshot-store.mjs";
|
|
8
8
|
import { ReportActionDump } from "./types.mjs";
|
|
9
|
-
import { getReportTpl, reportHTMLContent } from "./utils.mjs";
|
|
9
|
+
import { getReportTpl, getVersion, reportHTMLContent } from "./utils.mjs";
|
|
10
10
|
function _define_property(obj, key, value) {
|
|
11
11
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
12
12
|
value: value,
|
|
@@ -30,6 +30,17 @@ function dedupeExecutionsKeepLatest(executions) {
|
|
|
30
30
|
}
|
|
31
31
|
return Array.from(deduped.values());
|
|
32
32
|
}
|
|
33
|
+
function peekReportSdkVersion(reportFilePath) {
|
|
34
|
+
try {
|
|
35
|
+
const dump = extractLastDumpScriptSync(reportFilePath);
|
|
36
|
+
if (!dump) return;
|
|
37
|
+
const match = dump.match(/"sdkVersion"\s*:\s*"([^"]+)"/);
|
|
38
|
+
return match?.[1];
|
|
39
|
+
} catch {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const warnedMismatchedVersions = new Set();
|
|
33
44
|
class ReportMergingTool {
|
|
34
45
|
createEmptyDumpString(groupName, groupDescription) {
|
|
35
46
|
return new ReportActionDump({
|
|
@@ -41,6 +52,14 @@ class ReportMergingTool {
|
|
|
41
52
|
}).serialize();
|
|
42
53
|
}
|
|
43
54
|
append(reportInfo) {
|
|
55
|
+
if (reportInfo.reportFilePath) {
|
|
56
|
+
const sourceVersion = peekReportSdkVersion(reportInfo.reportFilePath);
|
|
57
|
+
const currentVersion = getVersion();
|
|
58
|
+
if (sourceVersion && currentVersion && sourceVersion !== currentVersion && !warnedMismatchedVersions.has(sourceVersion)) {
|
|
59
|
+
warnedMismatchedVersions.add(sourceVersion);
|
|
60
|
+
logMsg(`[@midscene/core] ReportMergingTool version mismatch: source report was written by @midscene/core@${sourceVersion} but the merger is @midscene/core@${currentVersion}. This commonly means @midscene/core and the device package (e.g. @midscene/android) resolve to different versions in node_modules. Merged output may silently drop intermediate steps. Align the versions and reinstall (rm -rf node_modules package-lock.json && npm install).`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
44
63
|
this.reportInfos.push(reportInfo);
|
|
45
64
|
}
|
|
46
65
|
clear() {
|
package/dist/es/report.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.mjs","sources":["../../src/report.ts"],"sourcesContent":["import {\n appendFileSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport { antiEscapeScriptTag, logMsg } from '@midscene/shared/utils';\nimport { getReportFileName } from './agent';\nimport {\n extractAllDumpScriptsSync,\n extractLastDumpScriptSync,\n getBaseUrlFixScript,\n streamDumpScriptsSync,\n streamImageScriptsToFile,\n} from './dump/html-utils';\nimport {\n normalizeScreenshotRef,\n resolveScreenshotSource,\n} from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n type IExecutionDump,\n ReportActionDump,\n} from './types';\nimport type { ReportFileWithAttributes } from './types';\nimport { getReportTpl, reportHTMLContent } from './utils';\n\n/**\n * Check if a report is in directory mode (html-and-external-assets).\n * Directory mode reports: {name}/index.html + {name}/screenshots/\n */\nexport function isDirectoryModeReport(reportFilePath: string): boolean {\n const reportDir = path.dirname(reportFilePath);\n return (\n path.basename(reportFilePath) === 'index.html' &&\n existsSync(path.join(reportDir, 'screenshots'))\n );\n}\n\n/**\n * Deduplicate executions by stable id, keeping only the last occurrence.\n * Old-format executions without id are always preserved.\n */\nexport function dedupeExecutionsKeepLatest<T extends Pick<ExecutionDump, 'id'>>(\n executions: T[],\n): T[] {\n let noIdCounter = 0;\n const deduped = new Map<string, T>();\n for (const exec of executions) {\n const key = exec.id || `__no_id_${noIdCounter++}`;\n deduped.set(key, exec);\n }\n return Array.from(deduped.values());\n}\nexport class ReportMergingTool {\n private reportInfos: ReportFileWithAttributes[] = [];\n\n private createEmptyDumpString(groupName: string, groupDescription?: string) {\n return new ReportActionDump({\n sdkVersion: '',\n groupName,\n groupDescription,\n modelBriefs: [],\n executions: [],\n }).serialize();\n }\n\n public append(reportInfo: ReportFileWithAttributes) {\n this.reportInfos.push(reportInfo);\n }\n public clear() {\n this.reportInfos = [];\n }\n\n /**\n * Merge multiple dump script contents (from the same source report)\n * into a single serialized ReportActionDump string.\n * If there's only one dump, return it as-is. If multiple, merge\n * all executions into the first dump's group structure.\n */\n private mergeDumpScripts(contents: string[]): string {\n const unescaped = contents\n .map((c) => antiEscapeScriptTag(c))\n .filter((c) => c.length > 0);\n if (unescaped.length === 0) return '';\n if (unescaped.length === 1) return unescaped[0];\n\n // Parse all dumps and collect executions, deduplicating by id (keep last).\n // Only executions with a stable id are deduped; old-format entries without\n // id are always kept (they may be distinct despite sharing the same name).\n const base = ReportActionDump.fromSerializedString(unescaped[0]);\n const allExecutions = [...base.executions];\n for (let i = 1; i < unescaped.length; i++) {\n const other = ReportActionDump.fromSerializedString(unescaped[i]);\n allExecutions.push(...other.executions);\n }\n base.executions = dedupeExecutionsKeepLatest(allExecutions);\n return base.serialize();\n }\n\n public mergeReports(\n reportFileName: 'AUTO' | string = 'AUTO',\n opts?: {\n rmOriginalReports?: boolean;\n overwrite?: boolean;\n },\n ): string | null {\n const { rmOriginalReports = false, overwrite = false } = opts ?? {};\n\n if (this.reportInfos.length === 0) {\n logMsg('No reports to merge');\n return null;\n }\n\n const targetDir = getMidsceneRunSubDir('report');\n\n // Check if any source report is directory mode\n const hasDirectoryModeReport = this.reportInfos.some((info) => {\n const reportFilePath = info.reportFilePath;\n return Boolean(reportFilePath && isDirectoryModeReport(reportFilePath));\n });\n\n const resolvedName =\n reportFileName === 'AUTO'\n ? getReportFileName('merged-report')\n : reportFileName;\n\n // Directory mode: output as {name}/index.html to keep relative paths working\n // Inline mode: output as {name}.html (single file)\n const outputFilePath = hasDirectoryModeReport\n ? path.resolve(targetDir, resolvedName, 'index.html')\n : path.resolve(targetDir, `${resolvedName}.html`);\n\n if (reportFileName !== 'AUTO' && existsSync(outputFilePath)) {\n if (!overwrite) {\n throw new Error(\n `Report file already exists: ${outputFilePath}\\nSet overwrite to true to overwrite this file.`,\n );\n }\n if (hasDirectoryModeReport) {\n rmSync(path.dirname(outputFilePath), { recursive: true, force: true });\n } else {\n unlinkSync(outputFilePath);\n }\n }\n\n if (hasDirectoryModeReport) {\n mkdirSync(path.dirname(outputFilePath), { recursive: true });\n }\n\n logMsg(\n `Start merging ${this.reportInfos.length} reports...\\nCreating template file...`,\n );\n\n try {\n // Write template without closing </html> tag so we can append\n // dump scripts before it. The closing tag is added at the end.\n const htmlEndTag = '</html>';\n const tpl = getReportTpl();\n const htmlEndIdx = tpl.lastIndexOf(htmlEndTag);\n const tplWithoutClose =\n htmlEndIdx !== -1 ? tpl.slice(0, htmlEndIdx) : tpl;\n appendFileSync(outputFilePath, tplWithoutClose);\n\n // For directory-mode output, inject base URL fix script\n if (hasDirectoryModeReport) {\n appendFileSync(outputFilePath, getBaseUrlFixScript());\n }\n\n // Process all reports one by one\n for (let i = 0; i < this.reportInfos.length; i++) {\n const reportInfo = this.reportInfos[i];\n logMsg(`Processing report ${i + 1}/${this.reportInfos.length}`);\n\n const { reportAttributes } = reportInfo;\n let dumpString = this.createEmptyDumpString(\n reportAttributes.testTitle,\n reportAttributes.testDescription,\n );\n let mergedGroupId = `merged-group-${i}`;\n\n if (reportInfo.reportFilePath) {\n if (isDirectoryModeReport(reportInfo.reportFilePath)) {\n // Directory mode: copy external screenshot files\n const reportDir = path.dirname(reportInfo.reportFilePath);\n const screenshotsDir = path.join(reportDir, 'screenshots');\n const mergedScreenshotsDir = path.join(\n path.dirname(outputFilePath),\n 'screenshots',\n );\n mkdirSync(mergedScreenshotsDir, { recursive: true });\n for (const file of readdirSync(screenshotsDir)) {\n const src = path.join(screenshotsDir, file);\n const dest = path.join(mergedScreenshotsDir, file);\n copyFileSync(src, dest);\n }\n } else {\n // Inline mode: stream image scripts to output file\n streamImageScriptsToFile(reportInfo.reportFilePath, outputFilePath);\n }\n\n // Extract all dump scripts from the source report.\n // After the per-execution append refactor, a single source report\n // may contain multiple <script type=\"midscene_web_dump\"> tags\n // (one per execution). We merge them into a single ReportActionDump.\n // Filter by data-group-id to exclude false matches from the template's\n // bundled JS code, which also references the midscene_web_dump type string.\n const allDumps = extractAllDumpScriptsSync(\n reportInfo.reportFilePath,\n ).filter((d) => d.openTag.includes('data-group-id'));\n const groupIdMatch = allDumps[0]?.openTag.match(\n /data-group-id=\"([^\"]+)\"/,\n );\n if (groupIdMatch) {\n mergedGroupId = decodeURIComponent(groupIdMatch[1]);\n }\n const extractedDumpString =\n allDumps.length > 0\n ? this.mergeDumpScripts(allDumps.map((d) => d.content))\n : extractLastDumpScriptSync(reportInfo.reportFilePath);\n if (extractedDumpString) {\n dumpString = extractedDumpString;\n }\n }\n\n const reportHtmlStr = `${reportHTMLContent(\n {\n dumpString,\n attributes: {\n 'data-group-id': mergedGroupId,\n playwright_test_duration: reportAttributes.testDuration,\n playwright_test_status: reportAttributes.testStatus,\n playwright_test_title: reportAttributes.testTitle,\n playwright_test_id: reportAttributes.testId,\n playwright_test_description: reportAttributes.testDescription,\n is_merged: true,\n },\n },\n undefined,\n undefined,\n false,\n )}\\n`;\n\n appendFileSync(outputFilePath, reportHtmlStr);\n }\n\n // Close the HTML document\n appendFileSync(outputFilePath, `${htmlEndTag}\\n`);\n\n logMsg(`Successfully merged new report: ${outputFilePath}`);\n\n // Remove original reports if needed\n if (rmOriginalReports) {\n for (const info of this.reportInfos) {\n if (!info.reportFilePath) continue;\n try {\n if (isDirectoryModeReport(info.reportFilePath)) {\n // Directory mode: remove the entire report directory\n const reportDir = path.dirname(info.reportFilePath);\n rmSync(reportDir, { recursive: true, force: true });\n } else {\n unlinkSync(info.reportFilePath);\n }\n } catch (error) {\n logMsg(`Error deleting report ${info.reportFilePath}: ${error}`);\n }\n }\n logMsg(`Removed ${this.reportInfos.length} original reports`);\n }\n return outputFilePath;\n } catch (error) {\n logMsg(`Error in mergeReports: ${error}`);\n throw error;\n }\n }\n}\n\nexport interface SplitReportHtmlOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport interface SplitReportHtmlResult {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n}\n\nexport interface CollectedReportExecutions {\n baseDump: ReportActionDump;\n executions: IExecutionDump[];\n}\n\n/**\n * Collect executions from a report HTML, deduplicating by stable id while\n * keeping only the latest occurrence. Old-format executions without id are\n * always preserved.\n */\nexport function collectDedupedExecutions(\n htmlPath: string,\n): CollectedReportExecutions {\n let baseDump: ReportActionDump | null = null;\n let executionSerial = 0;\n const latestSerialByExecutionId = new Map<string, number>();\n\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (execution.id) {\n latestSerialByExecutionId.set(execution.id, executionSerial);\n }\n }\n return false;\n });\n\n const executions: IExecutionDump[] = [];\n executionSerial = 0;\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n if (!baseDump) {\n baseDump = groupedDump;\n }\n\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (\n execution.id &&\n latestSerialByExecutionId.get(execution.id) !== executionSerial\n ) {\n continue;\n }\n executions.push(execution);\n }\n\n return false;\n });\n\n if (!baseDump) {\n throw new Error(`No report dump scripts found in ${htmlPath}`);\n }\n\n return {\n baseDump,\n executions,\n };\n}\n\nfunction extensionByMimeType(mimeType: string): 'png' | 'jpeg' {\n if (mimeType === 'image/png') return 'png';\n if (mimeType === 'image/jpeg') return 'jpeg';\n throw new Error(`Unsupported screenshot mime type: ${mimeType}`);\n}\n\nfunction externalizeScreenshotsInExecution(\n execution: IExecutionDump,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const visit = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const item of node) {\n visit(item);\n }\n return;\n }\n\n if (typeof node !== 'object' || node === null) return;\n\n const ref = normalizeScreenshotRef(node);\n if (ref) {\n const ext = extensionByMimeType(ref.mimeType);\n const fileName = `${ref.id}.${ext}`;\n const relativePath = `./screenshots/${fileName}`;\n const absolutePath = path.join(opts.screenshotsDir, fileName);\n\n if (!opts.writtenFiles.has(fileName)) {\n const resolved = resolveScreenshotSource(ref, {\n reportPath: opts.htmlPath,\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 } else {\n copyFileSync(resolved.filePath, absolutePath);\n }\n opts.writtenFiles.add(fileName);\n }\n\n ref.storage = 'file';\n ref.path = relativePath;\n return;\n }\n\n for (const value of Object.values(node)) {\n visit(value);\n }\n };\n\n visit(execution);\n}\n\n/**\n * Reverse parse a Midscene report HTML into per-execution JSON files and\n * externalized screenshots.\n */\nexport function splitReportHtmlByExecution(\n options: SplitReportHtmlOptions,\n): SplitReportHtmlResult {\n const { htmlPath, outputDir } = options;\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const executionJsonFiles: string[] = [];\n const writtenScreenshotFiles = new Set<string>();\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n let fileIndex = 0;\n for (const execution of executions) {\n fileIndex += 1;\n externalizeScreenshotsInExecution(execution, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshotFiles,\n });\n const singleExecutionDump = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions: [execution],\n });\n\n const jsonFilePath = path.join(outputDir, `${fileIndex}.execution.json`);\n writeFileSync(jsonFilePath, singleExecutionDump.serialize(2), 'utf-8');\n executionJsonFiles.push(jsonFilePath);\n }\n\n return {\n executionJsonFiles,\n screenshotFiles: Array.from(writtenScreenshotFiles)\n .sort()\n .map((fileName) => path.join(screenshotsDir, fileName)),\n };\n}\n"],"names":["isDirectoryModeReport","reportFilePath","reportDir","path","existsSync","dedupeExecutionsKeepLatest","executions","noIdCounter","deduped","Map","exec","key","Array","ReportMergingTool","groupName","groupDescription","ReportActionDump","reportInfo","contents","unescaped","c","antiEscapeScriptTag","base","allExecutions","i","other","reportFileName","opts","rmOriginalReports","overwrite","logMsg","targetDir","getMidsceneRunSubDir","hasDirectoryModeReport","info","Boolean","resolvedName","getReportFileName","outputFilePath","Error","rmSync","unlinkSync","mkdirSync","htmlEndTag","tpl","getReportTpl","htmlEndIdx","tplWithoutClose","appendFileSync","getBaseUrlFixScript","reportAttributes","dumpString","mergedGroupId","screenshotsDir","mergedScreenshotsDir","file","readdirSync","src","dest","copyFileSync","streamImageScriptsToFile","allDumps","extractAllDumpScriptsSync","d","groupIdMatch","decodeURIComponent","extractedDumpString","extractLastDumpScriptSync","reportHtmlStr","reportHTMLContent","undefined","error","collectDedupedExecutions","htmlPath","baseDump","executionSerial","latestSerialByExecutionId","streamDumpScriptsSync","dumpScript","groupedDump","execution","extensionByMimeType","mimeType","externalizeScreenshotsInExecution","visit","node","item","ref","normalizeScreenshotRef","ext","fileName","relativePath","absolutePath","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","value","Object","splitReportHtmlByExecution","options","outputDir","executionJsonFiles","writtenScreenshotFiles","Set","fileIndex","singleExecutionDump","jsonFilePath"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"report.mjs","sources":["../../src/report.ts"],"sourcesContent":["import {\n appendFileSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport { antiEscapeScriptTag, logMsg } from '@midscene/shared/utils';\nimport { getReportFileName } from './agent';\nimport {\n extractAllDumpScriptsSync,\n extractLastDumpScriptSync,\n getBaseUrlFixScript,\n streamDumpScriptsSync,\n streamImageScriptsToFile,\n} from './dump/html-utils';\nimport {\n normalizeScreenshotRef,\n resolveScreenshotSource,\n} from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n type IExecutionDump,\n ReportActionDump,\n} from './types';\nimport type { ReportFileWithAttributes } from './types';\nimport { getReportTpl, getVersion, reportHTMLContent } from './utils';\n\n/**\n * Check if a report is in directory mode (html-and-external-assets).\n * Directory mode reports: {name}/index.html + {name}/screenshots/\n */\nexport function isDirectoryModeReport(reportFilePath: string): boolean {\n const reportDir = path.dirname(reportFilePath);\n return (\n path.basename(reportFilePath) === 'index.html' &&\n existsSync(path.join(reportDir, 'screenshots'))\n );\n}\n\n/**\n * Deduplicate executions by stable id, keeping only the last occurrence.\n * Old-format executions without id are always preserved.\n */\nexport function dedupeExecutionsKeepLatest<T extends Pick<ExecutionDump, 'id'>>(\n executions: T[],\n): T[] {\n let noIdCounter = 0;\n const deduped = new Map<string, T>();\n for (const exec of executions) {\n const key = exec.id || `__no_id_${noIdCounter++}`;\n deduped.set(key, exec);\n }\n return Array.from(deduped.values());\n}\n/**\n * Peek at the first `sdkVersion` field embedded in a midscene_web_dump\n * script tag inside the given report file. Returns undefined if no\n * recognizable tag or sdkVersion is present.\n */\nfunction peekReportSdkVersion(reportFilePath: string): string | undefined {\n try {\n const dump = extractLastDumpScriptSync(reportFilePath);\n if (!dump) return undefined;\n const match = dump.match(/\"sdkVersion\"\\s*:\\s*\"([^\"]+)\"/);\n return match?.[1];\n } catch {\n return undefined;\n }\n}\n\nconst warnedMismatchedVersions = new Set<string>();\n\nexport class ReportMergingTool {\n private reportInfos: ReportFileWithAttributes[] = [];\n\n private createEmptyDumpString(groupName: string, groupDescription?: string) {\n return new ReportActionDump({\n sdkVersion: '',\n groupName,\n groupDescription,\n modelBriefs: [],\n executions: [],\n }).serialize();\n }\n\n public append(reportInfo: ReportFileWithAttributes) {\n if (reportInfo.reportFilePath) {\n const sourceVersion = peekReportSdkVersion(reportInfo.reportFilePath);\n const currentVersion = getVersion();\n if (\n sourceVersion &&\n currentVersion &&\n sourceVersion !== currentVersion &&\n !warnedMismatchedVersions.has(sourceVersion)\n ) {\n warnedMismatchedVersions.add(sourceVersion);\n logMsg(\n `[@midscene/core] ReportMergingTool version mismatch: source report was written by @midscene/core@${sourceVersion} but the merger is @midscene/core@${currentVersion}. This commonly means @midscene/core and the device package (e.g. @midscene/android) resolve to different versions in node_modules. Merged output may silently drop intermediate steps. Align the versions and reinstall (rm -rf node_modules package-lock.json && npm install).`,\n );\n }\n }\n this.reportInfos.push(reportInfo);\n }\n public clear() {\n this.reportInfos = [];\n }\n\n /**\n * Merge multiple dump script contents (from the same source report)\n * into a single serialized ReportActionDump string.\n * If there's only one dump, return it as-is. If multiple, merge\n * all executions into the first dump's group structure.\n */\n private mergeDumpScripts(contents: string[]): string {\n const unescaped = contents\n .map((c) => antiEscapeScriptTag(c))\n .filter((c) => c.length > 0);\n if (unescaped.length === 0) return '';\n if (unescaped.length === 1) return unescaped[0];\n\n // Parse all dumps and collect executions, deduplicating by id (keep last).\n // Only executions with a stable id are deduped; old-format entries without\n // id are always kept (they may be distinct despite sharing the same name).\n const base = ReportActionDump.fromSerializedString(unescaped[0]);\n const allExecutions = [...base.executions];\n for (let i = 1; i < unescaped.length; i++) {\n const other = ReportActionDump.fromSerializedString(unescaped[i]);\n allExecutions.push(...other.executions);\n }\n base.executions = dedupeExecutionsKeepLatest(allExecutions);\n return base.serialize();\n }\n\n public mergeReports(\n reportFileName: 'AUTO' | string = 'AUTO',\n opts?: {\n rmOriginalReports?: boolean;\n overwrite?: boolean;\n },\n ): string | null {\n const { rmOriginalReports = false, overwrite = false } = opts ?? {};\n\n if (this.reportInfos.length === 0) {\n logMsg('No reports to merge');\n return null;\n }\n\n const targetDir = getMidsceneRunSubDir('report');\n\n // Check if any source report is directory mode\n const hasDirectoryModeReport = this.reportInfos.some((info) => {\n const reportFilePath = info.reportFilePath;\n return Boolean(reportFilePath && isDirectoryModeReport(reportFilePath));\n });\n\n const resolvedName =\n reportFileName === 'AUTO'\n ? getReportFileName('merged-report')\n : reportFileName;\n\n // Directory mode: output as {name}/index.html to keep relative paths working\n // Inline mode: output as {name}.html (single file)\n const outputFilePath = hasDirectoryModeReport\n ? path.resolve(targetDir, resolvedName, 'index.html')\n : path.resolve(targetDir, `${resolvedName}.html`);\n\n if (reportFileName !== 'AUTO' && existsSync(outputFilePath)) {\n if (!overwrite) {\n throw new Error(\n `Report file already exists: ${outputFilePath}\\nSet overwrite to true to overwrite this file.`,\n );\n }\n if (hasDirectoryModeReport) {\n rmSync(path.dirname(outputFilePath), { recursive: true, force: true });\n } else {\n unlinkSync(outputFilePath);\n }\n }\n\n if (hasDirectoryModeReport) {\n mkdirSync(path.dirname(outputFilePath), { recursive: true });\n }\n\n logMsg(\n `Start merging ${this.reportInfos.length} reports...\\nCreating template file...`,\n );\n\n try {\n // Write template without closing </html> tag so we can append\n // dump scripts before it. The closing tag is added at the end.\n const htmlEndTag = '</html>';\n const tpl = getReportTpl();\n const htmlEndIdx = tpl.lastIndexOf(htmlEndTag);\n const tplWithoutClose =\n htmlEndIdx !== -1 ? tpl.slice(0, htmlEndIdx) : tpl;\n appendFileSync(outputFilePath, tplWithoutClose);\n\n // For directory-mode output, inject base URL fix script\n if (hasDirectoryModeReport) {\n appendFileSync(outputFilePath, getBaseUrlFixScript());\n }\n\n // Process all reports one by one\n for (let i = 0; i < this.reportInfos.length; i++) {\n const reportInfo = this.reportInfos[i];\n logMsg(`Processing report ${i + 1}/${this.reportInfos.length}`);\n\n const { reportAttributes } = reportInfo;\n let dumpString = this.createEmptyDumpString(\n reportAttributes.testTitle,\n reportAttributes.testDescription,\n );\n let mergedGroupId = `merged-group-${i}`;\n\n if (reportInfo.reportFilePath) {\n if (isDirectoryModeReport(reportInfo.reportFilePath)) {\n // Directory mode: copy external screenshot files\n const reportDir = path.dirname(reportInfo.reportFilePath);\n const screenshotsDir = path.join(reportDir, 'screenshots');\n const mergedScreenshotsDir = path.join(\n path.dirname(outputFilePath),\n 'screenshots',\n );\n mkdirSync(mergedScreenshotsDir, { recursive: true });\n for (const file of readdirSync(screenshotsDir)) {\n const src = path.join(screenshotsDir, file);\n const dest = path.join(mergedScreenshotsDir, file);\n copyFileSync(src, dest);\n }\n } else {\n // Inline mode: stream image scripts to output file\n streamImageScriptsToFile(reportInfo.reportFilePath, outputFilePath);\n }\n\n // Extract all dump scripts from the source report.\n // After the per-execution append refactor, a single source report\n // may contain multiple <script type=\"midscene_web_dump\"> tags\n // (one per execution). We merge them into a single ReportActionDump.\n // Filter by data-group-id to exclude false matches from the template's\n // bundled JS code, which also references the midscene_web_dump type string.\n const allDumps = extractAllDumpScriptsSync(\n reportInfo.reportFilePath,\n ).filter((d) => d.openTag.includes('data-group-id'));\n const groupIdMatch = allDumps[0]?.openTag.match(\n /data-group-id=\"([^\"]+)\"/,\n );\n if (groupIdMatch) {\n mergedGroupId = decodeURIComponent(groupIdMatch[1]);\n }\n const extractedDumpString =\n allDumps.length > 0\n ? this.mergeDumpScripts(allDumps.map((d) => d.content))\n : extractLastDumpScriptSync(reportInfo.reportFilePath);\n if (extractedDumpString) {\n dumpString = extractedDumpString;\n }\n }\n\n const reportHtmlStr = `${reportHTMLContent(\n {\n dumpString,\n attributes: {\n 'data-group-id': mergedGroupId,\n playwright_test_duration: reportAttributes.testDuration,\n playwright_test_status: reportAttributes.testStatus,\n playwright_test_title: reportAttributes.testTitle,\n playwright_test_id: reportAttributes.testId,\n playwright_test_description: reportAttributes.testDescription,\n is_merged: true,\n },\n },\n undefined,\n undefined,\n false,\n )}\\n`;\n\n appendFileSync(outputFilePath, reportHtmlStr);\n }\n\n // Close the HTML document\n appendFileSync(outputFilePath, `${htmlEndTag}\\n`);\n\n logMsg(`Successfully merged new report: ${outputFilePath}`);\n\n // Remove original reports if needed\n if (rmOriginalReports) {\n for (const info of this.reportInfos) {\n if (!info.reportFilePath) continue;\n try {\n if (isDirectoryModeReport(info.reportFilePath)) {\n // Directory mode: remove the entire report directory\n const reportDir = path.dirname(info.reportFilePath);\n rmSync(reportDir, { recursive: true, force: true });\n } else {\n unlinkSync(info.reportFilePath);\n }\n } catch (error) {\n logMsg(`Error deleting report ${info.reportFilePath}: ${error}`);\n }\n }\n logMsg(`Removed ${this.reportInfos.length} original reports`);\n }\n return outputFilePath;\n } catch (error) {\n logMsg(`Error in mergeReports: ${error}`);\n throw error;\n }\n }\n}\n\nexport interface SplitReportHtmlOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport interface SplitReportHtmlResult {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n}\n\nexport interface CollectedReportExecutions {\n baseDump: ReportActionDump;\n executions: IExecutionDump[];\n}\n\n/**\n * Collect executions from a report HTML, deduplicating by stable id while\n * keeping only the latest occurrence. Old-format executions without id are\n * always preserved.\n */\nexport function collectDedupedExecutions(\n htmlPath: string,\n): CollectedReportExecutions {\n let baseDump: ReportActionDump | null = null;\n let executionSerial = 0;\n const latestSerialByExecutionId = new Map<string, number>();\n\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (execution.id) {\n latestSerialByExecutionId.set(execution.id, executionSerial);\n }\n }\n return false;\n });\n\n const executions: IExecutionDump[] = [];\n executionSerial = 0;\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n if (!baseDump) {\n baseDump = groupedDump;\n }\n\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (\n execution.id &&\n latestSerialByExecutionId.get(execution.id) !== executionSerial\n ) {\n continue;\n }\n executions.push(execution);\n }\n\n return false;\n });\n\n if (!baseDump) {\n throw new Error(`No report dump scripts found in ${htmlPath}`);\n }\n\n return {\n baseDump,\n executions,\n };\n}\n\nfunction extensionByMimeType(mimeType: string): 'png' | 'jpeg' {\n if (mimeType === 'image/png') return 'png';\n if (mimeType === 'image/jpeg') return 'jpeg';\n throw new Error(`Unsupported screenshot mime type: ${mimeType}`);\n}\n\nfunction externalizeScreenshotsInExecution(\n execution: IExecutionDump,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const visit = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const item of node) {\n visit(item);\n }\n return;\n }\n\n if (typeof node !== 'object' || node === null) return;\n\n const ref = normalizeScreenshotRef(node);\n if (ref) {\n const ext = extensionByMimeType(ref.mimeType);\n const fileName = `${ref.id}.${ext}`;\n const relativePath = `./screenshots/${fileName}`;\n const absolutePath = path.join(opts.screenshotsDir, fileName);\n\n if (!opts.writtenFiles.has(fileName)) {\n const resolved = resolveScreenshotSource(ref, {\n reportPath: opts.htmlPath,\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 } else {\n copyFileSync(resolved.filePath, absolutePath);\n }\n opts.writtenFiles.add(fileName);\n }\n\n ref.storage = 'file';\n ref.path = relativePath;\n return;\n }\n\n for (const value of Object.values(node)) {\n visit(value);\n }\n };\n\n visit(execution);\n}\n\n/**\n * Reverse parse a Midscene report HTML into per-execution JSON files and\n * externalized screenshots.\n */\nexport function splitReportHtmlByExecution(\n options: SplitReportHtmlOptions,\n): SplitReportHtmlResult {\n const { htmlPath, outputDir } = options;\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const executionJsonFiles: string[] = [];\n const writtenScreenshotFiles = new Set<string>();\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n let fileIndex = 0;\n for (const execution of executions) {\n fileIndex += 1;\n externalizeScreenshotsInExecution(execution, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshotFiles,\n });\n const singleExecutionDump = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions: [execution],\n });\n\n const jsonFilePath = path.join(outputDir, `${fileIndex}.execution.json`);\n writeFileSync(jsonFilePath, singleExecutionDump.serialize(2), 'utf-8');\n executionJsonFiles.push(jsonFilePath);\n }\n\n return {\n executionJsonFiles,\n screenshotFiles: Array.from(writtenScreenshotFiles)\n .sort()\n .map((fileName) => path.join(screenshotsDir, fileName)),\n };\n}\n"],"names":["isDirectoryModeReport","reportFilePath","reportDir","path","existsSync","dedupeExecutionsKeepLatest","executions","noIdCounter","deduped","Map","exec","key","Array","peekReportSdkVersion","dump","extractLastDumpScriptSync","match","warnedMismatchedVersions","Set","ReportMergingTool","groupName","groupDescription","ReportActionDump","reportInfo","sourceVersion","currentVersion","getVersion","logMsg","contents","unescaped","c","antiEscapeScriptTag","base","allExecutions","i","other","reportFileName","opts","rmOriginalReports","overwrite","targetDir","getMidsceneRunSubDir","hasDirectoryModeReport","info","Boolean","resolvedName","getReportFileName","outputFilePath","Error","rmSync","unlinkSync","mkdirSync","htmlEndTag","tpl","getReportTpl","htmlEndIdx","tplWithoutClose","appendFileSync","getBaseUrlFixScript","reportAttributes","dumpString","mergedGroupId","screenshotsDir","mergedScreenshotsDir","file","readdirSync","src","dest","copyFileSync","streamImageScriptsToFile","allDumps","extractAllDumpScriptsSync","d","groupIdMatch","decodeURIComponent","extractedDumpString","reportHtmlStr","reportHTMLContent","undefined","error","collectDedupedExecutions","htmlPath","baseDump","executionSerial","latestSerialByExecutionId","streamDumpScriptsSync","dumpScript","groupedDump","execution","extensionByMimeType","mimeType","externalizeScreenshotsInExecution","visit","node","item","ref","normalizeScreenshotRef","ext","fileName","relativePath","absolutePath","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","value","Object","splitReportHtmlByExecution","options","outputDir","executionJsonFiles","writtenScreenshotFiles","fileIndex","singleExecutionDump","jsonFilePath"],"mappings":"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"}
|