testronaut 1.12.1 โ†’ 1.12.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/README.md CHANGED
@@ -230,6 +230,10 @@ testronaut upload run_1788745106444
230
230
  testronaut upload run_1788745106444.json --no-upload-screenshots
231
231
  ```
232
232
 
233
+ The local HTML report stays focused on inspecting one run without a service
234
+ dependency. Upload a report to Mission Control for filterable per-mission and
235
+ per-turn token, estimated-cost, and duration charts, including cumulative views.
236
+
233
237
  Chain missions together:
234
238
  ```js
235
239
  await runMissions({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testronaut",
3
- "version": "1.12.1",
3
+ "version": "1.12.2",
4
4
  "main": "./runner/testronaut.js",
5
5
  "bin": {
6
6
  "testronaut": "bin/cli.js"
@@ -18,6 +18,23 @@ afterEach(() => {
18
18
  });
19
19
 
20
20
  describe('generateHtmlReport', () => {
21
+ it('points local reports to hosted analytics for the same run', () => {
22
+ const report = { runId: 'run_analytics_123', missions: [] };
23
+ const outPath = path.join(tmpDir, 'analytics-cta.html');
24
+ generateHtmlReport(report, outPath);
25
+ const html = fs.readFileSync(outPath, 'utf8');
26
+ expect(html).toContain('Need deeper analytics?');
27
+ expect(html).toContain('filtered and cumulative token, estimated-cost, and duration charts');
28
+ expect(html).toContain('testronaut upload run_analytics_123');
29
+ expect(html).toContain('aria-label="Copy upload command"');
30
+ expect(html).toContain("navigator.clipboard.writeText(command)");
31
+ expect(html).toContain('id="analytics-toggle" aria-expanded="true"');
32
+ expect(html).toContain("card.classList.toggle('collapsed', expanded)");
33
+ expect(html).toContain('href="https://mission.testronaut.app/"');
34
+ expect(html).toContain('href="https://docs.testronaut.app/docs/category/reports-and-mission-control"');
35
+ expect(html.indexOf('<aside class="analytics-cta">')).toBeLessThan(html.indexOf('<div class="summary">'));
36
+ });
37
+
21
38
  it('renders explicit suite, mission, and turn durations', () => {
22
39
  const report = {
23
40
  runId: 'timed_run',
@@ -308,6 +308,7 @@ export function generateHtmlReport(report, outputPath) {
308
308
  /* ===== Header ===== */
309
309
  .header{ max-width:960px; margin:0 auto 16px; padding:22px 24px; position:relative; overflow:hidden; }
310
310
  .header::after{content:"";position:absolute;right:0;top:0;width:92px;height:4px;background:linear-gradient(90deg,var(--orange),var(--amber));}
311
+ .header-layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,340px);gap:22px;align-items:start;}
311
312
  .eyebrow{margin-bottom:7px;color:var(--cyan);font-size:11px;font-weight:800;letter-spacing:.18em;text-transform:uppercase;}
312
313
  h1{ margin:0 0 12px; font-size:27px; font-weight:850; color:#fff; letter-spacing:-.02em; }
313
314
  .run-meta{ color:var(--text-muted); display:grid; gap:4px; }
@@ -341,7 +342,7 @@ export function generateHtmlReport(report, outputPath) {
341
342
  .filter-empty{max-width:928px;margin:12px auto;padding:28px 16px;text-align:center;border:1px dashed var(--hairline-strong);border-radius:16px;color:var(--text-muted);}
342
343
  .filter-empty strong{display:block;color:var(--text);font-size:18px;margin-bottom:6px;}
343
344
  .mission-group.nonmatching{opacity:.45;}
344
- @media(max-width:640px){.match-count{width:100%;margin-left:0}.tag-filter{padding:14px 12px}.meta{display:none}}
345
+ @media(max-width:760px){.header-layout{grid-template-columns:1fr}.match-count{width:100%;margin-left:0}.tag-filter{padding:14px 12px}.meta{display:none}}
345
346
 
346
347
  /* ===== Disclosure blocks ===== */
347
348
  details{ background: var(--panel); border:1px solid var(--hairline); border-radius:16px; margin:10px 0; overflow:hidden; }
@@ -420,19 +421,76 @@ export function generateHtmlReport(report, outputPath) {
420
421
  /* container width */
421
422
  .container{ max-width:960px; margin:0 auto; }
422
423
  .empty{ color: var(--text-muted); padding:12px; }
424
+ .analytics-cta{
425
+ padding:16px; background:rgba(2,6,23,.34); border:1px solid var(--hairline-strong);
426
+ border-radius:13px;
427
+ color:var(--text-muted); font-size:13px;
428
+ }
429
+ .analytics-toggle{
430
+ width:100%;padding:0;display:flex;align-items:center;justify-content:space-between;gap:12px;
431
+ color:var(--text);background:transparent;border:0;cursor:pointer;text-align:left;font:inherit;font-weight:800;
432
+ }
433
+ .analytics-toggle svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;transition:transform .18s ease;}
434
+ .analytics-cta.collapsed .analytics-toggle svg{transform:rotate(-90deg);}
435
+ .analytics-body{display:grid;gap:11px;margin-top:11px;}
436
+ .analytics-cta.collapsed .analytics-body{display:none;}
437
+ .analytics-cta code{
438
+ color:#bfdbfe; background:rgba(59,130,246,.12); border:1px solid rgba(96,165,250,.25);
439
+ border-radius:8px; padding:6px 9px; white-space:nowrap;
440
+ }
441
+ .upload-command{display:flex;align-items:stretch;min-width:0;}
442
+ .upload-command code{flex:1;min-width:0;overflow:auto;border-radius:8px 0 0 8px;}
443
+ .copy-command{
444
+ display:grid;place-items:center;width:36px;flex:none;cursor:pointer;color:#bfdbfe;
445
+ background:rgba(59,130,246,.18);border:1px solid rgba(96,165,250,.32);border-left:0;
446
+ border-radius:0 8px 8px 0;
447
+ }
448
+ .copy-command:hover,.copy-command.copied{color:#fff;background:rgba(59,130,246,.3);}
449
+ .copy-command svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;}
450
+ .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}
451
+ .analytics-links{display:flex;gap:8px;flex-wrap:wrap;}
452
+ .analytics-links a{
453
+ color:#bfdbfe;text-decoration:none;border:1px solid rgba(96,165,250,.32);
454
+ background:rgba(59,130,246,.12);border-radius:999px;padding:6px 10px;font-size:12px;font-weight:750;
455
+ }
456
+ .analytics-links a:hover{background:rgba(59,130,246,.22);}
423
457
  </style>
424
458
  </head>
425
459
  <body>
426
460
  <div class="header glass">
427
- <div class="eyebrow">Mission Control ยท Flight Report</div>
428
- <h1>๐Ÿง‘โ€๐Ÿš€ <span class="gradient-text">Testronaut Report</span></h1>
429
- <div class="run-meta">
430
- <div class="provider-meta"><span class="provider-logo" title="${esc(provider.name)}" aria-label="${esc(provider.name)}">${provider.logo}</span><strong>${esc((reportModels.length ? reportModels : [llm.model ?? 'โ€”']).join(' + '))}</strong>${reportJevUsage.evaluations ? ` <span aria-hidden="true">+</span> <span class="provider-logo" title="Jev" aria-label="Jev">${jevLogo}</span><strong>Jev</strong>` : ''}</div>
431
- <div><strong>Run ID:</strong> ${esc(runId ?? 'โ€”')}</div>
432
- <div><strong>Start:</strong> ${esc(startTime ?? 'โ€”')}</div>
433
- <div><strong>End:</strong> ${esc(endTime ?? 'โ€”')}</div>
434
- <div><strong>Duration:</strong> ${durationSec}s</div>
435
- <div><strong>CLI version:</strong> ${esc(cli.version ?? 'โ€”')}</div>
461
+ <div class="header-layout">
462
+ <div>
463
+ <div class="eyebrow">Mission Control ยท Flight Report</div>
464
+ <h1>๐Ÿง‘โ€๐Ÿš€ <span class="gradient-text">Testronaut Report</span></h1>
465
+ <div class="run-meta">
466
+ <div class="provider-meta"><span class="provider-logo" title="${esc(provider.name)}" aria-label="${esc(provider.name)}">${provider.logo}</span><strong>${esc((reportModels.length ? reportModels : [llm.model ?? 'โ€”']).join(' + '))}</strong>${reportJevUsage.evaluations ? ` <span aria-hidden="true">+</span> <span class="provider-logo" title="Jev" aria-label="Jev">${jevLogo}</span><strong>Jev</strong>` : ''}</div>
467
+ <div><strong>Run ID:</strong> ${esc(runId ?? 'โ€”')}</div>
468
+ <div><strong>Start:</strong> ${esc(startTime ?? 'โ€”')}</div>
469
+ <div><strong>End:</strong> ${esc(endTime ?? 'โ€”')}</div>
470
+ <div><strong>Duration:</strong> ${durationSec}s</div>
471
+ <div><strong>CLI version:</strong> ${esc(cli.version ?? 'โ€”')}</div>
472
+ </div>
473
+ </div>
474
+ <aside class="analytics-cta">
475
+ <button type="button" class="analytics-toggle" id="analytics-toggle" aria-expanded="true" aria-controls="analytics-body">
476
+ <span>Need deeper analytics?</span>
477
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
478
+ </button>
479
+ <div class="analytics-body" id="analytics-body">
480
+ <span>Upload this run for filtered and cumulative token, estimated-cost, and duration charts.</span>
481
+ <div class="upload-command">
482
+ <code id="analytics-upload-command">testronaut upload ${esc(runId ?? '')}</code>
483
+ <button type="button" class="copy-command" id="copy-upload-command" aria-label="Copy upload command" title="Copy upload command">
484
+ <svg viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg>
485
+ </button>
486
+ <span class="sr-only" id="copy-upload-status" aria-live="polite"></span>
487
+ </div>
488
+ <div class="analytics-links">
489
+ <a href="https://mission.testronaut.app/">Open Mission Control</a>
490
+ <a href="https://docs.testronaut.app/docs/category/reports-and-mission-control">Upload guide</a>
491
+ </div>
492
+ </div>
493
+ </aside>
436
494
  </div>
437
495
  </div>
438
496
 
@@ -475,6 +533,50 @@ export function generateHtmlReport(report, outputPath) {
475
533
  </div>
476
534
  <script>
477
535
  (function () {
536
+ var analyticsToggle = document.getElementById('analytics-toggle');
537
+ var analyticsBody = document.getElementById('analytics-body');
538
+ if (analyticsToggle && analyticsBody) {
539
+ analyticsToggle.addEventListener('click', function () {
540
+ var card = analyticsToggle.closest('.analytics-cta');
541
+ var expanded = analyticsToggle.getAttribute('aria-expanded') === 'true';
542
+ analyticsToggle.setAttribute('aria-expanded', expanded ? 'false' : 'true');
543
+ analyticsBody.hidden = expanded;
544
+ if (card) card.classList.toggle('collapsed', expanded);
545
+ });
546
+ }
547
+ var copyUploadButton = document.getElementById('copy-upload-command');
548
+ var uploadCommand = document.getElementById('analytics-upload-command');
549
+ var copyUploadStatus = document.getElementById('copy-upload-status');
550
+ if (copyUploadButton && uploadCommand) {
551
+ copyUploadButton.addEventListener('click', function () {
552
+ var command = uploadCommand.textContent || '';
553
+ var copy = navigator.clipboard && window.isSecureContext
554
+ ? navigator.clipboard.writeText(command)
555
+ : new Promise(function (resolve, reject) {
556
+ var input = document.createElement('textarea');
557
+ input.value = command;
558
+ input.setAttribute('readonly', '');
559
+ input.style.position = 'fixed';
560
+ input.style.opacity = '0';
561
+ document.body.appendChild(input);
562
+ input.select();
563
+ try { document.execCommand('copy') ? resolve() : reject(new Error('Copy failed')); }
564
+ catch (error) { reject(error); }
565
+ finally { document.body.removeChild(input); }
566
+ });
567
+ copy.then(function () {
568
+ copyUploadButton.classList.add('copied');
569
+ copyUploadButton.title = 'Copied!';
570
+ if (copyUploadStatus) copyUploadStatus.textContent = 'Upload command copied';
571
+ window.setTimeout(function () {
572
+ copyUploadButton.classList.remove('copied');
573
+ copyUploadButton.title = 'Copy upload command';
574
+ }, 1600);
575
+ }).catch(function () {
576
+ if (copyUploadStatus) copyUploadStatus.textContent = 'Could not copy upload command';
577
+ });
578
+ });
579
+ }
478
580
  var selectedTags = [];
479
581
  function applyTagFilter() {
480
582
  var match = document.getElementById('tag-match');