@arcadialdev/arcality 2.4.29 → 2.4.30

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 (80) hide show
  1. package/package.json +3 -4
  2. package/src/arcalityClient.mjs +2 -1
  3. package/.agents/skills/e2e-testing-expert/SKILL.md +0 -28
  4. package/.agents/skills/frontend-design/LICENSE.txt +0 -177
  5. package/.agents/skills/frontend-design/SKILL.md +0 -42
  6. package/.agents/skills/nodejs-backend-patterns/SKILL.md +0 -639
  7. package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +0 -430
  8. package/.agents/skills/playwright-best-practices/LICENSE.md +0 -7
  9. package/.agents/skills/playwright-best-practices/README.md +0 -147
  10. package/.agents/skills/playwright-best-practices/SKILL.md +0 -303
  11. package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +0 -360
  12. package/.agents/skills/playwright-best-practices/advanced/authentication.md +0 -871
  13. package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +0 -364
  14. package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +0 -409
  15. package/.agents/skills/playwright-best-practices/advanced/multi-context.md +0 -288
  16. package/.agents/skills/playwright-best-practices/advanced/multi-user.md +0 -393
  17. package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +0 -452
  18. package/.agents/skills/playwright-best-practices/advanced/third-party.md +0 -464
  19. package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +0 -363
  20. package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +0 -369
  21. package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +0 -383
  22. package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +0 -391
  23. package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +0 -403
  24. package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +0 -504
  25. package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +0 -403
  26. package/.agents/skills/playwright-best-practices/core/annotations.md +0 -424
  27. package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +0 -361
  28. package/.agents/skills/playwright-best-practices/core/configuration.md +0 -452
  29. package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +0 -417
  30. package/.agents/skills/playwright-best-practices/core/global-setup.md +0 -434
  31. package/.agents/skills/playwright-best-practices/core/locators.md +0 -242
  32. package/.agents/skills/playwright-best-practices/core/page-object-model.md +0 -315
  33. package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +0 -453
  34. package/.agents/skills/playwright-best-practices/core/test-data.md +0 -492
  35. package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +0 -361
  36. package/.agents/skills/playwright-best-practices/core/test-tags.md +0 -298
  37. package/.agents/skills/playwright-best-practices/debugging/console-errors.md +0 -420
  38. package/.agents/skills/playwright-best-practices/debugging/debugging.md +0 -504
  39. package/.agents/skills/playwright-best-practices/debugging/error-testing.md +0 -360
  40. package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +0 -496
  41. package/.agents/skills/playwright-best-practices/frameworks/angular.md +0 -530
  42. package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +0 -469
  43. package/.agents/skills/playwright-best-practices/frameworks/react.md +0 -531
  44. package/.agents/skills/playwright-best-practices/frameworks/vue.md +0 -574
  45. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +0 -468
  46. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +0 -283
  47. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +0 -546
  48. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +0 -397
  49. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +0 -521
  50. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +0 -371
  51. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +0 -453
  52. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +0 -424
  53. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +0 -497
  54. package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +0 -359
  55. package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +0 -719
  56. package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +0 -506
  57. package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +0 -493
  58. package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +0 -500
  59. package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +0 -576
  60. package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +0 -509
  61. package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +0 -377
  62. package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +0 -562
  63. package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +0 -561
  64. package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +0 -331
  65. package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +0 -508
  66. package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +0 -476
  67. package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +0 -430
  68. package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +0 -634
  69. package/.agents/skills/security-qa/SKILL.md +0 -254
  70. package/public/ArcalityImagen.png +0 -0
  71. package/public/logo-arcadial-blanco.png +0 -0
  72. package/public/logo.png +0 -0
  73. package/tests/_helpers/ArcalityReporter.ts +0 -733
  74. package/tests/_helpers/agentic-runner.spec.ts +0 -783
  75. package/tests/_helpers/ai-agent-helper.ts +0 -1660
  76. package/tests/_helpers/discover-view.spec.ts +0 -238
  77. package/tests/_helpers/extract-view.spec.ts +0 -118
  78. package/tests/_helpers/qa-security-tools.ts +0 -265
  79. package/tests/_helpers/qa-tools.ts +0 -333
  80. package/tests/_helpers/smart-action.spec.ts +0 -1458
@@ -1,733 +0,0 @@
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
- let extension = '.png';
47
- if (att.contentType === 'application/json') extension = '.json';
48
- else if (att.contentType === 'text/plain') extension = '.txt';
49
- else if (att.contentType === 'text/yaml') extension = '.yaml';
50
-
51
- const fileName = att.path ? path.basename(att.path) : `${att.name || 'attachment'}-${Date.now()}${extension}`;
52
- const attachmentsDir = path.join(this.outputDir, 'attachments');
53
- const destPath = path.join(attachmentsDir, fileName);
54
-
55
- if (!fs.existsSync(attachmentsDir)) {
56
- fs.mkdirSync(attachmentsDir, { recursive: true });
57
- }
58
-
59
- try {
60
- if (att.path && fs.existsSync(att.path)) {
61
- // Si ya existe el archivo físico, lo copiamos
62
- fs.copyFileSync(att.path, destPath);
63
- } else if (att.body) {
64
- // Si viene como Buffer (evidencia del agente), lo guardamos
65
- fs.writeFileSync(destPath, att.body);
66
- }
67
- return { ...att, path: `attachments/${fileName}` };
68
- } catch (e) {
69
- console.error(`Failed to process attachment ${att.name}:`, e);
70
- return att;
71
- }
72
- });
73
-
74
- if (result.status === 'failed' || result.error) {
75
- console.log(`
76
- ❌ [TEST ENGINE FAILURE] ${test.title} failed:`);
77
- console.log(`${result.error?.message || result.error?.stack || 'Unknown error'}
78
- `);
79
- if (result.error?.stack) {
80
- console.log(result.error.stack);
81
- }
82
- }
83
-
84
- this.results.push({ test, result, stepsHtml, attachments: processedAttachments });
85
- }
86
-
87
- async onEnd(result: FullResult) {
88
- this.totalDuration = result.duration;
89
- const htmlContent = this.generateHtml();
90
- const reportPath = path.join(this.outputDir, 'index.html');
91
- fs.writeFileSync(reportPath, htmlContent);
92
- console.log(`✨ Arcality Mission Report generated at: ${reportPath}`);
93
- }
94
-
95
- private renderStep(step: TestStep, depth: number): string {
96
- // Humanize: Hide internal Playwright hooks to only show Agent decisions
97
- if (step.category === 'hook' || step.category === 'fixture') return '';
98
-
99
- const padding = depth * 16;
100
- const statusClass = step.error ? 'step-error' : 'step-passed';
101
- const icon = step.error ? '✕' : '✓';
102
- const duration = step.duration >= 0 ? `<span class="step-duration">${step.duration}ms</span>` : '';
103
-
104
- let title = this.escapeHtml(step.title);
105
- // Highlight common playwright actions
106
- title = title.replace(/^(click|fill|navigate|wait|expect|goto|press)/i, '<span class="step-action">$1</span>');
107
-
108
- let children = '';
109
- if (step.steps && step.steps.length > 0) {
110
- children = `<div class="step-children">${step.steps.map(s => this.renderStep(s, depth + 1)).join('')}</div>`;
111
- }
112
-
113
- const isExpandable = step.steps && step.steps.length > 0;
114
- const expandableAttr = isExpandable ? 'data-expandable="true"' : '';
115
-
116
- return `
117
- <div class="step-wrapper ${statusClass}" style="margin-left: ${padding}px" ${expandableAttr}>
118
- <div class="step-header">
119
- <span class="step-icon">${icon}</span>
120
- <span class="step-title">${title}</span>
121
- ${duration}
122
- </div>
123
- ${children}
124
- </div>
125
- `;
126
- }
127
-
128
- private getEngineVersion(): string {
129
- try {
130
- const packageJsonPath = path.join(process.cwd(), 'package.json');
131
- if (fs.existsSync(packageJsonPath)) {
132
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
133
- return pkg.version || '2.2.0';
134
- }
135
- } catch (e) {
136
- console.error('Error reading package.json version:', e);
137
- }
138
- return '2.2.0';
139
- }
140
-
141
- private generateHtml(): string {
142
- const stats = {
143
- total: this.results.length,
144
- passed: this.results.filter(r => r.result.status === 'passed').length,
145
- failed: this.results.filter(r => r.result.status === 'failed' || r.result.status === 'timedOut').length,
146
- skipped: this.results.filter(r => r.result.status === 'skipped').length,
147
- flaky: this.results.filter(r => r.result.status === 'passed' && r.result.retry > 0).length
148
- };
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: "✨";
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 / 1000).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
-
486
- return `
487
- <div class="test-entry ${statusClass}">
488
- <div class="test-header" onclick="toggleDetails('details-${i}')">
489
- <div class="test-info">
490
- <span class="status-dot"></span>
491
- <div>
492
- <div class="test-name">${this.escapeHtml(r.test.title)}</div>
493
- <div class="test-meta">
494
- <span class="tag tag-project">${projectName}</span>
495
- ${r.result.retry > 0 ? `<span class="tag tag-retry">Retry #${r.result.retry}</span>` : ''}
496
- <span>${(r.result.duration / 1000).toFixed(2)}s</span>
497
- </div>
498
- </div>
499
- </div>
500
- <div style="display:flex; gap: 10px; align-items: center;">
501
- <span style="opacity:0.5; font-size: 1.2rem">▼</span>
502
- </div>
503
- </div>
504
-
505
- <div class="test-details" id="details-${i}" style="display: none;">
506
- ${r.result.status === 'passed' ? (() => {
507
- const successSummary = r.attachments.find(a => a.name === 'success_summary');
508
- const text = this.getAttachmentText(successSummary);
509
- return text ? `<div class="success-block">${this.escapeHtml(text)}</div>` : '';
510
- })() : ''}
511
-
512
- ${r.result.error ? `
513
- <div class="steps-section">
514
- <span class="section-title">Analysis of the Obstacle</span>
515
- <div class="error-block">${this.escapeHtml(r.result.error.message || 'Unknown Failure')}</div>
516
- </div>
517
- ` : ''}
518
-
519
- ${this._renderSecuritySection(r.attachments, i)}
520
-
521
- <div class="steps-section">
522
- <span class="section-title">🧠 Agent Cognitive Process</span>
523
- <div class="steps-container">
524
- ${r.stepsHtml || '<p style="opacity:0.5">No steps recorded.</p>'}
525
- </div>
526
- </div>
527
-
528
- ${r.attachments.length > 0 ? `
529
- <div class="steps-section">
530
- <span class="section-title">Visual Evidence</span>
531
- <div class="attachments-grid">
532
- ${r.attachments.map(att => {
533
- if (att.contentType.startsWith('image/')) {
534
- return `
535
- <div class="attachment-item">
536
- <img src="${att.path}" alt="${att.name}" onclick="window.open('${att.path}')">
537
- <a href="${att.path}" target="_blank" class="att-label">${att.name}</a>
538
- </div>
539
- `;
540
- }
541
- if (att.contentType.startsWith('video/')) {
542
- return `
543
- <div class="attachment-item">
544
- <video controls src="${att.path}"></video>
545
- <a href="${att.path}" target="_blank" class="att-label">${att.name}</a>
546
- </div>
547
- `;
548
- }
549
- return '';
550
- }).join('')}
551
- </div>
552
- </div>
553
- ` : ''}
554
- </div>
555
- </div>
556
- `;
557
- }).join('')}
558
- </div>
559
-
560
- <footer>
561
- ARCALITY ENGINE v${this.getEngineVersion()} • SYSTEM TIME [${new Date().toISOString()}] • GENERATED FOR ARCADIAL
562
- </footer>
563
- </div>
564
-
565
- <script>
566
- function toggleDetails(id) {
567
- const el = document.getElementById(id);
568
- if (el) {
569
- const isSecurityDetails = id.startsWith('security-details');
570
- if (isSecurityDetails) {
571
- // Independent toggle for security section
572
- el.style.display = (el.style.display === 'none') ? 'block' : 'none';
573
- } else {
574
- // Main test details toggle
575
- el.style.display = (el.style.display === 'none' || el.style.display === '') ? 'block' : 'none';
576
- }
577
- }
578
- }
579
- </script>
580
- </body>
581
- </html>
582
- `;
583
- }
584
-
585
- private getAttachmentText(att: { body?: Buffer, path?: string } | undefined): string | null {
586
- if (!att) return null;
587
- if (att.body) return att.body.toString();
588
- if (att.path) {
589
- const actualPath = path.isAbsolute(att.path) ? att.path : path.join(this.outputDir, att.path);
590
- if (fs.existsSync(actualPath)) {
591
- return fs.readFileSync(actualPath, 'utf8');
592
- }
593
- }
594
- return null;
595
- }
596
-
597
- private _renderSecuritySection(attachments: any[], index: number): string {
598
- let vulnerabilities: any[] = [];
599
-
600
- const reportAtt = attachments.find(a => a.name === 'security_report');
601
- if (reportAtt) {
602
- const text = this.getAttachmentText(reportAtt);
603
- if (text) {
604
- try {
605
- const parsed = JSON.parse(text);
606
- if (Array.isArray(parsed)) vulnerabilities.push(...parsed);
607
- } catch (e) { }
608
- }
609
- }
610
-
611
- const findingAtts = attachments.filter(a => a.name === 'security_finding');
612
- for (const findingAtt of findingAtts) {
613
- const text = this.getAttachmentText(findingAtt);
614
- if (text) {
615
- try {
616
- const parsed = JSON.parse(text);
617
- vulnerabilities.push(parsed);
618
- } catch (e) { }
619
- }
620
- }
621
-
622
- if (vulnerabilities.length === 0) return '';
623
-
624
- const severityOrder = { 'Critical': 0, 'High': 1, 'Medium': 2, 'Low': 3, 'Info': 4 };
625
- type Severity = keyof typeof severityOrder;
626
-
627
- const severityCounts: Record<Severity, number> = { 'Critical': 0, 'High': 0, 'Medium': 0, 'Low': 0, 'Info': 0 };
628
- let worstSeverity: Severity = 'Info';
629
-
630
- vulnerabilities.forEach(v => {
631
- const severity = v.severity as Severity;
632
- if (severityCounts[severity] !== undefined) {
633
- severityCounts[severity]++;
634
- }
635
- if (severityOrder[severity] < severityOrder[worstSeverity]) {
636
- worstSeverity = severity;
637
- }
638
- });
639
-
640
- const riskColor = worstSeverity === 'Critical' ? 'var(--error)' :
641
- worstSeverity === 'High' ? '#fb923c' :
642
- worstSeverity === 'Medium' ? 'var(--warning)' :
643
- '#38bdf8';
644
-
645
- return `
646
- <div class="security-section">
647
- <div class="security-header" onclick="toggleDetails('security-details-${index}')">
648
- <h3>🛡️ Security Assessment</h3>
649
- <span class="security-risk-badge" style="background-color: ${riskColor};">${worstSeverity} Risk</span>
650
- </div>
651
- <div class="security-details" id="security-details-${index}" style="display: block;">
652
- <div class="security-summary">
653
- <div class="summary-item">
654
- <div class="count count-Critical">${severityCounts.Critical}</div>
655
- <div class="label">Critical</div>
656
- </div>
657
- <div class="summary-item">
658
- <div class="count count-High">${severityCounts.High}</div>
659
- <div class="label">High</div>
660
- </div>
661
- <div class="summary-item">
662
- <div class="count count-Medium">${severityCounts.Medium}</div>
663
- <div class="label">Medium</div>
664
- </div>
665
- <div class="summary-item">
666
- <div class="count count-Low">${severityCounts.Low}</div>
667
- <div class="label">Low</div>
668
- </div>
669
- </div>
670
- <div class="vuln-list">
671
- ${vulnerabilities.map(v => {
672
- const sev = v.severity as Severity;
673
- const cardColor = sev === 'Critical' ? 'var(--error)' :
674
- sev === 'High' ? '#fb923c' :
675
- sev === 'Medium' ? 'var(--warning)' :
676
- '#38bdf8';
677
- return `
678
- <div class="vuln-card" style="border-left-color: ${cardColor};">
679
- <div class="vuln-card-title">
680
- <span class="tag" style="background-color: ${cardColor}20; color: ${cardColor};">${v.severity}</span>
681
- <span>${this.escapeHtml(v.category)} ${v.subcategory ? `<span style="opacity:0.7">— ${this.escapeHtml(v.subcategory)}</span>` : ''}</span>
682
- </div>
683
- <div style="font-size: 0.8rem; opacity: 0.8; margin-top: 5px; margin-bottom: 10px; display: flex; flex-wrap: wrap; gap: 12px;">
684
- ${v.type ? `<span><strong style="color:var(--accent);">Type:</strong> ${this.escapeHtml(v.type)}</span>` : ''}
685
- ${v.confidence ? `<span><strong>Confidence:</strong> ${this.escapeHtml(v.confidence)}</span>` : ''}
686
- ${v.exploitability ? `<span><strong>Exploitability:</strong> ${this.escapeHtml(v.exploitability)}</span>` : ''}
687
- </div>
688
- <p class="vuln-card-desc">${this.escapeHtml(v.description)}</p>
689
- ${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>` : ''}
690
-
691
- <div class="vuln-card-evidence">
692
- <strong>Evidence:</strong>
693
- <pre>${this.escapeHtml(JSON.stringify(v.evidence, null, 2))}</pre>
694
- </div>
695
-
696
- ${v.reproductionSteps && v.reproductionSteps.length > 0 ? `
697
- <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;">
698
- <strong style="font-size: 0.85rem; color: var(--text-muted);">Reproduction Steps:</strong>
699
- <ol style="margin: 5px 0 0 20px; font-size: 0.85rem; color: var(--text-main);">
700
- ${v.reproductionSteps.map((s: string) => `<li>${this.escapeHtml(s)}</li>`).join('')}
701
- </ol>
702
- </div>` : ''}
703
-
704
- <div class="vuln-card-remediation">
705
- <strong>→ Remediation:</strong> ${this.escapeHtml(v.remediation)}
706
- </div>
707
-
708
- ${v.classification ? `
709
- <div style="font-size: 0.75rem; opacity: 0.5; margin-top: 12px; display: flex; gap: 15px; font-family: 'JetBrains Mono', monospace;">
710
- ${v.classification.owaspTop10 ? `<span>[OWASP: ${this.escapeHtml(v.classification.owaspTop10)}]</span>` : ''}
711
- ${v.classification.cwe ? `<span>[CWE: ${this.escapeHtml(v.classification.cwe)}]</span>` : ''}
712
- </div>` : ''}
713
- </div>
714
- `;
715
- }).join('')}
716
- </div>
717
- </div>
718
- </div>
719
- `;
720
- }
721
-
722
- private escapeHtml(unsafe: string | undefined): string {
723
- if (!unsafe) return '';
724
- return unsafe
725
- .replace(/&/g, "&amp;")
726
- .replace(/</g, "&lt;")
727
- .replace(/>/g, "&gt;")
728
- .replace(/"/g, "&quot;")
729
- .replace(/'/g, "&#039;");
730
- }
731
- }
732
-
733
- export default ArcalityReporter;