skopix 2.0.1 → 2.0.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.
@@ -274,6 +274,25 @@ export async function agentCommand(options) {
274
274
  duration: 0, type: 'replay', provider: 'replay',
275
275
  }, { spaces: 2 }).catch(() => {});
276
276
 
277
+ // Write report.html — two-column layout: video on left, step list on right
278
+ try {
279
+ const duration = '0.0';
280
+ const stepRows = allSteps.slice(0, stepNum).map((s, i) => {
281
+ const desc = (s.description || (s.action + ' ' + (s.stableSelector || s.selector || ''))).replace(/</g, '&lt;').replace(/>/g, '&gt;');
282
+ const screenshotFile = 'step-' + String(i + 1).padStart(3, '0') + '.png';
283
+ const isAssert = s.action === 'assert';
284
+ const isFailed = !passed && i === stepNum - 1;
285
+ if (isAssert) {
286
+ return '<div class="step assert-step" onclick="showStep(' + i + ')"><div class="step-num">' + String(i + 1).padStart(2, '0') + '</div><div class="step-body"><div class="step-action assert-action">✓ ' + (s.assertType || 'assert') + '</div><div class="step-desc">' + desc + '</div></div></div>';
287
+ }
288
+ return '<div class="step ' + (isFailed ? 'failed-step' : '') + '" onclick="showStep(' + i + ')"><div class="step-num">' + String(i + 1).padStart(2, '0') + '</div><div class="step-body"><div class="step-action">' + s.action.toUpperCase() + '</div><div class="step-desc">' + desc + '</div></div></div>';
289
+ }).join('\n');
290
+
291
+ const html = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>Skopix Report — ' + test.name + '</title><style>body{margin:0;font-family:-apple-system,sans-serif;background:#0d0d1a;color:#e8eaf0}.container{display:grid;grid-template-columns:1fr 1fr;height:100vh}.left{padding:24px;background:#080810;border-right:1px solid rgba(255,255,255,0.08);display:flex;flex-direction:column}.right{padding:24px;overflow-y:auto}h1{margin:0 0 8px 0;font-size:24px}.meta{color:#5a6180;font-size:13px;margin-bottom:24px}.status{display:inline-block;padding:4px 12px;border-radius:4px;font-size:12px;font-weight:600;margin-left:8px}.status.passed{background:rgba(34,197,94,0.15);color:#22c55e}.status.failed{background:rgba(239,68,68,0.15);color:#ef4444}video,img{width:100%;border-radius:8px;background:#000}.step{padding:14px 16px;border-bottom:1px solid rgba(255,255,255,0.06);display:flex;gap:14px;cursor:pointer;transition:background 0.15s}.step:hover{background:rgba(0,212,255,0.06)}.step.active{background:rgba(0,212,255,0.1)}.step-num{font-family:monospace;color:#5a6180;font-size:12px;min-width:24px}.step-action{font-family:monospace;font-size:11px;color:#00d4ff;letter-spacing:0.05em;margin-bottom:4px}.assert-action{color:#22c55e}.step-desc{font-size:13px}.failed-step .step-action{color:#ef4444}</style></head><body><div class="container"><div class="left"><h1>' + test.name + '<span class="status ' + (passed ? 'passed' : 'failed') + '">' + (passed ? 'PASSED' : 'FAILED') + '</span></h1><div class="meta">' + allSteps.length + ' steps · ' + duration + 's · agent: ' + os.hostname() + '</div><div id="media"><video src="replay.webm" controls autoplay muted loop></video></div></div><div class="right">' + stepRows + '</div></div><script>function showStep(i){document.querySelectorAll(".step").forEach(s=>s.classList.remove("active"));document.querySelectorAll(".step")[i].classList.add("active");const img=document.createElement("img");img.src="step-"+String(i+1).padStart(3,"0")+".png";document.getElementById("media").innerHTML="";document.getElementById("media").appendChild(img);}</script></body></html>';
292
+
293
+ await fs.writeFile(path.join(sessionDir, 'report.html'), html);
294
+ } catch (e) { /* ignore report html errors */ }
295
+
277
296
  send({ type: 'stdout', text: '' });
278
297
  send({ type: 'stdout', text: '━'.repeat(60) });
279
298
  send({ type: 'stdout', text: ' Status: ' + (passed ? 'PASSED ✓' : 'FAILED ✗') });
package/cli/index.js CHANGED
@@ -32,7 +32,7 @@ program
32
32
  .requiredOption('-u, --url <url>', 'Target URL to test')
33
33
  .requiredOption('-g, --goal <goal>', 'Testing goal (e.g. "complete the checkout flow")')
34
34
  .option('-c, --credentials <file>', 'Path to credentials YAML file')
35
- .option('-o, --output <dir>', 'Output directory for reports', './skopix-reports')
35
+ .option('-o, --output <dir>', 'Output directory for reports')
36
36
  .option('-m, --max-steps <number>', 'Maximum steps the agent will take', '20')
37
37
  .option('--headless', 'Run browser in headless mode', false)
38
38
  .option('--no-video', 'Disable video recording')
@@ -53,7 +53,7 @@ program
53
53
  program
54
54
  .command('report')
55
55
  .description('Open the latest report in your browser')
56
- .option('-d, --dir <dir>', 'Reports directory', './skopix-reports')
56
+ .option('-d, --dir <dir>', 'Reports directory')
57
57
  .action(reportCommand);
58
58
 
59
59
  program
@@ -68,7 +68,7 @@ program
68
68
  .command('dashboard')
69
69
  .description('Launch the web dashboard')
70
70
  .option('-p, --port <port>', 'Port to run the server on', '9000')
71
- .option('-d, --dir <dir>', 'Reports directory', './skopix-reports')
71
+ .option('-d, --dir <dir>', 'Reports directory')
72
72
  .option('-h, --host <host>', 'Host to bind to (default 127.0.0.1; use 0.0.0.0 for team mode)')
73
73
  .option('--team', 'Enable multi-user team mode (requires SQLite)')
74
74
  .option('--no-open', 'Do not auto-open the browser')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skopix",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Browser-based QA tool — record tests by using your app, replay them deterministically, generate Playwright code automatically",
5
5
  "main": "cli/index.js",
6
6
  "bin": {