@donggui/core 1.5.4-donggui.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/es/agent/agent.mjs +709 -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 +330 -0
- package/dist/es/agent/task-builder.mjs.map +1 -0
- package/dist/es/agent/task-cache.mjs +186 -0
- package/dist/es/agent/task-cache.mjs.map +1 -0
- package/dist/es/agent/tasks.mjs +422 -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 +198 -0
- package/dist/es/agent/utils.mjs.map +1 -0
- package/dist/es/ai-model/auto-glm/actions.mjs +224 -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/conversation-history.mjs +195 -0
- package/dist/es/ai-model/conversation-history.mjs.map +1 -0
- package/dist/es/ai-model/index.mjs +11 -0
- package/dist/es/ai-model/inspect.mjs +386 -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 +129 -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 +364 -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 +219 -0
- package/dist/es/ai-model/prompt/yaml-generator.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/index.mjs +466 -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 +300 -0
- package/dist/es/device/index.mjs.map +1 -0
- package/dist/es/dump/html-utils.mjs +211 -0
- package/dist/es/dump/html-utils.mjs.map +1 -0
- package/dist/es/dump/image-restoration.mjs +43 -0
- package/dist/es/dump/image-restoration.mjs.map +1 -0
- package/dist/es/dump/index.mjs +3 -0
- package/dist/es/index.mjs +15 -0
- package/dist/es/index.mjs.map +1 -0
- package/dist/es/report-generator.mjs +134 -0
- package/dist/es/report-generator.mjs.map +1 -0
- package/dist/es/report.mjs +111 -0
- package/dist/es/report.mjs.map +1 -0
- package/dist/es/screenshot-item.mjs +105 -0
- package/dist/es/screenshot-item.mjs.map +1 -0
- package/dist/es/service/index.mjs +256 -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 +258 -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 +196 -0
- package/dist/es/types.mjs.map +1 -0
- package/dist/es/utils.mjs +218 -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 +418 -0
- package/dist/es/yaml/player.mjs.map +1 -0
- package/dist/es/yaml/utils.mjs +73 -0
- package/dist/es/yaml/utils.mjs.map +1 -0
- package/dist/es/yaml.mjs +0 -0
- package/dist/lib/agent/agent.js +757 -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 +81 -0
- package/dist/lib/agent/index.js.map +1 -0
- package/dist/lib/agent/task-builder.js +367 -0
- package/dist/lib/agent/task-builder.js.map +1 -0
- package/dist/lib/agent/task-cache.js +238 -0
- package/dist/lib/agent/task-cache.js.map +1 -0
- package/dist/lib/agent/tasks.js +465 -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 +275 -0
- package/dist/lib/agent/utils.js.map +1 -0
- package/dist/lib/ai-model/auto-glm/actions.js +258 -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/conversation-history.js +229 -0
- package/dist/lib/ai-model/conversation-history.js.map +1 -0
- package/dist/lib/ai-model/index.js +125 -0
- package/dist/lib/ai-model/index.js.map +1 -0
- package/dist/lib/ai-model/inspect.js +429 -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 +169 -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 +401 -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 +280 -0
- package/dist/lib/ai-model/prompt/yaml-generator.js.map +1 -0
- package/dist/lib/ai-model/service-caller/index.js +531 -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 +418 -0
- package/dist/lib/device/index.js.map +1 -0
- package/dist/lib/dump/html-utils.js +281 -0
- package/dist/lib/dump/html-utils.js.map +1 -0
- package/dist/lib/dump/image-restoration.js +77 -0
- package/dist/lib/dump/image-restoration.js.map +1 -0
- package/dist/lib/dump/index.js +60 -0
- package/dist/lib/dump/index.js.map +1 -0
- package/dist/lib/index.js +146 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/report-generator.js +172 -0
- package/dist/lib/report-generator.js.map +1 -0
- package/dist/lib/report.js +145 -0
- package/dist/lib/report.js.map +1 -0
- package/dist/lib/screenshot-item.js +139 -0
- package/dist/lib/screenshot-item.js.map +1 -0
- package/dist/lib/service/index.js +290 -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 +295 -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 +285 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.js +297 -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 +452 -0
- package/dist/lib/yaml/player.js.map +1 -0
- package/dist/lib/yaml/utils.js +126 -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 +190 -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 +10 -0
- package/dist/types/agent/task-builder.d.ts +34 -0
- package/dist/types/agent/task-cache.d.ts +48 -0
- package/dist/types/agent/tasks.d.ts +70 -0
- package/dist/types/agent/ui-utils.d.ts +14 -0
- package/dist/types/agent/utils.d.ts +29 -0
- package/dist/types/ai-model/auto-glm/actions.d.ts +77 -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 +10 -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/conversation-history.d.ts +105 -0
- package/dist/types/ai-model/index.d.ts +14 -0
- package/dist/types/ai-model/inspect.d.ts +58 -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 +100 -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 +142 -0
- package/dist/types/device/index.d.ts +2315 -0
- package/dist/types/dump/html-utils.d.ts +52 -0
- package/dist/types/dump/image-restoration.d.ts +6 -0
- package/dist/types/dump/index.d.ts +5 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/report-generator.d.ts +48 -0
- package/dist/types/report.d.ts +15 -0
- package/dist/types/screenshot-item.d.ts +66 -0
- package/dist/types/service/index.d.ts +23 -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 +48 -0
- package/dist/types/task-timing.d.ts +8 -0
- package/dist/types/tree.d.ts +4 -0
- package/dist/types/types.d.ts +645 -0
- package/dist/types/utils.d.ts +40 -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 +203 -0
- package/package.json +111 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
|
|
2
|
+
import { antiEscapeScriptTag, escapeScriptTag } from "@midscene/shared/utils";
|
|
3
|
+
var __webpack_modules__ = {
|
|
4
|
+
"node:fs" (module) {
|
|
5
|
+
module.exports = __rspack_external_node_fs_5ea92f0c;
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var __webpack_module_cache__ = {};
|
|
9
|
+
function __webpack_require__(moduleId) {
|
|
10
|
+
var cachedModule = __webpack_module_cache__[moduleId];
|
|
11
|
+
if (void 0 !== cachedModule) return cachedModule.exports;
|
|
12
|
+
var module = __webpack_module_cache__[moduleId] = {
|
|
13
|
+
exports: {}
|
|
14
|
+
};
|
|
15
|
+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
16
|
+
return module.exports;
|
|
17
|
+
}
|
|
18
|
+
var external_node_fs_ = __webpack_require__("node:fs");
|
|
19
|
+
const escapeContent = escapeScriptTag;
|
|
20
|
+
const unescapeContent = antiEscapeScriptTag;
|
|
21
|
+
const STREAMING_CHUNK_SIZE = 65536;
|
|
22
|
+
function streamScanTags(filePath, openTag, closeTag, onMatch) {
|
|
23
|
+
const fd = (0, external_node_fs_.openSync)(filePath, 'r');
|
|
24
|
+
const fileSize = (0, external_node_fs_.statSync)(filePath).size;
|
|
25
|
+
const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);
|
|
26
|
+
let position = 0;
|
|
27
|
+
let leftover = '';
|
|
28
|
+
let capturing = false;
|
|
29
|
+
let currentContent = '';
|
|
30
|
+
try {
|
|
31
|
+
while(position < fileSize){
|
|
32
|
+
const bytesRead = (0, external_node_fs_.readSync)(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);
|
|
33
|
+
const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);
|
|
34
|
+
position += bytesRead;
|
|
35
|
+
let searchStart = 0;
|
|
36
|
+
while(searchStart < chunk.length)if (capturing) {
|
|
37
|
+
const endIdx = chunk.indexOf(closeTag, searchStart);
|
|
38
|
+
if (-1 !== endIdx) {
|
|
39
|
+
currentContent += chunk.slice(searchStart, endIdx);
|
|
40
|
+
const shouldStop = onMatch(currentContent);
|
|
41
|
+
if (shouldStop) return;
|
|
42
|
+
capturing = false;
|
|
43
|
+
currentContent = '';
|
|
44
|
+
searchStart = endIdx + closeTag.length;
|
|
45
|
+
} else {
|
|
46
|
+
currentContent += chunk.slice(searchStart, -closeTag.length);
|
|
47
|
+
leftover = chunk.slice(-closeTag.length);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
const startIdx = chunk.indexOf(openTag, searchStart);
|
|
52
|
+
if (-1 !== startIdx) {
|
|
53
|
+
capturing = true;
|
|
54
|
+
currentContent = chunk.slice(startIdx + openTag.length);
|
|
55
|
+
const endIdx = currentContent.indexOf(closeTag);
|
|
56
|
+
if (-1 !== endIdx) {
|
|
57
|
+
const shouldStop = onMatch(currentContent.slice(0, endIdx));
|
|
58
|
+
if (shouldStop) return;
|
|
59
|
+
capturing = false;
|
|
60
|
+
currentContent = '';
|
|
61
|
+
searchStart = startIdx + openTag.length + endIdx + closeTag.length;
|
|
62
|
+
} else {
|
|
63
|
+
leftover = currentContent.slice(-closeTag.length);
|
|
64
|
+
currentContent = currentContent.slice(0, -closeTag.length);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
leftover = chunk.slice(-openTag.length);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} finally{
|
|
74
|
+
(0, external_node_fs_.closeSync)(fd);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function extractImageByIdSync(htmlPath, imageId) {
|
|
78
|
+
const targetTag = `<script type="midscene-image" data-id="${imageId}">`;
|
|
79
|
+
const closeTag = "<\/script>";
|
|
80
|
+
let result = null;
|
|
81
|
+
streamScanTags(htmlPath, targetTag, closeTag, (content)=>{
|
|
82
|
+
result = unescapeContent(content);
|
|
83
|
+
return true;
|
|
84
|
+
});
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
function streamImageScriptsToFile(srcFilePath, destFilePath) {
|
|
88
|
+
const { appendFileSync } = __webpack_require__("node:fs");
|
|
89
|
+
const openTag = '<script type="midscene-image"';
|
|
90
|
+
const closeTag = "<\/script>";
|
|
91
|
+
streamScanTags(srcFilePath, openTag, closeTag, (content)=>{
|
|
92
|
+
appendFileSync(destFilePath, `${openTag}${content}${closeTag}\n`);
|
|
93
|
+
return false;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function extractLastDumpScriptSync(filePath) {
|
|
97
|
+
const openTagPrefix = '<script type="midscene_web_dump"';
|
|
98
|
+
const closeTag = "<\/script>";
|
|
99
|
+
let lastContent = '';
|
|
100
|
+
const fd = (0, external_node_fs_.openSync)(filePath, 'r');
|
|
101
|
+
const fileSize = (0, external_node_fs_.statSync)(filePath).size;
|
|
102
|
+
const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);
|
|
103
|
+
let position = 0;
|
|
104
|
+
let leftover = '';
|
|
105
|
+
let capturing = false;
|
|
106
|
+
let currentContent = '';
|
|
107
|
+
try {
|
|
108
|
+
while(position < fileSize){
|
|
109
|
+
const bytesRead = (0, external_node_fs_.readSync)(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);
|
|
110
|
+
const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);
|
|
111
|
+
position += bytesRead;
|
|
112
|
+
let searchStart = 0;
|
|
113
|
+
while(searchStart < chunk.length)if (capturing) {
|
|
114
|
+
const endIdx = chunk.indexOf(closeTag, searchStart);
|
|
115
|
+
if (-1 !== endIdx) {
|
|
116
|
+
currentContent += chunk.slice(searchStart, endIdx);
|
|
117
|
+
lastContent = currentContent.trim();
|
|
118
|
+
capturing = false;
|
|
119
|
+
currentContent = '';
|
|
120
|
+
searchStart = endIdx + closeTag.length;
|
|
121
|
+
} else {
|
|
122
|
+
currentContent += chunk.slice(searchStart, -closeTag.length);
|
|
123
|
+
leftover = chunk.slice(-closeTag.length);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
const startIdx = chunk.indexOf(openTagPrefix, searchStart);
|
|
128
|
+
if (-1 !== startIdx) {
|
|
129
|
+
const tagEndIdx = chunk.indexOf('>', startIdx);
|
|
130
|
+
if (-1 !== tagEndIdx) {
|
|
131
|
+
capturing = true;
|
|
132
|
+
currentContent = chunk.slice(tagEndIdx + 1);
|
|
133
|
+
const endIdx = currentContent.indexOf(closeTag);
|
|
134
|
+
if (-1 !== endIdx) {
|
|
135
|
+
lastContent = currentContent.slice(0, endIdx).trim();
|
|
136
|
+
capturing = false;
|
|
137
|
+
currentContent = '';
|
|
138
|
+
searchStart = tagEndIdx + 1 + endIdx + closeTag.length;
|
|
139
|
+
} else {
|
|
140
|
+
leftover = currentContent.slice(-closeTag.length);
|
|
141
|
+
currentContent = currentContent.slice(0, -closeTag.length);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
leftover = chunk.slice(startIdx);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
leftover = chunk.slice(-openTagPrefix.length);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} finally{
|
|
155
|
+
(0, external_node_fs_.closeSync)(fd);
|
|
156
|
+
}
|
|
157
|
+
return lastContent;
|
|
158
|
+
}
|
|
159
|
+
function parseImageScripts(html) {
|
|
160
|
+
const imageMap = {};
|
|
161
|
+
const regex = /<script type="midscene-image" data-id="([^"]+)">([\s\S]*?)<\/script>/g;
|
|
162
|
+
for (const match of html.matchAll(regex)){
|
|
163
|
+
const [, id, content] = match;
|
|
164
|
+
imageMap[id] = unescapeContent(content);
|
|
165
|
+
}
|
|
166
|
+
return imageMap;
|
|
167
|
+
}
|
|
168
|
+
function parseDumpScript(html) {
|
|
169
|
+
const scriptOpenTag = '<script type="midscene_web_dump"';
|
|
170
|
+
const scriptCloseTag = "<\/script>";
|
|
171
|
+
const lastOpenIndex = html.lastIndexOf(scriptOpenTag);
|
|
172
|
+
if (-1 === lastOpenIndex) throw new Error("No dump script found in HTML");
|
|
173
|
+
const tagEndIndex = html.indexOf('>', lastOpenIndex);
|
|
174
|
+
if (-1 === tagEndIndex) throw new Error("No dump script found in HTML");
|
|
175
|
+
const closeIndex = html.indexOf(scriptCloseTag, tagEndIndex);
|
|
176
|
+
if (-1 === closeIndex) throw new Error("No dump script found in HTML");
|
|
177
|
+
const content = html.substring(tagEndIndex + 1, closeIndex);
|
|
178
|
+
return unescapeContent(content);
|
|
179
|
+
}
|
|
180
|
+
function parseDumpScriptAttributes(html) {
|
|
181
|
+
const regex = /<script type="midscene_web_dump"([^>]*)>/;
|
|
182
|
+
const match = regex.exec(html);
|
|
183
|
+
if (!match) return {};
|
|
184
|
+
const attrString = match[1];
|
|
185
|
+
const attributes = {};
|
|
186
|
+
const attrRegex = /(\w+)="([^"]*)"/g;
|
|
187
|
+
for (const attrMatch of attrString.matchAll(attrRegex)){
|
|
188
|
+
const [, key, value] = attrMatch;
|
|
189
|
+
if ('type' !== key) attributes[key] = decodeURIComponent(value);
|
|
190
|
+
}
|
|
191
|
+
return attributes;
|
|
192
|
+
}
|
|
193
|
+
function generateImageScriptTag(id, data) {
|
|
194
|
+
return '<script type="midscene-image" data-id="' + id + '">' + escapeContent(data) + "<\/script>";
|
|
195
|
+
}
|
|
196
|
+
let _baseUrlFixScript;
|
|
197
|
+
function getBaseUrlFixScript() {
|
|
198
|
+
if (!_baseUrlFixScript) {
|
|
199
|
+
const close = "<\/script>";
|
|
200
|
+
_baseUrlFixScript = '\n<script>(function(){var p=window.location.pathname;if(p.endsWith("/")||/\\.\\w+$/.test(p))return;var b=document.createElement("base");b.href=p+"/";document.head.insertBefore(b,document.head.firstChild)})()' + close + '\n';
|
|
201
|
+
}
|
|
202
|
+
return _baseUrlFixScript;
|
|
203
|
+
}
|
|
204
|
+
function generateDumpScriptTag(json, attributes) {
|
|
205
|
+
let attrString = '';
|
|
206
|
+
if (attributes && Object.keys(attributes).length > 0) attrString = ' ' + Object.entries(attributes).map(([k, v])=>k + '="' + encodeURIComponent(v) + '"').join(' ');
|
|
207
|
+
return '<script type="midscene_web_dump"' + attrString + '>' + escapeContent(json) + "<\/script>";
|
|
208
|
+
}
|
|
209
|
+
export { STREAMING_CHUNK_SIZE, escapeContent, extractImageByIdSync, extractLastDumpScriptSync, generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, streamImageScriptsToFile, streamScanTags, unescapeContent };
|
|
210
|
+
|
|
211
|
+
//# sourceMappingURL=html-utils.mjs.map
|
|
@@ -0,0 +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';\n\nexport const escapeContent = escapeScriptTag;\nexport const unescapeContent = antiEscapeScriptTag;\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 = '</script>';\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 = '</script>';\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 = '</script>';\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\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 scriptCloseTag = '</script>';\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(scriptCloseTag, 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 return (\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene-image\" data-id=\"' +\n id +\n '\">' +\n escapeContent(data) +\n '</script>'\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 via scriptClose() so that no\n// bundler (rslib, webpack, rsbuild) can ever see or inline a literal\n// '</script>' into JS source. A literal '</script>' inside a <script> block\n// causes the HTML parser to prematurely close the block — which breaks the\n// report viewer when this module is bundled into the report HTML template.\n//\n// Do NOT replace this with a string constant, hex escape (\\x3c), or simple\n// concatenation — bundlers will optimise / inline them and re-introduce the\n// literal '</script>'.\nlet _baseUrlFixScript: string;\nexport function getBaseUrlFixScript(): string {\n if (!_baseUrlFixScript) {\n // Closing </script> MUST be split so that no bundler (rslib / webpack /\n // terser) can ever produce a literal '</script>' in bundle output.\n // A literal '</script>' inside a <script> block causes the HTML parser\n // to prematurely close the block, which breaks the report viewer when\n // this module is bundled into the report template.\n const close = '</' + 'script>';\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\nexport function generateDumpScriptTag(\n json: string,\n attributes?: Record<string, string>,\n): string {\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 '</script>'\n );\n}\n"],"names":["escapeContent","escapeScriptTag","unescapeContent","antiEscapeScriptTag","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","parseImageScripts","html","imageMap","regex","match","id","parseDumpScript","scriptOpenTag","scriptCloseTag","lastOpenIndex","Error","tagEndIndex","closeIndex","parseDumpScriptAttributes","attrString","attributes","attrRegex","attrMatch","key","value","decodeURIComponent","generateImageScriptTag","data","_baseUrlFixScript","getBaseUrlFixScript","close","generateDumpScriptTag","json","Object","k","v","encodeURIComponent"],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function restoreImageReferences(data, resolveImage) {
|
|
2
|
+
if ('string' == typeof data) return data;
|
|
3
|
+
if (Array.isArray(data)) return data.map((item)=>restoreImageReferences(item, resolveImage));
|
|
4
|
+
if ('object' == typeof data && null !== data) {
|
|
5
|
+
if ('$screenshot' in data) {
|
|
6
|
+
const screenshotData = data;
|
|
7
|
+
const id = screenshotData.$screenshot;
|
|
8
|
+
const capturedAt = 'number' == typeof screenshotData.capturedAt ? screenshotData.capturedAt : void 0;
|
|
9
|
+
if ('string' == typeof id) {
|
|
10
|
+
if (id.startsWith('data:image/') || id.startsWith('./') || id.startsWith('/')) return {
|
|
11
|
+
base64: id,
|
|
12
|
+
capturedAt
|
|
13
|
+
};
|
|
14
|
+
let resolved = null;
|
|
15
|
+
const lazy = Object.defineProperties({}, {
|
|
16
|
+
base64: {
|
|
17
|
+
get () {
|
|
18
|
+
if (null === resolved) resolved = resolveImage(id);
|
|
19
|
+
return resolved;
|
|
20
|
+
},
|
|
21
|
+
enumerable: true
|
|
22
|
+
},
|
|
23
|
+
capturedAt: {
|
|
24
|
+
value: capturedAt,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return lazy;
|
|
29
|
+
}
|
|
30
|
+
console.warn('Invalid $screenshot value type:', typeof id);
|
|
31
|
+
return {
|
|
32
|
+
base64: ''
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const result = {};
|
|
36
|
+
for (const [key, value] of Object.entries(data))result[key] = restoreImageReferences(value, resolveImage);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
return data;
|
|
40
|
+
}
|
|
41
|
+
export { restoreImageReferences };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=image-restoration.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dump/image-restoration.mjs","sources":["../../../src/dump/image-restoration.ts"],"sourcesContent":["/**\n * Recursively restore image references in parsed data.\n * Replaces { $screenshot: \"id\" } with lazy { get base64() {...}, capturedAt } objects.\n * The resolver is only called when .base64 is first accessed.\n */\nexport function restoreImageReferences<T>(\n data: T,\n resolveImage: (id: string) => string,\n): T {\n if (typeof data === 'string') {\n return data;\n }\n\n if (Array.isArray(data)) {\n return data.map((item) => restoreImageReferences(item, resolveImage)) as T;\n }\n\n if (typeof data === 'object' && data !== null) {\n if ('$screenshot' in data) {\n const screenshotData = data as {\n $screenshot: unknown;\n capturedAt?: unknown;\n };\n const id = screenshotData.$screenshot;\n const capturedAt =\n typeof screenshotData.capturedAt === 'number'\n ? screenshotData.capturedAt\n : undefined;\n if (typeof id === 'string') {\n // If id looks like a path or base64 data, use it directly (no lazy needed)\n if (\n id.startsWith('data:image/') ||\n id.startsWith('./') ||\n id.startsWith('/')\n ) {\n return { base64: id, capturedAt } as T;\n }\n\n // Create lazy getter — .base64 is only resolved when first accessed\n let resolved: string | null = null;\n const lazy: { base64: string; capturedAt?: number } =\n Object.defineProperties(\n {} as { base64: string; capturedAt?: number },\n {\n base64: {\n get() {\n if (resolved === null) {\n resolved = resolveImage(id);\n }\n return resolved;\n },\n enumerable: true,\n },\n capturedAt: { value: capturedAt, enumerable: true },\n },\n );\n return lazy as T;\n }\n // Invalid $screenshot value, return empty\n console.warn('Invalid $screenshot value type:', typeof id);\n return { base64: '' } as T;\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n result[key] = restoreImageReferences(value, resolveImage);\n }\n return result as T;\n }\n\n return data;\n}\n"],"names":["restoreImageReferences","data","resolveImage","Array","item","screenshotData","id","capturedAt","undefined","resolved","lazy","Object","console","result","key","value"],"mappings":"AAKO,SAASA,uBACdC,IAAO,EACPC,YAAoC;IAEpC,IAAI,AAAgB,YAAhB,OAAOD,MACT,OAAOA;IAGT,IAAIE,MAAM,OAAO,CAACF,OAChB,OAAOA,KAAK,GAAG,CAAC,CAACG,OAASJ,uBAAuBI,MAAMF;IAGzD,IAAI,AAAgB,YAAhB,OAAOD,QAAqBA,AAAS,SAATA,MAAe;QAC7C,IAAI,iBAAiBA,MAAM;YACzB,MAAMI,iBAAiBJ;YAIvB,MAAMK,KAAKD,eAAe,WAAW;YACrC,MAAME,aACJ,AAAqC,YAArC,OAAOF,eAAe,UAAU,GAC5BA,eAAe,UAAU,GACzBG;YACN,IAAI,AAAc,YAAd,OAAOF,IAAiB;gBAE1B,IACEA,GAAG,UAAU,CAAC,kBACdA,GAAG,UAAU,CAAC,SACdA,GAAG,UAAU,CAAC,MAEd,OAAO;oBAAE,QAAQA;oBAAIC;gBAAW;gBAIlC,IAAIE,WAA0B;gBAC9B,MAAMC,OACJC,OAAO,gBAAgB,CACrB,CAAC,GACD;oBACE,QAAQ;wBACN;4BACE,IAAIF,AAAa,SAAbA,UACFA,WAAWP,aAAaI;4BAE1B,OAAOG;wBACT;wBACA,YAAY;oBACd;oBACA,YAAY;wBAAE,OAAOF;wBAAY,YAAY;oBAAK;gBACpD;gBAEJ,OAAOG;YACT;YAEAE,QAAQ,IAAI,CAAC,mCAAmC,OAAON;YACvD,OAAO;gBAAE,QAAQ;YAAG;QACtB;QAEA,MAAMO,SAAkC,CAAC;QACzC,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIJ,OAAO,OAAO,CAACV,MACxCY,MAAM,CAACC,IAAI,GAAGd,uBAAuBe,OAAOb;QAE9C,OAAOW;IACT;IAEA,OAAOZ;AACT"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { restoreImageReferences } from "./image-restoration.mjs";
|
|
2
|
+
import { escapeContent, generateDumpScriptTag, generateImageScriptTag, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, unescapeContent } from "./html-utils.mjs";
|
|
3
|
+
export { escapeContent, generateDumpScriptTag, generateImageScriptTag, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, restoreImageReferences, unescapeContent };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import service from "./service/index.mjs";
|
|
3
|
+
import { TaskRunner } from "./task-runner.mjs";
|
|
4
|
+
import { getVersion } from "./utils.mjs";
|
|
5
|
+
import { AiLocateElement, PointSchema, RectSchema, SizeSchema, TMultimodalPromptSchema, TUserPromptSchema, getMidsceneLocationSchema, plan } from "./ai-model/index.mjs";
|
|
6
|
+
import { MIDSCENE_MODEL_NAME } from "@midscene/shared/env";
|
|
7
|
+
import { ExecutionDump, GroupedActionDump, ServiceError } from "./types.mjs";
|
|
8
|
+
import { Agent, createAgent } from "./agent/index.mjs";
|
|
9
|
+
import { escapeContent, generateDumpScriptTag, generateImageScriptTag, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, restoreImageReferences, unescapeContent } from "./dump/index.mjs";
|
|
10
|
+
import { ReportGenerator, nullReportGenerator } from "./report-generator.mjs";
|
|
11
|
+
import { ScreenshotItem } from "./screenshot-item.mjs";
|
|
12
|
+
const src = service;
|
|
13
|
+
export { Agent, AiLocateElement, ExecutionDump, GroupedActionDump, MIDSCENE_MODEL_NAME, PointSchema, RectSchema, ReportGenerator, ScreenshotItem, service as Service, ServiceError, SizeSchema, TMultimodalPromptSchema, TUserPromptSchema, TaskRunner, createAgent, src as default, escapeContent, generateDumpScriptTag, generateImageScriptTag, getMidsceneLocationSchema, getVersion, nullReportGenerator, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, plan, restoreImageReferences, unescapeContent, z };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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 getMidsceneLocationSchema,\n PointSchema,\n SizeSchema,\n RectSchema,\n TMultimodalPromptSchema,\n TUserPromptSchema,\n type TMultimodalPrompt,\n type TUserPrompt,\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 GroupedActionDump,\n type IExecutionDump,\n type IGroupedActionDump,\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 { Agent, type AgentOpt, type AiActOptions, createAgent } from './agent';\n\n// Dump utilities\nexport {\n restoreImageReferences,\n escapeContent,\n unescapeContent,\n parseImageScripts,\n parseDumpScript,\n parseDumpScriptAttributes,\n generateImageScriptTag,\n generateDumpScriptTag,\n} from './dump';\n\n// Report generator\nexport type { IReportGenerator } from './report-generator';\nexport { ReportGenerator, nullReportGenerator } from './report-generator';\n\n// ScreenshotItem\nexport { ScreenshotItem } from './screenshot-item';\n"],"names":["Service"],"mappings":";;;;;;;;;;;AAkCA,YAAeA"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, statSync, truncateSync, 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 } from "@midscene/shared/utils";
|
|
6
|
+
import { generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript } from "./dump/html-utils.mjs";
|
|
7
|
+
import { appendFileSync, getReportTpl } from "./utils.mjs";
|
|
8
|
+
function _define_property(obj, key, value) {
|
|
9
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
10
|
+
value: value,
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true
|
|
14
|
+
});
|
|
15
|
+
else obj[key] = value;
|
|
16
|
+
return obj;
|
|
17
|
+
}
|
|
18
|
+
const nullReportGenerator = {
|
|
19
|
+
onDumpUpdate: ()=>{},
|
|
20
|
+
flush: async ()=>{},
|
|
21
|
+
finalize: async ()=>void 0,
|
|
22
|
+
getReportPath: ()=>void 0
|
|
23
|
+
};
|
|
24
|
+
class ReportGenerator {
|
|
25
|
+
static create(reportFileName, opts) {
|
|
26
|
+
if (false === opts.generateReport) return nullReportGenerator;
|
|
27
|
+
if (ifInBrowser) return nullReportGenerator;
|
|
28
|
+
if ('html-and-external-assets' === opts.outputFormat) {
|
|
29
|
+
const outputDir = join(getMidsceneRunSubDir('report'), reportFileName);
|
|
30
|
+
return new ReportGenerator({
|
|
31
|
+
reportPath: join(outputDir, 'index.html'),
|
|
32
|
+
screenshotMode: 'directory',
|
|
33
|
+
autoPrint: opts.autoPrintReportMsg
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return new ReportGenerator({
|
|
37
|
+
reportPath: join(getMidsceneRunSubDir('report'), `${reportFileName}.html`),
|
|
38
|
+
screenshotMode: 'inline',
|
|
39
|
+
autoPrint: opts.autoPrintReportMsg
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
onDumpUpdate(dump) {
|
|
43
|
+
this.writeQueue = this.writeQueue.then(()=>{
|
|
44
|
+
if (this.destroyed) return;
|
|
45
|
+
this.doWrite(dump);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async flush() {
|
|
49
|
+
await this.writeQueue;
|
|
50
|
+
}
|
|
51
|
+
async finalize(dump) {
|
|
52
|
+
this.onDumpUpdate(dump);
|
|
53
|
+
await this.flush();
|
|
54
|
+
this.destroyed = true;
|
|
55
|
+
this.printReportPath('finalized');
|
|
56
|
+
return this.reportPath;
|
|
57
|
+
}
|
|
58
|
+
getReportPath() {
|
|
59
|
+
return this.reportPath;
|
|
60
|
+
}
|
|
61
|
+
printReportPath(verb) {
|
|
62
|
+
if (!this.autoPrint || !this.reportPath) return;
|
|
63
|
+
if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET)) return;
|
|
64
|
+
'directory' === this.screenshotMode ? logMsg(`Midscene - report ${verb}: npx serve ${dirname(this.reportPath)}`) : logMsg(`Midscene - report ${verb}: ${this.reportPath}`);
|
|
65
|
+
}
|
|
66
|
+
doWrite(dump) {
|
|
67
|
+
if ('inline' === this.screenshotMode) this.writeInlineReport(dump);
|
|
68
|
+
else this.writeDirectoryReport(dump);
|
|
69
|
+
if (!this.firstWriteDone) {
|
|
70
|
+
this.firstWriteDone = true;
|
|
71
|
+
this.printReportPath('generated');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
writeInlineReport(dump) {
|
|
75
|
+
const dir = dirname(this.reportPath);
|
|
76
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
77
|
+
recursive: true
|
|
78
|
+
});
|
|
79
|
+
if (!this.initialized) {
|
|
80
|
+
writeFileSync(this.reportPath, getReportTpl());
|
|
81
|
+
this.imageEndOffset = statSync(this.reportPath).size;
|
|
82
|
+
this.initialized = true;
|
|
83
|
+
}
|
|
84
|
+
truncateSync(this.reportPath, this.imageEndOffset);
|
|
85
|
+
const screenshots = dump.collectAllScreenshots();
|
|
86
|
+
for (const screenshot of screenshots)if (!this.writtenScreenshots.has(screenshot.id)) {
|
|
87
|
+
appendFileSync(this.reportPath, `\n${generateImageScriptTag(screenshot.id, screenshot.base64)}`);
|
|
88
|
+
this.writtenScreenshots.add(screenshot.id);
|
|
89
|
+
screenshot.markPersistedInline(this.reportPath);
|
|
90
|
+
}
|
|
91
|
+
this.imageEndOffset = statSync(this.reportPath).size;
|
|
92
|
+
const serialized = dump.serialize();
|
|
93
|
+
appendFileSync(this.reportPath, `\n${generateDumpScriptTag(serialized)}`);
|
|
94
|
+
}
|
|
95
|
+
writeDirectoryReport(dump) {
|
|
96
|
+
const dir = dirname(this.reportPath);
|
|
97
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
98
|
+
recursive: true
|
|
99
|
+
});
|
|
100
|
+
const screenshotsDir = join(dir, 'screenshots');
|
|
101
|
+
if (!existsSync(screenshotsDir)) mkdirSync(screenshotsDir, {
|
|
102
|
+
recursive: true
|
|
103
|
+
});
|
|
104
|
+
const screenshots = dump.collectAllScreenshots();
|
|
105
|
+
for (const screenshot of screenshots)if (!this.writtenScreenshots.has(screenshot.id)) {
|
|
106
|
+
const ext = screenshot.extension;
|
|
107
|
+
const absolutePath = join(screenshotsDir, `${screenshot.id}.${ext}`);
|
|
108
|
+
const buffer = Buffer.from(screenshot.rawBase64, 'base64');
|
|
109
|
+
writeFileSync(absolutePath, buffer);
|
|
110
|
+
this.writtenScreenshots.add(screenshot.id);
|
|
111
|
+
screenshot.markPersistedToPath(`./screenshots/${screenshot.id}.${ext}`, absolutePath);
|
|
112
|
+
}
|
|
113
|
+
const serialized = dump.serialize();
|
|
114
|
+
writeFileSync(this.reportPath, `${getReportTpl()}${getBaseUrlFixScript()}${generateDumpScriptTag(serialized)}`);
|
|
115
|
+
}
|
|
116
|
+
constructor(options){
|
|
117
|
+
_define_property(this, "reportPath", void 0);
|
|
118
|
+
_define_property(this, "screenshotMode", void 0);
|
|
119
|
+
_define_property(this, "autoPrint", void 0);
|
|
120
|
+
_define_property(this, "writtenScreenshots", new Set());
|
|
121
|
+
_define_property(this, "firstWriteDone", false);
|
|
122
|
+
_define_property(this, "imageEndOffset", 0);
|
|
123
|
+
_define_property(this, "initialized", false);
|
|
124
|
+
_define_property(this, "writeQueue", Promise.resolve());
|
|
125
|
+
_define_property(this, "destroyed", false);
|
|
126
|
+
this.reportPath = options.reportPath;
|
|
127
|
+
this.screenshotMode = options.screenshotMode;
|
|
128
|
+
this.autoPrint = options.autoPrint ?? true;
|
|
129
|
+
this.printReportPath('will be generated at');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export { ReportGenerator, nullReportGenerator };
|
|
133
|
+
|
|
134
|
+
//# sourceMappingURL=report-generator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-generator.mjs","sources":["../../src/report-generator.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n statSync,\n truncateSync,\n writeFileSync,\n} 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 } from '@midscene/shared/utils';\nimport {\n generateDumpScriptTag,\n generateImageScriptTag,\n getBaseUrlFixScript,\n} from './dump/html-utils';\nimport type { GroupedActionDump } from './types';\nimport { appendFileSync, getReportTpl } from './utils';\n\nexport interface IReportGenerator {\n /**\n * Schedule a dump update. Writes are queued internally to guarantee serial execution.\n * This method returns immediately (fire-and-forget).\n * Screenshots are written and memory is released during this call.\n */\n onDumpUpdate(dump: GroupedActionDump): void;\n /**\n * Wait for all queued write operations to complete.\n */\n flush(): Promise<void>;\n /**\n * Finalize the report. Calls flush() internally.\n */\n finalize(dump: GroupedActionDump): Promise<string | undefined>;\n getReportPath(): string | undefined;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n onDumpUpdate: () => {},\n flush: async () => {},\n finalize: async () => undefined,\n getReportPath: () => undefined,\n};\n\nexport class ReportGenerator implements IReportGenerator {\n private reportPath: string;\n private screenshotMode: 'inline' | 'directory';\n private autoPrint: boolean;\n private writtenScreenshots = new Set<string>();\n private firstWriteDone = false;\n\n // inline mode state\n private imageEndOffset = 0;\n private initialized = false;\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 autoPrint?: boolean;\n }) {\n this.reportPath = options.reportPath;\n this.screenshotMode = options.screenshotMode;\n this.autoPrint = options.autoPrint ?? true;\n this.printReportPath('will be generated at');\n }\n\n static create(\n reportFileName: string,\n opts: {\n generateReport?: boolean;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n autoPrintReportMsg?: boolean;\n },\n ): IReportGenerator {\n if (opts.generateReport === false) return nullReportGenerator;\n\n // In browser environment, file system is not available\n if (ifInBrowser) return nullReportGenerator;\n\n if (opts.outputFormat === 'html-and-external-assets') {\n const outputDir = join(getMidsceneRunSubDir('report'), reportFileName);\n return new ReportGenerator({\n reportPath: join(outputDir, 'index.html'),\n screenshotMode: 'directory',\n autoPrint: opts.autoPrintReportMsg,\n });\n }\n\n return new ReportGenerator({\n reportPath: join(\n getMidsceneRunSubDir('report'),\n `${reportFileName}.html`,\n ),\n screenshotMode: 'inline',\n autoPrint: opts.autoPrintReportMsg,\n });\n }\n\n onDumpUpdate(dump: GroupedActionDump): void {\n this.writeQueue = this.writeQueue.then(() => {\n if (this.destroyed) return;\n this.doWrite(dump);\n });\n }\n\n async flush(): Promise<void> {\n await this.writeQueue;\n }\n\n async finalize(dump: GroupedActionDump): Promise<string | undefined> {\n this.onDumpUpdate(dump);\n await this.flush();\n this.destroyed = true;\n this.printReportPath('finalized');\n\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 doWrite(dump: GroupedActionDump): void {\n if (this.screenshotMode === 'inline') {\n this.writeInlineReport(dump);\n } else {\n this.writeDirectoryReport(dump);\n }\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath('generated');\n }\n }\n\n private writeInlineReport(dump: GroupedActionDump): void {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n if (!this.initialized) {\n writeFileSync(this.reportPath, getReportTpl());\n this.imageEndOffset = statSync(this.reportPath).size;\n this.initialized = true;\n }\n\n // 1. truncate: remove old dump JSON, keep template + existing image tags\n truncateSync(this.reportPath, this.imageEndOffset);\n\n // 2. append new image tags and release memory immediately after writing\n // Screenshots can be recovered from HTML file via lazy loading\n const screenshots = dump.collectAllScreenshots();\n for (const screenshot of screenshots) {\n if (!this.writtenScreenshots.has(screenshot.id)) {\n appendFileSync(\n this.reportPath,\n `\\n${generateImageScriptTag(screenshot.id, screenshot.base64)}`,\n );\n this.writtenScreenshots.add(screenshot.id);\n // Release memory - screenshot can be recovered via extractImageByIdSync\n screenshot.markPersistedInline(this.reportPath);\n }\n }\n\n // 3. update image end offset\n this.imageEndOffset = statSync(this.reportPath).size;\n\n // 4. append new dump JSON (compact { $screenshot: id } format)\n const serialized = dump.serialize();\n appendFileSync(this.reportPath, `\\n${generateDumpScriptTag(serialized)}`);\n }\n\n private writeDirectoryReport(dump: GroupedActionDump): void {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // create screenshots subdirectory\n const screenshotsDir = join(dir, 'screenshots');\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n // 1. write new screenshots and release memory immediately\n // Screenshots can be recovered from disk via lazy loading\n const screenshots = dump.collectAllScreenshots();\n for (const screenshot of screenshots) {\n if (!this.writtenScreenshots.has(screenshot.id)) {\n const ext = screenshot.extension;\n const absolutePath = join(screenshotsDir, `${screenshot.id}.${ext}`);\n const buffer = Buffer.from(screenshot.rawBase64, 'base64');\n writeFileSync(absolutePath, buffer);\n this.writtenScreenshots.add(screenshot.id);\n screenshot.markPersistedToPath(\n `./screenshots/${screenshot.id}.${ext}`,\n absolutePath,\n );\n }\n }\n\n // 2. write HTML with dump JSON (toSerializable() returns { $screenshot: id } format)\n const serialized = dump.serialize();\n writeFileSync(\n this.reportPath,\n `${getReportTpl()}${getBaseUrlFixScript()}${generateDumpScriptTag(serialized)}`,\n );\n }\n}\n"],"names":["nullReportGenerator","undefined","ReportGenerator","reportFileName","opts","ifInBrowser","outputDir","join","getMidsceneRunSubDir","dump","verb","globalConfigManager","MIDSCENE_REPORT_QUIET","logMsg","dirname","dir","existsSync","mkdirSync","writeFileSync","getReportTpl","statSync","truncateSync","screenshots","screenshot","appendFileSync","generateImageScriptTag","serialized","generateDumpScriptTag","screenshotsDir","ext","absolutePath","buffer","Buffer","getBaseUrlFixScript","options","Set","Promise"],"mappings":";;;;;;;;;;;;;;;;;AAwCO,MAAMA,sBAAwC;IACnD,cAAc,KAAO;IACrB,OAAO,WAAa;IACpB,UAAU,UAAYC;IACtB,eAAe,IAAMA;AACvB;AAEO,MAAMC;IA0BX,OAAO,OACLC,cAAsB,EACtBC,IAIC,EACiB;QAClB,IAAIA,AAAwB,UAAxBA,KAAK,cAAc,EAAY,OAAOJ;QAG1C,IAAIK,aAAa,OAAOL;QAExB,IAAII,AAAsB,+BAAtBA,KAAK,YAAY,EAAiC;YACpD,MAAME,YAAYC,KAAKC,qBAAqB,WAAWL;YACvD,OAAO,IAAID,gBAAgB;gBACzB,YAAYK,KAAKD,WAAW;gBAC5B,gBAAgB;gBAChB,WAAWF,KAAK,kBAAkB;YACpC;QACF;QAEA,OAAO,IAAIF,gBAAgB;YACzB,YAAYK,KACVC,qBAAqB,WACrB,GAAGL,eAAe,KAAK,CAAC;YAE1B,gBAAgB;YAChB,WAAWC,KAAK,kBAAkB;QACpC;IACF;IAEA,aAAaK,IAAuB,EAAQ;QAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YACrC,IAAI,IAAI,CAAC,SAAS,EAAE;YACpB,IAAI,CAAC,OAAO,CAACA;QACf;IACF;IAEA,MAAM,QAAuB;QAC3B,MAAM,IAAI,CAAC,UAAU;IACvB;IAEA,MAAM,SAASA,IAAuB,EAA+B;QACnE,IAAI,CAAC,YAAY,CAACA;QAClB,MAAM,IAAI,CAAC,KAAK;QAChB,IAAI,CAAC,SAAS,GAAG;QACjB,IAAI,CAAC,eAAe,CAAC;QAErB,OAAO,IAAI,CAAC,UAAU;IACxB;IAEA,gBAAoC;QAClC,OAAO,IAAI,CAAC,UAAU;IACxB;IAEQ,gBAAgBC,IAAY,EAAQ;QAC1C,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;QACzC,IAAIC,oBAAoB,qBAAqB,CAACC,wBAC5C;QAE0B,gBAAxB,IAAI,CAAC,cAAc,GACrBC,OACE,CAAC,kBAAkB,EAAEH,KAAK,YAAY,EAAEI,QAAQ,IAAI,CAAC,UAAU,GAAG,IAGpED,OAAO,CAAC,kBAAkB,EAAEH,KAAK,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE;IAE1D;IAEQ,QAAQD,IAAuB,EAAQ;QAC7C,IAAI,AAAwB,aAAxB,IAAI,CAAC,cAAc,EACrB,IAAI,CAAC,iBAAiB,CAACA;aAEvB,IAAI,CAAC,oBAAoB,CAACA;QAE5B,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE;YACxB,IAAI,CAAC,cAAc,GAAG;YACtB,IAAI,CAAC,eAAe,CAAC;QACvB;IACF;IAEQ,kBAAkBA,IAAuB,EAAQ;QACvD,MAAMM,MAAMD,QAAQ,IAAI,CAAC,UAAU;QACnC,IAAI,CAACE,WAAWD,MACdE,UAAUF,KAAK;YAAE,WAAW;QAAK;QAGnC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YACrBG,cAAc,IAAI,CAAC,UAAU,EAAEC;YAC/B,IAAI,CAAC,cAAc,GAAGC,SAAS,IAAI,CAAC,UAAU,EAAE,IAAI;YACpD,IAAI,CAAC,WAAW,GAAG;QACrB;QAGAC,aAAa,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,cAAc;QAIjD,MAAMC,cAAcb,KAAK,qBAAqB;QAC9C,KAAK,MAAMc,cAAcD,YACvB,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAACC,WAAW,EAAE,GAAG;YAC/CC,eACE,IAAI,CAAC,UAAU,EACf,CAAC,EAAE,EAAEC,uBAAuBF,WAAW,EAAE,EAAEA,WAAW,MAAM,GAAG;YAEjE,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAACA,WAAW,EAAE;YAEzCA,WAAW,mBAAmB,CAAC,IAAI,CAAC,UAAU;QAChD;QAIF,IAAI,CAAC,cAAc,GAAGH,SAAS,IAAI,CAAC,UAAU,EAAE,IAAI;QAGpD,MAAMM,aAAajB,KAAK,SAAS;QACjCe,eAAe,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,EAAEG,sBAAsBD,aAAa;IAC1E;IAEQ,qBAAqBjB,IAAuB,EAAQ;QAC1D,MAAMM,MAAMD,QAAQ,IAAI,CAAC,UAAU;QACnC,IAAI,CAACE,WAAWD,MACdE,UAAUF,KAAK;YAAE,WAAW;QAAK;QAInC,MAAMa,iBAAiBrB,KAAKQ,KAAK;QACjC,IAAI,CAACC,WAAWY,iBACdX,UAAUW,gBAAgB;YAAE,WAAW;QAAK;QAK9C,MAAMN,cAAcb,KAAK,qBAAqB;QAC9C,KAAK,MAAMc,cAAcD,YACvB,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAACC,WAAW,EAAE,GAAG;YAC/C,MAAMM,MAAMN,WAAW,SAAS;YAChC,MAAMO,eAAevB,KAAKqB,gBAAgB,GAAGL,WAAW,EAAE,CAAC,CAAC,EAAEM,KAAK;YACnE,MAAME,SAASC,OAAO,IAAI,CAACT,WAAW,SAAS,EAAE;YACjDL,cAAcY,cAAcC;YAC5B,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAACR,WAAW,EAAE;YACzCA,WAAW,mBAAmB,CAC5B,CAAC,cAAc,EAAEA,WAAW,EAAE,CAAC,CAAC,EAAEM,KAAK,EACvCC;QAEJ;QAIF,MAAMJ,aAAajB,KAAK,SAAS;QACjCS,cACE,IAAI,CAAC,UAAU,EACf,GAAGC,iBAAiBc,wBAAwBN,sBAAsBD,aAAa;IAEnF;IAtKA,YAAYQ,OAIX,CAAE;QAlBH,uBAAQ,cAAR;QACA,uBAAQ,kBAAR;QACA,uBAAQ,aAAR;QACA,uBAAQ,sBAAqB,IAAIC;QACjC,uBAAQ,kBAAiB;QAGzB,uBAAQ,kBAAiB;QACzB,uBAAQ,eAAc;QAGtB,uBAAQ,cAA4BC,QAAQ,OAAO;QACnD,uBAAQ,aAAY;QAOlB,IAAI,CAAC,UAAU,GAAGF,QAAQ,UAAU;QACpC,IAAI,CAAC,cAAc,GAAGA,QAAQ,cAAc;QAC5C,IAAI,CAAC,SAAS,GAAGA,QAAQ,SAAS,IAAI;QACtC,IAAI,CAAC,eAAe,CAAC;IACvB;AA8JF"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { appendFileSync, copyFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { getMidsceneRunSubDir } from "@midscene/shared/common";
|
|
4
|
+
import { logMsg } from "@midscene/shared/utils";
|
|
5
|
+
import { getReportFileName } from "./agent/index.mjs";
|
|
6
|
+
import { extractLastDumpScriptSync, getBaseUrlFixScript, streamImageScriptsToFile } from "./dump/html-utils.mjs";
|
|
7
|
+
import { getReportTpl, reportHTMLContent } from "./utils.mjs";
|
|
8
|
+
function _define_property(obj, key, value) {
|
|
9
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
10
|
+
value: value,
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true
|
|
14
|
+
});
|
|
15
|
+
else obj[key] = value;
|
|
16
|
+
return obj;
|
|
17
|
+
}
|
|
18
|
+
class ReportMergingTool {
|
|
19
|
+
append(reportInfo) {
|
|
20
|
+
this.reportInfos.push(reportInfo);
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.reportInfos = [];
|
|
24
|
+
}
|
|
25
|
+
isDirectoryModeReport(reportFilePath) {
|
|
26
|
+
const reportDir = dirname(reportFilePath);
|
|
27
|
+
return 'index.html' === basename(reportFilePath) && existsSync(join(reportDir, 'screenshots'));
|
|
28
|
+
}
|
|
29
|
+
mergeReports(reportFileName = 'AUTO', opts) {
|
|
30
|
+
if (this.reportInfos.length <= 1) {
|
|
31
|
+
logMsg('Not enough reports to merge');
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const { rmOriginalReports = false, overwrite = false } = opts ?? {};
|
|
35
|
+
const targetDir = getMidsceneRunSubDir('report');
|
|
36
|
+
const hasDirectoryModeReport = this.reportInfos.some((info)=>this.isDirectoryModeReport(info.reportFilePath));
|
|
37
|
+
const resolvedName = 'AUTO' === reportFileName ? getReportFileName('merged-report') : reportFileName;
|
|
38
|
+
const outputFilePath = hasDirectoryModeReport ? resolve(targetDir, resolvedName, 'index.html') : resolve(targetDir, `${resolvedName}.html`);
|
|
39
|
+
if ('AUTO' !== reportFileName && existsSync(outputFilePath)) {
|
|
40
|
+
if (!overwrite) throw new Error(`Report file already exists: ${outputFilePath}\nSet overwrite to true to overwrite this file.`);
|
|
41
|
+
if (hasDirectoryModeReport) rmSync(dirname(outputFilePath), {
|
|
42
|
+
recursive: true,
|
|
43
|
+
force: true
|
|
44
|
+
});
|
|
45
|
+
else unlinkSync(outputFilePath);
|
|
46
|
+
}
|
|
47
|
+
if (hasDirectoryModeReport) mkdirSync(dirname(outputFilePath), {
|
|
48
|
+
recursive: true
|
|
49
|
+
});
|
|
50
|
+
logMsg(`Start merging ${this.reportInfos.length} reports...\nCreating template file...`);
|
|
51
|
+
try {
|
|
52
|
+
appendFileSync(outputFilePath, getReportTpl());
|
|
53
|
+
if (hasDirectoryModeReport) appendFileSync(outputFilePath, getBaseUrlFixScript());
|
|
54
|
+
for(let i = 0; i < this.reportInfos.length; i++){
|
|
55
|
+
const reportInfo = this.reportInfos[i];
|
|
56
|
+
logMsg(`Processing report ${i + 1}/${this.reportInfos.length}`);
|
|
57
|
+
if (this.isDirectoryModeReport(reportInfo.reportFilePath)) {
|
|
58
|
+
const reportDir = dirname(reportInfo.reportFilePath);
|
|
59
|
+
const screenshotsDir = join(reportDir, 'screenshots');
|
|
60
|
+
const mergedScreenshotsDir = join(dirname(outputFilePath), 'screenshots');
|
|
61
|
+
mkdirSync(mergedScreenshotsDir, {
|
|
62
|
+
recursive: true
|
|
63
|
+
});
|
|
64
|
+
for (const file of readdirSync(screenshotsDir)){
|
|
65
|
+
const src = join(screenshotsDir, file);
|
|
66
|
+
const dest = join(mergedScreenshotsDir, file);
|
|
67
|
+
copyFileSync(src, dest);
|
|
68
|
+
}
|
|
69
|
+
} else streamImageScriptsToFile(reportInfo.reportFilePath, outputFilePath);
|
|
70
|
+
const dumpString = extractLastDumpScriptSync(reportInfo.reportFilePath);
|
|
71
|
+
const { reportAttributes } = reportInfo;
|
|
72
|
+
const reportHtmlStr = `${reportHTMLContent({
|
|
73
|
+
dumpString,
|
|
74
|
+
attributes: {
|
|
75
|
+
playwright_test_duration: reportAttributes.testDuration,
|
|
76
|
+
playwright_test_status: reportAttributes.testStatus,
|
|
77
|
+
playwright_test_title: reportAttributes.testTitle,
|
|
78
|
+
playwright_test_id: reportAttributes.testId,
|
|
79
|
+
playwright_test_description: reportAttributes.testDescription
|
|
80
|
+
}
|
|
81
|
+
}, void 0, void 0, false)}\n`;
|
|
82
|
+
appendFileSync(outputFilePath, reportHtmlStr);
|
|
83
|
+
}
|
|
84
|
+
logMsg(`Successfully merged new report: ${outputFilePath}`);
|
|
85
|
+
if (rmOriginalReports) {
|
|
86
|
+
for (const info of this.reportInfos)try {
|
|
87
|
+
if (this.isDirectoryModeReport(info.reportFilePath)) {
|
|
88
|
+
const reportDir = dirname(info.reportFilePath);
|
|
89
|
+
rmSync(reportDir, {
|
|
90
|
+
recursive: true,
|
|
91
|
+
force: true
|
|
92
|
+
});
|
|
93
|
+
} else unlinkSync(info.reportFilePath);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logMsg(`Error deleting report ${info.reportFilePath}: ${error}`);
|
|
96
|
+
}
|
|
97
|
+
logMsg(`Removed ${this.reportInfos.length} original reports`);
|
|
98
|
+
}
|
|
99
|
+
return outputFilePath;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
logMsg(`Error in mergeReports: ${error}`);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
constructor(){
|
|
106
|
+
_define_property(this, "reportInfos", []);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export { ReportMergingTool };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=report.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.mjs","sources":["../../src/report.ts"],"sourcesContent":["import {\n appendFileSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n rmSync,\n unlinkSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport { logMsg } from '@midscene/shared/utils';\nimport { getReportFileName } from './agent';\nimport {\n extractLastDumpScriptSync,\n getBaseUrlFixScript,\n streamImageScriptsToFile,\n} from './dump/html-utils';\nimport type { ReportFileWithAttributes } from './types';\nimport { getReportTpl, reportHTMLContent } from './utils';\n\nexport class ReportMergingTool {\n private reportInfos: ReportFileWithAttributes[] = [];\n public append(reportInfo: ReportFileWithAttributes) {\n this.reportInfos.push(reportInfo);\n }\n public clear() {\n this.reportInfos = [];\n }\n\n /**\n * Check if a report is in directory mode (html-and-external-assets).\n * Directory mode reports: {name}/index.html + {name}/screenshots/\n */\n private isDirectoryModeReport(reportFilePath: string): boolean {\n const reportDir = path.dirname(reportFilePath);\n return (\n path.basename(reportFilePath) === 'index.html' &&\n existsSync(path.join(reportDir, 'screenshots'))\n );\n }\n\n public mergeReports(\n reportFileName: 'AUTO' | string = 'AUTO',\n opts?: {\n rmOriginalReports?: boolean;\n overwrite?: boolean;\n },\n ): string | null {\n if (this.reportInfos.length <= 1) {\n logMsg('Not enough reports to merge');\n return null;\n }\n\n const { rmOriginalReports = false, overwrite = false } = opts ?? {};\n const targetDir = getMidsceneRunSubDir('report');\n\n // Check if any source report is directory mode\n const hasDirectoryModeReport = this.reportInfos.some((info) =>\n this.isDirectoryModeReport(info.reportFilePath),\n );\n\n const resolvedName =\n reportFileName === 'AUTO'\n ? getReportFileName('merged-report')\n : reportFileName;\n\n // Directory mode: output as {name}/index.html to keep relative paths working\n // Inline mode: output as {name}.html (single file)\n const outputFilePath = hasDirectoryModeReport\n ? path.resolve(targetDir, resolvedName, 'index.html')\n : path.resolve(targetDir, `${resolvedName}.html`);\n\n if (reportFileName !== 'AUTO' && existsSync(outputFilePath)) {\n if (!overwrite) {\n throw new Error(\n `Report file already exists: ${outputFilePath}\\nSet overwrite to true to overwrite this file.`,\n );\n }\n if (hasDirectoryModeReport) {\n rmSync(path.dirname(outputFilePath), { recursive: true, force: true });\n } else {\n unlinkSync(outputFilePath);\n }\n }\n\n if (hasDirectoryModeReport) {\n mkdirSync(path.dirname(outputFilePath), { recursive: true });\n }\n\n logMsg(\n `Start merging ${this.reportInfos.length} reports...\\nCreating template file...`,\n );\n\n try {\n // Write template\n appendFileSync(outputFilePath, getReportTpl());\n\n // For directory-mode output, inject base URL fix script\n if (hasDirectoryModeReport) {\n appendFileSync(outputFilePath, getBaseUrlFixScript());\n }\n\n // Process all reports one by one\n for (let i = 0; i < this.reportInfos.length; i++) {\n const reportInfo = this.reportInfos[i];\n logMsg(`Processing report ${i + 1}/${this.reportInfos.length}`);\n\n if (this.isDirectoryModeReport(reportInfo.reportFilePath)) {\n // Directory mode: copy external screenshot files\n const reportDir = path.dirname(reportInfo.reportFilePath);\n const screenshotsDir = path.join(reportDir, 'screenshots');\n const mergedScreenshotsDir = path.join(\n path.dirname(outputFilePath),\n 'screenshots',\n );\n mkdirSync(mergedScreenshotsDir, { recursive: true });\n for (const file of readdirSync(screenshotsDir)) {\n const src = path.join(screenshotsDir, file);\n const dest = path.join(mergedScreenshotsDir, file);\n copyFileSync(src, dest);\n }\n } else {\n // Inline mode: stream image scripts to output file\n streamImageScriptsToFile(reportInfo.reportFilePath, outputFilePath);\n }\n\n const dumpString = extractLastDumpScriptSync(reportInfo.reportFilePath);\n const { reportAttributes } = reportInfo;\n\n const reportHtmlStr = `${reportHTMLContent(\n {\n dumpString,\n attributes: {\n playwright_test_duration: reportAttributes.testDuration,\n playwright_test_status: reportAttributes.testStatus,\n playwright_test_title: reportAttributes.testTitle,\n playwright_test_id: reportAttributes.testId,\n playwright_test_description: reportAttributes.testDescription,\n },\n },\n undefined,\n undefined,\n false,\n )}\\n`;\n\n appendFileSync(outputFilePath, reportHtmlStr);\n }\n\n logMsg(`Successfully merged new report: ${outputFilePath}`);\n\n // Remove original reports if needed\n if (rmOriginalReports) {\n for (const info of this.reportInfos) {\n try {\n if (this.isDirectoryModeReport(info.reportFilePath)) {\n // Directory mode: remove the entire report directory\n const reportDir = path.dirname(info.reportFilePath);\n rmSync(reportDir, { recursive: true, force: true });\n } else {\n unlinkSync(info.reportFilePath);\n }\n } catch (error) {\n logMsg(`Error deleting report ${info.reportFilePath}: ${error}`);\n }\n }\n logMsg(`Removed ${this.reportInfos.length} original reports`);\n }\n return outputFilePath;\n } catch (error) {\n logMsg(`Error in mergeReports: ${error}`);\n throw error;\n }\n }\n}\n"],"names":["ReportMergingTool","reportInfo","reportFilePath","reportDir","path","existsSync","reportFileName","opts","logMsg","rmOriginalReports","overwrite","targetDir","getMidsceneRunSubDir","hasDirectoryModeReport","info","resolvedName","getReportFileName","outputFilePath","Error","rmSync","unlinkSync","mkdirSync","appendFileSync","getReportTpl","getBaseUrlFixScript","i","screenshotsDir","mergedScreenshotsDir","file","readdirSync","src","dest","copyFileSync","streamImageScriptsToFile","dumpString","extractLastDumpScriptSync","reportAttributes","reportHtmlStr","reportHTMLContent","undefined","error"],"mappings":"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"}
|