@aiscene/core 1.1.1
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/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/es/agent/agent.mjs +753 -0
- package/dist/es/agent/agent.mjs.map +1 -0
- package/dist/es/agent/common.mjs +0 -0
- package/dist/es/agent/execution-session.mjs +41 -0
- package/dist/es/agent/execution-session.mjs.map +1 -0
- package/dist/es/agent/index.mjs +6 -0
- package/dist/es/agent/task-builder.mjs +332 -0
- package/dist/es/agent/task-builder.mjs.map +1 -0
- package/dist/es/agent/task-cache.mjs +214 -0
- package/dist/es/agent/task-cache.mjs.map +1 -0
- package/dist/es/agent/tasks.mjs +423 -0
- package/dist/es/agent/tasks.mjs.map +1 -0
- package/dist/es/agent/ui-utils.mjs +91 -0
- package/dist/es/agent/ui-utils.mjs.map +1 -0
- package/dist/es/agent/utils.mjs +169 -0
- package/dist/es/agent/utils.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/actions.mjs +239 -0
- package/dist/es/ai-model/auto-glm/actions.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/index.mjs +6 -0
- package/dist/es/ai-model/auto-glm/parser.mjs +239 -0
- package/dist/es/ai-model/auto-glm/parser.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/planning.mjs +71 -0
- package/dist/es/ai-model/auto-glm/planning.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/prompt.mjs +222 -0
- package/dist/es/ai-model/auto-glm/prompt.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/util.mjs +9 -0
- package/dist/es/ai-model/auto-glm/util.mjs.map +1 -0
- package/dist/es/ai-model/connectivity.mjs +138 -0
- package/dist/es/ai-model/connectivity.mjs.map +1 -0
- package/dist/es/ai-model/conversation-history.mjs +195 -0
- package/dist/es/ai-model/conversation-history.mjs.map +1 -0
- package/dist/es/ai-model/index.mjs +12 -0
- package/dist/es/ai-model/inspect.mjs +397 -0
- package/dist/es/ai-model/inspect.mjs.map +1 -0
- package/dist/es/ai-model/llm-planning.mjs +233 -0
- package/dist/es/ai-model/llm-planning.mjs.map +1 -0
- package/dist/es/ai-model/prompt/common.mjs +7 -0
- package/dist/es/ai-model/prompt/common.mjs.map +1 -0
- package/dist/es/ai-model/prompt/describe.mjs +66 -0
- package/dist/es/ai-model/prompt/describe.mjs.map +1 -0
- package/dist/es/ai-model/prompt/extraction.mjs +131 -0
- package/dist/es/ai-model/prompt/extraction.mjs.map +1 -0
- package/dist/es/ai-model/prompt/llm-locator.mjs +51 -0
- package/dist/es/ai-model/prompt/llm-locator.mjs.map +1 -0
- package/dist/es/ai-model/prompt/llm-planning.mjs +568 -0
- package/dist/es/ai-model/prompt/llm-planning.mjs.map +1 -0
- package/dist/es/ai-model/prompt/llm-section-locator.mjs +44 -0
- package/dist/es/ai-model/prompt/llm-section-locator.mjs.map +1 -0
- package/dist/es/ai-model/prompt/order-sensitive-judge.mjs +35 -0
- package/dist/es/ai-model/prompt/order-sensitive-judge.mjs.map +1 -0
- package/dist/es/ai-model/prompt/playwright-generator.mjs +117 -0
- package/dist/es/ai-model/prompt/playwright-generator.mjs.map +1 -0
- package/dist/es/ai-model/prompt/ui-tars-planning.mjs +36 -0
- package/dist/es/ai-model/prompt/ui-tars-planning.mjs.map +1 -0
- package/dist/es/ai-model/prompt/util.mjs +59 -0
- package/dist/es/ai-model/prompt/util.mjs.map +1 -0
- package/dist/es/ai-model/prompt/yaml-generator.mjs +203 -0
- package/dist/es/ai-model/prompt/yaml-generator.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/codex-app-server.mjs +575 -0
- package/dist/es/ai-model/service-caller/codex-app-server.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/image-detail.mjs +6 -0
- package/dist/es/ai-model/service-caller/image-detail.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/index.mjs +475 -0
- package/dist/es/ai-model/service-caller/index.mjs.map +1 -0
- package/dist/es/ai-model/ui-tars-planning.mjs +249 -0
- package/dist/es/ai-model/ui-tars-planning.mjs.map +1 -0
- package/dist/es/common.mjs +371 -0
- package/dist/es/common.mjs.map +1 -0
- package/dist/es/device/device-options.mjs +0 -0
- package/dist/es/device/index.mjs +341 -0
- package/dist/es/device/index.mjs.map +1 -0
- package/dist/es/dump/html-utils.mjs +292 -0
- package/dist/es/dump/html-utils.mjs.map +1 -0
- package/dist/es/dump/index.mjs +3 -0
- package/dist/es/dump/screenshot-restoration.mjs +32 -0
- package/dist/es/dump/screenshot-restoration.mjs.map +1 -0
- package/dist/es/dump/screenshot-store.mjs +126 -0
- package/dist/es/dump/screenshot-store.mjs.map +1 -0
- package/dist/es/index.mjs +19 -0
- package/dist/es/index.mjs.map +1 -0
- package/dist/es/report-cli.mjs +151 -0
- package/dist/es/report-cli.mjs.map +1 -0
- package/dist/es/report-generator.mjs +205 -0
- package/dist/es/report-generator.mjs.map +1 -0
- package/dist/es/report-markdown.mjs +218 -0
- package/dist/es/report-markdown.mjs.map +1 -0
- package/dist/es/report.mjs +270 -0
- package/dist/es/report.mjs.map +1 -0
- package/dist/es/screenshot-item.mjs +122 -0
- package/dist/es/screenshot-item.mjs.map +1 -0
- package/dist/es/service/index.mjs +274 -0
- package/dist/es/service/index.mjs.map +1 -0
- package/dist/es/service/utils.mjs +15 -0
- package/dist/es/service/utils.mjs.map +1 -0
- package/dist/es/skill/index.mjs +38 -0
- package/dist/es/skill/index.mjs.map +1 -0
- package/dist/es/task-runner.mjs +263 -0
- package/dist/es/task-runner.mjs.map +1 -0
- package/dist/es/task-timing.mjs +12 -0
- package/dist/es/task-timing.mjs.map +1 -0
- package/dist/es/tree.mjs +13 -0
- package/dist/es/tree.mjs.map +1 -0
- package/dist/es/types.mjs +204 -0
- package/dist/es/types.mjs.map +1 -0
- package/dist/es/utils.mjs +234 -0
- package/dist/es/utils.mjs.map +1 -0
- package/dist/es/yaml/builder.mjs +13 -0
- package/dist/es/yaml/builder.mjs.map +1 -0
- package/dist/es/yaml/index.mjs +4 -0
- package/dist/es/yaml/player.mjs +442 -0
- package/dist/es/yaml/player.mjs.map +1 -0
- package/dist/es/yaml/utils.mjs +102 -0
- package/dist/es/yaml/utils.mjs.map +1 -0
- package/dist/es/yaml.mjs +0 -0
- package/dist/lib/agent/agent.js +801 -0
- package/dist/lib/agent/agent.js.map +1 -0
- package/dist/lib/agent/common.js +5 -0
- package/dist/lib/agent/execution-session.js +75 -0
- package/dist/lib/agent/execution-session.js.map +1 -0
- package/dist/lib/agent/index.js +78 -0
- package/dist/lib/agent/index.js.map +1 -0
- package/dist/lib/agent/task-builder.js +369 -0
- package/dist/lib/agent/task-builder.js.map +1 -0
- package/dist/lib/agent/task-cache.js +266 -0
- package/dist/lib/agent/task-cache.js.map +1 -0
- package/dist/lib/agent/tasks.js +466 -0
- package/dist/lib/agent/tasks.js.map +1 -0
- package/dist/lib/agent/ui-utils.js +143 -0
- package/dist/lib/agent/ui-utils.js.map +1 -0
- package/dist/lib/agent/utils.js +240 -0
- package/dist/lib/agent/utils.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/actions.js +273 -0
- package/dist/lib/ai-model/auto-glm/actions.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/index.js +66 -0
- package/dist/lib/ai-model/auto-glm/index.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/parser.js +282 -0
- package/dist/lib/ai-model/auto-glm/parser.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/planning.js +105 -0
- package/dist/lib/ai-model/auto-glm/planning.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/prompt.js +259 -0
- package/dist/lib/ai-model/auto-glm/prompt.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/util.js +46 -0
- package/dist/lib/ai-model/auto-glm/util.js.map +1 -0
- package/dist/lib/ai-model/connectivity.js +182 -0
- package/dist/lib/ai-model/connectivity.js.map +1 -0
- package/dist/lib/ai-model/conversation-history.js +229 -0
- package/dist/lib/ai-model/conversation-history.js.map +1 -0
- package/dist/lib/ai-model/index.js +129 -0
- package/dist/lib/ai-model/index.js.map +1 -0
- package/dist/lib/ai-model/inspect.js +443 -0
- package/dist/lib/ai-model/inspect.js.map +1 -0
- package/dist/lib/ai-model/llm-planning.js +270 -0
- package/dist/lib/ai-model/llm-planning.js.map +1 -0
- package/dist/lib/ai-model/prompt/common.js +41 -0
- package/dist/lib/ai-model/prompt/common.js.map +1 -0
- package/dist/lib/ai-model/prompt/describe.js +100 -0
- package/dist/lib/ai-model/prompt/describe.js.map +1 -0
- package/dist/lib/ai-model/prompt/extraction.js +171 -0
- package/dist/lib/ai-model/prompt/extraction.js.map +1 -0
- package/dist/lib/ai-model/prompt/llm-locator.js +88 -0
- package/dist/lib/ai-model/prompt/llm-locator.js.map +1 -0
- package/dist/lib/ai-model/prompt/llm-planning.js +605 -0
- package/dist/lib/ai-model/prompt/llm-planning.js.map +1 -0
- package/dist/lib/ai-model/prompt/llm-section-locator.js +81 -0
- package/dist/lib/ai-model/prompt/llm-section-locator.js.map +1 -0
- package/dist/lib/ai-model/prompt/order-sensitive-judge.js +72 -0
- package/dist/lib/ai-model/prompt/order-sensitive-judge.js.map +1 -0
- package/dist/lib/ai-model/prompt/playwright-generator.js +178 -0
- package/dist/lib/ai-model/prompt/playwright-generator.js.map +1 -0
- package/dist/lib/ai-model/prompt/ui-tars-planning.js +73 -0
- package/dist/lib/ai-model/prompt/ui-tars-planning.js.map +1 -0
- package/dist/lib/ai-model/prompt/util.js +105 -0
- package/dist/lib/ai-model/prompt/util.js.map +1 -0
- package/dist/lib/ai-model/prompt/yaml-generator.js +264 -0
- package/dist/lib/ai-model/prompt/yaml-generator.js.map +1 -0
- package/dist/lib/ai-model/service-caller/codex-app-server.js +624 -0
- package/dist/lib/ai-model/service-caller/codex-app-server.js.map +1 -0
- package/dist/lib/ai-model/service-caller/image-detail.js +40 -0
- package/dist/lib/ai-model/service-caller/image-detail.js.map +1 -0
- package/dist/lib/ai-model/service-caller/index.js +540 -0
- package/dist/lib/ai-model/service-caller/index.js.map +1 -0
- package/dist/lib/ai-model/ui-tars-planning.js +283 -0
- package/dist/lib/ai-model/ui-tars-planning.js.map +1 -0
- package/dist/lib/common.js +480 -0
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/device/device-options.js +20 -0
- package/dist/lib/device/device-options.js.map +1 -0
- package/dist/lib/device/index.js +468 -0
- package/dist/lib/device/index.js.map +1 -0
- package/dist/lib/dump/html-utils.js +368 -0
- package/dist/lib/dump/html-utils.js.map +1 -0
- package/dist/lib/dump/index.js +60 -0
- package/dist/lib/dump/index.js.map +1 -0
- package/dist/lib/dump/screenshot-restoration.js +66 -0
- package/dist/lib/dump/screenshot-restoration.js.map +1 -0
- package/dist/lib/dump/screenshot-store.js +166 -0
- package/dist/lib/dump/screenshot-store.js.map +1 -0
- package/dist/lib/index.js +186 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/report-cli.js +191 -0
- package/dist/lib/report-cli.js.map +1 -0
- package/dist/lib/report-generator.js +246 -0
- package/dist/lib/report-generator.js.map +1 -0
- package/dist/lib/report-markdown.js +255 -0
- package/dist/lib/report-markdown.js.map +1 -0
- package/dist/lib/report.js +316 -0
- package/dist/lib/report.js.map +1 -0
- package/dist/lib/screenshot-item.js +156 -0
- package/dist/lib/screenshot-item.js.map +1 -0
- package/dist/lib/service/index.js +308 -0
- package/dist/lib/service/index.js.map +1 -0
- package/dist/lib/service/utils.js +49 -0
- package/dist/lib/service/utils.js.map +1 -0
- package/dist/lib/skill/index.js +72 -0
- package/dist/lib/skill/index.js.map +1 -0
- package/dist/lib/task-runner.js +300 -0
- package/dist/lib/task-runner.js.map +1 -0
- package/dist/lib/task-timing.js +46 -0
- package/dist/lib/task-timing.js.map +1 -0
- package/dist/lib/tree.js +53 -0
- package/dist/lib/tree.js.map +1 -0
- package/dist/lib/types.js +300 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.js +316 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/yaml/builder.js +57 -0
- package/dist/lib/yaml/builder.js.map +1 -0
- package/dist/lib/yaml/index.js +81 -0
- package/dist/lib/yaml/index.js.map +1 -0
- package/dist/lib/yaml/player.js +476 -0
- package/dist/lib/yaml/player.js.map +1 -0
- package/dist/lib/yaml/utils.js +155 -0
- package/dist/lib/yaml/utils.js.map +1 -0
- package/dist/lib/yaml.js +20 -0
- package/dist/lib/yaml.js.map +1 -0
- package/dist/types/agent/agent.d.ts +216 -0
- package/dist/types/agent/common.d.ts +0 -0
- package/dist/types/agent/execution-session.d.ts +36 -0
- package/dist/types/agent/index.d.ts +9 -0
- package/dist/types/agent/task-builder.d.ts +34 -0
- package/dist/types/agent/task-cache.d.ts +49 -0
- package/dist/types/agent/tasks.d.ts +69 -0
- package/dist/types/agent/ui-utils.d.ts +14 -0
- package/dist/types/agent/utils.d.ts +25 -0
- package/dist/types/ai-model/auto-glm/actions.d.ts +78 -0
- package/dist/types/ai-model/auto-glm/index.d.ts +6 -0
- package/dist/types/ai-model/auto-glm/parser.d.ts +18 -0
- package/dist/types/ai-model/auto-glm/planning.d.ts +12 -0
- package/dist/types/ai-model/auto-glm/prompt.d.ts +27 -0
- package/dist/types/ai-model/auto-glm/util.d.ts +13 -0
- package/dist/types/ai-model/connectivity.d.ts +20 -0
- package/dist/types/ai-model/conversation-history.d.ts +105 -0
- package/dist/types/ai-model/index.d.ts +16 -0
- package/dist/types/ai-model/inspect.d.ts +67 -0
- package/dist/types/ai-model/llm-planning.d.ts +19 -0
- package/dist/types/ai-model/prompt/common.d.ts +2 -0
- package/dist/types/ai-model/prompt/describe.d.ts +1 -0
- package/dist/types/ai-model/prompt/extraction.d.ts +7 -0
- package/dist/types/ai-model/prompt/llm-locator.d.ts +3 -0
- package/dist/types/ai-model/prompt/llm-planning.d.ts +10 -0
- package/dist/types/ai-model/prompt/llm-section-locator.d.ts +3 -0
- package/dist/types/ai-model/prompt/order-sensitive-judge.d.ts +2 -0
- package/dist/types/ai-model/prompt/playwright-generator.d.ts +26 -0
- package/dist/types/ai-model/prompt/ui-tars-planning.d.ts +2 -0
- package/dist/types/ai-model/prompt/util.d.ts +33 -0
- package/dist/types/ai-model/prompt/yaml-generator.d.ts +102 -0
- package/dist/types/ai-model/service-caller/codex-app-server.d.ts +42 -0
- package/dist/types/ai-model/service-caller/image-detail.d.ts +2 -0
- package/dist/types/ai-model/service-caller/index.d.ts +49 -0
- package/dist/types/ai-model/ui-tars-planning.d.ts +72 -0
- package/dist/types/common.d.ts +288 -0
- package/dist/types/device/device-options.d.ts +145 -0
- package/dist/types/device/index.d.ts +2528 -0
- package/dist/types/dump/html-utils.d.ts +75 -0
- package/dist/types/dump/index.d.ts +5 -0
- package/dist/types/dump/screenshot-restoration.d.ts +8 -0
- package/dist/types/dump/screenshot-store.d.ts +49 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/report-cli.d.ts +36 -0
- package/dist/types/report-generator.d.ts +81 -0
- package/dist/types/report-markdown.d.ts +24 -0
- package/dist/types/report.d.ts +52 -0
- package/dist/types/screenshot-item.d.ts +67 -0
- package/dist/types/service/index.d.ts +24 -0
- package/dist/types/service/utils.d.ts +2 -0
- package/dist/types/skill/index.d.ts +25 -0
- package/dist/types/task-runner.d.ts +50 -0
- package/dist/types/task-timing.d.ts +8 -0
- package/dist/types/tree.d.ts +4 -0
- package/dist/types/types.d.ts +681 -0
- package/dist/types/utils.d.ts +45 -0
- package/dist/types/yaml/builder.d.ts +2 -0
- package/dist/types/yaml/index.d.ts +4 -0
- package/dist/types/yaml/player.d.ts +34 -0
- package/dist/types/yaml/utils.d.ts +9 -0
- package/dist/types/yaml.d.ts +215 -0
- package/package.json +111 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { resolveScreenshotSource } from "./dump/screenshot-store.mjs";
|
|
5
|
+
import { collectDedupedExecutions, splitReportHtmlByExecution } from "./report.mjs";
|
|
6
|
+
import { reportToMarkdown } from "./report-markdown.mjs";
|
|
7
|
+
import { ReportActionDump } from "./types.mjs";
|
|
8
|
+
function writeAttachmentFromReport(attachment, opts) {
|
|
9
|
+
const { suggestedFileName, id, mimeType } = attachment;
|
|
10
|
+
if (opts.writtenFiles.has(suggestedFileName)) return;
|
|
11
|
+
const absolutePath = join(opts.screenshotsDir, suggestedFileName);
|
|
12
|
+
const outputRelativePath = `./screenshots/${suggestedFileName}`;
|
|
13
|
+
const sourceRef = attachment.filePath !== outputRelativePath ? {
|
|
14
|
+
type: 'midscene_screenshot_ref',
|
|
15
|
+
id,
|
|
16
|
+
capturedAt: 0,
|
|
17
|
+
mimeType: mimeType || 'image/png',
|
|
18
|
+
storage: 'file',
|
|
19
|
+
path: attachment.filePath
|
|
20
|
+
} : null;
|
|
21
|
+
const resolved = resolveScreenshotSource(sourceRef, {
|
|
22
|
+
reportPath: opts.htmlPath,
|
|
23
|
+
fallbackId: id,
|
|
24
|
+
fallbackMimeType: mimeType || 'image/png'
|
|
25
|
+
});
|
|
26
|
+
if ('data-uri' === resolved.type) {
|
|
27
|
+
const rawBase64 = resolved.dataUri.replace(/^data:image\/[a-zA-Z+]+;base64,/, '');
|
|
28
|
+
writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));
|
|
29
|
+
opts.writtenFiles.add(suggestedFileName);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!existsSync(resolved.filePath)) throw new Error(`Cannot resolve screenshot "${id}" for markdown attachment from ${opts.htmlPath}`);
|
|
33
|
+
copyFileSync(resolved.filePath, absolutePath);
|
|
34
|
+
opts.writtenFiles.add(suggestedFileName);
|
|
35
|
+
}
|
|
36
|
+
async function markdownFromReport(htmlPath, outputDir) {
|
|
37
|
+
const screenshotsDir = join(outputDir, 'screenshots');
|
|
38
|
+
mkdirSync(outputDir, {
|
|
39
|
+
recursive: true
|
|
40
|
+
});
|
|
41
|
+
mkdirSync(screenshotsDir, {
|
|
42
|
+
recursive: true
|
|
43
|
+
});
|
|
44
|
+
const { baseDump, executions } = collectDedupedExecutions(htmlPath);
|
|
45
|
+
const mergedReport = new ReportActionDump({
|
|
46
|
+
sdkVersion: baseDump.sdkVersion,
|
|
47
|
+
groupName: baseDump.groupName,
|
|
48
|
+
groupDescription: baseDump.groupDescription,
|
|
49
|
+
modelBriefs: baseDump.modelBriefs,
|
|
50
|
+
deviceType: baseDump.deviceType,
|
|
51
|
+
executions
|
|
52
|
+
});
|
|
53
|
+
const result = reportToMarkdown(mergedReport);
|
|
54
|
+
const markdownFiles = [];
|
|
55
|
+
const writtenScreenshots = new Set();
|
|
56
|
+
const mdPath = join(outputDir, 'report.md');
|
|
57
|
+
writeFileSync(mdPath, result.markdown, 'utf-8');
|
|
58
|
+
markdownFiles.push(mdPath);
|
|
59
|
+
for (const attachment of result.attachments)writeAttachmentFromReport(attachment, {
|
|
60
|
+
htmlPath,
|
|
61
|
+
screenshotsDir,
|
|
62
|
+
writtenFiles: writtenScreenshots
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
markdownFiles,
|
|
66
|
+
screenshotFiles: Array.from(writtenScreenshots).sort().map((f)=>join(screenshotsDir, f))
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function resolveReportHtmlPath(htmlPath) {
|
|
70
|
+
const normalizedPath = resolve(htmlPath);
|
|
71
|
+
if (!existsSync(normalizedPath)) throw new Error(`report-tool: --htmlPath does not exist: ${htmlPath}`);
|
|
72
|
+
const stats = statSync(normalizedPath);
|
|
73
|
+
if (!stats.isDirectory()) return normalizedPath;
|
|
74
|
+
const indexHtmlPath = join(normalizedPath, 'index.html');
|
|
75
|
+
if (!existsSync(indexHtmlPath)) throw new Error(`report-tool: "${htmlPath}" is not an HTML report file, and no index.html was found under this directory.`);
|
|
76
|
+
return indexHtmlPath;
|
|
77
|
+
}
|
|
78
|
+
function splitReportFile(options) {
|
|
79
|
+
const { htmlPath, outputDir } = options;
|
|
80
|
+
if (!htmlPath) throw new Error('splitReportFile: htmlPath is required');
|
|
81
|
+
if (!outputDir) throw new Error('splitReportFile: outputDir is required');
|
|
82
|
+
const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);
|
|
83
|
+
return splitReportHtmlByExecution({
|
|
84
|
+
htmlPath: resolvedHtmlPath,
|
|
85
|
+
outputDir
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async function reportFileToMarkdown(options) {
|
|
89
|
+
const { htmlPath, outputDir } = options;
|
|
90
|
+
if (!htmlPath) throw new Error('reportFileToMarkdown: htmlPath is required');
|
|
91
|
+
if (!outputDir) throw new Error('reportFileToMarkdown: outputDir is required');
|
|
92
|
+
const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);
|
|
93
|
+
return markdownFromReport(resolvedHtmlPath, outputDir);
|
|
94
|
+
}
|
|
95
|
+
const reportCommandDefinition = {
|
|
96
|
+
name: 'report-tool',
|
|
97
|
+
description: 'Transform Midscene report artifacts, including splitting executions and converting to markdown.',
|
|
98
|
+
schema: {
|
|
99
|
+
action: z["enum"]([
|
|
100
|
+
'split',
|
|
101
|
+
'to-markdown'
|
|
102
|
+
]).optional().describe('Report action to run. Supports: split, to-markdown. Defaults to split.'),
|
|
103
|
+
htmlPath: z.string().optional().describe('Input report HTML path (e.g. ./report/index.html)'),
|
|
104
|
+
outputDir: z.string().optional().describe('Output directory for generated report artifacts')
|
|
105
|
+
},
|
|
106
|
+
handler: async (args)=>{
|
|
107
|
+
const { action = 'split', htmlPath, outputDir } = args;
|
|
108
|
+
if ('split' !== action && 'to-markdown' !== action) throw new Error(`report-tool: unsupported --action value "${action}". Currently supported: split, to-markdown`);
|
|
109
|
+
if (!htmlPath) throw new Error('report-tool: --htmlPath is required');
|
|
110
|
+
if (!outputDir) throw new Error('report-tool: --outputDir is required');
|
|
111
|
+
if ('to-markdown' === action) {
|
|
112
|
+
const result = await reportFileToMarkdown({
|
|
113
|
+
htmlPath,
|
|
114
|
+
outputDir
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
isError: false,
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: `Markdown export completed. Generated ${result.markdownFiles.length} markdown file(s) and ${result.screenshotFiles.length} screenshot(s). Output path: ${outputDir}`
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const result = splitReportFile({
|
|
127
|
+
htmlPath,
|
|
128
|
+
outputDir
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
isError: false,
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: 'text',
|
|
135
|
+
text: `Report split completed. Generated ${result.executionJsonFiles.length} execution JSON files and ${result.screenshotFiles.length} screenshots. Output path: ${outputDir}`
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function createReportCliCommands() {
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
name: 'report-tool',
|
|
145
|
+
def: reportCommandDefinition
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
export { createReportCliCommands, reportFileToMarkdown, splitReportFile };
|
|
150
|
+
|
|
151
|
+
//# sourceMappingURL=report-cli.mjs.map
|
|
@@ -0,0 +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 { resolveScreenshotSource } from './dump/screenshot-store';\nimport { collectDedupedExecutions, splitReportHtmlByExecution } from './report';\nimport { reportToMarkdown } from './report-markdown';\nimport type { MarkdownAttachment } from './report-markdown';\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';\n\nexport interface ConsumeReportFileOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport type SplitReportFileOptions = ConsumeReportFileOptions;\nexport type ReportFileToMarkdownOptions = ConsumeReportFileOptions;\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 outputRelativePath = `./screenshots/${suggestedFileName}`;\n const sourceRef =\n attachment.filePath !== outputRelativePath\n ? {\n type: 'midscene_screenshot_ref' as const,\n id,\n capturedAt: 0,\n mimeType: (mimeType || 'image/png') as 'image/png' | 'image/jpeg',\n storage: 'file' as const,\n path: attachment.filePath,\n }\n : null;\n\n const resolved = resolveScreenshotSource(sourceRef, {\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 reportCommandDefinition: ReportCliCommandDefinition = {\n name: 'report-tool',\n description:\n 'Transform Midscene report artifacts, including splitting executions and converting to markdown.',\n schema: {\n action: z\n .enum(['split', 'to-markdown'])\n .optional()\n .describe(\n 'Report action to run. Supports: split, to-markdown. Defaults to split.',\n ),\n htmlPath: z\n .string()\n .optional()\n .describe('Input report HTML path (e.g. ./report/index.html)'),\n outputDir: z\n .string()\n .optional()\n .describe('Output directory for generated report artifacts'),\n },\n handler: async (args) => {\n const {\n action = 'split',\n htmlPath,\n outputDir,\n } = args as {\n action?: string;\n htmlPath?: string;\n outputDir?: string;\n };\n if (action !== 'split' && action !== 'to-markdown') {\n throw new Error(\n `report-tool: unsupported --action value \"${action}\". Currently supported: split, to-markdown`,\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","outputRelativePath","sourceRef","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","reportCommandDefinition","z","args","action","createReportCliCommands"],"mappings":"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"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { getMidsceneRunSubDir } from "@midscene/shared/common";
|
|
4
|
+
import { MIDSCENE_REPORT_QUIET, globalConfigManager } from "@midscene/shared/env";
|
|
5
|
+
import { ifInBrowser, logMsg, uuid } from "@midscene/shared/utils";
|
|
6
|
+
import { generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript } from "./dump/html-utils.mjs";
|
|
7
|
+
import { ScreenshotStore } from "./dump/screenshot-store.mjs";
|
|
8
|
+
import { ReportActionDump } from "./types.mjs";
|
|
9
|
+
import { appendFileSync, getReportTpl } from "./utils.mjs";
|
|
10
|
+
function _define_property(obj, key, value) {
|
|
11
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
12
|
+
value: value,
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true
|
|
16
|
+
});
|
|
17
|
+
else obj[key] = value;
|
|
18
|
+
return obj;
|
|
19
|
+
}
|
|
20
|
+
const nullReportGenerator = {
|
|
21
|
+
onExecutionUpdate: ()=>{},
|
|
22
|
+
flush: async ()=>{},
|
|
23
|
+
finalize: async ()=>void 0,
|
|
24
|
+
getReportPath: ()=>void 0
|
|
25
|
+
};
|
|
26
|
+
function assertReportGenerationOptions(opts) {
|
|
27
|
+
if (false === opts.generateReport && true === opts.persistExecutionDump) throw new Error('persistExecutionDump cannot be true when generateReport is false');
|
|
28
|
+
}
|
|
29
|
+
class ReportGenerator {
|
|
30
|
+
static create(reportFileName, opts) {
|
|
31
|
+
assertReportGenerationOptions(opts);
|
|
32
|
+
if (false === opts.generateReport) return nullReportGenerator;
|
|
33
|
+
if (ifInBrowser) return nullReportGenerator;
|
|
34
|
+
validateReportFileName(reportFileName);
|
|
35
|
+
const reportRootDir = getMidsceneRunSubDir('report');
|
|
36
|
+
const outputDir = join(reportRootDir, reportFileName);
|
|
37
|
+
const reportPath = 'html-and-external-assets' === opts.outputFormat ? join(outputDir, 'index.html') : join(reportRootDir, ensureHtmlFileName(reportFileName));
|
|
38
|
+
return new ReportGenerator({
|
|
39
|
+
reportPath,
|
|
40
|
+
screenshotMode: 'html-and-external-assets' === opts.outputFormat ? 'directory' : 'inline',
|
|
41
|
+
persistExecutionDump: opts.persistExecutionDump,
|
|
42
|
+
autoPrint: opts.autoPrintReportMsg
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
onExecutionUpdate(execution, reportMeta, attributes) {
|
|
46
|
+
this.lastExecution = execution;
|
|
47
|
+
this.lastReportMeta = reportMeta;
|
|
48
|
+
this.mergeReportAttributes(attributes);
|
|
49
|
+
this.writeQueue = this.writeQueue.then(()=>{
|
|
50
|
+
if (this.destroyed) return;
|
|
51
|
+
this.doWriteExecution(execution, reportMeta);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async flush() {
|
|
55
|
+
await this.writeQueue;
|
|
56
|
+
}
|
|
57
|
+
async finalize() {
|
|
58
|
+
if (this.lastExecution && this.lastReportMeta) this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);
|
|
59
|
+
await this.flush();
|
|
60
|
+
this.destroyed = true;
|
|
61
|
+
if (!this.initialized) return;
|
|
62
|
+
this.printReportPath('finalized');
|
|
63
|
+
return this.reportPath;
|
|
64
|
+
}
|
|
65
|
+
getReportPath() {
|
|
66
|
+
return this.reportPath;
|
|
67
|
+
}
|
|
68
|
+
printReportPath(verb) {
|
|
69
|
+
if (!this.autoPrint || !this.reportPath) return;
|
|
70
|
+
if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET)) return;
|
|
71
|
+
'directory' === this.screenshotMode ? logMsg(`Midscene - report ${verb}: npx serve ${dirname(this.reportPath)}`) : logMsg(`Midscene - report ${verb}: ${this.reportPath}`);
|
|
72
|
+
}
|
|
73
|
+
doWriteExecution(execution, reportMeta) {
|
|
74
|
+
const singleDump = this.wrapAsReportDump(execution, reportMeta);
|
|
75
|
+
if ('inline' === this.screenshotMode) this.writeInlineExecution(execution, singleDump);
|
|
76
|
+
else this.writeDirectoryExecution(execution, singleDump);
|
|
77
|
+
if (this.shouldPersistExecutionDump) this.persistExecutionDumpToFile(execution, singleDump);
|
|
78
|
+
if (!this.firstWriteDone) {
|
|
79
|
+
this.firstWriteDone = true;
|
|
80
|
+
this.printReportPath('generated');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
mergeReportAttributes(attributes) {
|
|
84
|
+
if (!attributes) return;
|
|
85
|
+
for (const [key, value] of Object.entries(attributes))if (null != value) {
|
|
86
|
+
if ('data-group-id' !== key) this.reportAttributes[key] = String(value);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
hydrateStateFromExistingReport() {
|
|
90
|
+
if (!existsSync(this.reportPath)) return;
|
|
91
|
+
this.initialized = true;
|
|
92
|
+
if (!this.shouldPersistExecutionDump) return;
|
|
93
|
+
const reportDir = dirname(this.reportPath);
|
|
94
|
+
const existingExecutionIndices = readdirSync(reportDir).map((name)=>/^(\d+)\.execution\.json$/.exec(name)?.[1]).filter((index)=>Boolean(index)).map((index)=>Number.parseInt(index, 10)).filter((index)=>Number.isFinite(index));
|
|
95
|
+
if (existingExecutionIndices.length > 0) this.executionLogIndex = Math.max(...existingExecutionIndices);
|
|
96
|
+
}
|
|
97
|
+
getDumpScriptAttributes() {
|
|
98
|
+
return {
|
|
99
|
+
'data-group-id': this.reportStreamId,
|
|
100
|
+
...this.reportAttributes
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
wrapAsReportDump(execution, reportMeta) {
|
|
104
|
+
return new ReportActionDump({
|
|
105
|
+
sdkVersion: reportMeta.sdkVersion,
|
|
106
|
+
groupName: reportMeta.groupName,
|
|
107
|
+
groupDescription: reportMeta.groupDescription,
|
|
108
|
+
modelBriefs: reportMeta.modelBriefs,
|
|
109
|
+
deviceType: reportMeta.deviceType,
|
|
110
|
+
executions: [
|
|
111
|
+
execution
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
writeInlineExecution(execution, singleDump) {
|
|
116
|
+
const dir = dirname(this.reportPath);
|
|
117
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
118
|
+
recursive: true
|
|
119
|
+
});
|
|
120
|
+
if (!this.initialized) {
|
|
121
|
+
writeFileSync(this.reportPath, getReportTpl());
|
|
122
|
+
this.initialized = true;
|
|
123
|
+
}
|
|
124
|
+
for (const screenshot of execution.collectScreenshots())this.screenshotStore.persist(screenshot);
|
|
125
|
+
const serialized = singleDump.serialize();
|
|
126
|
+
appendFileSync(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
|
|
127
|
+
}
|
|
128
|
+
writeDirectoryExecution(execution, singleDump) {
|
|
129
|
+
const dir = dirname(this.reportPath);
|
|
130
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
131
|
+
recursive: true
|
|
132
|
+
});
|
|
133
|
+
for (const screenshot of execution.collectScreenshots())this.screenshotStore.persist(screenshot);
|
|
134
|
+
const serialized = singleDump.serialize();
|
|
135
|
+
if (!this.initialized) {
|
|
136
|
+
writeFileSync(this.reportPath, `${getReportTpl()}${getBaseUrlFixScript()}`);
|
|
137
|
+
this.initialized = true;
|
|
138
|
+
}
|
|
139
|
+
appendFileSync(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
|
|
140
|
+
}
|
|
141
|
+
getExecutionLogKey(execution) {
|
|
142
|
+
if (!execution.id) throw new Error('ReportGenerator: execution.id is required for persisting execution dumps');
|
|
143
|
+
return `id:${execution.id}`;
|
|
144
|
+
}
|
|
145
|
+
persistExecutionDumpToFile(execution, singleDump) {
|
|
146
|
+
const dir = dirname(this.reportPath);
|
|
147
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
148
|
+
recursive: true
|
|
149
|
+
});
|
|
150
|
+
const executionLogKey = this.getExecutionLogKey(execution);
|
|
151
|
+
let fileIndex = this.executionLogFileIndexByExecutionKey.get(executionLogKey);
|
|
152
|
+
if (!fileIndex) {
|
|
153
|
+
this.executionLogIndex += 1;
|
|
154
|
+
fileIndex = this.executionLogIndex;
|
|
155
|
+
this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);
|
|
156
|
+
}
|
|
157
|
+
const fileName = `${fileIndex}.execution.json`;
|
|
158
|
+
const filePath = join(dirname(this.reportPath), fileName);
|
|
159
|
+
writeFileSync(filePath, singleDump.serialize(2), 'utf-8');
|
|
160
|
+
}
|
|
161
|
+
constructor(options){
|
|
162
|
+
_define_property(this, "reportPath", void 0);
|
|
163
|
+
_define_property(this, "screenshotMode", void 0);
|
|
164
|
+
_define_property(this, "shouldPersistExecutionDump", void 0);
|
|
165
|
+
_define_property(this, "autoPrint", void 0);
|
|
166
|
+
_define_property(this, "firstWriteDone", false);
|
|
167
|
+
_define_property(this, "executionLogIndex", 0);
|
|
168
|
+
_define_property(this, "executionLogFileIndexByExecutionKey", new Map());
|
|
169
|
+
_define_property(this, "reportStreamId", void 0);
|
|
170
|
+
_define_property(this, "screenshotStore", void 0);
|
|
171
|
+
_define_property(this, "initialized", false);
|
|
172
|
+
_define_property(this, "lastExecution", void 0);
|
|
173
|
+
_define_property(this, "lastReportMeta", void 0);
|
|
174
|
+
_define_property(this, "reportAttributes", {});
|
|
175
|
+
_define_property(this, "writeQueue", Promise.resolve());
|
|
176
|
+
_define_property(this, "destroyed", false);
|
|
177
|
+
this.reportPath = options.reportPath;
|
|
178
|
+
this.screenshotMode = options.screenshotMode;
|
|
179
|
+
this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;
|
|
180
|
+
this.autoPrint = options.autoPrint ?? true;
|
|
181
|
+
this.reportStreamId = uuid();
|
|
182
|
+
this.screenshotStore = new ScreenshotStore({
|
|
183
|
+
mode: 'inline' === this.screenshotMode ? 'inline' : 'directory',
|
|
184
|
+
reportPath: this.reportPath,
|
|
185
|
+
screenshotsDir: join(dirname(this.reportPath), 'screenshots'),
|
|
186
|
+
writeInlineImage: (id, base64)=>{
|
|
187
|
+
appendFileSync(this.reportPath, `\n${generateImageScriptTag(id, base64)}`);
|
|
188
|
+
},
|
|
189
|
+
alsoWriteFileCopy: this.shouldPersistExecutionDump
|
|
190
|
+
});
|
|
191
|
+
this.hydrateStateFromExistingReport();
|
|
192
|
+
this.printReportPath('will be generated at');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function ensureHtmlFileName(reportFileName) {
|
|
196
|
+
return reportFileName.endsWith('.html') ? reportFileName : `${reportFileName}.html`;
|
|
197
|
+
}
|
|
198
|
+
function validateReportFileName(reportFileName) {
|
|
199
|
+
if (!reportFileName?.trim()) throw new Error('reportFileName must be a non-empty string');
|
|
200
|
+
if (/[\\/]/.test(reportFileName)) throw new Error('reportFileName must not contain path separators (`/` or `\\\\`)');
|
|
201
|
+
if (/[:*?"<>|]/.test(reportFileName)) throw new Error('reportFileName contains illegal filename characters: : * ? " < > |');
|
|
202
|
+
}
|
|
203
|
+
export { ReportGenerator, assertReportGenerationOptions, nullReportGenerator };
|
|
204
|
+
|
|
205
|
+
//# sourceMappingURL=report-generator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-generator.mjs","sources":["../../src/report-generator.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n MIDSCENE_REPORT_QUIET,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { ifInBrowser, logMsg, uuid } from '@midscene/shared/utils';\nimport {\n generateDumpScriptTag,\n generateImageScriptTag,\n getBaseUrlFixScript,\n} from './dump/html-utils';\nimport { ScreenshotStore } from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n ReportActionDump,\n type ReportAttributes,\n type ReportMeta,\n} from './types';\nimport { appendFileSync, getReportTpl } from './utils';\n\nexport interface IReportGenerator {\n /**\n * Write or update a single execution.\n * Each call appends a new dump script tag. The frontend deduplicates\n * executions with the same id/name, keeping only the last one.\n *\n * @param execution Current execution's full data\n * @param reportMeta Report-level metadata (groupName, sdkVersion, etc.)\n */\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void;\n\n /**\n * @deprecated Use onExecutionUpdate instead. Kept for backward compatibility.\n */\n onDumpUpdate?(dump: ReportActionDump): void;\n\n /**\n * Wait for all queued write operations to complete.\n */\n flush(): Promise<void>;\n\n /**\n * Finalize the report. Calls flush() internally.\n */\n finalize(): Promise<string | undefined>;\n\n getReportPath(): string | undefined;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n onExecutionUpdate: () => {},\n flush: async () => {},\n finalize: async () => undefined,\n getReportPath: () => undefined,\n};\n\nexport function assertReportGenerationOptions(opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n}): void {\n if (opts.generateReport === false && opts.persistExecutionDump === true) {\n throw new Error(\n 'persistExecutionDump cannot be true when generateReport is false',\n );\n }\n}\n\nexport class ReportGenerator implements IReportGenerator {\n private reportPath: string;\n private screenshotMode: 'inline' | 'directory';\n private shouldPersistExecutionDump: boolean;\n private autoPrint: boolean;\n private firstWriteDone = false;\n private executionLogIndex = 0;\n private executionLogFileIndexByExecutionKey = new Map<string, number>();\n\n // Unique identifier for this report stream — used as data-group-id\n private readonly reportStreamId: string;\n\n // Tracks screenshots already written to disk (by id) to avoid duplicates\n private screenshotStore: ScreenshotStore;\n private initialized = false;\n\n // Tracks the last execution + groupMeta for re-writing on finalize\n private lastExecution?: ExecutionDump;\n private lastReportMeta?: ReportMeta;\n private reportAttributes: Record<string, string> = {};\n\n // write queue for serial execution\n private writeQueue: Promise<void> = Promise.resolve();\n private destroyed = false;\n\n constructor(options: {\n reportPath: string;\n screenshotMode: 'inline' | 'directory';\n persistExecutionDump?: boolean;\n autoPrint?: boolean;\n }) {\n this.reportPath = options.reportPath;\n this.screenshotMode = options.screenshotMode;\n this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;\n this.autoPrint = options.autoPrint ?? true;\n this.reportStreamId = uuid();\n this.screenshotStore = new ScreenshotStore({\n mode: this.screenshotMode === 'inline' ? 'inline' : 'directory',\n reportPath: this.reportPath,\n screenshotsDir: join(dirname(this.reportPath), 'screenshots'),\n writeInlineImage: (id, base64) => {\n appendFileSync(\n this.reportPath,\n `\\n${generateImageScriptTag(id, base64)}`,\n );\n },\n alsoWriteFileCopy: this.shouldPersistExecutionDump,\n });\n this.hydrateStateFromExistingReport();\n this.printReportPath('will be generated at');\n }\n\n static create(\n reportFileName: string,\n opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n autoPrintReportMsg?: boolean;\n },\n ): IReportGenerator {\n assertReportGenerationOptions(opts);\n if (opts.generateReport === false) return nullReportGenerator;\n\n // In browser environment, file system is not available\n if (ifInBrowser) return nullReportGenerator;\n validateReportFileName(reportFileName);\n\n const reportRootDir = getMidsceneRunSubDir('report');\n const outputDir = join(reportRootDir, reportFileName);\n const reportPath =\n opts.outputFormat === 'html-and-external-assets'\n ? join(outputDir, 'index.html')\n : join(reportRootDir, ensureHtmlFileName(reportFileName));\n return new ReportGenerator({\n reportPath,\n screenshotMode:\n opts.outputFormat === 'html-and-external-assets'\n ? 'directory'\n : 'inline',\n persistExecutionDump: opts.persistExecutionDump,\n autoPrint: opts.autoPrintReportMsg,\n });\n }\n\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void {\n this.lastExecution = execution;\n this.lastReportMeta = reportMeta;\n this.mergeReportAttributes(attributes);\n this.writeQueue = this.writeQueue.then(() => {\n if (this.destroyed) return;\n this.doWriteExecution(execution, reportMeta);\n });\n }\n\n async flush(): Promise<void> {\n await this.writeQueue;\n }\n\n async finalize(): Promise<string | undefined> {\n // Re-write the last execution to capture any final state changes\n if (this.lastExecution && this.lastReportMeta) {\n this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);\n }\n await this.flush();\n this.destroyed = true;\n\n if (!this.initialized) {\n // No executions were ever written — no file exists\n return undefined;\n }\n\n this.printReportPath('finalized');\n return this.reportPath;\n }\n\n getReportPath(): string | undefined {\n return this.reportPath;\n }\n\n private printReportPath(verb: string): void {\n if (!this.autoPrint || !this.reportPath) return;\n if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET))\n return;\n\n if (this.screenshotMode === 'directory') {\n logMsg(\n `Midscene - report ${verb}: npx serve ${dirname(this.reportPath)}`,\n );\n } else {\n logMsg(`Midscene - report ${verb}: ${this.reportPath}`);\n }\n }\n\n private doWriteExecution(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): void {\n const singleDump = this.wrapAsReportDump(execution, reportMeta);\n\n if (this.screenshotMode === 'inline') {\n this.writeInlineExecution(execution, singleDump);\n } else {\n this.writeDirectoryExecution(execution, singleDump);\n }\n\n if (this.shouldPersistExecutionDump) {\n this.persistExecutionDumpToFile(execution, singleDump);\n }\n\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath('generated');\n }\n }\n\n private mergeReportAttributes(attributes?: ReportAttributes): void {\n if (!attributes) {\n return;\n }\n\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined || value === null) {\n continue;\n }\n if (key === 'data-group-id') {\n continue;\n }\n this.reportAttributes[key] = String(value);\n }\n }\n\n private hydrateStateFromExistingReport(): void {\n if (!existsSync(this.reportPath)) {\n return;\n }\n\n // Reuse existing report file and append new updates instead of rewriting.\n this.initialized = true;\n\n if (!this.shouldPersistExecutionDump) {\n return;\n }\n\n const reportDir = dirname(this.reportPath);\n const existingExecutionIndices = readdirSync(reportDir)\n .map((name) => /^(\\d+)\\.execution\\.json$/.exec(name)?.[1])\n .filter((index): index is string => Boolean(index))\n .map((index) => Number.parseInt(index, 10))\n .filter((index) => Number.isFinite(index));\n\n if (existingExecutionIndices.length > 0) {\n this.executionLogIndex = Math.max(...existingExecutionIndices);\n }\n }\n\n private getDumpScriptAttributes(): Record<string, string> {\n return {\n 'data-group-id': this.reportStreamId,\n ...this.reportAttributes,\n };\n }\n\n /**\n * Wrap an ExecutionDump + ReportMeta into a single-execution ReportActionDump.\n */\n private wrapAsReportDump(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): ReportActionDump {\n return new ReportActionDump({\n sdkVersion: reportMeta.sdkVersion,\n groupName: reportMeta.groupName,\n groupDescription: reportMeta.groupDescription,\n modelBriefs: reportMeta.modelBriefs,\n deviceType: reportMeta.deviceType,\n executions: [execution],\n });\n }\n\n /**\n * Append-only inline mode: write new screenshots and a dump tag on every call.\n * The frontend deduplicates executions with the same id/name (keeps last).\n * Duplicate dump JSON is acceptable; only screenshots are deduplicated.\n */\n private writeInlineExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): void {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Initialize: write HTML template once\n if (!this.initialized) {\n writeFileSync(this.reportPath, getReportTpl());\n this.initialized = true;\n }\n\n // Append new screenshots (skip already-written ones)\n for (const screenshot of execution.collectScreenshots()) {\n this.screenshotStore.persist(screenshot);\n }\n\n // Append dump tag (always — frontend keeps only last per execution id)\n const serialized = singleDump.serialize();\n appendFileSync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n\n private writeDirectoryExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): void {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n for (const screenshot of execution.collectScreenshots()) {\n this.screenshotStore.persist(screenshot);\n }\n\n // 2. Append dump tag (always — frontend keeps only last per execution id)\n const serialized = singleDump.serialize();\n\n if (!this.initialized) {\n writeFileSync(\n this.reportPath,\n `${getReportTpl()}${getBaseUrlFixScript()}`,\n );\n this.initialized = true;\n }\n\n appendFileSync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n\n private getExecutionLogKey(execution: ExecutionDump): string {\n if (!execution.id) {\n throw new Error(\n 'ReportGenerator: execution.id is required for persisting execution dumps',\n );\n }\n return `id:${execution.id}`;\n }\n\n private persistExecutionDumpToFile(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): void {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const executionLogKey = this.getExecutionLogKey(execution);\n let fileIndex =\n this.executionLogFileIndexByExecutionKey.get(executionLogKey);\n if (!fileIndex) {\n this.executionLogIndex += 1;\n fileIndex = this.executionLogIndex;\n this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);\n }\n\n const fileName = `${fileIndex}.execution.json`;\n const filePath = join(dirname(this.reportPath), fileName);\n writeFileSync(filePath, singleDump.serialize(2), 'utf-8');\n }\n}\n\nfunction ensureHtmlFileName(reportFileName: string): string {\n return reportFileName.endsWith('.html')\n ? reportFileName\n : `${reportFileName}.html`;\n}\n\nfunction validateReportFileName(reportFileName: string): void {\n if (!reportFileName?.trim()) {\n throw new Error('reportFileName must be a non-empty string');\n }\n\n if (/[\\\\/]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName must not contain path separators (`/` or `\\\\\\\\`)',\n );\n }\n\n if (/[:*?\"<>|]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName contains illegal filename characters: : * ? \" < > |',\n );\n }\n}\n"],"names":["nullReportGenerator","undefined","assertReportGenerationOptions","opts","Error","ReportGenerator","reportFileName","ifInBrowser","validateReportFileName","reportRootDir","getMidsceneRunSubDir","outputDir","join","reportPath","ensureHtmlFileName","execution","reportMeta","attributes","verb","globalConfigManager","MIDSCENE_REPORT_QUIET","logMsg","dirname","singleDump","key","value","Object","String","existsSync","reportDir","existingExecutionIndices","readdirSync","name","index","Boolean","Number","Math","ReportActionDump","dir","mkdirSync","writeFileSync","getReportTpl","screenshot","serialized","appendFileSync","generateDumpScriptTag","getBaseUrlFixScript","executionLogKey","fileIndex","fileName","filePath","options","Map","Promise","uuid","ScreenshotStore","id","base64","generateImageScriptTag"],"mappings":"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"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import { extractInsightParam, paramStr, typeStr } from "./agent/ui-utils.mjs";
|
|
3
|
+
import { ScreenshotItem } from "./screenshot-item.mjs";
|
|
4
|
+
import { normalizeScreenshotRef } from "./dump/screenshot-store.mjs";
|
|
5
|
+
function toExecutionDump(execution) {
|
|
6
|
+
if (!execution || 'object' != typeof execution) throw new Error('executionToMarkdown: execution is required');
|
|
7
|
+
if (!Array.isArray(execution.tasks)) throw new Error('executionToMarkdown: execution.tasks must be an array');
|
|
8
|
+
if (!execution.name) throw new Error('executionToMarkdown: execution.name is required');
|
|
9
|
+
return execution;
|
|
10
|
+
}
|
|
11
|
+
function toReportDump(report) {
|
|
12
|
+
if (!report || 'object' != typeof report) throw new Error('reportToMarkdown: report is required');
|
|
13
|
+
if (!Array.isArray(report.executions)) throw new Error('reportToMarkdown: report.executions must be an array');
|
|
14
|
+
return report;
|
|
15
|
+
}
|
|
16
|
+
function formatTime(ts) {
|
|
17
|
+
if ('number' != typeof ts || Number.isNaN(ts)) return 'N/A';
|
|
18
|
+
return new Date(ts).toISOString();
|
|
19
|
+
}
|
|
20
|
+
function resolveTaskTiming(task) {
|
|
21
|
+
const timing = task.timing;
|
|
22
|
+
if (!timing) return {};
|
|
23
|
+
const start = timing.start ?? timing.callAiStart ?? timing.callActionStart;
|
|
24
|
+
const end = timing.end ?? timing.callAiEnd ?? timing.callActionEnd ?? timing.captureAfterCallingSnapshotEnd;
|
|
25
|
+
const cost = timing.cost ?? ('number' == typeof start && 'number' == typeof end ? end - start : void 0);
|
|
26
|
+
return {
|
|
27
|
+
start,
|
|
28
|
+
end,
|
|
29
|
+
cost
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function safeTaskParam(task) {
|
|
33
|
+
const readable = paramStr(task);
|
|
34
|
+
if (readable) return readable;
|
|
35
|
+
if ('Insight' === task.type) return extractInsightParam(task.param).content;
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
function formatSize(size) {
|
|
39
|
+
if (!size || 'number' != typeof size.width || 'number' != typeof size.height || Number.isNaN(size.width) || Number.isNaN(size.height)) return;
|
|
40
|
+
return `${size.width} x ${size.height}`;
|
|
41
|
+
}
|
|
42
|
+
function extractLocateCenter(task) {
|
|
43
|
+
const outputCenter = task.output?.element?.center;
|
|
44
|
+
if (Array.isArray(outputCenter) && outputCenter.length >= 2 && 'number' == typeof outputCenter[0] && 'number' == typeof outputCenter[1]) return [
|
|
45
|
+
outputCenter[0],
|
|
46
|
+
outputCenter[1]
|
|
47
|
+
];
|
|
48
|
+
const paramLocateCenter = task.param?.locate?.center;
|
|
49
|
+
if (Array.isArray(paramLocateCenter) && paramLocateCenter.length >= 2 && 'number' == typeof paramLocateCenter[0] && 'number' == typeof paramLocateCenter[1]) return [
|
|
50
|
+
paramLocateCenter[0],
|
|
51
|
+
paramLocateCenter[1]
|
|
52
|
+
];
|
|
53
|
+
const paramCenter = task.param?.center;
|
|
54
|
+
if (Array.isArray(paramCenter) && paramCenter.length >= 2 && 'number' == typeof paramCenter[0] && 'number' == typeof paramCenter[1]) return [
|
|
55
|
+
paramCenter[0],
|
|
56
|
+
paramCenter[1]
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
function tryExtractBase64(screenshot) {
|
|
60
|
+
if (!screenshot || 'object' != typeof screenshot) return;
|
|
61
|
+
const s = screenshot;
|
|
62
|
+
if ('string' == typeof s.base64 && s.base64.length > 0) return s.base64;
|
|
63
|
+
}
|
|
64
|
+
function screenshotAttachment(screenshot, screenshotBaseDir, executionIndex, taskIndex) {
|
|
65
|
+
if (screenshot instanceof ScreenshotItem) {
|
|
66
|
+
const ext = screenshot.extension;
|
|
67
|
+
const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${screenshot.id}.${ext}`;
|
|
68
|
+
const filePath = `${screenshotBaseDir}/${suggestedFileName}`;
|
|
69
|
+
return {
|
|
70
|
+
markdown: `\n`,
|
|
71
|
+
attachment: {
|
|
72
|
+
id: screenshot.id,
|
|
73
|
+
suggestedFileName,
|
|
74
|
+
filePath,
|
|
75
|
+
mimeType: `image/${'jpeg' === ext ? 'jpeg' : 'png'}`,
|
|
76
|
+
executionIndex,
|
|
77
|
+
taskIndex,
|
|
78
|
+
base64Data: tryExtractBase64(screenshot)
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const ref = normalizeScreenshotRef(screenshot);
|
|
83
|
+
if (ref) {
|
|
84
|
+
const ext = 'image/jpeg' === ref.mimeType ? 'jpeg' : 'png';
|
|
85
|
+
const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${ref.id}.${ext}`;
|
|
86
|
+
const filePath = ref.path || `${screenshotBaseDir}/${suggestedFileName}`;
|
|
87
|
+
return {
|
|
88
|
+
markdown: `\n`,
|
|
89
|
+
attachment: {
|
|
90
|
+
id: ref.id,
|
|
91
|
+
suggestedFileName,
|
|
92
|
+
filePath,
|
|
93
|
+
mimeType: ref.mimeType,
|
|
94
|
+
executionIndex,
|
|
95
|
+
taskIndex,
|
|
96
|
+
base64Data: tryExtractBase64(screenshot)
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const base64 = tryExtractBase64(screenshot);
|
|
101
|
+
if (base64) {
|
|
102
|
+
const ext = base64.startsWith('data:image/jpeg') ? 'jpeg' : 'png';
|
|
103
|
+
const id = `restored-${executionIndex + 1}-${taskIndex + 1}`;
|
|
104
|
+
const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${id}.${ext}`;
|
|
105
|
+
const filePath = `${screenshotBaseDir}/${suggestedFileName}`;
|
|
106
|
+
return {
|
|
107
|
+
markdown: `\n`,
|
|
108
|
+
attachment: {
|
|
109
|
+
id,
|
|
110
|
+
suggestedFileName,
|
|
111
|
+
filePath,
|
|
112
|
+
mimeType: `image/${ext}`,
|
|
113
|
+
executionIndex,
|
|
114
|
+
taskIndex,
|
|
115
|
+
base64Data: base64
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`executionToMarkdown: missing screenshot for execution #${executionIndex + 1} task #${taskIndex + 1}`);
|
|
120
|
+
}
|
|
121
|
+
function recorderMarkdownSection(recorder, screenshotBaseDir, executionIndex, taskIndex) {
|
|
122
|
+
if (!recorder?.length) return {
|
|
123
|
+
lines: [],
|
|
124
|
+
attachments: []
|
|
125
|
+
};
|
|
126
|
+
const lines = [
|
|
127
|
+
'',
|
|
128
|
+
'### Recorder'
|
|
129
|
+
];
|
|
130
|
+
const attachments = [];
|
|
131
|
+
recorder.forEach((item, recorderIndex)=>{
|
|
132
|
+
lines.push(`- #${recorderIndex + 1} type=${item.type}, ts=${formatTime(item.ts)}, timing=${item.timing || 'N/A'}`);
|
|
133
|
+
if (!item.screenshot) return;
|
|
134
|
+
const imageResult = screenshotAttachment(item.screenshot, screenshotBaseDir, executionIndex, taskIndex);
|
|
135
|
+
lines.push(imageResult.markdown);
|
|
136
|
+
attachments.push(imageResult.attachment);
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
lines,
|
|
140
|
+
attachments
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function renderExecution(executionRaw, executionIndex, options) {
|
|
144
|
+
const execution = toExecutionDump(executionRaw);
|
|
145
|
+
const screenshotBaseDir = options?.screenshotBaseDir ?? './screenshots';
|
|
146
|
+
const lines = [];
|
|
147
|
+
const attachments = [];
|
|
148
|
+
lines.push(`# ${execution.name}`);
|
|
149
|
+
if (execution.description) lines.push('', execution.description);
|
|
150
|
+
lines.push('', `- Execution start: ${formatTime(execution.logTime)}`);
|
|
151
|
+
lines.push(`- Task count: ${execution.tasks.length}`);
|
|
152
|
+
execution.tasks.forEach((task, taskIndex)=>{
|
|
153
|
+
const title = typeStr(task);
|
|
154
|
+
const detail = safeTaskParam(task);
|
|
155
|
+
const time = resolveTaskTiming(task);
|
|
156
|
+
lines.push('', `## ${taskIndex + 1}. ${title}${detail ? ` - ${detail}` : ''}`);
|
|
157
|
+
lines.push(`- Status: ${task.status || 'unknown'}`);
|
|
158
|
+
lines.push(`- Start: ${formatTime(time.start)}`);
|
|
159
|
+
lines.push(`- End: ${formatTime(time.end)}`);
|
|
160
|
+
lines.push(`- Cost(ms): ${'number' == typeof time.cost ? time.cost : 'N/A'}`);
|
|
161
|
+
lines.push(`- Screen size: ${formatSize(task.uiContext?.shotSize) || 'N/A'}`);
|
|
162
|
+
if ('Locate' === task.subType) {
|
|
163
|
+
const locateCenter = extractLocateCenter(task);
|
|
164
|
+
if (locateCenter) lines.push(`- Locate center: (${locateCenter[0]}, ${locateCenter[1]})`);
|
|
165
|
+
}
|
|
166
|
+
if (task.errorMessage) lines.push(`- Error: ${task.errorMessage}`);
|
|
167
|
+
if (task.uiContext?.screenshot) {
|
|
168
|
+
const imageResult = screenshotAttachment(task.uiContext.screenshot, screenshotBaseDir, executionIndex, taskIndex);
|
|
169
|
+
lines.push(imageResult.markdown);
|
|
170
|
+
attachments.push(imageResult.attachment);
|
|
171
|
+
}
|
|
172
|
+
const recorderSection = recorderMarkdownSection(task.recorder, screenshotBaseDir, executionIndex, taskIndex);
|
|
173
|
+
if (recorderSection.lines.length) {
|
|
174
|
+
lines.push(...recorderSection.lines);
|
|
175
|
+
attachments.push(...recorderSection.attachments);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
markdown: lines.join('\n'),
|
|
180
|
+
attachments
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function reportFileName(execution, executionIndex) {
|
|
184
|
+
const safeName = execution.name.trim().replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-_]/g, '') || `execution-${executionIndex + 1}`;
|
|
185
|
+
return `${executionIndex + 1}-${basename(safeName)}.md`;
|
|
186
|
+
}
|
|
187
|
+
function executionToMarkdown(execution, options) {
|
|
188
|
+
return renderExecution(execution, 0, options);
|
|
189
|
+
}
|
|
190
|
+
function reportToMarkdown(report) {
|
|
191
|
+
const reportDump = toReportDump(report);
|
|
192
|
+
const executionResults = reportDump.executions.map((execution, index)=>{
|
|
193
|
+
const rendered = renderExecution(execution, index);
|
|
194
|
+
return {
|
|
195
|
+
executionIndex: index,
|
|
196
|
+
executionName: execution.name,
|
|
197
|
+
markdown: rendered.markdown,
|
|
198
|
+
attachments: rendered.attachments,
|
|
199
|
+
suggestedFileName: reportFileName(execution, index)
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
const attachments = executionResults.flatMap((item)=>item.attachments);
|
|
203
|
+
const header = [
|
|
204
|
+
`# ${reportDump.groupName}`,
|
|
205
|
+
reportDump.groupDescription ? `\n${reportDump.groupDescription}` : '',
|
|
206
|
+
`\n- SDK Version: ${reportDump.sdkVersion}`,
|
|
207
|
+
`- Execution count: ${reportDump.executions.length}`,
|
|
208
|
+
'\n## Suggested execution markdown files',
|
|
209
|
+
...executionResults.map((item)=>`- ${item.suggestedFileName} (${item.executionName})`)
|
|
210
|
+
].filter(Boolean).join('\n');
|
|
211
|
+
return {
|
|
212
|
+
markdown: `${header}\n\n${executionResults.map((item)=>item.markdown).join('\n\n---\n\n')}`,
|
|
213
|
+
attachments
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export { executionToMarkdown, reportToMarkdown };
|
|
217
|
+
|
|
218
|
+
//# sourceMappingURL=report-markdown.mjs.map
|