@arcadialdev/arcality 2.4.24 → 2.4.25

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