@midscene/core 1.10.10 → 1.10.11-beta-20260811045313.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/dump/html-utils.mjs +40 -2
- package/dist/es/dump/html-utils.mjs.map +1 -1
- package/dist/es/index.mjs +2 -1
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/report-cli.mjs +22 -15
- package/dist/es/report-cli.mjs.map +1 -1
- package/dist/es/report-inspection.mjs +104 -0
- package/dist/es/report-inspection.mjs.map +1 -0
- package/dist/es/report.mjs +10 -3
- 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/dump/html-utils.js +40 -2
- package/dist/lib/dump/html-utils.js.map +1 -1
- package/dist/lib/index.js +4 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/report-cli.js +23 -16
- package/dist/lib/report-cli.js.map +1 -1
- package/dist/lib/report-inspection.js +141 -0
- package/dist/lib/report-inspection.js.map +1 -0
- package/dist/lib/report.js +10 -3
- package/dist/lib/report.js.map +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/types/dump/html-utils.d.ts +6 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/report-cli.d.ts +1 -1
- package/dist/types/report-inspection.d.ts +25 -0
- package/dist/types/report.d.ts +4 -1
- package/package.json +2 -2
package/dist/es/agent/utils.mjs
CHANGED
|
@@ -181,7 +181,7 @@ async function matchElementFromCache(context, cacheEntry, cachePrompt, cacheable
|
|
|
181
181
|
return;
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
-
const getMidsceneVersion = ()=>"1.10.
|
|
184
|
+
const getMidsceneVersion = ()=>"1.10.11-beta-20260811045313.0";
|
|
185
185
|
const parsePrompt = (prompt)=>{
|
|
186
186
|
if ('string' == typeof prompt) return {
|
|
187
187
|
textPrompt: prompt,
|
|
@@ -229,6 +229,21 @@ function extractAllDumpScriptsSync(filePath) {
|
|
|
229
229
|
});
|
|
230
230
|
return results;
|
|
231
231
|
}
|
|
232
|
+
function hasTrailingOpenTagFragment(value, openTagPrefix) {
|
|
233
|
+
if (value.includes(openTagPrefix)) return true;
|
|
234
|
+
const maxLength = Math.min(value.length, openTagPrefix.length - 1);
|
|
235
|
+
for(let length = maxLength; length > 0; length -= 1)if (value.endsWith(openTagPrefix.slice(0, length))) return true;
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
function isCompleteJson(value) {
|
|
239
|
+
if (!value) return false;
|
|
240
|
+
try {
|
|
241
|
+
JSON.parse(value);
|
|
242
|
+
return true;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
232
247
|
function streamDumpScriptsSync(filePath, onMatch) {
|
|
233
248
|
const openTagPrefix = '<script type="midscene_web_dump"';
|
|
234
249
|
const closeTag = htmlScriptCloseTag();
|
|
@@ -254,7 +269,10 @@ function streamDumpScriptsSync(filePath, onMatch) {
|
|
|
254
269
|
openTag: currentOpenTag,
|
|
255
270
|
content: currentContent.trim()
|
|
256
271
|
});
|
|
257
|
-
if (shouldStop) return
|
|
272
|
+
if (shouldStop) return {
|
|
273
|
+
stoppedEarly: true,
|
|
274
|
+
incompleteDumpReason: null
|
|
275
|
+
};
|
|
258
276
|
capturing = false;
|
|
259
277
|
currentContent = '';
|
|
260
278
|
currentOpenTag = '';
|
|
@@ -278,7 +296,10 @@ function streamDumpScriptsSync(filePath, onMatch) {
|
|
|
278
296
|
openTag: currentOpenTag,
|
|
279
297
|
content: currentContent.slice(0, endIdx).trim()
|
|
280
298
|
});
|
|
281
|
-
if (shouldStop) return
|
|
299
|
+
if (shouldStop) return {
|
|
300
|
+
stoppedEarly: true,
|
|
301
|
+
incompleteDumpReason: null
|
|
302
|
+
};
|
|
282
303
|
capturing = false;
|
|
283
304
|
currentContent = '';
|
|
284
305
|
currentOpenTag = '';
|
|
@@ -301,6 +322,23 @@ function streamDumpScriptsSync(filePath, onMatch) {
|
|
|
301
322
|
} finally{
|
|
302
323
|
(0, external_node_fs_.closeSync)(fd);
|
|
303
324
|
}
|
|
325
|
+
if (capturing) {
|
|
326
|
+
const trailingContent = `${currentContent}${leftover}`.trim();
|
|
327
|
+
if (isCompleteJson(trailingContent)) {
|
|
328
|
+
const shouldStop = onMatch({
|
|
329
|
+
openTag: currentOpenTag,
|
|
330
|
+
content: trailingContent
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
stoppedEarly: shouldStop,
|
|
334
|
+
incompleteDumpReason: null
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
stoppedEarly: false,
|
|
340
|
+
incompleteDumpReason: capturing ? 'content' : hasTrailingOpenTagFragment(leftover, openTagPrefix) ? 'opening-tag' : null
|
|
341
|
+
};
|
|
304
342
|
}
|
|
305
343
|
function parseImageScripts(html) {
|
|
306
344
|
const imageMap = {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dump/html-utils.mjs","sources":["../../../src/dump/html-utils.ts"],"sourcesContent":["import { closeSync, openSync, readSync, statSync } from 'node:fs';\nimport { antiEscapeScriptTag, escapeScriptTag } from '@midscene/shared/utils';\nimport type { AIUsageInfo, IReportActionDump, ModelBrief } from '../types';\n\nexport const escapeContent = escapeScriptTag;\nexport const unescapeContent = antiEscapeScriptTag;\n\nfunction cleanHtmlCommentValue(value: unknown): string {\n return String(value ?? 'N/A')\n .replace(/\\0/g, '')\n .replace(/--/g, '- -');\n}\n\nfunction formatModelBriefForAgent(brief: ModelBrief): string {\n const intent = brief.intent || 'default';\n const model = brief.name || 'unknown';\n const description = brief.modelDescription\n ? ` (${brief.modelDescription})`\n : '';\n return `${intent}: ${model}${description}`;\n}\n\nfunction hasUsageModelInfo(usage?: AIUsageInfo): usage is AIUsageInfo {\n return Boolean(\n usage &&\n (usage.model_name ||\n usage.response_model_name ||\n usage.model_description ||\n usage.intent),\n );\n}\n\nfunction formatUsageModelForAgent(source: string, usage: AIUsageInfo): string {\n const intent = usage.intent || source;\n const model = usage.model_name || usage.response_model_name || 'unknown';\n const description = usage.model_description\n ? ` (${usage.model_description})`\n : '';\n return `${intent}: ${model}${description}`;\n}\n\nfunction collectUsageModelsForAgent(report: IReportActionDump): string {\n const models = new Map<string, string>();\n const executions = Array.isArray(report.executions) ? report.executions : [];\n for (const execution of executions) {\n const tasks = Array.isArray(execution.tasks) ? execution.tasks : [];\n for (const task of tasks) {\n const taskWithUsage = task as {\n usage?: AIUsageInfo;\n searchAreaUsage?: AIUsageInfo;\n };\n const usages: Array<[string, AIUsageInfo | undefined]> = [\n ['main', taskWithUsage.usage],\n ['searchArea', taskWithUsage.searchAreaUsage],\n ];\n\n for (const [source, usage] of usages) {\n if (!hasUsageModelInfo(usage)) {\n continue;\n }\n const formatted = formatUsageModelForAgent(source, usage);\n models.set(formatted, formatted);\n }\n }\n }\n\n return Array.from(models.values()).join('; ');\n}\n\nexport function generateAgentReportComment(report: IReportActionDump): string {\n const executions = Array.isArray(report.executions) ? report.executions : [];\n const taskCount = executions.reduce(\n (sum, execution) =>\n sum + (Array.isArray(execution.tasks) ? execution.tasks.length : 0),\n 0,\n );\n const modelBriefs = report.modelBriefs?.length\n ? report.modelBriefs.map(formatModelBriefForAgent).join('; ')\n : collectUsageModelsForAgent(report) || 'No model metadata recorded';\n\n const lines = [\n 'For Agent Analysis:',\n `Report: ${cleanHtmlCommentValue(report.groupName)}`,\n `SDK: ${cleanHtmlCommentValue(report.sdkVersion)}`,\n `Device: ${cleanHtmlCommentValue(report.deviceType)}`,\n `Executions: ${executions.length}; Tasks: ${taskCount}`,\n `Models: ${cleanHtmlCommentValue(modelBriefs)}`,\n 'Structured report JSON is stored in script[type=\"midscene_web_dump\"] tags near this comment.',\n 'Screenshots are stored as script[type=\"midscene-image\"] tags or files referenced by screenshot refs.',\n 'For AI analysis, inspect each execution task for type, status, timing, param, output, usage, searchAreaUsage, recorder, and screenshot refs.',\n 'Use the Markdown export when available; it contains the same report context plus image links for agent review.',\n ];\n\n return `\\n<!--\\n${lines.join('\\n')}\\n-->\\n`;\n}\n\nfunction htmlScriptCloseTag(): string {\n // biome-ignore lint/style/useTemplate: keep this token runtime-built for inline report bundles\n return String.fromCharCode(60) + '/script>';\n}\n\n/** Chunk size for streaming file operations (64KB) */\nexport const STREAMING_CHUNK_SIZE = 64 * 1024;\n\n/**\n * Callback for processing matched tags during streaming.\n * @param content - The content between open and close tags\n * @returns true to stop scanning, false to continue\n */\ntype TagMatchCallback = (content: string) => boolean;\n\n/**\n * Stream through a file and find tags matching the pattern.\n * Memory usage: O(chunk_size + tag_size), not O(file_size).\n *\n * @param filePath - Absolute path to the file\n * @param openTag - Opening tag to search for\n * @param closeTag - Closing tag\n * @param onMatch - Callback for each matched tag content\n */\nexport function streamScanTags(\n filePath: string,\n openTag: string,\n closeTag: string,\n onMatch: TagMatchCallback,\n): void {\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTag, searchStart);\n if (startIdx !== -1) {\n capturing = true;\n currentContent = chunk.slice(startIdx + openTag.length);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n const shouldStop = onMatch(currentContent.slice(0, endIdx));\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n searchStart =\n startIdx + openTag.length + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(-openTag.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n const shouldStop = onMatch(currentContent);\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Synchronously extract a specific image's base64 data from an HTML file by its id.\n * Uses streaming to avoid loading the entire file into memory.\n *\n * @param htmlPath - Absolute path to the HTML file\n * @param imageId - The id of the image to extract\n * @returns The base64 data string, or null if not found\n */\nexport function extractImageByIdSync(\n htmlPath: string,\n imageId: string,\n): string | null {\n const targetTag = `<script type=\"midscene-image\" data-id=\"${imageId}\">`;\n const closeTag = htmlScriptCloseTag();\n\n let result: string | null = null;\n\n streamScanTags(htmlPath, targetTag, closeTag, (content) => {\n result = unescapeContent(content);\n return true; // Stop after first match\n });\n\n return result;\n}\n\n/**\n * Stream image script tags from source file directly to output file.\n * Memory usage: O(single_image_size), not O(all_images_size).\n *\n * @param srcFilePath - Source HTML file path\n * @param destFilePath - Destination file path to append to\n */\nexport function streamImageScriptsToFile(\n srcFilePath: string,\n destFilePath: string,\n): void {\n const { appendFileSync } = require('node:fs');\n const openTag = '<script type=\"midscene-image\"';\n const closeTag = htmlScriptCloseTag();\n\n streamScanTags(srcFilePath, openTag, closeTag, (content) => {\n // Write complete tag immediately to destination, don't accumulate\n appendFileSync(destFilePath, `${openTag}${content}${closeTag}\\n`);\n return false; // Continue scanning for more tags\n });\n}\n\n/**\n * Extract the LAST dump script content from HTML file using streaming.\n * Memory usage: O(dump_size), not O(file_size).\n *\n * @param filePath - Absolute path to the HTML file\n * @returns The dump script content (trimmed), or empty string if not found\n */\nexport function extractLastDumpScriptSync(filePath: string): string {\n const openTagPrefix = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n let lastContent = '';\n\n // Custom streaming to handle the special case where open tag has variable attributes\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTagPrefix, searchStart);\n if (startIdx !== -1) {\n // Find the end of the opening tag (the '>' character)\n const tagEndIdx = chunk.indexOf('>', startIdx);\n if (tagEndIdx !== -1) {\n capturing = true;\n currentContent = chunk.slice(tagEndIdx + 1);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n lastContent = currentContent.slice(0, endIdx).trim();\n capturing = false;\n currentContent = '';\n searchStart = tagEndIdx + 1 + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(startIdx);\n break;\n }\n } else {\n leftover = chunk.slice(-openTagPrefix.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n lastContent = currentContent.trim();\n capturing = false;\n currentContent = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n\n return lastContent;\n}\n\n/**\n * Extract ALL dump script contents from an HTML file using streaming.\n * Each entry includes the full opening tag (for attribute extraction) and the content.\n *\n * @param filePath - Absolute path to the HTML file\n * @returns Array of { openTag, content } for each dump script found\n */\nexport function extractAllDumpScriptsSync(\n filePath: string,\n): { openTag: string; content: string }[] {\n const results: { openTag: string; content: string }[] = [];\n streamDumpScriptsSync(filePath, (dumpScript) => {\n results.push(dumpScript);\n return false;\n });\n return results;\n}\n\n/**\n * Stream ALL dump scripts from an HTML file.\n * Calls onMatch for each dump script and keeps memory bounded to a single\n * dump script payload instead of accumulating every dump in memory.\n *\n * @param filePath - Absolute path to the HTML file\n * @param onMatch - Callback for each dump script; return true to stop early\n */\nexport function streamDumpScriptsSync(\n filePath: string,\n onMatch: (dumpScript: { openTag: string; content: string }) => boolean,\n): void {\n const openTagPrefix = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n let currentOpenTag = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTagPrefix, searchStart);\n if (startIdx !== -1) {\n const tagEndIdx = chunk.indexOf('>', startIdx);\n if (tagEndIdx !== -1) {\n capturing = true;\n currentOpenTag = chunk.slice(startIdx, tagEndIdx + 1);\n currentContent = chunk.slice(tagEndIdx + 1);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n const shouldStop = onMatch({\n openTag: currentOpenTag,\n content: currentContent.slice(0, endIdx).trim(),\n });\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n currentOpenTag = '';\n searchStart = tagEndIdx + 1 + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(startIdx);\n break;\n }\n } else {\n leftover = chunk.slice(-openTagPrefix.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n const shouldStop = onMatch({\n openTag: currentOpenTag,\n content: currentContent.trim(),\n });\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n currentOpenTag = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n}\n\nexport function parseImageScripts(html: string): Record<string, string> {\n const imageMap: Record<string, string> = {};\n const regex =\n /<script type=\"midscene-image\" data-id=\"([^\"]+)\">([\\s\\S]*?)<\\/script>/g;\n\n for (const match of html.matchAll(regex)) {\n const [, id, content] = match;\n imageMap[id] = unescapeContent(content);\n }\n\n return imageMap;\n}\n\nexport function parseDumpScript(html: string): string {\n // Use string search instead of regex to avoid ReDoS vulnerability\n // Find the LAST dump script tag (template may contain similar patterns in bundled JS)\n const scriptOpenTag = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n // Find the last occurrence of the opening tag\n const lastOpenIndex = html.lastIndexOf(scriptOpenTag);\n if (lastOpenIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Find the end of the opening tag (the '>' character)\n const tagEndIndex = html.indexOf('>', lastOpenIndex);\n if (tagEndIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Find the closing tag after the opening tag\n const closeIndex = html.indexOf(closeTag, tagEndIndex);\n if (closeIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Extract content between opening and closing tags\n const content = html.substring(tagEndIndex + 1, closeIndex);\n return unescapeContent(content);\n}\n\nexport function parseDumpScriptAttributes(\n html: string,\n): Record<string, string> {\n const regex = /<script type=\"midscene_web_dump\"([^>]*)>/;\n const match = regex.exec(html);\n\n if (!match) {\n return {};\n }\n\n const attrString = match[1];\n const attributes: Record<string, string> = {};\n const attrRegex = /(\\w+)=\"([^\"]*)\"/g;\n\n for (const attrMatch of attrString.matchAll(attrRegex)) {\n const [, key, value] = attrMatch;\n if (key !== 'type') {\n attributes[key] = decodeURIComponent(value);\n }\n }\n\n return attributes;\n}\n\nexport function generateImageScriptTag(id: string, data: string): string {\n // Do not use template string here, will cause bundle error with <script\n const closeTag = htmlScriptCloseTag();\n return (\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene-image\" data-id=\"' +\n id +\n '\">' +\n escapeContent(data) +\n closeTag\n );\n}\n\n/**\n * Inline script that fixes relative URL resolution for directory-mode reports.\n *\n * Problem: when a static server (e.g. `npx serve`) serves `name/index.html`\n * at URL `/name` (without trailing slash), relative paths like\n * `./screenshots/xxx.png` resolve to `/screenshots/xxx.png` instead of\n * `/name/screenshots/xxx.png`.\n *\n * Fix: dynamically insert a <base> tag so relative URLs resolve correctly.\n */\n// Do not use template string here, will cause bundle error with <script\n//\n// The closing script tag is built at runtime so bundlers cannot inline the\n// token that would prematurely close the report template's inline app bundle.\n//\n// Do NOT replace this with a string constant, hex escape (\\x3c), or literal\n// string concatenation. Bundlers may optimise those forms back to the unsafe\n// token.\nlet _baseUrlFixScript: string;\nexport function getBaseUrlFixScript(): string {\n if (!_baseUrlFixScript) {\n const close = htmlScriptCloseTag();\n _baseUrlFixScript =\n // biome-ignore lint/style/useTemplate: see above\n '\\n<script>(function(){' +\n 'var p=window.location.pathname;' +\n 'if(p.endsWith(\"/\")||/\\\\.\\\\w+$/.test(p))return;' +\n 'var b=document.createElement(\"base\");' +\n 'b.href=p+\"/\";' +\n 'document.head.insertBefore(b,document.head.firstChild)' +\n '})()' +\n close +\n '\\n';\n }\n return _baseUrlFixScript;\n}\n\n/**\n * Dump-script attribute that records how the report file stores screenshots.\n * Written by the report generator and the merger, read back when deciding\n * whether a report is in directory mode. Kept here, next to dump-tag\n * generation, so the writer and reader share one source of truth.\n */\nexport const DATA_SCREENSHOT_MODE_ATTR = 'data-screenshot-mode';\n\nexport function generateDumpScriptTag(\n json: string,\n attributes?: Record<string, string | number | boolean>,\n): string {\n const closeTag = htmlScriptCloseTag();\n let attrString = '';\n if (attributes && Object.keys(attributes).length > 0) {\n // Do not use template string here, will cause bundle error with <script\n attrString =\n // biome-ignore lint/style/useTemplate: <explanation>\n ' ' +\n Object.entries(attributes)\n // biome-ignore lint/style/useTemplate: <explanation>\n .map(([k, v]) => k + '=\"' + encodeURIComponent(v) + '\"')\n .join(' ');\n }\n\n // Do not use template string here, will cause bundle error with <script\n return (\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene_web_dump\"' +\n attrString +\n '>' +\n escapeContent(json) +\n closeTag\n );\n}\n"],"names":["escapeContent","escapeScriptTag","unescapeContent","antiEscapeScriptTag","cleanHtmlCommentValue","value","String","formatModelBriefForAgent","brief","intent","model","description","hasUsageModelInfo","usage","Boolean","formatUsageModelForAgent","source","collectUsageModelsForAgent","report","models","Map","executions","Array","execution","tasks","task","taskWithUsage","usages","formatted","generateAgentReportComment","taskCount","sum","modelBriefs","lines","htmlScriptCloseTag","STREAMING_CHUNK_SIZE","streamScanTags","filePath","openTag","closeTag","onMatch","fd","openSync","fileSize","statSync","buffer","Buffer","position","leftover","capturing","currentContent","bytesRead","readSync","chunk","searchStart","endIdx","shouldStop","startIdx","closeSync","extractImageByIdSync","htmlPath","imageId","targetTag","result","content","streamImageScriptsToFile","srcFilePath","destFilePath","appendFileSync","require","extractLastDumpScriptSync","openTagPrefix","lastContent","tagEndIdx","extractAllDumpScriptsSync","results","streamDumpScriptsSync","dumpScript","currentOpenTag","parseImageScripts","html","imageMap","regex","match","id","parseDumpScript","scriptOpenTag","lastOpenIndex","Error","tagEndIndex","closeIndex","parseDumpScriptAttributes","attrString","attributes","attrRegex","attrMatch","key","decodeURIComponent","generateImageScriptTag","data","_baseUrlFixScript","getBaseUrlFixScript","close","DATA_SCREENSHOT_MODE_ATTR","generateDumpScriptTag","json","Object","k","v","encodeURIComponent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dump/html-utils.mjs","sources":["../../../src/dump/html-utils.ts"],"sourcesContent":["import { closeSync, openSync, readSync, statSync } from 'node:fs';\nimport { antiEscapeScriptTag, escapeScriptTag } from '@midscene/shared/utils';\nimport type { AIUsageInfo, IReportActionDump, ModelBrief } from '../types';\n\nexport const escapeContent = escapeScriptTag;\nexport const unescapeContent = antiEscapeScriptTag;\n\nfunction cleanHtmlCommentValue(value: unknown): string {\n return String(value ?? 'N/A')\n .replace(/\\0/g, '')\n .replace(/--/g, '- -');\n}\n\nfunction formatModelBriefForAgent(brief: ModelBrief): string {\n const intent = brief.intent || 'default';\n const model = brief.name || 'unknown';\n const description = brief.modelDescription\n ? ` (${brief.modelDescription})`\n : '';\n return `${intent}: ${model}${description}`;\n}\n\nfunction hasUsageModelInfo(usage?: AIUsageInfo): usage is AIUsageInfo {\n return Boolean(\n usage &&\n (usage.model_name ||\n usage.response_model_name ||\n usage.model_description ||\n usage.intent),\n );\n}\n\nfunction formatUsageModelForAgent(source: string, usage: AIUsageInfo): string {\n const intent = usage.intent || source;\n const model = usage.model_name || usage.response_model_name || 'unknown';\n const description = usage.model_description\n ? ` (${usage.model_description})`\n : '';\n return `${intent}: ${model}${description}`;\n}\n\nfunction collectUsageModelsForAgent(report: IReportActionDump): string {\n const models = new Map<string, string>();\n const executions = Array.isArray(report.executions) ? report.executions : [];\n for (const execution of executions) {\n const tasks = Array.isArray(execution.tasks) ? execution.tasks : [];\n for (const task of tasks) {\n const taskWithUsage = task as {\n usage?: AIUsageInfo;\n searchAreaUsage?: AIUsageInfo;\n };\n const usages: Array<[string, AIUsageInfo | undefined]> = [\n ['main', taskWithUsage.usage],\n ['searchArea', taskWithUsage.searchAreaUsage],\n ];\n\n for (const [source, usage] of usages) {\n if (!hasUsageModelInfo(usage)) {\n continue;\n }\n const formatted = formatUsageModelForAgent(source, usage);\n models.set(formatted, formatted);\n }\n }\n }\n\n return Array.from(models.values()).join('; ');\n}\n\nexport function generateAgentReportComment(report: IReportActionDump): string {\n const executions = Array.isArray(report.executions) ? report.executions : [];\n const taskCount = executions.reduce(\n (sum, execution) =>\n sum + (Array.isArray(execution.tasks) ? execution.tasks.length : 0),\n 0,\n );\n const modelBriefs = report.modelBriefs?.length\n ? report.modelBriefs.map(formatModelBriefForAgent).join('; ')\n : collectUsageModelsForAgent(report) || 'No model metadata recorded';\n\n const lines = [\n 'For Agent Analysis:',\n `Report: ${cleanHtmlCommentValue(report.groupName)}`,\n `SDK: ${cleanHtmlCommentValue(report.sdkVersion)}`,\n `Device: ${cleanHtmlCommentValue(report.deviceType)}`,\n `Executions: ${executions.length}; Tasks: ${taskCount}`,\n `Models: ${cleanHtmlCommentValue(modelBriefs)}`,\n 'Structured report JSON is stored in script[type=\"midscene_web_dump\"] tags near this comment.',\n 'Screenshots are stored as script[type=\"midscene-image\"] tags or files referenced by screenshot refs.',\n 'For AI analysis, inspect each execution task for type, status, timing, param, output, usage, searchAreaUsage, recorder, and screenshot refs.',\n 'Use the Markdown export when available; it contains the same report context plus image links for agent review.',\n ];\n\n return `\\n<!--\\n${lines.join('\\n')}\\n-->\\n`;\n}\n\nfunction htmlScriptCloseTag(): string {\n // biome-ignore lint/style/useTemplate: keep this token runtime-built for inline report bundles\n return String.fromCharCode(60) + '/script>';\n}\n\n/** Chunk size for streaming file operations (64KB) */\nexport const STREAMING_CHUNK_SIZE = 64 * 1024;\n\n/**\n * Callback for processing matched tags during streaming.\n * @param content - The content between open and close tags\n * @returns true to stop scanning, false to continue\n */\ntype TagMatchCallback = (content: string) => boolean;\n\n/**\n * Stream through a file and find tags matching the pattern.\n * Memory usage: O(chunk_size + tag_size), not O(file_size).\n *\n * @param filePath - Absolute path to the file\n * @param openTag - Opening tag to search for\n * @param closeTag - Closing tag\n * @param onMatch - Callback for each matched tag content\n */\nexport function streamScanTags(\n filePath: string,\n openTag: string,\n closeTag: string,\n onMatch: TagMatchCallback,\n): void {\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTag, searchStart);\n if (startIdx !== -1) {\n capturing = true;\n currentContent = chunk.slice(startIdx + openTag.length);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n const shouldStop = onMatch(currentContent.slice(0, endIdx));\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n searchStart =\n startIdx + openTag.length + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(-openTag.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n const shouldStop = onMatch(currentContent);\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Synchronously extract a specific image's base64 data from an HTML file by its id.\n * Uses streaming to avoid loading the entire file into memory.\n *\n * @param htmlPath - Absolute path to the HTML file\n * @param imageId - The id of the image to extract\n * @returns The base64 data string, or null if not found\n */\nexport function extractImageByIdSync(\n htmlPath: string,\n imageId: string,\n): string | null {\n const targetTag = `<script type=\"midscene-image\" data-id=\"${imageId}\">`;\n const closeTag = htmlScriptCloseTag();\n\n let result: string | null = null;\n\n streamScanTags(htmlPath, targetTag, closeTag, (content) => {\n result = unescapeContent(content);\n return true; // Stop after first match\n });\n\n return result;\n}\n\n/**\n * Stream image script tags from source file directly to output file.\n * Memory usage: O(single_image_size), not O(all_images_size).\n *\n * @param srcFilePath - Source HTML file path\n * @param destFilePath - Destination file path to append to\n */\nexport function streamImageScriptsToFile(\n srcFilePath: string,\n destFilePath: string,\n): void {\n const { appendFileSync } = require('node:fs');\n const openTag = '<script type=\"midscene-image\"';\n const closeTag = htmlScriptCloseTag();\n\n streamScanTags(srcFilePath, openTag, closeTag, (content) => {\n // Write complete tag immediately to destination, don't accumulate\n appendFileSync(destFilePath, `${openTag}${content}${closeTag}\\n`);\n return false; // Continue scanning for more tags\n });\n}\n\n/**\n * Extract the LAST dump script content from HTML file using streaming.\n * Memory usage: O(dump_size), not O(file_size).\n *\n * @param filePath - Absolute path to the HTML file\n * @returns The dump script content (trimmed), or empty string if not found\n */\nexport function extractLastDumpScriptSync(filePath: string): string {\n const openTagPrefix = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n let lastContent = '';\n\n // Custom streaming to handle the special case where open tag has variable attributes\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTagPrefix, searchStart);\n if (startIdx !== -1) {\n // Find the end of the opening tag (the '>' character)\n const tagEndIdx = chunk.indexOf('>', startIdx);\n if (tagEndIdx !== -1) {\n capturing = true;\n currentContent = chunk.slice(tagEndIdx + 1);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n lastContent = currentContent.slice(0, endIdx).trim();\n capturing = false;\n currentContent = '';\n searchStart = tagEndIdx + 1 + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(startIdx);\n break;\n }\n } else {\n leftover = chunk.slice(-openTagPrefix.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n lastContent = currentContent.trim();\n capturing = false;\n currentContent = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n\n return lastContent;\n}\n\n/**\n * Extract ALL dump script contents from an HTML file using streaming.\n * Each entry includes the full opening tag (for attribute extraction) and the content.\n *\n * @param filePath - Absolute path to the HTML file\n * @returns Array of { openTag, content } for each dump script found\n */\nexport function extractAllDumpScriptsSync(\n filePath: string,\n): { openTag: string; content: string }[] {\n const results: { openTag: string; content: string }[] = [];\n streamDumpScriptsSync(filePath, (dumpScript) => {\n results.push(dumpScript);\n return false;\n });\n return results;\n}\n\nexport type IncompleteReportDumpReason = 'opening-tag' | 'content';\n\nexport interface ReportDumpScanResult {\n stoppedEarly: boolean;\n incompleteDumpReason: IncompleteReportDumpReason | null;\n}\n\nfunction hasTrailingOpenTagFragment(\n value: string,\n openTagPrefix: string,\n): boolean {\n if (value.includes(openTagPrefix)) return true;\n\n const maxLength = Math.min(value.length, openTagPrefix.length - 1);\n for (let length = maxLength; length > 0; length -= 1) {\n if (value.endsWith(openTagPrefix.slice(0, length))) return true;\n }\n\n return false;\n}\n\nfunction isCompleteJson(value: string): boolean {\n if (!value) return false;\n try {\n JSON.parse(value);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Stream ALL dump scripts from an HTML file.\n * Calls onMatch for each dump script and keeps memory bounded to a single\n * dump script payload instead of accumulating every dump in memory.\n *\n * @param filePath - Absolute path to the HTML file\n * @param onMatch - Callback for each dump script; return true to stop early\n */\nexport function streamDumpScriptsSync(\n filePath: string,\n onMatch: (dumpScript: { openTag: string; content: string }) => boolean,\n): ReportDumpScanResult {\n const openTagPrefix = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n let currentOpenTag = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTagPrefix, searchStart);\n if (startIdx !== -1) {\n const tagEndIdx = chunk.indexOf('>', startIdx);\n if (tagEndIdx !== -1) {\n capturing = true;\n currentOpenTag = chunk.slice(startIdx, tagEndIdx + 1);\n currentContent = chunk.slice(tagEndIdx + 1);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n const shouldStop = onMatch({\n openTag: currentOpenTag,\n content: currentContent.slice(0, endIdx).trim(),\n });\n if (shouldStop) {\n return {\n stoppedEarly: true,\n incompleteDumpReason: null,\n };\n }\n capturing = false;\n currentContent = '';\n currentOpenTag = '';\n searchStart = tagEndIdx + 1 + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(startIdx);\n break;\n }\n } else {\n leftover = chunk.slice(-openTagPrefix.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n const shouldStop = onMatch({\n openTag: currentOpenTag,\n content: currentContent.trim(),\n });\n if (shouldStop) {\n return {\n stoppedEarly: true,\n incompleteDumpReason: null,\n };\n }\n capturing = false;\n currentContent = '';\n currentOpenTag = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n\n if (capturing) {\n const trailingContent = `${currentContent}${leftover}`.trim();\n if (isCompleteJson(trailingContent)) {\n const shouldStop = onMatch({\n openTag: currentOpenTag,\n content: trailingContent,\n });\n return {\n stoppedEarly: shouldStop,\n incompleteDumpReason: null,\n };\n }\n }\n\n return {\n stoppedEarly: false,\n incompleteDumpReason: capturing\n ? 'content'\n : hasTrailingOpenTagFragment(leftover, openTagPrefix)\n ? 'opening-tag'\n : null,\n };\n}\n\nexport function parseImageScripts(html: string): Record<string, string> {\n const imageMap: Record<string, string> = {};\n const regex =\n /<script type=\"midscene-image\" data-id=\"([^\"]+)\">([\\s\\S]*?)<\\/script>/g;\n\n for (const match of html.matchAll(regex)) {\n const [, id, content] = match;\n imageMap[id] = unescapeContent(content);\n }\n\n return imageMap;\n}\n\nexport function parseDumpScript(html: string): string {\n // Use string search instead of regex to avoid ReDoS vulnerability\n // Find the LAST dump script tag (template may contain similar patterns in bundled JS)\n const scriptOpenTag = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n // Find the last occurrence of the opening tag\n const lastOpenIndex = html.lastIndexOf(scriptOpenTag);\n if (lastOpenIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Find the end of the opening tag (the '>' character)\n const tagEndIndex = html.indexOf('>', lastOpenIndex);\n if (tagEndIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Find the closing tag after the opening tag\n const closeIndex = html.indexOf(closeTag, tagEndIndex);\n if (closeIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Extract content between opening and closing tags\n const content = html.substring(tagEndIndex + 1, closeIndex);\n return unescapeContent(content);\n}\n\nexport function parseDumpScriptAttributes(\n html: string,\n): Record<string, string> {\n const regex = /<script type=\"midscene_web_dump\"([^>]*)>/;\n const match = regex.exec(html);\n\n if (!match) {\n return {};\n }\n\n const attrString = match[1];\n const attributes: Record<string, string> = {};\n const attrRegex = /(\\w+)=\"([^\"]*)\"/g;\n\n for (const attrMatch of attrString.matchAll(attrRegex)) {\n const [, key, value] = attrMatch;\n if (key !== 'type') {\n attributes[key] = decodeURIComponent(value);\n }\n }\n\n return attributes;\n}\n\nexport function generateImageScriptTag(id: string, data: string): string {\n // Do not use template string here, will cause bundle error with <script\n const closeTag = htmlScriptCloseTag();\n return (\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene-image\" data-id=\"' +\n id +\n '\">' +\n escapeContent(data) +\n closeTag\n );\n}\n\n/**\n * Inline script that fixes relative URL resolution for directory-mode reports.\n *\n * Problem: when a static server (e.g. `npx serve`) serves `name/index.html`\n * at URL `/name` (without trailing slash), relative paths like\n * `./screenshots/xxx.png` resolve to `/screenshots/xxx.png` instead of\n * `/name/screenshots/xxx.png`.\n *\n * Fix: dynamically insert a <base> tag so relative URLs resolve correctly.\n */\n// Do not use template string here, will cause bundle error with <script\n//\n// The closing script tag is built at runtime so bundlers cannot inline the\n// token that would prematurely close the report template's inline app bundle.\n//\n// Do NOT replace this with a string constant, hex escape (\\x3c), or literal\n// string concatenation. Bundlers may optimise those forms back to the unsafe\n// token.\nlet _baseUrlFixScript: string;\nexport function getBaseUrlFixScript(): string {\n if (!_baseUrlFixScript) {\n const close = htmlScriptCloseTag();\n _baseUrlFixScript =\n // biome-ignore lint/style/useTemplate: see above\n '\\n<script>(function(){' +\n 'var p=window.location.pathname;' +\n 'if(p.endsWith(\"/\")||/\\\\.\\\\w+$/.test(p))return;' +\n 'var b=document.createElement(\"base\");' +\n 'b.href=p+\"/\";' +\n 'document.head.insertBefore(b,document.head.firstChild)' +\n '})()' +\n close +\n '\\n';\n }\n return _baseUrlFixScript;\n}\n\n/**\n * Dump-script attribute that records how the report file stores screenshots.\n * Written by the report generator and the merger, read back when deciding\n * whether a report is in directory mode. Kept here, next to dump-tag\n * generation, so the writer and reader share one source of truth.\n */\nexport const DATA_SCREENSHOT_MODE_ATTR = 'data-screenshot-mode';\n\nexport function generateDumpScriptTag(\n json: string,\n attributes?: Record<string, string | number | boolean>,\n): string {\n const closeTag = htmlScriptCloseTag();\n let attrString = '';\n if (attributes && Object.keys(attributes).length > 0) {\n // Do not use template string here, will cause bundle error with <script\n attrString =\n // biome-ignore lint/style/useTemplate: <explanation>\n ' ' +\n Object.entries(attributes)\n // biome-ignore lint/style/useTemplate: <explanation>\n .map(([k, v]) => k + '=\"' + encodeURIComponent(v) + '\"')\n .join(' ');\n }\n\n // Do not use template string here, will cause bundle error with <script\n return (\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene_web_dump\"' +\n attrString +\n '>' +\n escapeContent(json) +\n closeTag\n );\n}\n"],"names":["escapeContent","escapeScriptTag","unescapeContent","antiEscapeScriptTag","cleanHtmlCommentValue","value","String","formatModelBriefForAgent","brief","intent","model","description","hasUsageModelInfo","usage","Boolean","formatUsageModelForAgent","source","collectUsageModelsForAgent","report","models","Map","executions","Array","execution","tasks","task","taskWithUsage","usages","formatted","generateAgentReportComment","taskCount","sum","modelBriefs","lines","htmlScriptCloseTag","STREAMING_CHUNK_SIZE","streamScanTags","filePath","openTag","closeTag","onMatch","fd","openSync","fileSize","statSync","buffer","Buffer","position","leftover","capturing","currentContent","bytesRead","readSync","chunk","searchStart","endIdx","shouldStop","startIdx","closeSync","extractImageByIdSync","htmlPath","imageId","targetTag","result","content","streamImageScriptsToFile","srcFilePath","destFilePath","appendFileSync","require","extractLastDumpScriptSync","openTagPrefix","lastContent","tagEndIdx","extractAllDumpScriptsSync","results","streamDumpScriptsSync","dumpScript","hasTrailingOpenTagFragment","maxLength","Math","length","isCompleteJson","JSON","currentOpenTag","trailingContent","parseImageScripts","html","imageMap","regex","match","id","parseDumpScript","scriptOpenTag","lastOpenIndex","Error","tagEndIndex","closeIndex","parseDumpScriptAttributes","attrString","attributes","attrRegex","attrMatch","key","decodeURIComponent","generateImageScriptTag","data","_baseUrlFixScript","getBaseUrlFixScript","close","DATA_SCREENSHOT_MODE_ATTR","generateDumpScriptTag","json","Object","k","v","encodeURIComponent"],"mappings":"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"}
|
package/dist/es/index.mjs
CHANGED
|
@@ -12,10 +12,11 @@ import { getTaskSearchArea, getTaskServiceDump } from "./dump/task-service-dump.
|
|
|
12
12
|
import { ReportGenerator, nullReportGenerator } from "./report-generator.mjs";
|
|
13
13
|
import { ReportMergingTool, collectDedupedExecutions, dedupeExecutionsKeepLatest, splitReportHtmlByExecution } from "./report.mjs";
|
|
14
14
|
import { createReportCliCommands, mergeReportFiles, reportFileToMarkdown, splitReportFile } from "./report-cli.mjs";
|
|
15
|
+
import { inspectReportFile } from "./report-inspection.mjs";
|
|
15
16
|
import { ScreenshotItem } from "./screenshot-item.mjs";
|
|
16
17
|
import { ScreenshotStore } from "./dump/screenshot-store.mjs";
|
|
17
18
|
import { executionToMarkdown, reportToMarkdown } from "./report-markdown.mjs";
|
|
18
19
|
const src = service;
|
|
19
|
-
export { Agent, AiLocateElement, ExecutionDump, GroupedActionDump, MIDSCENE_MODEL_NAME, PointSchema, RectSchema, ReportActionDump, ReportGenerator, ReportMergingTool, ScreenshotItem, ScreenshotStore, service as Service, ServiceError, SizeSchema, TMultimodalPromptSchema, TUserPromptSchema, TaskRunner, collectDedupedExecutions, createAgent, createReportCliCommands, dedupeExecutionsKeepLatest, src as default, deriveCaseStatus, deriveTaskStatus, describeElementAtPoint, escapeContent, executionToMarkdown, generateDumpScriptTag, generateImageScriptTag, getMidsceneLocationSchema, getTaskSearchArea, getTaskServiceDump, getVersion, mergeReportFiles, nullReportGenerator, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, plan, reportFileToMarkdown, reportToMarkdown, restoreImageReferences, runConnectivityTest, splitReportFile, splitReportHtmlByExecution, unescapeContent, verifyElementDescriptionAtPoint, verifyLocator, z };
|
|
20
|
+
export { Agent, AiLocateElement, ExecutionDump, GroupedActionDump, MIDSCENE_MODEL_NAME, PointSchema, RectSchema, ReportActionDump, ReportGenerator, ReportMergingTool, ScreenshotItem, ScreenshotStore, service as Service, ServiceError, SizeSchema, TMultimodalPromptSchema, TUserPromptSchema, TaskRunner, collectDedupedExecutions, createAgent, createReportCliCommands, dedupeExecutionsKeepLatest, src as default, deriveCaseStatus, deriveTaskStatus, describeElementAtPoint, escapeContent, executionToMarkdown, generateDumpScriptTag, generateImageScriptTag, getMidsceneLocationSchema, getTaskSearchArea, getTaskServiceDump, getVersion, inspectReportFile, mergeReportFiles, nullReportGenerator, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, plan, reportFileToMarkdown, reportToMarkdown, restoreImageReferences, runConnectivityTest, splitReportFile, splitReportHtmlByExecution, unescapeContent, verifyElementDescriptionAtPoint, verifyLocator, z };
|
|
20
21
|
|
|
21
22
|
//# sourceMappingURL=index.mjs.map
|
package/dist/es/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../src/index.ts"],"sourcesContent":["import { z } from 'zod';\nimport Service from './service/index';\nimport { TaskRunner } from './task-runner';\nimport { getVersion } from './utils';\n\nexport {\n plan,\n AiLocateElement,\n runConnectivityTest,\n getMidsceneLocationSchema,\n PointSchema,\n SizeSchema,\n RectSchema,\n TMultimodalPromptSchema,\n TUserPromptSchema,\n type TMultimodalPrompt,\n type TUserPrompt,\n type ConnectivityTestConfig,\n type ConnectivityTestResult,\n} from './ai-model/index';\n\nexport {\n MIDSCENE_MODEL_NAME,\n type CreateOpenAIClientFn,\n} from '@midscene/shared/env';\n\nexport type * from './types';\nexport {\n ServiceError,\n ExecutionDump,\n ReportActionDump,\n GroupedActionDump,\n type IExecutionDump,\n type IReportActionDump,\n type IGroupedActionDump,\n type ReportMeta,\n type GroupMeta,\n} from './types';\n\nexport { z };\n\nexport default Service;\nexport { TaskRunner, Service, getVersion };\n\nexport type {\n MidsceneYamlScript,\n MidsceneYamlTask,\n MidsceneYamlFlowItem,\n MidsceneYamlConfigResult,\n MidsceneYamlConfig,\n MidsceneYamlScriptWebEnv,\n MidsceneYamlScriptAndroidEnv,\n MidsceneYamlScriptIOSEnv,\n MidsceneYamlScriptEnv,\n LocateOption,\n DetailedLocateParam,\n} from './yaml';\n\nexport {\n Agent,\n type AgentOpt,\n type AiActOptions,\n type GherkinStepKeyword,\n type MidsceneUsageMetrics,\n type RunGherkinScenarioOptions,\n type UsageBucket,\n createAgent,\n type UIObservation,\n type UIObserver,\n type UIObserverOption,\n} from './agent';\nexport {\n describeElementAtPoint,\n verifyElementDescriptionAtPoint,\n verifyLocator,\n type DescribeElementAtPointOptions,\n type DescribeElementCoordinateSpace,\n type ElementDescriberRuntime,\n type VerifyElementDescriptionAtPointOptions,\n} from './element-describer';\n\n// Dump utilities\nexport {\n restoreImageReferences,\n escapeContent,\n unescapeContent,\n parseImageScripts,\n parseDumpScript,\n parseDumpScriptAttributes,\n generateImageScriptTag,\n generateDumpScriptTag,\n deriveTaskStatus,\n deriveCaseStatus,\n} from './dump';\nexport type { TaskStatusFields, DerivedTaskStatus } from './dump';\nexport {\n getTaskSearchArea,\n getTaskServiceDump,\n} from './dump/task-service-dump';\n\n// Report generator\nexport type { IReportGenerator } from './report-generator';\nexport { ReportGenerator, nullReportGenerator } from './report-generator';\nexport {\n collectDedupedExecutions,\n ReportMergingTool,\n dedupeExecutionsKeepLatest,\n splitReportHtmlByExecution,\n} from './report';\nexport {\n createReportCliCommands,\n reportFileToMarkdown,\n splitReportFile,\n mergeReportFiles,\n type ConsumeReportFileAction,\n type ReportFileToMarkdownOptions,\n type ReportCliCommandDefinition,\n type ReportCliCommandEntry,\n type SplitReportFileOptions,\n type MergeReportFilesOptions,\n type MergeReportFilesResult,\n} from './report-cli';\n\n// ScreenshotItem\nexport { ScreenshotItem } from './screenshot-item';\nexport { ScreenshotStore, type ScreenshotRef } from './dump/screenshot-store';\n\nexport {\n executionToMarkdown,\n reportToMarkdown,\n type ExecutionMarkdownOptions,\n type ExecutionMarkdownResult,\n type ReportMarkdownResult,\n type MarkdownAttachment,\n} from './report-markdown';\n"],"names":["Service"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/index.ts"],"sourcesContent":["import { z } from 'zod';\nimport Service from './service/index';\nimport { TaskRunner } from './task-runner';\nimport { getVersion } from './utils';\n\nexport {\n plan,\n AiLocateElement,\n runConnectivityTest,\n getMidsceneLocationSchema,\n PointSchema,\n SizeSchema,\n RectSchema,\n TMultimodalPromptSchema,\n TUserPromptSchema,\n type TMultimodalPrompt,\n type TUserPrompt,\n type ConnectivityTestConfig,\n type ConnectivityTestResult,\n} from './ai-model/index';\n\nexport {\n MIDSCENE_MODEL_NAME,\n type CreateOpenAIClientFn,\n} from '@midscene/shared/env';\n\nexport type * from './types';\nexport {\n ServiceError,\n ExecutionDump,\n ReportActionDump,\n GroupedActionDump,\n type IExecutionDump,\n type IReportActionDump,\n type IGroupedActionDump,\n type ReportMeta,\n type GroupMeta,\n} from './types';\n\nexport { z };\n\nexport default Service;\nexport { TaskRunner, Service, getVersion };\n\nexport type {\n MidsceneYamlScript,\n MidsceneYamlTask,\n MidsceneYamlFlowItem,\n MidsceneYamlConfigResult,\n MidsceneYamlConfig,\n MidsceneYamlScriptWebEnv,\n MidsceneYamlScriptAndroidEnv,\n MidsceneYamlScriptIOSEnv,\n MidsceneYamlScriptEnv,\n LocateOption,\n DetailedLocateParam,\n} from './yaml';\n\nexport {\n Agent,\n type AgentOpt,\n type AiActOptions,\n type GherkinStepKeyword,\n type MidsceneUsageMetrics,\n type RunGherkinScenarioOptions,\n type UsageBucket,\n createAgent,\n type UIObservation,\n type UIObserver,\n type UIObserverOption,\n} from './agent';\nexport {\n describeElementAtPoint,\n verifyElementDescriptionAtPoint,\n verifyLocator,\n type DescribeElementAtPointOptions,\n type DescribeElementCoordinateSpace,\n type ElementDescriberRuntime,\n type VerifyElementDescriptionAtPointOptions,\n} from './element-describer';\n\n// Dump utilities\nexport {\n restoreImageReferences,\n escapeContent,\n unescapeContent,\n parseImageScripts,\n parseDumpScript,\n parseDumpScriptAttributes,\n generateImageScriptTag,\n generateDumpScriptTag,\n deriveTaskStatus,\n deriveCaseStatus,\n} from './dump';\nexport type { TaskStatusFields, DerivedTaskStatus } from './dump';\nexport {\n getTaskSearchArea,\n getTaskServiceDump,\n} from './dump/task-service-dump';\n\n// Report generator\nexport type { IReportGenerator } from './report-generator';\nexport { ReportGenerator, nullReportGenerator } from './report-generator';\nexport {\n collectDedupedExecutions,\n ReportMergingTool,\n dedupeExecutionsKeepLatest,\n splitReportHtmlByExecution,\n} from './report';\nexport {\n createReportCliCommands,\n reportFileToMarkdown,\n splitReportFile,\n mergeReportFiles,\n type ConsumeReportFileAction,\n type ReportFileToMarkdownOptions,\n type ReportCliCommandDefinition,\n type ReportCliCommandEntry,\n type SplitReportFileOptions,\n type MergeReportFilesOptions,\n type MergeReportFilesResult,\n} from './report-cli';\nexport {\n inspectReportFile,\n type InspectReportFileOptions,\n type ReportInspectionFailedTask,\n type ReportInspectionResult,\n type ReportInspectionStatus,\n type ReportInspectionUnknownReason,\n} from './report-inspection';\n\n// ScreenshotItem\nexport { ScreenshotItem } from './screenshot-item';\nexport { ScreenshotStore, type ScreenshotRef } from './dump/screenshot-store';\n\nexport {\n executionToMarkdown,\n reportToMarkdown,\n type ExecutionMarkdownOptions,\n type ExecutionMarkdownResult,\n type ReportMarkdownResult,\n type MarkdownAttachment,\n} from './report-markdown';\n"],"names":["Service"],"mappings":";;;;;;;;;;;;;;;;;;AAyCA,YAAeA"}
|
package/dist/es/report-cli.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync,
|
|
2
|
-
import { basename, dirname, extname, join
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { extractAllDumpScriptsSync } from "./dump/html-utils.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";
|
|
8
|
+
import { inspectReportFile, resolveReportHtmlPath } from "./report-inspection.mjs";
|
|
8
9
|
import { reportToMarkdown } from "./report-markdown.mjs";
|
|
9
10
|
import { ReportActionDump } from "./types.mjs";
|
|
10
11
|
function writeAttachmentFromReport(attachment, opts) {
|
|
@@ -59,15 +60,6 @@ async function markdownFromReport(htmlPath, outputDir) {
|
|
|
59
60
|
screenshotFiles: Array.from(writtenScreenshots).sort().map((f)=>join(screenshotsDir, f))
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
|
-
function resolveReportHtmlPath(htmlPath) {
|
|
63
|
-
const normalizedPath = resolve(htmlPath);
|
|
64
|
-
if (!existsSync(normalizedPath)) throw new Error(`report-tool: --htmlPath does not exist: ${htmlPath}`);
|
|
65
|
-
const stats = statSync(normalizedPath);
|
|
66
|
-
if (!stats.isDirectory()) return normalizedPath;
|
|
67
|
-
const indexHtmlPath = join(normalizedPath, 'index.html');
|
|
68
|
-
if (!existsSync(indexHtmlPath)) throw new Error(`report-tool: "${htmlPath}" is not an HTML report file, and no index.html was found under this directory.`);
|
|
69
|
-
return indexHtmlPath;
|
|
70
|
-
}
|
|
71
63
|
function splitReportFile(options) {
|
|
72
64
|
const { htmlPath, outputDir } = options;
|
|
73
65
|
if (!htmlPath) throw new Error('splitReportFile: htmlPath is required');
|
|
@@ -162,14 +154,15 @@ function normalizeHtmlReportArg(raw) {
|
|
|
162
154
|
}
|
|
163
155
|
const reportCommandDefinition = {
|
|
164
156
|
name: 'report-tool',
|
|
165
|
-
description: '
|
|
157
|
+
description: 'Inspect and transform Midscene report artifacts, including status inspection, splitting executions, converting to markdown, and merging multiple reports.',
|
|
166
158
|
schema: {
|
|
167
159
|
action: z["enum"]([
|
|
160
|
+
'inspect',
|
|
168
161
|
'split',
|
|
169
162
|
'to-markdown',
|
|
170
163
|
'merge-html'
|
|
171
|
-
]).optional().describe('Report action to run. Supports: split, to-markdown, merge-html. Defaults to split.'),
|
|
172
|
-
htmlPath: z.string().optional().describe('Input report HTML path (e.g. ./report/index.html). Used by split and to-markdown.'),
|
|
164
|
+
]).optional().describe('Report action to run. Supports: inspect, split, to-markdown, merge-html. Defaults to split.'),
|
|
165
|
+
htmlPath: z.string().optional().describe('Input report HTML path (e.g. ./report/index.html). Used by inspect, split, and to-markdown.'),
|
|
173
166
|
htmlReport: z.union([
|
|
174
167
|
z.string(),
|
|
175
168
|
z.array(z.string())
|
|
@@ -183,7 +176,7 @@ const reportCommandDefinition = {
|
|
|
183
176
|
},
|
|
184
177
|
handler: async (args)=>{
|
|
185
178
|
const { action = 'split', htmlPath, htmlReport, outputDir, outputName, overwrite } = args;
|
|
186
|
-
if ('split' !== action && 'to-markdown' !== action && 'merge-html' !== action) throw new Error(`report-tool: unsupported --action value "${action}". Currently supported: split, to-markdown, merge-html`);
|
|
179
|
+
if ('inspect' !== action && 'split' !== action && 'to-markdown' !== action && 'merge-html' !== action) throw new Error(`report-tool: unsupported --action value "${action}". Currently supported: inspect, split, to-markdown, merge-html`);
|
|
187
180
|
if ('merge-html' === action) {
|
|
188
181
|
const paths = normalizeHtmlReportArg(htmlReport);
|
|
189
182
|
if (!paths || 0 === paths.length) throw new Error('report-tool: --htmlReport is required for action "merge-html". Repeat --htmlReport for each report (e.g. --htmlReport ./a/index.html --htmlReport ./b.html).');
|
|
@@ -205,6 +198,20 @@ const reportCommandDefinition = {
|
|
|
205
198
|
};
|
|
206
199
|
}
|
|
207
200
|
if (!htmlPath) throw new Error('report-tool: --htmlPath is required');
|
|
201
|
+
if ('inspect' === action) {
|
|
202
|
+
const result = inspectReportFile({
|
|
203
|
+
htmlPath
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
isError: false,
|
|
207
|
+
content: [
|
|
208
|
+
{
|
|
209
|
+
type: 'text',
|
|
210
|
+
text: JSON.stringify(result, null, 2)
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
};
|
|
214
|
+
}
|
|
208
215
|
if (!outputDir) throw new Error('report-tool: --outputDir is required');
|
|
209
216
|
if ('to-markdown' === action) {
|
|
210
217
|
const result = await reportFileToMarkdown({
|
|
@@ -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 { extractAllDumpScriptsSync } 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 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 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 readSourceTestStatus(htmlPath) ?? deriveCaseStatus(executions);\n return {\n testId: fallbackId,\n testTitle: baseDump.groupName || fallbackId,\n testDescription: baseDump.groupDescription ?? '',\n testDuration: 0,\n testStatus,\n };\n } catch {\n return {\n testId: fallbackId,\n testTitle: fallbackId,\n testDescription: '',\n testDuration: 0,\n testStatus: 'passed' as TestStatus,\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","testStatus","deriveCaseStatus","mergeReportFiles","htmlPaths","outputName","overwrite","resolvedPaths","p","tool","ReportMergingTool","mergedReportPath","normalizeHtmlReportArg","raw","trimmed","reportCommandDefinition","z","args","action","htmlReport","paths","overwriteFlag","createReportCliCommands"],"mappings":";;;;;;;;;AAiEA,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;IACjC,KAAK,MAAM,EAAEC,OAAO,EAAE,IAAIC,0BAA0BhC,UAAW;QAC7D,MAAMiC,QAAQF,QAAQ,KAAK,CAAC;QAC5B,IAAI,CAACE,OAAO;QACZ,MAAMC,SAASC,mBAAmBF,KAAK,CAAC,EAAE;QAC1C,IAAIN,mBAAmB,GAAG,CAACO,SACzBJ,SAAS,IAAI,CAACI;IAElB;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,IAAI;QACF,MAAM,EAAElC,QAAQ,EAAEC,UAAU,EAAE,GAAGC,yBAAyBN;QAI1D,MAAMwC,aACJX,qBAAqB7B,aAAayC,iBAAiBpC;QACrD,OAAO;YACL,QAAQkC;YACR,WAAWnC,SAAS,SAAS,IAAImC;YACjC,iBAAiBnC,SAAS,gBAAgB,IAAI;YAC9C,cAAc;YACdoC;QACF;IACF,EAAE,OAAM;QACN,OAAO;YACL,QAAQD;YACR,WAAWA;YACX,iBAAiB;YACjB,cAAc;YACd,YAAY;QACd;IACF;AACF;AAEO,SAASG,iBACdnB,OAAgC;IAEhC,MAAM,EAAEoB,SAAS,EAAE1C,SAAS,EAAE2C,UAAU,EAAEC,YAAY,KAAK,EAAE,GAAGtB;IAChE,IAAI,CAACoB,aAAaA,AAAqB,MAArBA,UAAU,MAAM,EAChC,MAAM,IAAI9C,MAAM;IAGlB,MAAMiD,gBAAgBH,UAAU,GAAG,CAAC,CAACI,IAAM9B,sBAAsB8B;IAEjE,MAAMC,OAAO,IAAIC;IACjBH,cAAc,OAAO,CAAC,CAAC9C,UAAUsC;QAC/BU,KAAK,MAAM,CAAC;YACV,gBAAgBhD;YAChB,kBAAkBqC,+BAA+BrC,UAAUsC;QAC7D;IACF;IAEA,MAAMY,mBAAmBF,KAAK,YAAY,CAACJ,cAAc,QAAQ;QAC/DC;QACA5C;IACF;IAEA,IAAI,CAACiD,kBACH,MAAM,IAAIrD,MAAM;IAGlB,OAAO;QAAEqD;IAAiB;AAC5B;AAEA,SAASC,uBAAuBC,GAAY;IAC1C,IAAIA,QAAAA,KAAmC;IACvC,IAAIrC,MAAM,OAAO,CAACqC,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,EAChBzD,QAAQ,EACR0D,UAAU,EACVzD,SAAS,EACT2C,UAAU,EACVC,SAAS,EACV,GAAGW;QAQJ,IACEC,AAAW,YAAXA,UACAA,AAAW,kBAAXA,UACAA,AAAW,iBAAXA,QAEA,MAAM,IAAI5D,MACR,CAAC,yCAAyC,EAAE4D,OAAO,sDAAsD,CAAC;QAI9G,IAAIA,AAAW,iBAAXA,QAAyB;YAC3B,MAAME,QAAQR,uBAAuBO;YACrC,IAAI,CAACC,SAASA,AAAiB,MAAjBA,MAAM,MAAM,EACxB,MAAM,IAAI9D,MACR;YAIJ,MAAM+D,gBACJf,AAAc,SAAdA,aAAsBA,AAAc,WAAdA,aAAwBA,AAAc,QAAdA;YAEhD,MAAMpC,SAASiC,iBAAiB;gBAC9B,WAAWiB;gBACX1D;gBACA2C;gBACA,WAAWgB;YACb;YAEA,OAAO;gBACL,SAAS;gBACT,SAAS;oBACP;wBACE,MAAM;wBACN,MAAM,CAAC,OAAO,EAAED,MAAM,MAAM,CAAC,gBAAgB,EAAElD,OAAO,gBAAgB,EAAE;oBAC1E;iBACD;YACH;QACF;QAEA,IAAI,CAACT,UACH,MAAM,IAAIH,MAAM;QAGlB,IAAI,CAACI,WACH,MAAM,IAAIJ,MAAM;QAGlB,IAAI4D,AAAW,kBAAXA,QAA0B;YAC5B,MAAMhD,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,SAAS4D;IACd,OAAO;QACL;YACE,MAAM;YACN,KAAKP;QACP;KACD;AACH"}
|
|
1
|
+
{"version":3,"file":"report-cli.mjs","sources":["../../src/report-cli.ts"],"sourcesContent":["import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport { extractAllDumpScriptsSync } 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 { inspectReportFile, resolveReportHtmlPath } from './report-inspection';\nimport { reportToMarkdown } from './report-markdown';\nimport type { MarkdownAttachment } from './report-markdown';\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 =\n | 'inspect'\n | 'split'\n | 'to-markdown'\n | '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\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 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 readSourceTestStatus(htmlPath) ?? deriveCaseStatus(executions);\n return {\n testId: fallbackId,\n testTitle: baseDump.groupName || fallbackId,\n testDescription: baseDump.groupDescription ?? '',\n testDuration: 0,\n testStatus,\n };\n } catch {\n return {\n testId: fallbackId,\n testTitle: fallbackId,\n testDescription: '',\n testDuration: 0,\n testStatus: 'passed' as TestStatus,\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 'Inspect and transform Midscene report artifacts, including status inspection, splitting executions, converting to markdown, and merging multiple reports.',\n schema: {\n action: z\n .enum(['inspect', 'split', 'to-markdown', 'merge-html'])\n .optional()\n .describe(\n 'Report action to run. Supports: inspect, 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 inspect, 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 !== 'inspect' &&\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: inspect, 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 (action === 'inspect') {\n const result = inspectReportFile({ htmlPath });\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\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","splitReportFile","options","resolvedHtmlPath","resolveReportHtmlPath","splitReportHtmlByExecution","reportFileToMarkdown","TEST_STATUS_VALUES","FAILING_TEST_STATUSES","readSourceTestStatus","recorded","openTag","extractAllDumpScriptsSync","match","status","decodeURIComponent","s","deriveReportAttributesFromHtml","index","fallbackId","testStatus","deriveCaseStatus","mergeReportFiles","htmlPaths","outputName","overwrite","resolvedPaths","p","tool","ReportMergingTool","mergedReportPath","normalizeHtmlReportArg","raw","trimmed","reportCommandDefinition","z","args","action","htmlReport","paths","overwriteFlag","inspectReportFile","JSON","createReportCliCommands"],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { deriveTaskStatus } from "./dump/task-status.mjs";
|
|
4
|
+
import { collectDedupedExecutions } from "./report.mjs";
|
|
5
|
+
const MAX_ERROR_SUMMARY_LENGTH = 1000;
|
|
6
|
+
function resolveReportHtmlPath(htmlPath) {
|
|
7
|
+
const normalizedPath = resolve(htmlPath);
|
|
8
|
+
if (!existsSync(normalizedPath)) throw new Error(`Report path does not exist: ${htmlPath}`);
|
|
9
|
+
const stats = statSync(normalizedPath);
|
|
10
|
+
if (!stats.isDirectory()) return normalizedPath;
|
|
11
|
+
const indexHtmlPath = join(normalizedPath, 'index.html');
|
|
12
|
+
if (!existsSync(indexHtmlPath)) throw new Error(`"${htmlPath}" is not an HTML report file, and no index.html was found under this directory.`);
|
|
13
|
+
return indexHtmlPath;
|
|
14
|
+
}
|
|
15
|
+
function scalarText(value) {
|
|
16
|
+
return 'string' == typeof value || 'number' == typeof value ? String(value) : null;
|
|
17
|
+
}
|
|
18
|
+
function clipText(value) {
|
|
19
|
+
const normalized = value.replace(/[ \t]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
|
|
20
|
+
return normalized.length > MAX_ERROR_SUMMARY_LENGTH ? `${normalized.slice(0, MAX_ERROR_SUMMARY_LENGTH)}…` : normalized;
|
|
21
|
+
}
|
|
22
|
+
function errorText(value) {
|
|
23
|
+
if (null == value) return null;
|
|
24
|
+
if ('string' == typeof value) {
|
|
25
|
+
const text = clipText(value);
|
|
26
|
+
return text || null;
|
|
27
|
+
}
|
|
28
|
+
if (value instanceof Error) {
|
|
29
|
+
const text = clipText(value.message || value.stack || String(value));
|
|
30
|
+
return text || null;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(value) && 0 === value.length) return null;
|
|
33
|
+
if ('object' == typeof value && !Array.isArray(value) && 0 === Object.keys(value).length) return null;
|
|
34
|
+
try {
|
|
35
|
+
const serialized = JSON.stringify(value);
|
|
36
|
+
if (!serialized) return null;
|
|
37
|
+
const text = clipText(serialized);
|
|
38
|
+
return text || null;
|
|
39
|
+
} catch {
|
|
40
|
+
const text = clipText(String(value));
|
|
41
|
+
return text || null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function taskErrorMessage(task) {
|
|
45
|
+
return errorText(task.errorMessage) ?? errorText(task.error) ?? errorText(task.errorStack);
|
|
46
|
+
}
|
|
47
|
+
function summarizeExecution(execution, executionIndex) {
|
|
48
|
+
const executionId = scalarText(execution.id);
|
|
49
|
+
const executionName = scalarText(execution.name);
|
|
50
|
+
return execution.tasks.map((task, taskIndex)=>({
|
|
51
|
+
executionId,
|
|
52
|
+
executionName,
|
|
53
|
+
executionIndex,
|
|
54
|
+
taskIndex,
|
|
55
|
+
taskId: scalarText(task.taskId),
|
|
56
|
+
status: scalarText(task.status),
|
|
57
|
+
type: scalarText(task.type),
|
|
58
|
+
subType: scalarText(task.subType),
|
|
59
|
+
errorMessage: taskErrorMessage(task),
|
|
60
|
+
displayStatus: deriveTaskStatus(task)
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
function inspectReportFile(options) {
|
|
64
|
+
if (!options.htmlPath) throw new Error('inspectReportFile: htmlPath is required');
|
|
65
|
+
const resolvedHtmlPath = resolveReportHtmlPath(options.htmlPath);
|
|
66
|
+
const { executions } = collectDedupedExecutions(resolvedHtmlPath, {
|
|
67
|
+
rejectTruncatedDump: true
|
|
68
|
+
});
|
|
69
|
+
const taskRecords = executions.flatMap(summarizeExecution);
|
|
70
|
+
const failedTasks = taskRecords.filter((task)=>'failed' === task.displayStatus).map(({ executionId, executionName, executionIndex, taskIndex, taskId, status, type, subType, errorMessage })=>({
|
|
71
|
+
executionId,
|
|
72
|
+
executionName,
|
|
73
|
+
executionIndex,
|
|
74
|
+
taskIndex,
|
|
75
|
+
taskId,
|
|
76
|
+
status,
|
|
77
|
+
type,
|
|
78
|
+
subType,
|
|
79
|
+
errorMessage
|
|
80
|
+
}));
|
|
81
|
+
let reportStatus = 'passed';
|
|
82
|
+
let unknownReason = null;
|
|
83
|
+
if (failedTasks.length > 0) reportStatus = 'failed';
|
|
84
|
+
else if (taskRecords.some((task)=>'pending' === task.displayStatus || 'running' === task.displayStatus)) {
|
|
85
|
+
reportStatus = 'unknown';
|
|
86
|
+
unknownReason = 'incomplete';
|
|
87
|
+
} else if (taskRecords.some((task)=>'cancelled' === task.displayStatus)) {
|
|
88
|
+
reportStatus = 'unknown';
|
|
89
|
+
unknownReason = 'cancelled';
|
|
90
|
+
} else if (0 === taskRecords.length) {
|
|
91
|
+
reportStatus = 'unknown';
|
|
92
|
+
unknownReason = 'no-tasks';
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
schemaVersion: 1,
|
|
96
|
+
report: resolvedHtmlPath,
|
|
97
|
+
reportStatus,
|
|
98
|
+
unknownReason,
|
|
99
|
+
failedTasks
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export { inspectReportFile, resolveReportHtmlPath };
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=report-inspection.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-inspection.mjs","sources":["../../src/report-inspection.ts"],"sourcesContent":["import { existsSync, statSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { deriveTaskStatus } from './dump/task-status';\nimport type { DerivedTaskStatus } from './dump/task-status';\nimport { collectDedupedExecutions } from './report';\nimport type { ExecutionTask, IExecutionDump } from './types';\n\nconst MAX_ERROR_SUMMARY_LENGTH = 1000;\n\nexport type ReportInspectionStatus = 'passed' | 'failed' | 'unknown';\n\nexport type ReportInspectionUnknownReason =\n | 'incomplete'\n | 'cancelled'\n | 'no-tasks';\n\nexport interface InspectReportFileOptions {\n htmlPath: string;\n}\n\nexport interface ReportInspectionFailedTask {\n executionId: string | null;\n executionName: string | null;\n executionIndex: number;\n taskIndex: number;\n taskId: string | null;\n status: string | null;\n type: string | null;\n subType: string | null;\n errorMessage: string | null;\n}\n\ninterface ReportInspectionTaskRecord extends ReportInspectionFailedTask {\n displayStatus: DerivedTaskStatus;\n}\n\nexport interface ReportInspectionResult {\n schemaVersion: 1;\n report: string;\n reportStatus: ReportInspectionStatus;\n unknownReason: ReportInspectionUnknownReason | null;\n failedTasks: ReportInspectionFailedTask[];\n}\n\nexport function resolveReportHtmlPath(htmlPath: string): string {\n const normalizedPath = path.resolve(htmlPath);\n\n if (!existsSync(normalizedPath)) {\n throw new Error(`Report path 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 `\"${htmlPath}\" is not an HTML report file, and no index.html was found under this directory.`,\n );\n }\n\n return indexHtmlPath;\n}\n\nfunction scalarText(value: unknown): string | null {\n return typeof value === 'string' || typeof value === 'number'\n ? String(value)\n : null;\n}\n\nfunction clipText(value: string): string {\n const normalized = value\n .replace(/[ \\t]+/g, ' ')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n return normalized.length > MAX_ERROR_SUMMARY_LENGTH\n ? `${normalized.slice(0, MAX_ERROR_SUMMARY_LENGTH)}…`\n : normalized;\n}\n\nfunction errorText(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value === 'string') {\n const text = clipText(value);\n return text || null;\n }\n if (value instanceof Error) {\n const text = clipText(value.message || value.stack || String(value));\n return text || null;\n }\n if (Array.isArray(value) && value.length === 0) return null;\n if (\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0\n ) {\n return null;\n }\n\n try {\n const serialized = JSON.stringify(value);\n if (!serialized) return null;\n const text = clipText(serialized);\n return text || null;\n } catch {\n const text = clipText(String(value));\n return text || null;\n }\n}\n\nfunction taskErrorMessage(task: ExecutionTask): string | null {\n return (\n errorText(task.errorMessage) ??\n errorText(task.error) ??\n errorText(task.errorStack)\n );\n}\n\nfunction summarizeExecution(\n execution: IExecutionDump,\n executionIndex: number,\n): ReportInspectionTaskRecord[] {\n const executionId = scalarText(execution.id);\n const executionName = scalarText(execution.name);\n return execution.tasks.map((task, taskIndex) => ({\n executionId,\n executionName,\n executionIndex,\n taskIndex,\n taskId: scalarText(task.taskId),\n status: scalarText(task.status),\n type: scalarText(task.type),\n subType: scalarText(task.subType),\n errorMessage: taskErrorMessage(task),\n displayStatus: deriveTaskStatus(task),\n }));\n}\n\nexport function inspectReportFile(\n options: InspectReportFileOptions,\n): ReportInspectionResult {\n if (!options.htmlPath) {\n throw new Error('inspectReportFile: htmlPath is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(options.htmlPath);\n const { executions } = collectDedupedExecutions(resolvedHtmlPath, {\n rejectTruncatedDump: true,\n });\n const taskRecords = executions.flatMap(summarizeExecution);\n const failedTasks = taskRecords\n .filter((task) => task.displayStatus === 'failed')\n .map(\n ({\n executionId,\n executionName,\n executionIndex,\n taskIndex,\n taskId,\n status,\n type,\n subType,\n errorMessage,\n }) => ({\n executionId,\n executionName,\n executionIndex,\n taskIndex,\n taskId,\n status,\n type,\n subType,\n errorMessage,\n }),\n );\n\n let reportStatus: ReportInspectionStatus = 'passed';\n let unknownReason: ReportInspectionUnknownReason | null = null;\n\n if (failedTasks.length > 0) {\n reportStatus = 'failed';\n } else if (\n taskRecords.some(\n (task) =>\n task.displayStatus === 'pending' || task.displayStatus === 'running',\n )\n ) {\n reportStatus = 'unknown';\n unknownReason = 'incomplete';\n } else if (taskRecords.some((task) => task.displayStatus === 'cancelled')) {\n reportStatus = 'unknown';\n unknownReason = 'cancelled';\n } else if (taskRecords.length === 0) {\n reportStatus = 'unknown';\n unknownReason = 'no-tasks';\n }\n\n return {\n schemaVersion: 1,\n report: resolvedHtmlPath,\n reportStatus,\n unknownReason,\n failedTasks,\n };\n}\n"],"names":["MAX_ERROR_SUMMARY_LENGTH","resolveReportHtmlPath","htmlPath","normalizedPath","path","existsSync","Error","stats","statSync","indexHtmlPath","scalarText","value","String","clipText","normalized","errorText","text","Array","Object","serialized","JSON","taskErrorMessage","task","summarizeExecution","execution","executionIndex","executionId","executionName","taskIndex","deriveTaskStatus","inspectReportFile","options","resolvedHtmlPath","executions","collectDedupedExecutions","taskRecords","failedTasks","taskId","status","type","subType","errorMessage","reportStatus","unknownReason"],"mappings":"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"}
|