@arcadialdev/arcality 2.2.9 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadialdev/arcality",
3
- "version": "2.2.9",
3
+ "version": "2.3.2",
4
4
  "description": "AI-powered QA testing tool — Autonomous web testing agent by Arcadial",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -3,7 +3,7 @@ import { defineConfig } from '@playwright/test';
3
3
  export default defineConfig({
4
4
  testDir: './tests',
5
5
  reporter: [
6
- ['./tests/_helpers/ArcalityReporter.ts', { outputDir: process.env.REPORTS_DIR || 'tests-report' }]
6
+ ['./tests/_helpers/ArcalityReporter.js', { outputDir: process.env.REPORTS_DIR || 'tests-report' }]
7
7
  ],
8
8
  use: {
9
9
  video: 'on',
@@ -732,7 +732,9 @@ async function main() {
732
732
  const configFile = path.join(PROJECT_ROOT, 'playwright.config.ts');
733
733
 
734
734
  try {
735
- await run("node", [playwrightCli, "test", path.join(PROJECT_ROOT, "tests/_helpers/agentic-runner.spec.ts"), "--headed", `--config=${configFile}`], {
735
+ // IMPORTANT: Node 22.12+ breaks Playwright's TS transpiler (pirates.js) with sync require()
736
+ // We MUST pass --no-experimental-require-module to let Playwright compile TS safely.
737
+ await run("node", ["--no-experimental-require-module", playwrightCli, "test", path.join(PROJECT_ROOT, "tests/_helpers/agentic-runner.spec.ts"), "--headed", `--config=${configFile}`], {
736
738
  env: mergedEnv,
737
739
  onStatus: (msg) => s.message(msg)
738
740
  });
@@ -24,22 +24,15 @@ async function rebrandReport() {
24
24
  try {
25
25
  const logoBuffer = fs.readFileSync(logoPngPath);
26
26
  logoBase64 = `data:image/png;base64,${logoBuffer.toString('base64')}`;
27
- console.log("✅ Logo Arcality cargado desde public/logo.png");
28
- } catch (e) {
29
- console.error("⚠️ Error leyendo logo:", e.message);
30
- }
31
- } else {
32
- console.warn("⚠️ No se encontró public/logo.png");
27
+ } catch (e) { /* silent */ }
33
28
  }
34
29
 
35
30
  for (const dir of targetDirs) {
36
31
  if (fs.existsSync(dir)) {
37
- console.log(`Processing report directory: ${dir}`);
38
32
  processDirectory(dir, logoBase64);
39
33
  }
40
34
  }
41
-
42
- console.log("🏁 Branding process finished.");
35
+ // Silent — CLI spinner communicates progress
43
36
  }
44
37
 
45
38
  function processDirectory(reportDir, logoBase64) {
@@ -168,7 +161,7 @@ function processDirectory(reportDir, logoBase64) {
168
161
  }
169
162
 
170
163
  fs.writeFileSync(indexPath, html, 'utf8');
171
- console.log(`✨ [REBRAND] Report transformado en: ${indexPath}`);
164
+ // Silent success CLI handles notification
172
165
  } catch (err) {
173
166
  console.error(`❌ Error al procesar ${indexPath}: ${err.message}`);
174
167
  }
@@ -237,7 +230,7 @@ function processTraceViewer(reportDir, logoBase64) {
237
230
  }
238
231
 
239
232
  fs.writeFileSync(tracePath, traceHtml, 'utf8');
240
- console.log(`✨ [REBRAND] Trace Viewer transformado en: ${tracePath}`);
233
+ // Silent success
241
234
  } catch (e) {
242
235
  console.error(`⚠️ Error en Trace Viewer ${tracePath}: ${e.message}`);
243
236
  }
@@ -0,0 +1,279 @@
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
+
10
+ class ArcalityReporter {
11
+ constructor(options = {}) {
12
+ this.outputDir = options.outputDir || 'arcality-report';
13
+ this.results = [];
14
+ this.totalDuration = 0;
15
+ this.startTime = new Date();
16
+ }
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();
30
+ }
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
+ this.results.push({ test, result, stepsHtml, attachments: processedAttachments });
57
+ }
58
+
59
+ async onEnd(result) {
60
+ this.totalDuration = result.duration;
61
+ const htmlContent = this.generateHtml();
62
+ const reportPath = path.join(this.outputDir, 'index.html');
63
+ fs.writeFileSync(reportPath, htmlContent);
64
+ // Intentionally silent — CLI handles the report open notification
65
+ }
66
+
67
+ renderStep(step, depth) {
68
+ // Humanize: hide internal Playwright hooks
69
+ if (step.category === 'hook' || step.category === 'fixture') return '';
70
+
71
+ const padding = depth * 16;
72
+ const statusClass = step.error ? 'step-error' : 'step-passed';
73
+ const icon = step.error ? '✕' : '✓';
74
+ const duration = step.duration >= 0 ? `<span class="step-duration">${step.duration}ms</span>` : '';
75
+
76
+ let title = this.escapeHtml(step.title);
77
+ title = title.replace(/^(click|fill|navigate|wait|expect|goto|press)/i, '<span class="step-action">$1</span>');
78
+
79
+ let children = '';
80
+ if (step.steps && step.steps.length > 0) {
81
+ children = `<div class="step-children">${step.steps.map(s => this.renderStep(s, depth + 1)).join('')}</div>`;
82
+ }
83
+
84
+ return `
85
+ <div class="step-wrapper ${statusClass}" style="margin-left: ${padding}px">
86
+ <div class="step-header">
87
+ <span class="step-icon">${icon}</span>
88
+ <span class="step-title">${title}</span>
89
+ ${duration}
90
+ </div>
91
+ ${children}
92
+ </div>
93
+ `;
94
+ }
95
+
96
+ getEngineVersion() {
97
+ try {
98
+ // Check Arcality's own package.json first, then cwd fallback
99
+ const toolPkg = path.join(__dirname, '..', '..', 'package.json');
100
+ const cwdPkg = path.join(process.cwd(), 'package.json');
101
+ const pkgPath = fs.existsSync(toolPkg) ? toolPkg : cwdPkg;
102
+ if (fs.existsSync(pkgPath)) {
103
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
104
+ return pkg.version || '2.2.9';
105
+ }
106
+ } catch (e) { /* ignore */ }
107
+ return '2.2.9';
108
+ }
109
+
110
+ generateHtml() {
111
+ const stats = {
112
+ total: this.results.length,
113
+ passed: this.results.filter(r => r.result.status === 'passed').length,
114
+ failed: this.results.filter(r => r.result.status === 'failed' || r.result.status === 'timedOut').length,
115
+ flaky: this.results.filter(r => r.result.status === 'passed' && r.result.retry > 0).length,
116
+ };
117
+
118
+ const testCards = this.results.map((r, i) => {
119
+ const statusClass = `status-${r.result.status}`;
120
+ const projectName = (r.test.parent && r.test.parent.project && r.test.parent.project()) ? r.test.parent.project().name : 'default';
121
+
122
+ const successSummaryAtt = r.attachments.find(a => a.name === 'success_summary');
123
+ const successBlock = (r.result.status === 'passed' && successSummaryAtt && successSummaryAtt.body)
124
+ ? `<div class="success-block">${this.escapeHtml(successSummaryAtt.body.toString())}</div>`
125
+ : '';
126
+
127
+ const errorBlock = r.result.error
128
+ ? `<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>`
129
+ : '';
130
+
131
+ const mediaAttachments = r.attachments.filter(a => a.contentType && (a.contentType.startsWith('image/') || a.contentType.startsWith('video/')));
132
+ const mediaGrid = mediaAttachments.length > 0 ? `
133
+ <div class="steps-section">
134
+ <span class="section-title">Visual Evidence</span>
135
+ <div class="attachments-grid">
136
+ ${mediaAttachments.map(att => {
137
+ if (att.contentType.startsWith('image/')) {
138
+ 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>`;
139
+ }
140
+ if (att.contentType.startsWith('video/')) {
141
+ return `<div class="attachment-item"><video controls src="${att.path}"></video><a href="${att.path}" target="_blank" class="att-label">${att.name}</a></div>`;
142
+ }
143
+ return '';
144
+ }).join('')}
145
+ </div>
146
+ </div>` : '';
147
+
148
+ return `
149
+ <div class="test-entry ${statusClass}">
150
+ <div class="test-header" onclick="toggleDetails(${i})">
151
+ <div class="test-info">
152
+ <span class="status-dot"></span>
153
+ <div>
154
+ <div class="test-name">${this.escapeHtml(r.test.title)}</div>
155
+ <div class="test-meta">
156
+ <span class="tag tag-project">${projectName}</span>
157
+ ${r.result.retry > 0 ? `<span class="tag tag-retry">Retry #${r.result.retry}</span>` : ''}
158
+ <span>${(r.result.duration / 1000).toFixed(2)}s</span>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ <span style="opacity:0.5; font-size: 1.2rem">▼</span>
163
+ </div>
164
+ <div class="test-details" id="details-${i}">
165
+ ${successBlock}
166
+ ${errorBlock}
167
+ <div class="steps-section">
168
+ <span class="section-title">🧠 Agent Cognitive Process</span>
169
+ <div class="steps-container">${r.stepsHtml || '<p style="opacity:0.5">No steps recorded.</p>'}</div>
170
+ </div>
171
+ ${mediaGrid}
172
+ </div>
173
+ </div>`;
174
+ }).join('');
175
+
176
+ return `<!DOCTYPE html>
177
+ <html lang="en">
178
+ <head>
179
+ <meta charset="UTF-8">
180
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
181
+ <title>Arcality Mission Report</title>
182
+ <style>
183
+ @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');
184
+ :root {
185
+ --bg-deep: #020617; --bg-card: #0f172a; --text-main: #f8fafc; --text-muted: #94a3b8;
186
+ --primary: #6366f1; --success: #10b981; --error: #f43f5e; --warning: #f59e0b;
187
+ --border: #1e293b; --accent: #c084fc;
188
+ }
189
+ * { box-sizing: border-box; }
190
+ body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: var(--bg-deep); color: var(--text-main); margin: 0; line-height: 1.6; }
191
+ .container { max-width: 1400px; margin: 0 auto; padding: 2rem; }
192
+ .main-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 3rem; padding-bottom: 2rem; border-bottom: 1px solid var(--border); }
193
+ .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; }
194
+ .mission-id { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 3px; }
195
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1.5rem; margin-bottom: 3rem; }
196
+ .stat-card { background: var(--bg-card); padding: 1.5rem; border-radius: 16px; border: 1px solid var(--border); text-align: center; }
197
+ .stat-value { font-size: 2rem; font-weight: 800; display: block; }
198
+ .stat-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; font-weight: 700; }
199
+ .val-total { color: var(--text-main); } .val-passed { color: var(--success); } .val-failed { color: var(--error); } .val-flaky { color: var(--warning); }
200
+ .test-entry { background: var(--bg-card); border: 1px solid var(--border); border-radius: 20px; margin-bottom: 2rem; overflow: hidden; }
201
+ .test-entry:hover { border-color: var(--primary); }
202
+ .test-header { padding: 1.5rem 2rem; display: flex; justify-content: space-between; align-items: center; cursor: pointer; background: rgba(255,255,255,0.02); }
203
+ .test-info { display: flex; align-items: center; gap: 1rem; }
204
+ .status-dot { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
205
+ .status-passed .status-dot { background: var(--success); box-shadow: 0 0 10px var(--success); }
206
+ .status-failed .status-dot { background: var(--error); box-shadow: 0 0 10px var(--error); }
207
+ .test-name { font-weight: 700; font-size: 1.1rem; }
208
+ .test-meta { font-size: 0.8rem; color: var(--text-muted); font-family: 'JetBrains Mono', monospace; }
209
+ .test-details { padding: 0 2rem 2rem; }
210
+ .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; }
211
+ .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; }
212
+ .success-block::before { content: "✨"; font-size: 1.4rem; }
213
+ .steps-section { margin-top: 2rem; }
214
+ .section-title { font-size: 0.9rem; text-transform: uppercase; letter-spacing: 2px; color: var(--accent); margin-bottom: 1rem; display: block; }
215
+ .step-wrapper { border-left: 1px solid var(--border); padding: 8px 16px; position: relative; }
216
+ .step-header { display: flex; align-items: flex-start; gap: 10px; font-size: 0.9rem; }
217
+ .step-icon { width: 16px; font-size: 0.8rem; opacity: 0.7; }
218
+ .step-action { color: var(--primary); font-weight: 700; }
219
+ .step-duration { font-size: 0.7rem; color: var(--text-muted); margin-left: auto; }
220
+ .step-error { border-left-color: var(--error); color: #fb7185; }
221
+ .attachments-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 2rem; }
222
+ .attachment-item { background: var(--bg-deep); border-radius: 12px; overflow: hidden; border: 1px solid var(--border); }
223
+ .attachment-item img, .attachment-item video { width: 100%; height: 180px; object-fit: cover; display: block; cursor: zoom-in; }
224
+ .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); }
225
+ .tag { padding: 4px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 800; text-transform: uppercase; }
226
+ .tag-project { background: rgba(192,132,252,0.2); color: var(--accent); }
227
+ .tag-retry { background: rgba(245,158,11,0.2); color: var(--warning); }
228
+ footer { margin-top: 5rem; text-align: center; padding: 2rem; font-size: 0.8rem; color: var(--text-muted); border-top: 1px solid var(--border); }
229
+ </style>
230
+ </head>
231
+ <body>
232
+ <div class="container">
233
+ <div class="main-header">
234
+ <div class="brand-section">
235
+ <span class="mission-id">Mission Alpha System</span>
236
+ <h1>Arcality Mission Report</h1>
237
+ <p style="color: var(--text-muted)">Testing Intelligence &amp; Diagnostics Console</p>
238
+ </div>
239
+ <div style="text-align: right">
240
+ <div style="color: var(--accent); font-weight: 700;">${this.startTime.toLocaleDateString()}</div>
241
+ <div style="font-family: 'JetBrains Mono'; font-size: 0.8rem;">ENGINE VERSION ${this.getEngineVersion()}</div>
242
+ </div>
243
+ </div>
244
+
245
+ <div class="stats-grid">
246
+ <div class="stat-card"><span class="stat-value val-total">${stats.total}</span><span class="stat-label">Total Missions</span></div>
247
+ <div class="stat-card"><span class="stat-value val-passed">${stats.passed}</span><span class="stat-label">Successful</span></div>
248
+ <div class="stat-card"><span class="stat-value val-failed">${stats.failed}</span><span class="stat-label">Fatal Errors</span></div>
249
+ <div class="stat-card"><span class="stat-value val-flaky">${stats.flaky}</span><span class="stat-label">Flaky Tests</span></div>
250
+ <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>
251
+ </div>
252
+
253
+ <div class="test-list">${testCards}</div>
254
+
255
+ <footer>ARCALITY ENGINE v${this.getEngineVersion()} • SYSTEM TIME [${new Date().toISOString()}] • GENERATED FOR ARCADIAL</footer>
256
+ </div>
257
+ <script>
258
+ function toggleDetails(id) {
259
+ const el = document.getElementById('details-' + id);
260
+ el.style.display = (el.style.display === 'none' || el.style.display === '') ? 'block' : 'none';
261
+ }
262
+ document.querySelectorAll('.test-details').forEach(d => d.style.display = 'none');
263
+ </script>
264
+ </body>
265
+ </html>`;
266
+ }
267
+
268
+ escapeHtml(unsafe) {
269
+ if (!unsafe) return '';
270
+ return String(unsafe)
271
+ .replace(/&/g, '&amp;')
272
+ .replace(/</g, '&lt;')
273
+ .replace(/>/g, '&gt;')
274
+ .replace(/"/g, '&quot;')
275
+ .replace(/'/g, '&#039;');
276
+ }
277
+ }
278
+
279
+ module.exports = ArcalityReporter;