@arcadialdev/arcality 2.2.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.
Files changed (97) hide show
  1. package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
  2. package/.agents/skills/frontend-design/LICENSE.txt +177 -0
  3. package/.agents/skills/frontend-design/SKILL.md +42 -0
  4. package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
  5. package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
  6. package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
  7. package/.agents/skills/playwright-best-practices/README.md +147 -0
  8. package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
  9. package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
  10. package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
  11. package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
  12. package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
  13. package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
  14. package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
  15. package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
  16. package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
  17. package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
  18. package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
  19. package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
  20. package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
  21. package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
  22. package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
  23. package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
  24. package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
  25. package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
  26. package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
  27. package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
  28. package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
  29. package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
  30. package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
  31. package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
  32. package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
  33. package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
  34. package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
  35. package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
  36. package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
  37. package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
  38. package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
  39. package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
  40. package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
  41. package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
  42. package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
  43. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
  44. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
  45. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
  46. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
  47. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
  48. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
  49. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
  50. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
  51. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
  52. package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
  53. package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
  54. package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
  55. package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
  56. package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
  57. package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
  58. package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
  59. package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
  60. package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
  61. package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
  62. package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
  63. package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
  64. package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
  65. package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
  66. package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
  67. package/.env.example +21 -0
  68. package/README.md +30 -0
  69. package/bin/arcality.mjs +86 -0
  70. package/package.json +66 -0
  71. package/playwright.config.ts +12 -0
  72. package/scripts/cleanup-qmsdev.mjs +63 -0
  73. package/scripts/discover-view.mjs +52 -0
  74. package/scripts/extract-view.mjs +64 -0
  75. package/scripts/gen-and-run.mjs +838 -0
  76. package/scripts/init.mjs +290 -0
  77. package/scripts/migrate-to-central-out.mjs +157 -0
  78. package/scripts/postinstall.mjs +63 -0
  79. package/scripts/rebrand-report.mjs +241 -0
  80. package/scripts/setup.mjs +166 -0
  81. package/src/KnowledgeService.ts +239 -0
  82. package/src/arcalityClient.mjs +266 -0
  83. package/src/configLoader.mjs +179 -0
  84. package/src/configManager.mjs +172 -0
  85. package/src/consoleBanner.ts +32 -0
  86. package/src/envSetup.ts +205 -0
  87. package/src/index.ts +25 -0
  88. package/src/projectInspector.ts +42 -0
  89. package/src/services/collectiveMemoryService.ts +178 -0
  90. package/src/testRunner.ts +201 -0
  91. package/tests/_helpers/ArcalityReporter.ts +490 -0
  92. package/tests/_helpers/agentic-runner.spec.ts +741 -0
  93. package/tests/_helpers/ai-agent-helper.ts +1573 -0
  94. package/tests/_helpers/discover-view.spec.ts +238 -0
  95. package/tests/_helpers/extract-view.spec.ts +118 -0
  96. package/tests/_helpers/qa-tools.ts +333 -0
  97. package/tests/_helpers/smart-action.spec.ts +1458 -0
@@ -0,0 +1,490 @@
1
+ import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult, TestStep } from '@playwright/test/reporter';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ interface EnhancedResult {
6
+ test: TestCase;
7
+ result: TestResult;
8
+ stepsHtml: string;
9
+ attachments: { name: string, path?: string, contentType: string, body?: Buffer }[];
10
+ }
11
+
12
+ class ArcalityReporter implements Reporter {
13
+ private outputDir: string;
14
+ private results: EnhancedResult[] = [];
15
+ private totalDuration: number = 0;
16
+ private startTime: Date = new Date();
17
+
18
+ constructor(options: { outputDir?: string } = {}) {
19
+ this.outputDir = options.outputDir || 'arcality-report';
20
+ }
21
+
22
+ onBegin(config: FullConfig, suite: Suite) {
23
+ // Clean output directory to avoid stale reports/attachments
24
+ if (fs.existsSync(this.outputDir)) {
25
+ try {
26
+ fs.rmSync(this.outputDir, { recursive: true, force: true });
27
+ console.log(`🧹 Cleaned old report data at: ${this.outputDir}`);
28
+ } catch (err: any) {
29
+ console.warn(`⚠️ Could not fully clean report dir: ${err.message}`);
30
+ }
31
+ }
32
+
33
+ if (!fs.existsSync(this.outputDir)) {
34
+ fs.mkdirSync(this.outputDir, { recursive: true });
35
+ }
36
+ this.startTime = new Date();
37
+ console.log(`🚀 Arcality Mission started at ${this.startTime.toLocaleTimeString()}`);
38
+ }
39
+
40
+ onTestEnd(test: TestCase, result: TestResult) {
41
+ // Collect steps with recursion
42
+ const stepsHtml = result.steps.map(step => this.renderStep(step, 0)).join('');
43
+
44
+ // Handle attachments: copy to report dir
45
+ const processedAttachments = result.attachments.map(att => {
46
+ const fileName = att.path ? path.basename(att.path) : `${att.name || 'attachment'}-${Date.now()}.png`;
47
+ const attachmentsDir = path.join(this.outputDir, 'attachments');
48
+ const destPath = path.join(attachmentsDir, fileName);
49
+
50
+ if (!fs.existsSync(attachmentsDir)) {
51
+ fs.mkdirSync(attachmentsDir, { recursive: true });
52
+ }
53
+
54
+ try {
55
+ if (att.path && fs.existsSync(att.path)) {
56
+ // Si ya existe el archivo físico, lo copiamos
57
+ fs.copyFileSync(att.path, destPath);
58
+ } else if (att.body) {
59
+ // Si viene como Buffer (evidencia del agente), lo guardamos
60
+ fs.writeFileSync(destPath, att.body);
61
+ }
62
+ return { ...att, path: `attachments/${fileName}` };
63
+ } catch (e) {
64
+ console.error(`Failed to process attachment ${att.name}:`, e);
65
+ return att;
66
+ }
67
+ });
68
+
69
+ this.results.push({ test, result, stepsHtml, attachments: processedAttachments });
70
+ }
71
+
72
+ async onEnd(result: FullResult) {
73
+ this.totalDuration = result.duration;
74
+ const htmlContent = this.generateHtml();
75
+ const reportPath = path.join(this.outputDir, 'index.html');
76
+ fs.writeFileSync(reportPath, htmlContent);
77
+ console.log(`✨ Arcality Mission Report generated at: ${reportPath}`);
78
+ }
79
+
80
+ private renderStep(step: TestStep, depth: number): string {
81
+ // Humanize: Hide internal Playwright hooks to only show Agent decisions
82
+ if (step.category === 'hook' || step.category === 'fixture') return '';
83
+
84
+ const padding = depth * 16;
85
+ const statusClass = step.error ? 'step-error' : 'step-passed';
86
+ const icon = step.error ? '✕' : '✓';
87
+ const duration = step.duration >= 0 ? `<span class="step-duration">${step.duration}ms</span>` : '';
88
+
89
+ let title = this.escapeHtml(step.title);
90
+ // Highlight common playwright actions
91
+ title = title.replace(/^(click|fill|navigate|wait|expect|goto|press)/i, '<span class="step-action">$1</span>');
92
+
93
+ let children = '';
94
+ if (step.steps && step.steps.length > 0) {
95
+ children = `<div class="step-children">${step.steps.map(s => this.renderStep(s, depth + 1)).join('')}</div>`;
96
+ }
97
+
98
+ const isExpandable = step.steps && step.steps.length > 0;
99
+ const expandableAttr = isExpandable ? 'data-expandable="true"' : '';
100
+
101
+ return `
102
+ <div class="step-wrapper ${statusClass}" style="margin-left: ${padding}px" ${expandableAttr}>
103
+ <div class="step-header">
104
+ <span class="step-icon">${icon}</span>
105
+ <span class="step-title">${title}</span>
106
+ ${duration}
107
+ </div>
108
+ ${children}
109
+ </div>
110
+ `;
111
+ }
112
+
113
+ private getEngineVersion(): string {
114
+ try {
115
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
116
+ if (fs.existsSync(packageJsonPath)) {
117
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
118
+ return pkg.version || '2.2.0';
119
+ }
120
+ } catch (e) {
121
+ console.error('Error reading package.json version:', e);
122
+ }
123
+ return '2.2.0';
124
+ }
125
+
126
+ private generateHtml(): string {
127
+ const stats = {
128
+ total: this.results.length,
129
+ passed: this.results.filter(r => r.result.status === 'passed').length,
130
+ failed: this.results.filter(r => r.result.status === 'failed' || r.result.status === 'timedOut').length,
131
+ skipped: this.results.filter(r => r.result.status === 'skipped').length,
132
+ flaky: this.results.filter(r => r.result.status === 'passed' && r.result.retry > 0).length
133
+ };
134
+
135
+ return `
136
+ <!DOCTYPE html>
137
+ <html lang="en">
138
+ <head>
139
+ <meta charset="UTF-8">
140
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
141
+ <title>Arcality Mission Report</title>
142
+ <link rel="shortcut icon" href="https://raw.githubusercontent.com/microsoft/playwright/main/packages/playwright-core/src/server/chromium/test-results/favicon.ico">
143
+ <style>
144
+ @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');
145
+
146
+ :root {
147
+ --bg-deep: #020617;
148
+ --bg-card: #0f172a;
149
+ --bg-hover: #1e293b;
150
+ --text-main: #f8fafc;
151
+ --text-muted: #94a3b8;
152
+ --primary: #6366f1;
153
+ --success: #10b981;
154
+ --error: #f43f5e;
155
+ --warning: #f59e0b;
156
+ --border: #1e293b;
157
+ --accent: #c084fc;
158
+ }
159
+
160
+ * { box-sizing: border-box; }
161
+ body {
162
+ font-family: 'Plus Jakarta Sans', sans-serif;
163
+ background-color: var(--bg-deep);
164
+ color: var(--text-main);
165
+ margin: 0;
166
+ line-height: 1.6;
167
+ }
168
+
169
+ .container { max-width: 1400px; margin: 0 auto; padding: 2rem; }
170
+
171
+ /* HEADER */
172
+ .main-header {
173
+ display: flex;
174
+ justify-content: space-between;
175
+ align-items: flex-start;
176
+ margin-bottom: 3rem;
177
+ padding-bottom: 2rem;
178
+ border-bottom: 1px solid var(--border);
179
+ }
180
+
181
+ .brand-section h1 {
182
+ font-size: 2.5rem;
183
+ font-weight: 800;
184
+ margin: 0;
185
+ background: linear-gradient(to right, #818cf8, #c084fc);
186
+ -webkit-background-clip: text;
187
+ -webkit-text-fill-color: transparent;
188
+ }
189
+ .mission-id {
190
+ font-family: 'JetBrains Mono', monospace;
191
+ font-size: 0.8rem;
192
+ color: var(--text-muted);
193
+ text-transform: uppercase;
194
+ letter-spacing: 3px;
195
+ }
196
+
197
+ /* DASHBOARD STATS */
198
+ .stats-grid {
199
+ display: grid;
200
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
201
+ gap: 1.5rem;
202
+ margin-bottom: 3rem;
203
+ }
204
+ .stat-card {
205
+ background: var(--bg-card);
206
+ padding: 1.5rem;
207
+ border-radius: 16px;
208
+ border: 1px solid var(--border);
209
+ text-align: center;
210
+ }
211
+ .stat-value { font-size: 2rem; font-weight: 800; display: block; }
212
+ .stat-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; font-weight: 700; }
213
+
214
+ .val-total { color: var(--text-main); }
215
+ .val-passed { color: var(--success); }
216
+ .val-failed { color: var(--error); }
217
+ .val-skipped { color: var(--text-muted); }
218
+ .val-flaky { color: var(--warning); }
219
+
220
+ /* TEST LIST */
221
+ .test-entry {
222
+ background: var(--bg-card);
223
+ border: 1px solid var(--border);
224
+ border-radius: 20px;
225
+ margin-bottom: 2rem;
226
+ overflow: hidden;
227
+ transition: transform 0.2s;
228
+ }
229
+ .test-entry:hover { border-color: var(--primary); }
230
+
231
+ .test-header {
232
+ padding: 1.5rem 2rem;
233
+ display: flex;
234
+ justify-content: space-between;
235
+ align-items: center;
236
+ cursor: pointer;
237
+ background: rgba(255,255,255,0.02);
238
+ }
239
+ .test-info { display: flex; align-items: center; gap: 1rem; }
240
+ .status-dot { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
241
+ .status-passed .status-dot { background: var(--success); box-shadow: 0 0 10px var(--success); }
242
+ .status-failed .status-dot { background: var(--error); box-shadow: 0 0 10px var(--error); }
243
+ .status-skipped .status-dot { background: var(--text-muted); }
244
+
245
+ .test-name { font-weight: 700; font-size: 1.1rem; }
246
+ .test-meta { font-size: 0.8rem; color: var(--text-muted); font-family: 'JetBrains Mono', monospace; }
247
+
248
+ .test-details { padding: 0 2rem 2rem; }
249
+
250
+ /* ERROR BLOCK */
251
+ .error-block {
252
+ background: rgba(244, 63, 94, 0.1);
253
+ border-left: 4px solid var(--error);
254
+ padding: 1.5rem;
255
+ border-radius: 8px;
256
+ margin: 1rem 0;
257
+ font-family: 'JetBrains Mono', monospace;
258
+ font-size: 0.9rem;
259
+ color: #fda4af;
260
+ white-space: pre-wrap;
261
+ }
262
+
263
+ /* SUCCESS BLOCK */
264
+ .success-block {
265
+ background: rgba(16, 185, 129, 0.1);
266
+ border-left: 4px solid var(--success);
267
+ padding: 1.2rem;
268
+ border-radius: 12px;
269
+ margin: 1rem 0;
270
+ font-size: 1.1rem;
271
+ font-weight: 600;
272
+ color: #6ee7b7;
273
+ display: flex;
274
+ align-items: center;
275
+ gap: 12px;
276
+ }
277
+ .success-block::before {
278
+ content: "✨";
279
+ font-size: 1.4rem;
280
+ }
281
+
282
+ /* STEPS UI */
283
+ .steps-section { margin-top: 2rem; }
284
+ .section-title { font-size: 0.9rem; text-transform: uppercase; letter-spacing: 2px; color: var(--accent); margin-bottom: 1rem; display: block; }
285
+
286
+ .step-wrapper {
287
+ border-left: 1px solid var(--border);
288
+ padding: 8px 16px;
289
+ position: relative;
290
+ }
291
+ .step-header { display: flex; align-items: flex-start; gap: 10px; font-size: 0.9rem; }
292
+ .step-icon { width: 16px; font-size: 0.8rem; opacity: 0.7; }
293
+ .step-action { color: var(--primary); font-weight: 700; }
294
+ .step-duration { font-size: 0.7rem; color: var(--text-muted); margin-left: auto; }
295
+ .step-error { border-left-color: var(--error); color: #fb7185; }
296
+ .step-error .step-icon { color: var(--error); }
297
+
298
+ /* ATTACHMENTS (Screenshots, Videos) */
299
+ .attachments-grid {
300
+ display: grid;
301
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
302
+ gap: 1.5rem;
303
+ margin-top: 2rem;
304
+ }
305
+ .attachment-item {
306
+ background: var(--bg-deep);
307
+ border-radius: 12px;
308
+ overflow: hidden;
309
+ border: 1px solid var(--border);
310
+ }
311
+ .attachment-item img, .attachment-item video {
312
+ width: 100%;
313
+ height: 180px;
314
+ object-fit: cover;
315
+ display: block;
316
+ cursor: zoom-in;
317
+ }
318
+ .att-label {
319
+ padding: 0.75rem;
320
+ font-size: 0.75rem;
321
+ font-weight: 600;
322
+ text-align: center;
323
+ background: rgba(255,255,255,0.05);
324
+ display: block;
325
+ text-decoration: none;
326
+ color: var(--text-muted);
327
+ }
328
+ .att-label:hover { color: var(--text-main); }
329
+
330
+ .btn-trace {
331
+ background: var(--primary);
332
+ color: white;
333
+ padding: 6px 12px;
334
+ border-radius: 6px;
335
+ font-size: 0.7rem;
336
+ font-weight: 700;
337
+ text-decoration: none;
338
+ display: inline-flex;
339
+ align-items: center;
340
+ gap: 5px;
341
+ }
342
+
343
+ footer {
344
+ margin-top: 5rem;
345
+ text-align: center;
346
+ padding: 2rem;
347
+ font-size: 0.8rem;
348
+ color: var(--text-muted);
349
+ border-top: 1px solid var(--border);
350
+ }
351
+
352
+ /* UTILS */
353
+ .tag { padding: 4px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 800; text-transform: uppercase; }
354
+ .tag-project { background: rgba(192, 132, 252, 0.2); color: var(--accent); }
355
+ .tag-retry { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
356
+ </style>
357
+ </head>
358
+ <body>
359
+ <div class="container">
360
+ <div class="main-header">
361
+ <div class="brand-section">
362
+ <span class="mission-id">Mission Alpha System</span>
363
+ <h1>Arcality Mission Report</h1>
364
+ <p style="color: var(--text-muted)">Testing Intelligence & Diagnostics Console</p>
365
+ </div>
366
+ <div style="text-align: right">
367
+ <div class="meta-item" style="color: var(--accent); font-weight: 700;">${this.startTime.toLocaleDateString()}</div>
368
+ <div class="meta-item" style="font-family: 'JetBrains Mono'; font-size: 0.8rem;">ENGINE VERSION ${this.getEngineVersion()}</div>
369
+ </div>
370
+ </div>
371
+
372
+ <div class="stats-grid">
373
+ <div class="stat-card"><span class="stat-value val-total">${stats.total}</span><span class="stat-label">Total Missions</span></div>
374
+ <div class="stat-card"><span class="stat-value val-passed">${stats.passed}</span><span class="stat-label">Successful</span></div>
375
+ <div class="stat-card"><span class="stat-value val-failed">${stats.failed}</span><span class="stat-label">Fatal Errors</span></div>
376
+ <div class="stat-card"><span class="stat-value val-flaky">${stats.flaky}</span><span class="stat-label">Flaky Tests</span></div>
377
+ <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>
378
+ </div>
379
+
380
+ <div class="test-list">
381
+ ${this.results.map((r, i) => {
382
+ const statusClass = `status-${r.result.status}`;
383
+ const projectName = r.test.parent.project()?.name || 'default';
384
+ const hasTrace = r.attachments.some(a => a.name === 'trace');
385
+ const traceFile = r.attachments.find(a => a.name === 'trace')?.path;
386
+
387
+ return `
388
+ <div class="test-entry ${statusClass}">
389
+ <div class="test-header" onclick="toggleDetails(${i})">
390
+ <div class="test-info">
391
+ <span class="status-dot"></span>
392
+ <div>
393
+ <div class="test-name">${this.escapeHtml(r.test.title)}</div>
394
+ <div class="test-meta">
395
+ <span class="tag tag-project">${projectName}</span>
396
+ ${r.result.retry > 0 ? `<span class="tag tag-retry">Retry #${r.result.retry}</span>` : ''}
397
+ <span>${(r.result.duration / 1000).toFixed(2)}s</span>
398
+ </div>
399
+ </div>
400
+ </div>
401
+ <div style="display:flex; gap: 10px; align-items: center;">
402
+ <span style="opacity:0.5; font-size: 1.2rem">▼</span>
403
+ </div>
404
+ </div>
405
+
406
+ <div class="test-details" id="details-${i}">
407
+ ${r.result.status === 'passed' ? (() => {
408
+ const successSummary = r.attachments.find(a => a.name === 'success_summary');
409
+ if (successSummary && successSummary.body) {
410
+ return `<div class="success-block">${this.escapeHtml(successSummary.body.toString())}</div>`;
411
+ }
412
+ return '';
413
+ })() : ''}
414
+
415
+ ${r.result.error ? `
416
+ <div class="steps-section">
417
+ <span class="section-title">Analysis of the Obstacle</span>
418
+ <div class="error-block">${this.escapeHtml(r.result.error.message || 'Unknown Failure')}</div>
419
+ </div>
420
+ ` : ''}
421
+
422
+ <div class="steps-section">
423
+ <span class="section-title">🧠 Agent Cognitive Process</span>
424
+ <div class="steps-container">
425
+ ${r.stepsHtml || '<p style="opacity:0.5">No steps recorded.</p>'}
426
+ </div>
427
+ </div>
428
+
429
+ ${r.attachments.length > 0 ? `
430
+ <div class="steps-section">
431
+ <span class="section-title">Visual Evidence</span>
432
+ <div class="attachments-grid">
433
+ ${r.attachments.map(att => {
434
+ if (att.contentType.startsWith('image/')) {
435
+ return `
436
+ <div class="attachment-item">
437
+ <img src="${att.path}" alt="${att.name}" onclick="window.open('${att.path}')">
438
+ <a href="${att.path}" target="_blank" class="att-label">${att.name}</a>
439
+ </div>
440
+ `;
441
+ }
442
+ if (att.contentType.startsWith('video/')) {
443
+ return `
444
+ <div class="attachment-item">
445
+ <video controls src="${att.path}"></video>
446
+ <a href="${att.path}" target="_blank" class="att-label">${att.name}</a>
447
+ </div>
448
+ `;
449
+ }
450
+ return '';
451
+ }).join('')}
452
+ </div>
453
+ </div>
454
+ ` : ''}
455
+ </div>
456
+ </div>
457
+ `;
458
+ }).join('')}
459
+ </div>
460
+
461
+ <footer>
462
+ ARCALITY ENGINE v${this.getEngineVersion()} • SYSTEM TIME [${new Date().toISOString()}] • GENERATED FOR ARCADIAL
463
+ </footer>
464
+ </div>
465
+
466
+ <script>
467
+ function toggleDetails(id) {
468
+ const el = document.getElementById('details-' + id);
469
+ el.style.display = (el.style.display === 'none' || el.style.display === '') ? 'block' : 'none';
470
+ }
471
+ // Collapse all by default
472
+ document.querySelectorAll('.test-details').forEach(d => d.style.display = 'none');
473
+ </script>
474
+ </body>
475
+ </html>
476
+ `;
477
+ }
478
+
479
+ private escapeHtml(unsafe: string | undefined): string {
480
+ if (!unsafe) return '';
481
+ return unsafe
482
+ .replace(/&/g, "&amp;")
483
+ .replace(/</g, "&lt;")
484
+ .replace(/>/g, "&gt;")
485
+ .replace(/"/g, "&quot;")
486
+ .replace(/'/g, "&#039;");
487
+ }
488
+ }
489
+
490
+ export default ArcalityReporter;