@arcadialdev/arcality 2.4.50 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -21
- package/package.json +1 -1
- package/scripts/gen-and-run.mjs +72 -84
- package/src/EvidenceUploader.mjs +45 -45
- package/src/arcalityClient.mjs +15 -8
- package/tests/_helpers/ArcalityReporter.js +729 -729
|
@@ -1,729 +1,729 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
-
var ArcalityReporter_exports = {};
|
|
29
|
-
__export(ArcalityReporter_exports, {
|
|
30
|
-
default: () => ArcalityReporter_default
|
|
31
|
-
});
|
|
32
|
-
module.exports = __toCommonJS(ArcalityReporter_exports);
|
|
33
|
-
var import_fs = __toESM(require("fs"));
|
|
34
|
-
var import_path = __toESM(require("path"));
|
|
35
|
-
class ArcalityReporter {
|
|
36
|
-
outputDir;
|
|
37
|
-
results = [];
|
|
38
|
-
totalDuration = 0;
|
|
39
|
-
startTime = /* @__PURE__ */ new Date();
|
|
40
|
-
constructor(options = {}) {
|
|
41
|
-
this.outputDir = options.outputDir || "arcality-report";
|
|
42
|
-
}
|
|
43
|
-
onBegin(config, suite) {
|
|
44
|
-
if (import_fs.default.existsSync(this.outputDir)) {
|
|
45
|
-
try {
|
|
46
|
-
import_fs.default.rmSync(this.outputDir, { recursive: true, force: true });
|
|
47
|
-
console.log(`\u{1F9F9} Cleaned old report data at: ${this.outputDir}`);
|
|
48
|
-
} catch (err) {
|
|
49
|
-
console.warn(`\u26A0\uFE0F Could not fully clean report dir: ${err.message}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (!import_fs.default.existsSync(this.outputDir)) {
|
|
53
|
-
import_fs.default.mkdirSync(this.outputDir, { recursive: true });
|
|
54
|
-
}
|
|
55
|
-
this.startTime = /* @__PURE__ */ new Date();
|
|
56
|
-
console.log(`\u{1F680} Arcality Mission started at ${this.startTime.toLocaleTimeString()}`);
|
|
57
|
-
}
|
|
58
|
-
onTestEnd(test, result) {
|
|
59
|
-
const stepsHtml = result.steps.map((step) => this.renderStep(step, 0)).join("");
|
|
60
|
-
const processedAttachments = result.attachments.map((att) => {
|
|
61
|
-
let extension = ".png";
|
|
62
|
-
if (att.contentType === "application/json")
|
|
63
|
-
extension = ".json";
|
|
64
|
-
else if (att.contentType === "text/plain")
|
|
65
|
-
extension = ".txt";
|
|
66
|
-
else if (att.contentType === "text/yaml")
|
|
67
|
-
extension = ".yaml";
|
|
68
|
-
const fileName = att.path ? import_path.default.basename(att.path) : `${att.name || "attachment"}-${Date.now()}${extension}`;
|
|
69
|
-
const attachmentsDir = import_path.default.join(this.outputDir, "attachments");
|
|
70
|
-
const destPath = import_path.default.join(attachmentsDir, fileName);
|
|
71
|
-
if (!import_fs.default.existsSync(attachmentsDir)) {
|
|
72
|
-
import_fs.default.mkdirSync(attachmentsDir, { recursive: true });
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
if (att.path && import_fs.default.existsSync(att.path)) {
|
|
76
|
-
import_fs.default.copyFileSync(att.path, destPath);
|
|
77
|
-
} else if (att.body) {
|
|
78
|
-
import_fs.default.writeFileSync(destPath, att.body);
|
|
79
|
-
}
|
|
80
|
-
return { ...att, path: `attachments/${fileName}` };
|
|
81
|
-
} catch (e) {
|
|
82
|
-
console.error(`Failed to process attachment ${att.name}:`, e);
|
|
83
|
-
return att;
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
if (result.status === "failed" || result.error) {
|
|
87
|
-
console.log(`
|
|
88
|
-
\u274C [TEST ENGINE FAILURE] ${test.title} failed:`);
|
|
89
|
-
console.log(`${result.error?.message || result.error?.stack || "Unknown error"}
|
|
90
|
-
`);
|
|
91
|
-
if (result.error?.stack) {
|
|
92
|
-
console.log(result.error.stack);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
this.results.push({ test, result, stepsHtml, attachments: processedAttachments });
|
|
96
|
-
}
|
|
97
|
-
async onEnd(result) {
|
|
98
|
-
this.totalDuration = result.duration;
|
|
99
|
-
const htmlContent = this.generateHtml();
|
|
100
|
-
const reportPath = import_path.default.join(this.outputDir, "index.html");
|
|
101
|
-
import_fs.default.writeFileSync(reportPath, htmlContent);
|
|
102
|
-
console.log(`\u2728 Arcality Mission Report generated at: ${reportPath}`);
|
|
103
|
-
}
|
|
104
|
-
renderStep(step, depth) {
|
|
105
|
-
if (step.category === "hook" || step.category === "fixture")
|
|
106
|
-
return "";
|
|
107
|
-
const padding = depth * 16;
|
|
108
|
-
const statusClass = step.error ? "step-error" : "step-passed";
|
|
109
|
-
const icon = step.error ? "\u2715" : "\u2713";
|
|
110
|
-
const duration = step.duration >= 0 ? `<span class="step-duration">${step.duration}ms</span>` : "";
|
|
111
|
-
let title = this.escapeHtml(step.title);
|
|
112
|
-
title = title.replace(/^(click|fill|navigate|wait|expect|goto|press)/i, '<span class="step-action">$1</span>');
|
|
113
|
-
let children = "";
|
|
114
|
-
if (step.steps && step.steps.length > 0) {
|
|
115
|
-
children = `<div class="step-children">${step.steps.map((s) => this.renderStep(s, depth + 1)).join("")}</div>`;
|
|
116
|
-
}
|
|
117
|
-
const isExpandable = step.steps && step.steps.length > 0;
|
|
118
|
-
const expandableAttr = isExpandable ? 'data-expandable="true"' : "";
|
|
119
|
-
return `
|
|
120
|
-
<div class="step-wrapper ${statusClass}" style="margin-left: ${padding}px" ${expandableAttr}>
|
|
121
|
-
<div class="step-header">
|
|
122
|
-
<span class="step-icon">${icon}</span>
|
|
123
|
-
<span class="step-title">${title}</span>
|
|
124
|
-
${duration}
|
|
125
|
-
</div>
|
|
126
|
-
${children}
|
|
127
|
-
</div>
|
|
128
|
-
`;
|
|
129
|
-
}
|
|
130
|
-
getEngineVersion() {
|
|
131
|
-
try {
|
|
132
|
-
const packageJsonPath = import_path.default.join(process.cwd(), "package.json");
|
|
133
|
-
if (import_fs.default.existsSync(packageJsonPath)) {
|
|
134
|
-
const pkg = JSON.parse(import_fs.default.readFileSync(packageJsonPath, "utf8"));
|
|
135
|
-
return pkg.version || "2.2.0";
|
|
136
|
-
}
|
|
137
|
-
} catch (e) {
|
|
138
|
-
console.error("Error reading package.json version:", e);
|
|
139
|
-
}
|
|
140
|
-
return "2.2.0";
|
|
141
|
-
}
|
|
142
|
-
generateHtml() {
|
|
143
|
-
const stats = {
|
|
144
|
-
total: this.results.length,
|
|
145
|
-
passed: this.results.filter((r) => r.result.status === "passed").length,
|
|
146
|
-
failed: this.results.filter((r) => r.result.status === "failed" || r.result.status === "timedOut").length,
|
|
147
|
-
skipped: this.results.filter((r) => r.result.status === "skipped").length,
|
|
148
|
-
flaky: this.results.filter((r) => r.result.status === "passed" && r.result.retry > 0).length
|
|
149
|
-
};
|
|
150
|
-
return `
|
|
151
|
-
<!DOCTYPE html>
|
|
152
|
-
<html lang="en">
|
|
153
|
-
<head>
|
|
154
|
-
<meta charset="UTF-8">
|
|
155
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
156
|
-
<title>Arcality Mission Report</title>
|
|
157
|
-
<link rel="shortcut icon" href="https://raw.githubusercontent.com/microsoft/playwright/main/packages/playwright-core/src/server/chromium/test-results/favicon.ico">
|
|
158
|
-
<style>
|
|
159
|
-
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
160
|
-
|
|
161
|
-
:root {
|
|
162
|
-
--bg-deep: #020617;
|
|
163
|
-
--bg-card: #0f172a;
|
|
164
|
-
--bg-hover: #1e293b;
|
|
165
|
-
--text-main: #f8fafc;
|
|
166
|
-
--text-muted: #94a3b8;
|
|
167
|
-
--primary: #6366f1;
|
|
168
|
-
--success: #10b981;
|
|
169
|
-
--error: #f43f5e;
|
|
170
|
-
--warning: #f59e0b;
|
|
171
|
-
--border: #1e293b;
|
|
172
|
-
--accent: #c084fc;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
* { box-sizing: border-box; }
|
|
176
|
-
body {
|
|
177
|
-
font-family: 'Plus Jakarta Sans', sans-serif;
|
|
178
|
-
background-color: var(--bg-deep);
|
|
179
|
-
color: var(--text-main);
|
|
180
|
-
margin: 0;
|
|
181
|
-
line-height: 1.6;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.container { max-width: 1400px; margin: 0 auto; padding: 2rem; }
|
|
185
|
-
|
|
186
|
-
/* HEADER */
|
|
187
|
-
.main-header {
|
|
188
|
-
display: flex;
|
|
189
|
-
justify-content: space-between;
|
|
190
|
-
align-items: flex-start;
|
|
191
|
-
margin-bottom: 3rem;
|
|
192
|
-
padding-bottom: 2rem;
|
|
193
|
-
border-bottom: 1px solid var(--border);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.brand-section h1 {
|
|
197
|
-
font-size: 2.5rem;
|
|
198
|
-
font-weight: 800;
|
|
199
|
-
margin: 0;
|
|
200
|
-
background: linear-gradient(to right, #818cf8, #c084fc);
|
|
201
|
-
-webkit-background-clip: text;
|
|
202
|
-
-webkit-text-fill-color: transparent;
|
|
203
|
-
}
|
|
204
|
-
.mission-id {
|
|
205
|
-
font-family: 'JetBrains Mono', monospace;
|
|
206
|
-
font-size: 0.8rem;
|
|
207
|
-
color: var(--text-muted);
|
|
208
|
-
text-transform: uppercase;
|
|
209
|
-
letter-spacing: 3px;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/* DASHBOARD STATS */
|
|
213
|
-
.stats-grid {
|
|
214
|
-
display: grid;
|
|
215
|
-
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
216
|
-
gap: 1.5rem;
|
|
217
|
-
margin-bottom: 3rem;
|
|
218
|
-
}
|
|
219
|
-
.stat-card {
|
|
220
|
-
background: var(--bg-card);
|
|
221
|
-
padding: 1.5rem;
|
|
222
|
-
border-radius: 16px;
|
|
223
|
-
border: 1px solid var(--border);
|
|
224
|
-
text-align: center;
|
|
225
|
-
}
|
|
226
|
-
.stat-value { font-size: 2rem; font-weight: 800; display: block; }
|
|
227
|
-
.stat-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; font-weight: 700; }
|
|
228
|
-
|
|
229
|
-
.val-total { color: var(--text-main); }
|
|
230
|
-
.val-passed { color: var(--success); }
|
|
231
|
-
.val-failed { color: var(--error); }
|
|
232
|
-
.val-skipped { color: var(--text-muted); }
|
|
233
|
-
.val-flaky { color: var(--warning); }
|
|
234
|
-
|
|
235
|
-
/* TEST LIST */
|
|
236
|
-
.test-entry {
|
|
237
|
-
background: var(--bg-card);
|
|
238
|
-
border: 1px solid var(--border);
|
|
239
|
-
border-radius: 20px;
|
|
240
|
-
margin-bottom: 2rem;
|
|
241
|
-
overflow: hidden;
|
|
242
|
-
transition: transform 0.2s;
|
|
243
|
-
}
|
|
244
|
-
.test-entry:hover { border-color: var(--primary); }
|
|
245
|
-
|
|
246
|
-
.test-header {
|
|
247
|
-
padding: 1.5rem 2rem;
|
|
248
|
-
display: flex;
|
|
249
|
-
justify-content: space-between;
|
|
250
|
-
align-items: center;
|
|
251
|
-
cursor: pointer;
|
|
252
|
-
background: rgba(255,255,255,0.02);
|
|
253
|
-
}
|
|
254
|
-
.test-info { display: flex; align-items: center; gap: 1rem; }
|
|
255
|
-
.status-dot { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
|
|
256
|
-
.status-passed .status-dot { background: var(--success); box-shadow: 0 0 10px var(--success); }
|
|
257
|
-
.status-failed .status-dot { background: var(--error); box-shadow: 0 0 10px var(--error); }
|
|
258
|
-
.status-skipped .status-dot { background: var(--text-muted); }
|
|
259
|
-
|
|
260
|
-
.test-name { font-weight: 700; font-size: 1.1rem; }
|
|
261
|
-
.test-meta { font-size: 0.8rem; color: var(--text-muted); font-family: 'JetBrains Mono', monospace; }
|
|
262
|
-
|
|
263
|
-
.test-details { padding: 0 2rem 2rem; }
|
|
264
|
-
|
|
265
|
-
/* ERROR BLOCK */
|
|
266
|
-
.error-block {
|
|
267
|
-
background: rgba(244, 63, 94, 0.1);
|
|
268
|
-
border-left: 4px solid var(--error);
|
|
269
|
-
padding: 1.5rem;
|
|
270
|
-
border-radius: 8px;
|
|
271
|
-
margin: 1rem 0;
|
|
272
|
-
font-family: 'JetBrains Mono', monospace;
|
|
273
|
-
font-size: 0.9rem;
|
|
274
|
-
color: #fda4af;
|
|
275
|
-
white-space: pre-wrap;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/* SUCCESS BLOCK */
|
|
279
|
-
.success-block {
|
|
280
|
-
background: rgba(16, 185, 129, 0.1);
|
|
281
|
-
border-left: 4px solid var(--success);
|
|
282
|
-
padding: 1.2rem;
|
|
283
|
-
border-radius: 12px;
|
|
284
|
-
margin: 1rem 0;
|
|
285
|
-
font-size: 1.1rem;
|
|
286
|
-
font-weight: 600;
|
|
287
|
-
color: #6ee7b7;
|
|
288
|
-
display: flex;
|
|
289
|
-
align-items: center;
|
|
290
|
-
gap: 12px;
|
|
291
|
-
}
|
|
292
|
-
.success-block::before {
|
|
293
|
-
content: "\u2728";
|
|
294
|
-
font-size: 1.4rem;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/* STEPS UI */
|
|
298
|
-
.steps-section { margin-top: 2rem; }
|
|
299
|
-
.section-title { font-size: 0.9rem; text-transform: uppercase; letter-spacing: 2px; color: var(--accent); margin-bottom: 1rem; display: block; }
|
|
300
|
-
|
|
301
|
-
.step-wrapper {
|
|
302
|
-
border-left: 1px solid var(--border);
|
|
303
|
-
padding: 8px 16px;
|
|
304
|
-
position: relative;
|
|
305
|
-
}
|
|
306
|
-
.step-header { display: flex; align-items: flex-start; gap: 10px; font-size: 0.9rem; }
|
|
307
|
-
.step-icon { width: 16px; font-size: 0.8rem; opacity: 0.7; }
|
|
308
|
-
.step-action { color: var(--primary); font-weight: 700; }
|
|
309
|
-
.step-duration { font-size: 0.7rem; color: var(--text-muted); margin-left: auto; }
|
|
310
|
-
.step-error { border-left-color: var(--error); color: #fb7185; }
|
|
311
|
-
.step-error .step-icon { color: var(--error); }
|
|
312
|
-
|
|
313
|
-
/* ATTACHMENTS (Screenshots, Videos) */
|
|
314
|
-
.attachments-grid {
|
|
315
|
-
display: grid;
|
|
316
|
-
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
317
|
-
gap: 1.5rem;
|
|
318
|
-
margin-top: 2rem;
|
|
319
|
-
}
|
|
320
|
-
.attachment-item {
|
|
321
|
-
background: var(--bg-deep);
|
|
322
|
-
border-radius: 12px;
|
|
323
|
-
overflow: hidden;
|
|
324
|
-
border: 1px solid var(--border);
|
|
325
|
-
}
|
|
326
|
-
.attachment-item img, .attachment-item video {
|
|
327
|
-
width: 100%;
|
|
328
|
-
height: 180px;
|
|
329
|
-
object-fit: cover;
|
|
330
|
-
display: block;
|
|
331
|
-
cursor: zoom-in;
|
|
332
|
-
}
|
|
333
|
-
.att-label {
|
|
334
|
-
padding: 0.75rem;
|
|
335
|
-
font-size: 0.75rem;
|
|
336
|
-
font-weight: 600;
|
|
337
|
-
text-align: center;
|
|
338
|
-
background: rgba(255,255,255,0.05);
|
|
339
|
-
display: block;
|
|
340
|
-
text-decoration: none;
|
|
341
|
-
color: var(--text-muted);
|
|
342
|
-
}
|
|
343
|
-
.att-label:hover { color: var(--text-main); }
|
|
344
|
-
|
|
345
|
-
.btn-trace {
|
|
346
|
-
background: var(--primary);
|
|
347
|
-
color: white;
|
|
348
|
-
padding: 6px 12px;
|
|
349
|
-
border-radius: 6px;
|
|
350
|
-
font-size: 0.7rem;
|
|
351
|
-
font-weight: 700;
|
|
352
|
-
text-decoration: none;
|
|
353
|
-
display: inline-flex;
|
|
354
|
-
align-items: center;
|
|
355
|
-
gap: 5px;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
footer {
|
|
359
|
-
margin-top: 5rem;
|
|
360
|
-
text-align: center;
|
|
361
|
-
padding: 2rem;
|
|
362
|
-
font-size: 0.8rem;
|
|
363
|
-
color: var(--text-muted);
|
|
364
|
-
border-top: 1px solid var(--border);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/* UTILS */
|
|
368
|
-
.tag { padding: 4px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 800; text-transform: uppercase; }
|
|
369
|
-
.tag-project { background: rgba(192, 132, 252, 0.2); color: var(--accent); }
|
|
370
|
-
.tag-retry { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
|
|
371
|
-
|
|
372
|
-
/* SECURITY REPORT SECTION */
|
|
373
|
-
.security-section {
|
|
374
|
-
margin: 2rem 0;
|
|
375
|
-
background: var(--bg-card);
|
|
376
|
-
border-radius: 16px;
|
|
377
|
-
overflow: hidden;
|
|
378
|
-
border: 1px solid #4a4a4a
|
|
379
|
-
}
|
|
380
|
-
.security-header {
|
|
381
|
-
padding: 1rem 1.5rem;
|
|
382
|
-
cursor: pointer;
|
|
383
|
-
display: flex;
|
|
384
|
-
align-items: center;
|
|
385
|
-
gap: 12px;
|
|
386
|
-
background: linear-gradient(90deg, rgba(245, 158, 11, 0.1), rgba(245, 158, 11, 0.0) 70%);
|
|
387
|
-
}
|
|
388
|
-
.security-header:hover {
|
|
389
|
-
background: linear-gradient(90deg, rgba(245, 158, 11, 0.15), rgba(245, 158, 11, 0.05) 70%);
|
|
390
|
-
}
|
|
391
|
-
.security-header h3 {
|
|
392
|
-
margin: 0;
|
|
393
|
-
font-size: 1.2rem;
|
|
394
|
-
color: var(--warning);
|
|
395
|
-
font-weight: 700;
|
|
396
|
-
}
|
|
397
|
-
.security-risk-badge {
|
|
398
|
-
padding: 4px 10px;
|
|
399
|
-
border-radius: 6px;
|
|
400
|
-
font-size: 0.8rem;
|
|
401
|
-
font-weight: 800;
|
|
402
|
-
color: white;
|
|
403
|
-
margin-left: auto;
|
|
404
|
-
}
|
|
405
|
-
.security-details {
|
|
406
|
-
padding: 1.5rem;
|
|
407
|
-
background: #0c1322;
|
|
408
|
-
}
|
|
409
|
-
.security-summary {
|
|
410
|
-
display: grid;
|
|
411
|
-
grid-template-columns: repeat(4, 1fr);
|
|
412
|
-
gap: 1rem;
|
|
413
|
-
margin-bottom: 2rem;
|
|
414
|
-
padding-bottom: 1.5rem;
|
|
415
|
-
border-bottom: 1px solid var(--border);
|
|
416
|
-
text-align: center;
|
|
417
|
-
}
|
|
418
|
-
.summary-item .count {
|
|
419
|
-
font-size: 1.5rem;
|
|
420
|
-
font-weight: 800;
|
|
421
|
-
}
|
|
422
|
-
.summary-item .label {
|
|
423
|
-
font-size: 0.7rem;
|
|
424
|
-
text-transform: uppercase;
|
|
425
|
-
color: var(--text-muted);
|
|
426
|
-
}
|
|
427
|
-
.count-Critical { color: var(--error); }
|
|
428
|
-
.count-High { color: #fb923c; }
|
|
429
|
-
.count-Medium { color: var(--warning); }
|
|
430
|
-
.count-Low { color: #38bdf8; }
|
|
431
|
-
|
|
432
|
-
.vuln-list { display: flex; flex-direction: column; gap: 1rem; }
|
|
433
|
-
.vuln-card {
|
|
434
|
-
background: var(--bg-deep);
|
|
435
|
-
border: 1px solid var(--border);
|
|
436
|
-
border-left-width: 4px;
|
|
437
|
-
border-radius: 8px;
|
|
438
|
-
padding: 1.5rem;
|
|
439
|
-
}
|
|
440
|
-
.vuln-card-title { font-weight: 700; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 10px;}
|
|
441
|
-
.vuln-card-desc { color: var(--text-muted); font-size: 0.9rem; margin-bottom: 1rem; }
|
|
442
|
-
.vuln-card-evidence {
|
|
443
|
-
background: #020617;
|
|
444
|
-
padding: 0.75rem;
|
|
445
|
-
border-radius: 4px;
|
|
446
|
-
font-family: 'JetBrains Mono', monospace;
|
|
447
|
-
font-size: 0.8rem;
|
|
448
|
-
white-space: pre-wrap;
|
|
449
|
-
word-break: break-all;
|
|
450
|
-
margin-bottom: 1rem;
|
|
451
|
-
border: 1px solid var(--border);
|
|
452
|
-
max-height: 200px;
|
|
453
|
-
overflow-y: auto;
|
|
454
|
-
}
|
|
455
|
-
.vuln-card-remediation { font-size: 0.9rem; color: var(--success); }
|
|
456
|
-
pre { margin: 0; }
|
|
457
|
-
</style>
|
|
458
|
-
</head>
|
|
459
|
-
<body>
|
|
460
|
-
<div class="container">
|
|
461
|
-
<div class="main-header">
|
|
462
|
-
<div class="brand-section">
|
|
463
|
-
<span class="mission-id">Mission Alpha System</span>
|
|
464
|
-
<h1>Arcality Mission Report</h1>
|
|
465
|
-
<p style="color: var(--text-muted)">Testing Intelligence & Diagnostics Console</p>
|
|
466
|
-
</div>
|
|
467
|
-
<div style="text-align: right">
|
|
468
|
-
<div class="meta-item" style="color: var(--accent); font-weight: 700;">${this.startTime.toLocaleDateString()}</div>
|
|
469
|
-
<div class="meta-item" style="font-family: 'JetBrains Mono'; font-size: 0.8rem;">ENGINE VERSION ${this.getEngineVersion()}</div>
|
|
470
|
-
</div>
|
|
471
|
-
</div>
|
|
472
|
-
|
|
473
|
-
<div class="stats-grid">
|
|
474
|
-
<div class="stat-card"><span class="stat-value val-total">${stats.total}</span><span class="stat-label">Total Missions</span></div>
|
|
475
|
-
<div class="stat-card"><span class="stat-value val-passed">${stats.passed}</span><span class="stat-label">Successful</span></div>
|
|
476
|
-
<div class="stat-card"><span class="stat-value val-failed">${stats.failed}</span><span class="stat-label">Fatal Errors</span></div>
|
|
477
|
-
<div class="stat-card"><span class="stat-value val-flaky">${stats.flaky}</span><span class="stat-label">Flaky Tests</span></div>
|
|
478
|
-
<div class="stat-card"><span class="stat-value val-total">${(this.totalDuration / 1e3).toFixed(2)}s</span><span class="stat-label">Total Duration</span></div>
|
|
479
|
-
</div>
|
|
480
|
-
|
|
481
|
-
<div class="test-list">
|
|
482
|
-
${this.results.map((r, i) => {
|
|
483
|
-
const statusClass = `status-${r.result.status}`;
|
|
484
|
-
const projectName = r.test.parent.project()?.name || "default";
|
|
485
|
-
return `
|
|
486
|
-
<div class="test-entry ${statusClass}">
|
|
487
|
-
<div class="test-header" onclick="toggleDetails('details-${i}')">
|
|
488
|
-
<div class="test-info">
|
|
489
|
-
<span class="status-dot"></span>
|
|
490
|
-
<div>
|
|
491
|
-
<div class="test-name">${this.escapeHtml(r.test.title)}</div>
|
|
492
|
-
<div class="test-meta">
|
|
493
|
-
<span class="tag tag-project">${projectName}</span>
|
|
494
|
-
${r.result.retry > 0 ? `<span class="tag tag-retry">Retry #${r.result.retry}</span>` : ""}
|
|
495
|
-
<span>${(r.result.duration / 1e3).toFixed(2)}s</span>
|
|
496
|
-
</div>
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
<div style="display:flex; gap: 10px; align-items: center;">
|
|
500
|
-
<span style="opacity:0.5; font-size: 1.2rem">\u25BC</span>
|
|
501
|
-
</div>
|
|
502
|
-
</div>
|
|
503
|
-
|
|
504
|
-
<div class="test-details" id="details-${i}" style="display: none;">
|
|
505
|
-
${r.result.status === "passed" ? (() => {
|
|
506
|
-
const successSummary = r.attachments.find((a) => a.name === "success_summary");
|
|
507
|
-
const text = this.getAttachmentText(successSummary);
|
|
508
|
-
return text ? `<div class="success-block">${this.escapeHtml(text)}</div>` : "";
|
|
509
|
-
})() : ""}
|
|
510
|
-
|
|
511
|
-
${r.result.error ? `
|
|
512
|
-
<div class="steps-section">
|
|
513
|
-
<span class="section-title">Analysis of the Obstacle</span>
|
|
514
|
-
<div class="error-block">${this.escapeHtml(r.result.error.message || "Unknown Failure")}</div>
|
|
515
|
-
</div>
|
|
516
|
-
` : ""}
|
|
517
|
-
|
|
518
|
-
${this._renderSecuritySection(r.attachments, i)}
|
|
519
|
-
|
|
520
|
-
<div class="steps-section">
|
|
521
|
-
<span class="section-title">\u{1F9E0} Agent Cognitive Process</span>
|
|
522
|
-
<div class="steps-container">
|
|
523
|
-
${r.stepsHtml || '<p style="opacity:0.5">No steps recorded.</p>'}
|
|
524
|
-
</div>
|
|
525
|
-
</div>
|
|
526
|
-
|
|
527
|
-
${r.attachments.length > 0 ? `
|
|
528
|
-
<div class="steps-section">
|
|
529
|
-
<span class="section-title">Visual Evidence</span>
|
|
530
|
-
<div class="attachments-grid">
|
|
531
|
-
${r.attachments.map((att) => {
|
|
532
|
-
if (att.contentType.startsWith("image/")) {
|
|
533
|
-
return `
|
|
534
|
-
<div class="attachment-item">
|
|
535
|
-
<img src="${att.path}" alt="${att.name}" onclick="window.open('${att.path}')">
|
|
536
|
-
<a href="${att.path}" target="_blank" class="att-label">${att.name}</a>
|
|
537
|
-
</div>
|
|
538
|
-
`;
|
|
539
|
-
}
|
|
540
|
-
if (att.contentType.startsWith("video/")) {
|
|
541
|
-
return `
|
|
542
|
-
<div class="attachment-item">
|
|
543
|
-
<video controls src="${att.path}"></video>
|
|
544
|
-
<a href="${att.path}" target="_blank" class="att-label">${att.name}</a>
|
|
545
|
-
</div>
|
|
546
|
-
`;
|
|
547
|
-
}
|
|
548
|
-
return "";
|
|
549
|
-
}).join("")}
|
|
550
|
-
</div>
|
|
551
|
-
</div>
|
|
552
|
-
` : ""}
|
|
553
|
-
</div>
|
|
554
|
-
</div>
|
|
555
|
-
`;
|
|
556
|
-
}).join("")}
|
|
557
|
-
</div>
|
|
558
|
-
|
|
559
|
-
<footer>
|
|
560
|
-
ARCALITY ENGINE v${this.getEngineVersion()} \u2022 SYSTEM TIME [${(/* @__PURE__ */ new Date()).toISOString()}] \u2022 GENERATED FOR ARCADIAL
|
|
561
|
-
</footer>
|
|
562
|
-
</div>
|
|
563
|
-
|
|
564
|
-
<script>
|
|
565
|
-
function toggleDetails(id) {
|
|
566
|
-
const el = document.getElementById(id);
|
|
567
|
-
if (el) {
|
|
568
|
-
const isSecurityDetails = id.startsWith('security-details');
|
|
569
|
-
if (isSecurityDetails) {
|
|
570
|
-
// Independent toggle for security section
|
|
571
|
-
el.style.display = (el.style.display === 'none') ? 'block' : 'none';
|
|
572
|
-
} else {
|
|
573
|
-
// Main test details toggle
|
|
574
|
-
el.style.display = (el.style.display === 'none' || el.style.display === '') ? 'block' : 'none';
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
</script>
|
|
579
|
-
</body>
|
|
580
|
-
</html>
|
|
581
|
-
`;
|
|
582
|
-
}
|
|
583
|
-
getAttachmentText(att) {
|
|
584
|
-
if (!att)
|
|
585
|
-
return null;
|
|
586
|
-
if (att.body)
|
|
587
|
-
return att.body.toString();
|
|
588
|
-
if (att.path) {
|
|
589
|
-
const actualPath = import_path.default.isAbsolute(att.path) ? att.path : import_path.default.join(this.outputDir, att.path);
|
|
590
|
-
if (import_fs.default.existsSync(actualPath)) {
|
|
591
|
-
return import_fs.default.readFileSync(actualPath, "utf8");
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
_renderSecuritySection(attachments, index) {
|
|
597
|
-
let vulnerabilities = [];
|
|
598
|
-
const reportAtt = attachments.find((a) => a.name === "security_report");
|
|
599
|
-
if (reportAtt) {
|
|
600
|
-
const text = this.getAttachmentText(reportAtt);
|
|
601
|
-
if (text) {
|
|
602
|
-
try {
|
|
603
|
-
const parsed = JSON.parse(text);
|
|
604
|
-
if (Array.isArray(parsed))
|
|
605
|
-
vulnerabilities.push(...parsed);
|
|
606
|
-
} catch (e) {
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
const findingAtts = attachments.filter((a) => a.name === "security_finding");
|
|
611
|
-
for (const findingAtt of findingAtts) {
|
|
612
|
-
const text = this.getAttachmentText(findingAtt);
|
|
613
|
-
if (text) {
|
|
614
|
-
try {
|
|
615
|
-
const parsed = JSON.parse(text);
|
|
616
|
-
vulnerabilities.push(parsed);
|
|
617
|
-
} catch (e) {
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
if (vulnerabilities.length === 0)
|
|
622
|
-
return "";
|
|
623
|
-
const severityOrder = { "Critical": 0, "High": 1, "Medium": 2, "Low": 3, "Info": 4 };
|
|
624
|
-
const severityCounts = { "Critical": 0, "High": 0, "Medium": 0, "Low": 0, "Info": 0 };
|
|
625
|
-
let worstSeverity = "Info";
|
|
626
|
-
vulnerabilities.forEach((v) => {
|
|
627
|
-
const severity = v.severity;
|
|
628
|
-
if (severityCounts[severity] !== void 0) {
|
|
629
|
-
severityCounts[severity]++;
|
|
630
|
-
}
|
|
631
|
-
if (severityOrder[severity] < severityOrder[worstSeverity]) {
|
|
632
|
-
worstSeverity = severity;
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
const getSeverityColor = (sev) => {
|
|
636
|
-
switch (sev) {
|
|
637
|
-
case "Critical":
|
|
638
|
-
return "var(--error)";
|
|
639
|
-
case "High":
|
|
640
|
-
return "#fb923c";
|
|
641
|
-
case "Medium":
|
|
642
|
-
return "var(--warning)";
|
|
643
|
-
case "Low":
|
|
644
|
-
return "#38bdf8";
|
|
645
|
-
case "Info":
|
|
646
|
-
return "#94a3b8";
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
const riskColor = getSeverityColor(worstSeverity);
|
|
650
|
-
return `
|
|
651
|
-
<div class="security-section">
|
|
652
|
-
<div class="security-header" onclick="toggleDetails('security-details-${index}')">
|
|
653
|
-
<h3>\u{1F6E1}\uFE0F Security Assessment</h3>
|
|
654
|
-
<span class="security-risk-badge" style="background-color: ${riskColor};">${worstSeverity} Risk</span>
|
|
655
|
-
</div>
|
|
656
|
-
<div class="security-details" id="security-details-${index}" style="display: block;">
|
|
657
|
-
<div class="security-summary">
|
|
658
|
-
<div class="summary-item">
|
|
659
|
-
<div class="count count-Critical">${severityCounts.Critical}</div>
|
|
660
|
-
<div class="label">Critical</div>
|
|
661
|
-
</div>
|
|
662
|
-
<div class="summary-item">
|
|
663
|
-
<div class="count count-High">${severityCounts.High}</div>
|
|
664
|
-
<div class="label">High</div>
|
|
665
|
-
</div>
|
|
666
|
-
<div class="summary-item">
|
|
667
|
-
<div class="count count-Medium">${severityCounts.Medium}</div>
|
|
668
|
-
<div class="label">Medium</div>
|
|
669
|
-
</div>
|
|
670
|
-
<div class="summary-item">
|
|
671
|
-
<div class="count count-Low">${severityCounts.Low}</div>
|
|
672
|
-
<div class="label">Low</div>
|
|
673
|
-
</div>
|
|
674
|
-
</div>
|
|
675
|
-
<div class="vuln-list">
|
|
676
|
-
${vulnerabilities.map((v) => {
|
|
677
|
-
const sev = v.severity;
|
|
678
|
-
const cardColor = getSeverityColor(sev);
|
|
679
|
-
return `
|
|
680
|
-
<div class="vuln-card" style="border-left-color: ${cardColor};">
|
|
681
|
-
<div class="vuln-card-title">
|
|
682
|
-
<span class="tag" style="background-color: ${cardColor}20; color: ${cardColor};">${v.severity}</span>
|
|
683
|
-
<span>${this.escapeHtml(v.category)} ${v.subcategory ? `<span style="opacity:0.7">\u2014 ${this.escapeHtml(v.subcategory)}</span>` : ""}</span>
|
|
684
|
-
</div>
|
|
685
|
-
<div style="font-size: 0.8rem; opacity: 0.8; margin-top: 5px; margin-bottom: 10px; display: flex; flex-wrap: wrap; gap: 12px;">
|
|
686
|
-
${v.type ? `<span><strong style="color:var(--accent);">Type:</strong> ${this.escapeHtml(v.type)}</span>` : ""}
|
|
687
|
-
${v.confidence ? `<span><strong>Confidence:</strong> ${this.escapeHtml(v.confidence)}</span>` : ""}
|
|
688
|
-
${v.exploitability ? `<span><strong>Exploitability:</strong> ${this.escapeHtml(v.exploitability)}</span>` : ""}
|
|
689
|
-
</div>
|
|
690
|
-
<p class="vuln-card-desc">${this.escapeHtml(v.description)}</p>
|
|
691
|
-
${v.impact ? `<p class="vuln-card-impact" style="font-size: 0.85rem; color: #fb923c; margin-bottom: 10px;"><strong>Potential Impact:</strong> ${this.escapeHtml(v.impact)}</p>` : ""}
|
|
692
|
-
|
|
693
|
-
<div class="vuln-card-evidence">
|
|
694
|
-
<strong>Evidence:</strong>
|
|
695
|
-
<pre>${this.escapeHtml(JSON.stringify(v.evidence, null, 2))}</pre>
|
|
696
|
-
</div>
|
|
697
|
-
|
|
698
|
-
${v.reproductionSteps && v.reproductionSteps.length > 0 ? `
|
|
699
|
-
<div class="vuln-card-repro" style="margin: 10px 0; background: rgba(0,0,0,0.2); padding: 10px; border-radius: 6px; border-left: 2px solid ${cardColor}80;">
|
|
700
|
-
<strong style="font-size: 0.85rem; color: var(--text-muted);">Reproduction Steps:</strong>
|
|
701
|
-
<ol style="margin: 5px 0 0 20px; font-size: 0.85rem; color: var(--text-main);">
|
|
702
|
-
${v.reproductionSteps.map((s) => `<li>${this.escapeHtml(s)}</li>`).join("")}
|
|
703
|
-
</ol>
|
|
704
|
-
</div>` : ""}
|
|
705
|
-
|
|
706
|
-
<div class="vuln-card-remediation">
|
|
707
|
-
<strong>\u2192 Remediation:</strong> ${this.escapeHtml(v.remediation)}
|
|
708
|
-
</div>
|
|
709
|
-
|
|
710
|
-
${v.classification ? `
|
|
711
|
-
<div style="font-size: 0.75rem; opacity: 0.5; margin-top: 12px; display: flex; gap: 15px; font-family: 'JetBrains Mono', monospace;">
|
|
712
|
-
${v.classification.owaspTop10 ? `<span>[OWASP: ${this.escapeHtml(v.classification.owaspTop10)}]</span>` : ""}
|
|
713
|
-
${v.classification.cwe ? `<span>[CWE: ${this.escapeHtml(v.classification.cwe)}]</span>` : ""}
|
|
714
|
-
</div>` : ""}
|
|
715
|
-
</div>
|
|
716
|
-
`;
|
|
717
|
-
}).join("")}
|
|
718
|
-
</div>
|
|
719
|
-
</div>
|
|
720
|
-
</div>
|
|
721
|
-
`;
|
|
722
|
-
}
|
|
723
|
-
escapeHtml(unsafe) {
|
|
724
|
-
if (!unsafe)
|
|
725
|
-
return "";
|
|
726
|
-
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
var ArcalityReporter_default = ArcalityReporter;
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var ArcalityReporter_exports = {};
|
|
29
|
+
__export(ArcalityReporter_exports, {
|
|
30
|
+
default: () => ArcalityReporter_default
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(ArcalityReporter_exports);
|
|
33
|
+
var import_fs = __toESM(require("fs"));
|
|
34
|
+
var import_path = __toESM(require("path"));
|
|
35
|
+
class ArcalityReporter {
|
|
36
|
+
outputDir;
|
|
37
|
+
results = [];
|
|
38
|
+
totalDuration = 0;
|
|
39
|
+
startTime = /* @__PURE__ */ new Date();
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.outputDir = options.outputDir || "arcality-report";
|
|
42
|
+
}
|
|
43
|
+
onBegin(config, suite) {
|
|
44
|
+
if (import_fs.default.existsSync(this.outputDir)) {
|
|
45
|
+
try {
|
|
46
|
+
import_fs.default.rmSync(this.outputDir, { recursive: true, force: true });
|
|
47
|
+
console.log(`\u{1F9F9} Cleaned old report data at: ${this.outputDir}`);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.warn(`\u26A0\uFE0F Could not fully clean report dir: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!import_fs.default.existsSync(this.outputDir)) {
|
|
53
|
+
import_fs.default.mkdirSync(this.outputDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
this.startTime = /* @__PURE__ */ new Date();
|
|
56
|
+
console.log(`\u{1F680} Arcality Mission started at ${this.startTime.toLocaleTimeString()}`);
|
|
57
|
+
}
|
|
58
|
+
onTestEnd(test, result) {
|
|
59
|
+
const stepsHtml = result.steps.map((step) => this.renderStep(step, 0)).join("");
|
|
60
|
+
const processedAttachments = result.attachments.map((att) => {
|
|
61
|
+
let extension = ".png";
|
|
62
|
+
if (att.contentType === "application/json")
|
|
63
|
+
extension = ".json";
|
|
64
|
+
else if (att.contentType === "text/plain")
|
|
65
|
+
extension = ".txt";
|
|
66
|
+
else if (att.contentType === "text/yaml")
|
|
67
|
+
extension = ".yaml";
|
|
68
|
+
const fileName = att.path ? import_path.default.basename(att.path) : `${att.name || "attachment"}-${Date.now()}${extension}`;
|
|
69
|
+
const attachmentsDir = import_path.default.join(this.outputDir, "attachments");
|
|
70
|
+
const destPath = import_path.default.join(attachmentsDir, fileName);
|
|
71
|
+
if (!import_fs.default.existsSync(attachmentsDir)) {
|
|
72
|
+
import_fs.default.mkdirSync(attachmentsDir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
if (att.path && import_fs.default.existsSync(att.path)) {
|
|
76
|
+
import_fs.default.copyFileSync(att.path, destPath);
|
|
77
|
+
} else if (att.body) {
|
|
78
|
+
import_fs.default.writeFileSync(destPath, att.body);
|
|
79
|
+
}
|
|
80
|
+
return { ...att, path: `attachments/${fileName}` };
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error(`Failed to process attachment ${att.name}:`, e);
|
|
83
|
+
return att;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
if (result.status === "failed" || result.error) {
|
|
87
|
+
console.log(`
|
|
88
|
+
\u274C [TEST ENGINE FAILURE] ${test.title} failed:`);
|
|
89
|
+
console.log(`${result.error?.message || result.error?.stack || "Unknown error"}
|
|
90
|
+
`);
|
|
91
|
+
if (result.error?.stack) {
|
|
92
|
+
console.log(result.error.stack);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.results.push({ test, result, stepsHtml, attachments: processedAttachments });
|
|
96
|
+
}
|
|
97
|
+
async onEnd(result) {
|
|
98
|
+
this.totalDuration = result.duration;
|
|
99
|
+
const htmlContent = this.generateHtml();
|
|
100
|
+
const reportPath = import_path.default.join(this.outputDir, "index.html");
|
|
101
|
+
import_fs.default.writeFileSync(reportPath, htmlContent);
|
|
102
|
+
console.log(`\u2728 Arcality Mission Report generated at: ${reportPath}`);
|
|
103
|
+
}
|
|
104
|
+
renderStep(step, depth) {
|
|
105
|
+
if (step.category === "hook" || step.category === "fixture")
|
|
106
|
+
return "";
|
|
107
|
+
const padding = depth * 16;
|
|
108
|
+
const statusClass = step.error ? "step-error" : "step-passed";
|
|
109
|
+
const icon = step.error ? "\u2715" : "\u2713";
|
|
110
|
+
const duration = step.duration >= 0 ? `<span class="step-duration">${step.duration}ms</span>` : "";
|
|
111
|
+
let title = this.escapeHtml(step.title);
|
|
112
|
+
title = title.replace(/^(click|fill|navigate|wait|expect|goto|press)/i, '<span class="step-action">$1</span>');
|
|
113
|
+
let children = "";
|
|
114
|
+
if (step.steps && step.steps.length > 0) {
|
|
115
|
+
children = `<div class="step-children">${step.steps.map((s) => this.renderStep(s, depth + 1)).join("")}</div>`;
|
|
116
|
+
}
|
|
117
|
+
const isExpandable = step.steps && step.steps.length > 0;
|
|
118
|
+
const expandableAttr = isExpandable ? 'data-expandable="true"' : "";
|
|
119
|
+
return `
|
|
120
|
+
<div class="step-wrapper ${statusClass}" style="margin-left: ${padding}px" ${expandableAttr}>
|
|
121
|
+
<div class="step-header">
|
|
122
|
+
<span class="step-icon">${icon}</span>
|
|
123
|
+
<span class="step-title">${title}</span>
|
|
124
|
+
${duration}
|
|
125
|
+
</div>
|
|
126
|
+
${children}
|
|
127
|
+
</div>
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
getEngineVersion() {
|
|
131
|
+
try {
|
|
132
|
+
const packageJsonPath = import_path.default.join(process.cwd(), "package.json");
|
|
133
|
+
if (import_fs.default.existsSync(packageJsonPath)) {
|
|
134
|
+
const pkg = JSON.parse(import_fs.default.readFileSync(packageJsonPath, "utf8"));
|
|
135
|
+
return pkg.version || "2.2.0";
|
|
136
|
+
}
|
|
137
|
+
} catch (e) {
|
|
138
|
+
console.error("Error reading package.json version:", e);
|
|
139
|
+
}
|
|
140
|
+
return "2.2.0";
|
|
141
|
+
}
|
|
142
|
+
generateHtml() {
|
|
143
|
+
const stats = {
|
|
144
|
+
total: this.results.length,
|
|
145
|
+
passed: this.results.filter((r) => r.result.status === "passed").length,
|
|
146
|
+
failed: this.results.filter((r) => r.result.status === "failed" || r.result.status === "timedOut").length,
|
|
147
|
+
skipped: this.results.filter((r) => r.result.status === "skipped").length,
|
|
148
|
+
flaky: this.results.filter((r) => r.result.status === "passed" && r.result.retry > 0).length
|
|
149
|
+
};
|
|
150
|
+
return `
|
|
151
|
+
<!DOCTYPE html>
|
|
152
|
+
<html lang="en">
|
|
153
|
+
<head>
|
|
154
|
+
<meta charset="UTF-8">
|
|
155
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
156
|
+
<title>Arcality Mission Report</title>
|
|
157
|
+
<link rel="shortcut icon" href="https://raw.githubusercontent.com/microsoft/playwright/main/packages/playwright-core/src/server/chromium/test-results/favicon.ico">
|
|
158
|
+
<style>
|
|
159
|
+
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
160
|
+
|
|
161
|
+
:root {
|
|
162
|
+
--bg-deep: #020617;
|
|
163
|
+
--bg-card: #0f172a;
|
|
164
|
+
--bg-hover: #1e293b;
|
|
165
|
+
--text-main: #f8fafc;
|
|
166
|
+
--text-muted: #94a3b8;
|
|
167
|
+
--primary: #6366f1;
|
|
168
|
+
--success: #10b981;
|
|
169
|
+
--error: #f43f5e;
|
|
170
|
+
--warning: #f59e0b;
|
|
171
|
+
--border: #1e293b;
|
|
172
|
+
--accent: #c084fc;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
* { box-sizing: border-box; }
|
|
176
|
+
body {
|
|
177
|
+
font-family: 'Plus Jakarta Sans', sans-serif;
|
|
178
|
+
background-color: var(--bg-deep);
|
|
179
|
+
color: var(--text-main);
|
|
180
|
+
margin: 0;
|
|
181
|
+
line-height: 1.6;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.container { max-width: 1400px; margin: 0 auto; padding: 2rem; }
|
|
185
|
+
|
|
186
|
+
/* HEADER */
|
|
187
|
+
.main-header {
|
|
188
|
+
display: flex;
|
|
189
|
+
justify-content: space-between;
|
|
190
|
+
align-items: flex-start;
|
|
191
|
+
margin-bottom: 3rem;
|
|
192
|
+
padding-bottom: 2rem;
|
|
193
|
+
border-bottom: 1px solid var(--border);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.brand-section h1 {
|
|
197
|
+
font-size: 2.5rem;
|
|
198
|
+
font-weight: 800;
|
|
199
|
+
margin: 0;
|
|
200
|
+
background: linear-gradient(to right, #818cf8, #c084fc);
|
|
201
|
+
-webkit-background-clip: text;
|
|
202
|
+
-webkit-text-fill-color: transparent;
|
|
203
|
+
}
|
|
204
|
+
.mission-id {
|
|
205
|
+
font-family: 'JetBrains Mono', monospace;
|
|
206
|
+
font-size: 0.8rem;
|
|
207
|
+
color: var(--text-muted);
|
|
208
|
+
text-transform: uppercase;
|
|
209
|
+
letter-spacing: 3px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* DASHBOARD STATS */
|
|
213
|
+
.stats-grid {
|
|
214
|
+
display: grid;
|
|
215
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
216
|
+
gap: 1.5rem;
|
|
217
|
+
margin-bottom: 3rem;
|
|
218
|
+
}
|
|
219
|
+
.stat-card {
|
|
220
|
+
background: var(--bg-card);
|
|
221
|
+
padding: 1.5rem;
|
|
222
|
+
border-radius: 16px;
|
|
223
|
+
border: 1px solid var(--border);
|
|
224
|
+
text-align: center;
|
|
225
|
+
}
|
|
226
|
+
.stat-value { font-size: 2rem; font-weight: 800; display: block; }
|
|
227
|
+
.stat-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; font-weight: 700; }
|
|
228
|
+
|
|
229
|
+
.val-total { color: var(--text-main); }
|
|
230
|
+
.val-passed { color: var(--success); }
|
|
231
|
+
.val-failed { color: var(--error); }
|
|
232
|
+
.val-skipped { color: var(--text-muted); }
|
|
233
|
+
.val-flaky { color: var(--warning); }
|
|
234
|
+
|
|
235
|
+
/* TEST LIST */
|
|
236
|
+
.test-entry {
|
|
237
|
+
background: var(--bg-card);
|
|
238
|
+
border: 1px solid var(--border);
|
|
239
|
+
border-radius: 20px;
|
|
240
|
+
margin-bottom: 2rem;
|
|
241
|
+
overflow: hidden;
|
|
242
|
+
transition: transform 0.2s;
|
|
243
|
+
}
|
|
244
|
+
.test-entry:hover { border-color: var(--primary); }
|
|
245
|
+
|
|
246
|
+
.test-header {
|
|
247
|
+
padding: 1.5rem 2rem;
|
|
248
|
+
display: flex;
|
|
249
|
+
justify-content: space-between;
|
|
250
|
+
align-items: center;
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
background: rgba(255,255,255,0.02);
|
|
253
|
+
}
|
|
254
|
+
.test-info { display: flex; align-items: center; gap: 1rem; }
|
|
255
|
+
.status-dot { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
|
|
256
|
+
.status-passed .status-dot { background: var(--success); box-shadow: 0 0 10px var(--success); }
|
|
257
|
+
.status-failed .status-dot { background: var(--error); box-shadow: 0 0 10px var(--error); }
|
|
258
|
+
.status-skipped .status-dot { background: var(--text-muted); }
|
|
259
|
+
|
|
260
|
+
.test-name { font-weight: 700; font-size: 1.1rem; }
|
|
261
|
+
.test-meta { font-size: 0.8rem; color: var(--text-muted); font-family: 'JetBrains Mono', monospace; }
|
|
262
|
+
|
|
263
|
+
.test-details { padding: 0 2rem 2rem; }
|
|
264
|
+
|
|
265
|
+
/* ERROR BLOCK */
|
|
266
|
+
.error-block {
|
|
267
|
+
background: rgba(244, 63, 94, 0.1);
|
|
268
|
+
border-left: 4px solid var(--error);
|
|
269
|
+
padding: 1.5rem;
|
|
270
|
+
border-radius: 8px;
|
|
271
|
+
margin: 1rem 0;
|
|
272
|
+
font-family: 'JetBrains Mono', monospace;
|
|
273
|
+
font-size: 0.9rem;
|
|
274
|
+
color: #fda4af;
|
|
275
|
+
white-space: pre-wrap;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* SUCCESS BLOCK */
|
|
279
|
+
.success-block {
|
|
280
|
+
background: rgba(16, 185, 129, 0.1);
|
|
281
|
+
border-left: 4px solid var(--success);
|
|
282
|
+
padding: 1.2rem;
|
|
283
|
+
border-radius: 12px;
|
|
284
|
+
margin: 1rem 0;
|
|
285
|
+
font-size: 1.1rem;
|
|
286
|
+
font-weight: 600;
|
|
287
|
+
color: #6ee7b7;
|
|
288
|
+
display: flex;
|
|
289
|
+
align-items: center;
|
|
290
|
+
gap: 12px;
|
|
291
|
+
}
|
|
292
|
+
.success-block::before {
|
|
293
|
+
content: "\u2728";
|
|
294
|
+
font-size: 1.4rem;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* STEPS UI */
|
|
298
|
+
.steps-section { margin-top: 2rem; }
|
|
299
|
+
.section-title { font-size: 0.9rem; text-transform: uppercase; letter-spacing: 2px; color: var(--accent); margin-bottom: 1rem; display: block; }
|
|
300
|
+
|
|
301
|
+
.step-wrapper {
|
|
302
|
+
border-left: 1px solid var(--border);
|
|
303
|
+
padding: 8px 16px;
|
|
304
|
+
position: relative;
|
|
305
|
+
}
|
|
306
|
+
.step-header { display: flex; align-items: flex-start; gap: 10px; font-size: 0.9rem; }
|
|
307
|
+
.step-icon { width: 16px; font-size: 0.8rem; opacity: 0.7; }
|
|
308
|
+
.step-action { color: var(--primary); font-weight: 700; }
|
|
309
|
+
.step-duration { font-size: 0.7rem; color: var(--text-muted); margin-left: auto; }
|
|
310
|
+
.step-error { border-left-color: var(--error); color: #fb7185; }
|
|
311
|
+
.step-error .step-icon { color: var(--error); }
|
|
312
|
+
|
|
313
|
+
/* ATTACHMENTS (Screenshots, Videos) */
|
|
314
|
+
.attachments-grid {
|
|
315
|
+
display: grid;
|
|
316
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
317
|
+
gap: 1.5rem;
|
|
318
|
+
margin-top: 2rem;
|
|
319
|
+
}
|
|
320
|
+
.attachment-item {
|
|
321
|
+
background: var(--bg-deep);
|
|
322
|
+
border-radius: 12px;
|
|
323
|
+
overflow: hidden;
|
|
324
|
+
border: 1px solid var(--border);
|
|
325
|
+
}
|
|
326
|
+
.attachment-item img, .attachment-item video {
|
|
327
|
+
width: 100%;
|
|
328
|
+
height: 180px;
|
|
329
|
+
object-fit: cover;
|
|
330
|
+
display: block;
|
|
331
|
+
cursor: zoom-in;
|
|
332
|
+
}
|
|
333
|
+
.att-label {
|
|
334
|
+
padding: 0.75rem;
|
|
335
|
+
font-size: 0.75rem;
|
|
336
|
+
font-weight: 600;
|
|
337
|
+
text-align: center;
|
|
338
|
+
background: rgba(255,255,255,0.05);
|
|
339
|
+
display: block;
|
|
340
|
+
text-decoration: none;
|
|
341
|
+
color: var(--text-muted);
|
|
342
|
+
}
|
|
343
|
+
.att-label:hover { color: var(--text-main); }
|
|
344
|
+
|
|
345
|
+
.btn-trace {
|
|
346
|
+
background: var(--primary);
|
|
347
|
+
color: white;
|
|
348
|
+
padding: 6px 12px;
|
|
349
|
+
border-radius: 6px;
|
|
350
|
+
font-size: 0.7rem;
|
|
351
|
+
font-weight: 700;
|
|
352
|
+
text-decoration: none;
|
|
353
|
+
display: inline-flex;
|
|
354
|
+
align-items: center;
|
|
355
|
+
gap: 5px;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
footer {
|
|
359
|
+
margin-top: 5rem;
|
|
360
|
+
text-align: center;
|
|
361
|
+
padding: 2rem;
|
|
362
|
+
font-size: 0.8rem;
|
|
363
|
+
color: var(--text-muted);
|
|
364
|
+
border-top: 1px solid var(--border);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* UTILS */
|
|
368
|
+
.tag { padding: 4px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 800; text-transform: uppercase; }
|
|
369
|
+
.tag-project { background: rgba(192, 132, 252, 0.2); color: var(--accent); }
|
|
370
|
+
.tag-retry { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
|
|
371
|
+
|
|
372
|
+
/* SECURITY REPORT SECTION */
|
|
373
|
+
.security-section {
|
|
374
|
+
margin: 2rem 0;
|
|
375
|
+
background: var(--bg-card);
|
|
376
|
+
border-radius: 16px;
|
|
377
|
+
overflow: hidden;
|
|
378
|
+
border: 1px solid #4a4a4a
|
|
379
|
+
}
|
|
380
|
+
.security-header {
|
|
381
|
+
padding: 1rem 1.5rem;
|
|
382
|
+
cursor: pointer;
|
|
383
|
+
display: flex;
|
|
384
|
+
align-items: center;
|
|
385
|
+
gap: 12px;
|
|
386
|
+
background: linear-gradient(90deg, rgba(245, 158, 11, 0.1), rgba(245, 158, 11, 0.0) 70%);
|
|
387
|
+
}
|
|
388
|
+
.security-header:hover {
|
|
389
|
+
background: linear-gradient(90deg, rgba(245, 158, 11, 0.15), rgba(245, 158, 11, 0.05) 70%);
|
|
390
|
+
}
|
|
391
|
+
.security-header h3 {
|
|
392
|
+
margin: 0;
|
|
393
|
+
font-size: 1.2rem;
|
|
394
|
+
color: var(--warning);
|
|
395
|
+
font-weight: 700;
|
|
396
|
+
}
|
|
397
|
+
.security-risk-badge {
|
|
398
|
+
padding: 4px 10px;
|
|
399
|
+
border-radius: 6px;
|
|
400
|
+
font-size: 0.8rem;
|
|
401
|
+
font-weight: 800;
|
|
402
|
+
color: white;
|
|
403
|
+
margin-left: auto;
|
|
404
|
+
}
|
|
405
|
+
.security-details {
|
|
406
|
+
padding: 1.5rem;
|
|
407
|
+
background: #0c1322;
|
|
408
|
+
}
|
|
409
|
+
.security-summary {
|
|
410
|
+
display: grid;
|
|
411
|
+
grid-template-columns: repeat(4, 1fr);
|
|
412
|
+
gap: 1rem;
|
|
413
|
+
margin-bottom: 2rem;
|
|
414
|
+
padding-bottom: 1.5rem;
|
|
415
|
+
border-bottom: 1px solid var(--border);
|
|
416
|
+
text-align: center;
|
|
417
|
+
}
|
|
418
|
+
.summary-item .count {
|
|
419
|
+
font-size: 1.5rem;
|
|
420
|
+
font-weight: 800;
|
|
421
|
+
}
|
|
422
|
+
.summary-item .label {
|
|
423
|
+
font-size: 0.7rem;
|
|
424
|
+
text-transform: uppercase;
|
|
425
|
+
color: var(--text-muted);
|
|
426
|
+
}
|
|
427
|
+
.count-Critical { color: var(--error); }
|
|
428
|
+
.count-High { color: #fb923c; }
|
|
429
|
+
.count-Medium { color: var(--warning); }
|
|
430
|
+
.count-Low { color: #38bdf8; }
|
|
431
|
+
|
|
432
|
+
.vuln-list { display: flex; flex-direction: column; gap: 1rem; }
|
|
433
|
+
.vuln-card {
|
|
434
|
+
background: var(--bg-deep);
|
|
435
|
+
border: 1px solid var(--border);
|
|
436
|
+
border-left-width: 4px;
|
|
437
|
+
border-radius: 8px;
|
|
438
|
+
padding: 1.5rem;
|
|
439
|
+
}
|
|
440
|
+
.vuln-card-title { font-weight: 700; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 10px;}
|
|
441
|
+
.vuln-card-desc { color: var(--text-muted); font-size: 0.9rem; margin-bottom: 1rem; }
|
|
442
|
+
.vuln-card-evidence {
|
|
443
|
+
background: #020617;
|
|
444
|
+
padding: 0.75rem;
|
|
445
|
+
border-radius: 4px;
|
|
446
|
+
font-family: 'JetBrains Mono', monospace;
|
|
447
|
+
font-size: 0.8rem;
|
|
448
|
+
white-space: pre-wrap;
|
|
449
|
+
word-break: break-all;
|
|
450
|
+
margin-bottom: 1rem;
|
|
451
|
+
border: 1px solid var(--border);
|
|
452
|
+
max-height: 200px;
|
|
453
|
+
overflow-y: auto;
|
|
454
|
+
}
|
|
455
|
+
.vuln-card-remediation { font-size: 0.9rem; color: var(--success); }
|
|
456
|
+
pre { margin: 0; }
|
|
457
|
+
</style>
|
|
458
|
+
</head>
|
|
459
|
+
<body>
|
|
460
|
+
<div class="container">
|
|
461
|
+
<div class="main-header">
|
|
462
|
+
<div class="brand-section">
|
|
463
|
+
<span class="mission-id">Mission Alpha System</span>
|
|
464
|
+
<h1>Arcality Mission Report</h1>
|
|
465
|
+
<p style="color: var(--text-muted)">Testing Intelligence & Diagnostics Console</p>
|
|
466
|
+
</div>
|
|
467
|
+
<div style="text-align: right">
|
|
468
|
+
<div class="meta-item" style="color: var(--accent); font-weight: 700;">${this.startTime.toLocaleDateString()}</div>
|
|
469
|
+
<div class="meta-item" style="font-family: 'JetBrains Mono'; font-size: 0.8rem;">ENGINE VERSION ${this.getEngineVersion()}</div>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
|
|
473
|
+
<div class="stats-grid">
|
|
474
|
+
<div class="stat-card"><span class="stat-value val-total">${stats.total}</span><span class="stat-label">Total Missions</span></div>
|
|
475
|
+
<div class="stat-card"><span class="stat-value val-passed">${stats.passed}</span><span class="stat-label">Successful</span></div>
|
|
476
|
+
<div class="stat-card"><span class="stat-value val-failed">${stats.failed}</span><span class="stat-label">Fatal Errors</span></div>
|
|
477
|
+
<div class="stat-card"><span class="stat-value val-flaky">${stats.flaky}</span><span class="stat-label">Flaky Tests</span></div>
|
|
478
|
+
<div class="stat-card"><span class="stat-value val-total">${(this.totalDuration / 1e3).toFixed(2)}s</span><span class="stat-label">Total Duration</span></div>
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
<div class="test-list">
|
|
482
|
+
${this.results.map((r, i) => {
|
|
483
|
+
const statusClass = `status-${r.result.status}`;
|
|
484
|
+
const projectName = r.test.parent.project()?.name || "default";
|
|
485
|
+
return `
|
|
486
|
+
<div class="test-entry ${statusClass}">
|
|
487
|
+
<div class="test-header" onclick="toggleDetails('details-${i}')">
|
|
488
|
+
<div class="test-info">
|
|
489
|
+
<span class="status-dot"></span>
|
|
490
|
+
<div>
|
|
491
|
+
<div class="test-name">${this.escapeHtml(r.test.title)}</div>
|
|
492
|
+
<div class="test-meta">
|
|
493
|
+
<span class="tag tag-project">${projectName}</span>
|
|
494
|
+
${r.result.retry > 0 ? `<span class="tag tag-retry">Retry #${r.result.retry}</span>` : ""}
|
|
495
|
+
<span>${(r.result.duration / 1e3).toFixed(2)}s</span>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
<div style="display:flex; gap: 10px; align-items: center;">
|
|
500
|
+
<span style="opacity:0.5; font-size: 1.2rem">\u25BC</span>
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<div class="test-details" id="details-${i}" style="display: none;">
|
|
505
|
+
${r.result.status === "passed" ? (() => {
|
|
506
|
+
const successSummary = r.attachments.find((a) => a.name === "success_summary");
|
|
507
|
+
const text = this.getAttachmentText(successSummary);
|
|
508
|
+
return text ? `<div class="success-block">${this.escapeHtml(text)}</div>` : "";
|
|
509
|
+
})() : ""}
|
|
510
|
+
|
|
511
|
+
${r.result.error ? `
|
|
512
|
+
<div class="steps-section">
|
|
513
|
+
<span class="section-title">Analysis of the Obstacle</span>
|
|
514
|
+
<div class="error-block">${this.escapeHtml(r.result.error.message || "Unknown Failure")}</div>
|
|
515
|
+
</div>
|
|
516
|
+
` : ""}
|
|
517
|
+
|
|
518
|
+
${this._renderSecuritySection(r.attachments, i)}
|
|
519
|
+
|
|
520
|
+
<div class="steps-section">
|
|
521
|
+
<span class="section-title">\u{1F9E0} Agent Cognitive Process</span>
|
|
522
|
+
<div class="steps-container">
|
|
523
|
+
${r.stepsHtml || '<p style="opacity:0.5">No steps recorded.</p>'}
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
|
|
527
|
+
${r.attachments.length > 0 ? `
|
|
528
|
+
<div class="steps-section">
|
|
529
|
+
<span class="section-title">Visual Evidence</span>
|
|
530
|
+
<div class="attachments-grid">
|
|
531
|
+
${r.attachments.map((att) => {
|
|
532
|
+
if (att.contentType.startsWith("image/")) {
|
|
533
|
+
return `
|
|
534
|
+
<div class="attachment-item">
|
|
535
|
+
<img src="${att.path}" alt="${att.name}" onclick="window.open('${att.path}')">
|
|
536
|
+
<a href="${att.path}" target="_blank" class="att-label">${att.name}</a>
|
|
537
|
+
</div>
|
|
538
|
+
`;
|
|
539
|
+
}
|
|
540
|
+
if (att.contentType.startsWith("video/")) {
|
|
541
|
+
return `
|
|
542
|
+
<div class="attachment-item">
|
|
543
|
+
<video controls src="${att.path}"></video>
|
|
544
|
+
<a href="${att.path}" target="_blank" class="att-label">${att.name}</a>
|
|
545
|
+
</div>
|
|
546
|
+
`;
|
|
547
|
+
}
|
|
548
|
+
return "";
|
|
549
|
+
}).join("")}
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
` : ""}
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
`;
|
|
556
|
+
}).join("")}
|
|
557
|
+
</div>
|
|
558
|
+
|
|
559
|
+
<footer>
|
|
560
|
+
ARCALITY ENGINE v${this.getEngineVersion()} \u2022 SYSTEM TIME [${(/* @__PURE__ */ new Date()).toISOString()}] \u2022 GENERATED FOR ARCADIAL
|
|
561
|
+
</footer>
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<script>
|
|
565
|
+
function toggleDetails(id) {
|
|
566
|
+
const el = document.getElementById(id);
|
|
567
|
+
if (el) {
|
|
568
|
+
const isSecurityDetails = id.startsWith('security-details');
|
|
569
|
+
if (isSecurityDetails) {
|
|
570
|
+
// Independent toggle for security section
|
|
571
|
+
el.style.display = (el.style.display === 'none') ? 'block' : 'none';
|
|
572
|
+
} else {
|
|
573
|
+
// Main test details toggle
|
|
574
|
+
el.style.display = (el.style.display === 'none' || el.style.display === '') ? 'block' : 'none';
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
</script>
|
|
579
|
+
</body>
|
|
580
|
+
</html>
|
|
581
|
+
`;
|
|
582
|
+
}
|
|
583
|
+
getAttachmentText(att) {
|
|
584
|
+
if (!att)
|
|
585
|
+
return null;
|
|
586
|
+
if (att.body)
|
|
587
|
+
return att.body.toString();
|
|
588
|
+
if (att.path) {
|
|
589
|
+
const actualPath = import_path.default.isAbsolute(att.path) ? att.path : import_path.default.join(this.outputDir, att.path);
|
|
590
|
+
if (import_fs.default.existsSync(actualPath)) {
|
|
591
|
+
return import_fs.default.readFileSync(actualPath, "utf8");
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
_renderSecuritySection(attachments, index) {
|
|
597
|
+
let vulnerabilities = [];
|
|
598
|
+
const reportAtt = attachments.find((a) => a.name === "security_report");
|
|
599
|
+
if (reportAtt) {
|
|
600
|
+
const text = this.getAttachmentText(reportAtt);
|
|
601
|
+
if (text) {
|
|
602
|
+
try {
|
|
603
|
+
const parsed = JSON.parse(text);
|
|
604
|
+
if (Array.isArray(parsed))
|
|
605
|
+
vulnerabilities.push(...parsed);
|
|
606
|
+
} catch (e) {
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const findingAtts = attachments.filter((a) => a.name === "security_finding");
|
|
611
|
+
for (const findingAtt of findingAtts) {
|
|
612
|
+
const text = this.getAttachmentText(findingAtt);
|
|
613
|
+
if (text) {
|
|
614
|
+
try {
|
|
615
|
+
const parsed = JSON.parse(text);
|
|
616
|
+
vulnerabilities.push(parsed);
|
|
617
|
+
} catch (e) {
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (vulnerabilities.length === 0)
|
|
622
|
+
return "";
|
|
623
|
+
const severityOrder = { "Critical": 0, "High": 1, "Medium": 2, "Low": 3, "Info": 4 };
|
|
624
|
+
const severityCounts = { "Critical": 0, "High": 0, "Medium": 0, "Low": 0, "Info": 0 };
|
|
625
|
+
let worstSeverity = "Info";
|
|
626
|
+
vulnerabilities.forEach((v) => {
|
|
627
|
+
const severity = v.severity;
|
|
628
|
+
if (severityCounts[severity] !== void 0) {
|
|
629
|
+
severityCounts[severity]++;
|
|
630
|
+
}
|
|
631
|
+
if (severityOrder[severity] < severityOrder[worstSeverity]) {
|
|
632
|
+
worstSeverity = severity;
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
const getSeverityColor = (sev) => {
|
|
636
|
+
switch (sev) {
|
|
637
|
+
case "Critical":
|
|
638
|
+
return "var(--error)";
|
|
639
|
+
case "High":
|
|
640
|
+
return "#fb923c";
|
|
641
|
+
case "Medium":
|
|
642
|
+
return "var(--warning)";
|
|
643
|
+
case "Low":
|
|
644
|
+
return "#38bdf8";
|
|
645
|
+
case "Info":
|
|
646
|
+
return "#94a3b8";
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
const riskColor = getSeverityColor(worstSeverity);
|
|
650
|
+
return `
|
|
651
|
+
<div class="security-section">
|
|
652
|
+
<div class="security-header" onclick="toggleDetails('security-details-${index}')">
|
|
653
|
+
<h3>\u{1F6E1}\uFE0F Security Assessment</h3>
|
|
654
|
+
<span class="security-risk-badge" style="background-color: ${riskColor};">${worstSeverity} Risk</span>
|
|
655
|
+
</div>
|
|
656
|
+
<div class="security-details" id="security-details-${index}" style="display: block;">
|
|
657
|
+
<div class="security-summary">
|
|
658
|
+
<div class="summary-item">
|
|
659
|
+
<div class="count count-Critical">${severityCounts.Critical}</div>
|
|
660
|
+
<div class="label">Critical</div>
|
|
661
|
+
</div>
|
|
662
|
+
<div class="summary-item">
|
|
663
|
+
<div class="count count-High">${severityCounts.High}</div>
|
|
664
|
+
<div class="label">High</div>
|
|
665
|
+
</div>
|
|
666
|
+
<div class="summary-item">
|
|
667
|
+
<div class="count count-Medium">${severityCounts.Medium}</div>
|
|
668
|
+
<div class="label">Medium</div>
|
|
669
|
+
</div>
|
|
670
|
+
<div class="summary-item">
|
|
671
|
+
<div class="count count-Low">${severityCounts.Low}</div>
|
|
672
|
+
<div class="label">Low</div>
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
<div class="vuln-list">
|
|
676
|
+
${vulnerabilities.map((v) => {
|
|
677
|
+
const sev = v.severity;
|
|
678
|
+
const cardColor = getSeverityColor(sev);
|
|
679
|
+
return `
|
|
680
|
+
<div class="vuln-card" style="border-left-color: ${cardColor};">
|
|
681
|
+
<div class="vuln-card-title">
|
|
682
|
+
<span class="tag" style="background-color: ${cardColor}20; color: ${cardColor};">${v.severity}</span>
|
|
683
|
+
<span>${this.escapeHtml(v.category)} ${v.subcategory ? `<span style="opacity:0.7">\u2014 ${this.escapeHtml(v.subcategory)}</span>` : ""}</span>
|
|
684
|
+
</div>
|
|
685
|
+
<div style="font-size: 0.8rem; opacity: 0.8; margin-top: 5px; margin-bottom: 10px; display: flex; flex-wrap: wrap; gap: 12px;">
|
|
686
|
+
${v.type ? `<span><strong style="color:var(--accent);">Type:</strong> ${this.escapeHtml(v.type)}</span>` : ""}
|
|
687
|
+
${v.confidence ? `<span><strong>Confidence:</strong> ${this.escapeHtml(v.confidence)}</span>` : ""}
|
|
688
|
+
${v.exploitability ? `<span><strong>Exploitability:</strong> ${this.escapeHtml(v.exploitability)}</span>` : ""}
|
|
689
|
+
</div>
|
|
690
|
+
<p class="vuln-card-desc">${this.escapeHtml(v.description)}</p>
|
|
691
|
+
${v.impact ? `<p class="vuln-card-impact" style="font-size: 0.85rem; color: #fb923c; margin-bottom: 10px;"><strong>Potential Impact:</strong> ${this.escapeHtml(v.impact)}</p>` : ""}
|
|
692
|
+
|
|
693
|
+
<div class="vuln-card-evidence">
|
|
694
|
+
<strong>Evidence:</strong>
|
|
695
|
+
<pre>${this.escapeHtml(JSON.stringify(v.evidence, null, 2))}</pre>
|
|
696
|
+
</div>
|
|
697
|
+
|
|
698
|
+
${v.reproductionSteps && v.reproductionSteps.length > 0 ? `
|
|
699
|
+
<div class="vuln-card-repro" style="margin: 10px 0; background: rgba(0,0,0,0.2); padding: 10px; border-radius: 6px; border-left: 2px solid ${cardColor}80;">
|
|
700
|
+
<strong style="font-size: 0.85rem; color: var(--text-muted);">Reproduction Steps:</strong>
|
|
701
|
+
<ol style="margin: 5px 0 0 20px; font-size: 0.85rem; color: var(--text-main);">
|
|
702
|
+
${v.reproductionSteps.map((s) => `<li>${this.escapeHtml(s)}</li>`).join("")}
|
|
703
|
+
</ol>
|
|
704
|
+
</div>` : ""}
|
|
705
|
+
|
|
706
|
+
<div class="vuln-card-remediation">
|
|
707
|
+
<strong>\u2192 Remediation:</strong> ${this.escapeHtml(v.remediation)}
|
|
708
|
+
</div>
|
|
709
|
+
|
|
710
|
+
${v.classification ? `
|
|
711
|
+
<div style="font-size: 0.75rem; opacity: 0.5; margin-top: 12px; display: flex; gap: 15px; font-family: 'JetBrains Mono', monospace;">
|
|
712
|
+
${v.classification.owaspTop10 ? `<span>[OWASP: ${this.escapeHtml(v.classification.owaspTop10)}]</span>` : ""}
|
|
713
|
+
${v.classification.cwe ? `<span>[CWE: ${this.escapeHtml(v.classification.cwe)}]</span>` : ""}
|
|
714
|
+
</div>` : ""}
|
|
715
|
+
</div>
|
|
716
|
+
`;
|
|
717
|
+
}).join("")}
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
`;
|
|
722
|
+
}
|
|
723
|
+
escapeHtml(unsafe) {
|
|
724
|
+
if (!unsafe)
|
|
725
|
+
return "";
|
|
726
|
+
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
var ArcalityReporter_default = ArcalityReporter;
|