test-reporting 3.2.0__tar.gz → 3.2.2__tar.gz

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 (21) hide show
  1. {test_reporting-3.2.0/test_reporting.egg-info → test_reporting-3.2.2}/PKG-INFO +1 -1
  2. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/templates/project.html +144 -59
  3. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/templates/run.html +7 -7
  4. {test_reporting-3.2.0 → test_reporting-3.2.2}/setup.py +1 -1
  5. {test_reporting-3.2.0 → test_reporting-3.2.2/test_reporting.egg-info}/PKG-INFO +1 -1
  6. {test_reporting-3.2.0 → test_reporting-3.2.2}/LICENSE +0 -0
  7. {test_reporting-3.2.0 → test_reporting-3.2.2}/README.md +0 -0
  8. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/__init__.py +0 -0
  9. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/classifier.py +0 -0
  10. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/cli.py +0 -0
  11. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/config.py +0 -0
  12. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/plugin.py +0 -0
  13. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/publisher.py +0 -0
  14. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/storage.py +0 -0
  15. {test_reporting-3.2.0 → test_reporting-3.2.2}/reporting/templates/index.html +0 -0
  16. {test_reporting-3.2.0 → test_reporting-3.2.2}/setup.cfg +0 -0
  17. {test_reporting-3.2.0 → test_reporting-3.2.2}/test_reporting.egg-info/SOURCES.txt +0 -0
  18. {test_reporting-3.2.0 → test_reporting-3.2.2}/test_reporting.egg-info/dependency_links.txt +0 -0
  19. {test_reporting-3.2.0 → test_reporting-3.2.2}/test_reporting.egg-info/entry_points.txt +0 -0
  20. {test_reporting-3.2.0 → test_reporting-3.2.2}/test_reporting.egg-info/requires.txt +0 -0
  21. {test_reporting-3.2.0 → test_reporting-3.2.2}/test_reporting.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: test-reporting
3
- Version: 3.2.0
3
+ Version: 3.2.2
4
4
  Summary: Multi-project test reporting dashboard — collect results locally or push to S3
5
5
  Home-page: https://github.com/amahdy77/test-reporting.git
6
6
  Author: Ashfaqur Mahdy
@@ -6,7 +6,7 @@
6
6
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
7
7
  <meta http-equiv="Pragma" content="no-cache" />
8
8
  <meta http-equiv="Expires" content="0" />
9
- <title>Project Test Dashboard</title>
9
+ <title>Project - Test Dashboard</title>
10
10
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
11
11
  <style>
12
12
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@@ -139,6 +139,37 @@
139
139
  .section-explain { font-size: 13px; color: var(--muted); line-height: 1.5; }
140
140
  .section-body { padding: 20px 24px; }
141
141
  .section-body-flush { }
142
+
143
+ /* Collapsible sections with preview */
144
+ .section-head.collapsible { cursor: pointer; user-select: none; display: flex;
145
+ justify-content: space-between; align-items: flex-start; }
146
+ .section-head.collapsible:hover { background: var(--bg); }
147
+ .section-head-content { flex: 1; }
148
+ .section-toggle { color: var(--muted); font-size: 14px; transition: transform .2s;
149
+ flex-shrink: 0; margin-left: 12px; margin-top: 2px; }
150
+ .section-toggle.collapsed { transform: rotate(-90deg); }
151
+
152
+ /* Preview mode - show partial content */
153
+ .section-body-flush.preview { max-height: 200px; overflow: hidden; position: relative;
154
+ cursor: pointer; transition: max-height .3s ease; }
155
+ .section-body-flush.preview:hover { background: var(--blue-bg); }
156
+ .section-body-flush.preview::after {
157
+ content: 'Click to expand ▼'; position: absolute; bottom: 0; left: 0; right: 0;
158
+ height: 60px; display: flex; align-items: flex-end; justify-content: center;
159
+ padding-bottom: 12px; font-size: 13px; font-weight: 600; color: var(--blue);
160
+ background: linear-gradient(to bottom, transparent, var(--surface) 50%);
161
+ pointer-events: none; transition: opacity .2s;
162
+ }
163
+ .section-body-flush.preview:hover::after { opacity: 0.8; }
164
+ .section-body-flush.expanded { max-height: none; cursor: pointer; }
165
+ .section-body-flush.expanded:hover { background: var(--blue-bg); }
166
+ .section-body-flush.expanded::after {
167
+ content: 'Click to collapse ▲'; position: relative; display: block;
168
+ text-align: center; padding: 16px; font-size: 13px; font-weight: 600;
169
+ color: var(--blue); background: var(--bg); border-top: 1px solid var(--border);
170
+ transition: background .1s;
171
+ }
172
+ .section-body-flush.expanded:hover::after { background: var(--border); }
142
173
 
143
174
  /* ── Two-column grid ── */
144
175
  .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
@@ -157,6 +188,8 @@
157
188
  td { padding: 12px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; }
158
189
  tr:last-child td { border-bottom: none; }
159
190
  tr:hover td { background: var(--bg); }
191
+ tr.clickable-row { cursor: pointer; }
192
+ tr.clickable-row:hover td { background: var(--blue-bg); }
160
193
  .test-name { font-weight: 600; max-width: 280px; overflow: hidden;
161
194
  text-overflow: ellipsis; white-space: nowrap; }
162
195
  .file-name { color: var(--muted); font-size: 12px; margin-top: 2px; }
@@ -271,6 +304,13 @@
271
304
  opacity: 0; transition: opacity .15s; z-index: 200;
272
305
  }
273
306
  [data-tooltip]:hover::after { opacity: 1; }
307
+
308
+ /* ── Expandable tables ── */
309
+ .table-expand-btn { padding: 12px 24px; text-align: center; border-top: 1px solid var(--border);
310
+ cursor: pointer; color: var(--blue); font-size: 13px; font-weight: 600;
311
+ transition: background .1s; }
312
+ .table-expand-btn:hover { background: var(--bg); }
313
+ .table-row-hidden { display: none; }
274
314
  </style>
275
315
  </head>
276
316
  <body>
@@ -325,7 +365,7 @@
325
365
 
326
366
  /* ── Helpers ── */
327
367
  function relTime(iso) {
328
- if (!iso) return '';
368
+ if (!iso) return '-';
329
369
  const diff = Date.now() - new Date(iso).getTime();
330
370
  const m = Math.floor(diff / 60000);
331
371
  if (m < 1) return 'Just now';
@@ -336,12 +376,12 @@
336
376
  }
337
377
 
338
378
  function fmtDate(iso) {
339
- if (!iso) return '';
379
+ if (!iso) return '-';
340
380
  return new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
341
381
  }
342
382
 
343
383
  function fmtDur(s) {
344
- if (!s || s === 0) return '';
384
+ if (!s || s === 0) return '-';
345
385
  if (s >= 60) return Math.floor(s / 60) + 'm ' + Math.floor(s % 60) + 's';
346
386
  return s.toFixed(1) + 's';
347
387
  }
@@ -519,7 +559,7 @@
519
559
  return hero +
520
560
  '<div class="section">' +
521
561
  '<div class="section-head">' +
522
- '<h3>Regressions</h3>' +
562
+ '<h3>Recent Test Runs</h3>' +
523
563
  '<p class="section-explain">Each group shows all test files that ran together on a given tag. Click a test file to see the full test breakdown.</p>' +
524
564
  '</div>' +
525
565
  '<div class="section-body">' + regsHtml + '</div>' +
@@ -533,6 +573,31 @@
533
573
  ch.classList.toggle('open', open);
534
574
  }
535
575
 
576
+ function toggleTableRows(btn) {
577
+ const table = btn.previousElementSibling;
578
+ const hiddenRows = table.querySelectorAll('.table-row-hidden');
579
+ const isExpanded = hiddenRows[0].style.display === 'table-row';
580
+
581
+ hiddenRows.forEach(function(row) {
582
+ row.style.display = isExpanded ? 'none' : 'table-row';
583
+ });
584
+
585
+ const count = hiddenRows.length;
586
+ btn.textContent = isExpanded
587
+ ? 'Show ' + count + ' more ▼'
588
+ : 'Show less ▲';
589
+ }
590
+
591
+ function toggleSectionExpand(body) {
592
+ if (body.classList.contains('preview')) {
593
+ body.classList.remove('preview');
594
+ body.classList.add('expanded');
595
+ } else if (body.classList.contains('expanded')) {
596
+ body.classList.remove('expanded');
597
+ body.classList.add('preview');
598
+ }
599
+ }
600
+
536
601
  function goToRun(runId) {
537
602
  if (runId) location.href = 'run.html?r=' + encodeURIComponent(runId) + '&p=' + encodeURIComponent(projectName);
538
603
  }
@@ -567,33 +632,31 @@
567
632
  '</div>' +
568
633
  '</div>' +
569
634
 
570
- '<div class="two-col">' +
571
- '<div class="section">' +
572
- '<div class="section-head">' +
573
- '<h3>Why Are Tests Failing?</h3>' +
574
- '<p class="section-explain">Failure type breakdown. Real bugs need code fixes; infrastructure issues are environment problems.</p>' +
575
- '</div>' +
576
- '<div class="section-body">' +
577
- ((a.failure_type_breakdown || []).length === 0
578
- ? '<p class="empty-section">No failures recorded yet.</p>'
579
- : '<div class="chart-wrap chart-wrap-sm"><canvas id="failureTypeChart"></canvas></div>') +
580
- '</div>' +
635
+ '<div class="section">' +
636
+ '<div class="section-head">' +
637
+ '<h3>Why Are Tests Failing?</h3>' +
638
+ '<p class="section-explain">Failure type breakdown. Real bugs need code fixes; infrastructure issues are environment problems.</p>' +
581
639
  '</div>' +
640
+ '<div class="section-body">' +
641
+ ((a.failure_type_breakdown || []).length === 0
642
+ ? '<p class="empty-section">No failures recorded yet.</p>'
643
+ : '<div class="chart-wrap chart-wrap-sm"><canvas id="failureTypeChart"></canvas></div>') +
644
+ '</div>' +
645
+ '</div>' +
582
646
 
583
- '<div class="section">' +
584
- '<div class="section-head">' +
585
- '<h3>Test File Reliability (Treemap)</h3>' +
586
- '<p class="section-explain">Each block = one file. Size ∝ test count. Color = pass rate. Worst files at top-left.</p>' +
587
- '</div>' +
588
- '<div class="section-body">' +
589
- renderTreemap(a.file_health || []) +
590
- '</div>' +
647
+ '<div class="section">' +
648
+ '<div class="section-head">' +
649
+ '<h3>Test File Reliability (Treemap)</h3>' +
650
+ '<p class="section-explain">Each block = one file. Size ∝ test count. Color = pass rate. Worst files at top-left.</p>' +
651
+ '</div>' +
652
+ '<div class="section-body">' +
653
+ renderTreemap(a.file_health || []) +
591
654
  '</div>' +
592
655
  '</div>' +
593
656
 
594
657
  '<div class="section">' +
595
658
  '<div class="section-head">' +
596
- '<h3>Duration vs Failures Scatter</h3>' +
659
+ '<h3>Duration vs Failures - Scatter</h3>' +
597
660
  '<p class="section-explain">Each dot = one test. X = avg duration, Y = failure count. Top-right quadrant = highest priority fixes.</p>' +
598
661
  '</div>' +
599
662
  '<div class="section-body">' +
@@ -607,10 +670,10 @@
607
670
 
608
671
  '<div class="section">' +
609
672
  '<div class="section-head">' +
610
- '<h3>Unreliable Tests Flaky</h3>' +
673
+ '<h3>Unreliable Tests - Flaky</h3>' +
611
674
  '<p class="section-explain">Tests that pass sometimes and fail other times without code changes. Flaky tests erode trust in your test suite.</p>' +
612
675
  '</div>' +
613
- '<div class="section-body-flush">' +
676
+ '<div class="section-body-flush preview" onclick="toggleSectionExpand(this)">' +
614
677
  renderFlakyTable(a.flaky_tests || []) +
615
678
  '</div>' +
616
679
  '</div>' +
@@ -618,32 +681,30 @@
618
681
  '<div class="section">' +
619
682
  '<div class="section-head">' +
620
683
  '<h3>Tests That Break the Most</h3>' +
621
- '<p class="section-explain">Highest-priority fixes resolving these has the biggest positive impact on your overall pass rate.</p>' +
684
+ '<p class="section-explain">Highest-priority fixes - resolving these has the biggest positive impact on your overall pass rate.</p>' +
622
685
  '</div>' +
623
- '<div class="section-body-flush">' +
686
+ '<div class="section-body-flush preview" onclick="toggleSectionExpand(this)">' +
624
687
  renderHotspotsTable(a.failure_hotspots || []) +
625
688
  '</div>' +
626
689
  '</div>' +
627
690
 
628
- '<div class="two-col">' +
629
- '<div class="section">' +
630
- '<div class="section-head">' +
631
- '<h3>Slowest Tests</h3>' +
632
- '<p class="section-explain">Tests taking the longest to run. Good candidates for optimization.</p>' +
633
- '</div>' +
634
- '<div class="section-body-flush">' +
635
- renderSlowTable(a.slowest_tests || []) +
636
- '</div>' +
691
+ '<div class="section">' +
692
+ '<div class="section-head">' +
693
+ '<h3>Slowest Tests</h3>' +
694
+ '<p class="section-explain">Tests taking the longest to run. Good candidates for optimization.</p>' +
695
+ '</div>' +
696
+ '<div class="section-body-flush preview" onclick="toggleSectionExpand(this)">' +
697
+ renderSlowTable(a.slowest_tests || []) +
637
698
  '</div>' +
699
+ '</div>' +
638
700
 
639
- '<div class="section">' +
640
- '<div class="section-head">' +
641
- '<h3>Tests Getting Slower</h3>' +
642
- '<p class="section-explain">Tests that have become significantly slower over the last 7 days. A slowing test can signal feature degradation.</p>' +
643
- '</div>' +
644
- '<div class="section-body-flush">' +
645
- renderRegressionTable(a.performance_regressions || []) +
646
- '</div>' +
701
+ '<div class="section">' +
702
+ '<div class="section-head">' +
703
+ '<h3>Tests Getting Slower</h3>' +
704
+ '<p class="section-explain">Tests that have become significantly slower over the last 7 days. A slowing test can signal feature degradation.</p>' +
705
+ '</div>' +
706
+ '<div class="section-body-flush preview" onclick="toggleSectionExpand(this)">' +
707
+ renderRegressionTable(a.performance_regressions || []) +
647
708
  '</div>' +
648
709
  '</div>';
649
710
  }
@@ -750,55 +811,79 @@
750
811
 
751
812
  function renderFlakyTable(tests) {
752
813
  if (tests.length === 0) return '<p class="empty-section" style="padding:20px">No flaky tests detected.</p>';
814
+ const limit = 5;
753
815
  const rows = tests.map(function(t, i) {
754
- return '<tr class="anim-row" style="animation-delay:' + (i * 0.04) + 's">' +
816
+ const hiddenClass = i >= limit ? ' table-row-hidden' : '';
817
+ const clickAttr = t.last_run_id ? ' class="anim-row clickable-row' + hiddenClass + '" onclick="goToRun(\'' + t.last_run_id + '\')"' : ' class="anim-row' + hiddenClass + '"';
818
+ return '<tr' + clickAttr + ' style="animation-delay:' + (i * 0.04) + 's">' +
755
819
  '<td><div class="test-name" title="' + t.test_name + '">' + t.test_name + '</div><div class="file-name">' + t.file_name + '</div></td>' +
756
820
  '<td><span class="pill pill-yellow">' + t.pass_rate + '% pass rate</span></td>' +
757
821
  '<td style="text-align:center">' + t.passed + '</td>' +
758
822
  '<td style="text-align:center;color:var(--red)">' + t.failed + '</td>' +
759
823
  '<td style="text-align:center">' + t.total_runs + '</td>' +
760
- '<td style="color:var(--muted);font-size:12px">' + (t.last_failed ? fmtDate(t.last_failed) : '') + '</td>' +
824
+ '<td style="color:var(--muted);font-size:12px">' + (t.last_failed ? fmtDate(t.last_failed) : '-') + '</td>' +
761
825
  '</tr>';
762
826
  }).join('');
763
- return '<table><thead><tr><th>Test</th><th>Consistency</th><th>Passed</th><th>Failed</th><th>Total Runs</th><th>Last Failed</th></tr></thead><tbody>' + rows + '</tbody></table>';
827
+ const expandBtn = tests.length > limit
828
+ ? '<div class="table-expand-btn" onclick="toggleTableRows(this)">Show ' + (tests.length - limit) + ' more ▼</div>'
829
+ : '';
830
+ return '<table><thead><tr><th>Test</th><th>Consistency</th><th>Passed</th><th>Failed</th><th>Total Runs</th><th>Last Failed</th></tr></thead><tbody>' + rows + '</tbody></table>' + expandBtn;
764
831
  }
765
832
 
766
833
  function renderHotspotsTable(tests) {
767
834
  if (tests.length === 0) return '<p class="empty-section" style="padding:20px">No failure hotspots detected yet.</p>';
835
+ const limit = 5;
768
836
  const rows = tests.map(function(t, i) {
769
- return '<tr class="anim-row" style="animation-delay:' + (i * 0.04) + 's">' +
837
+ const hiddenClass = i >= limit ? ' table-row-hidden' : '';
838
+ const clickAttr = t.last_run_id ? ' class="anim-row clickable-row' + hiddenClass + '" onclick="goToRun(\'' + t.last_run_id + '\')"' : ' class="anim-row' + hiddenClass + '"';
839
+ return '<tr' + clickAttr + ' style="animation-delay:' + (i * 0.04) + 's">' +
770
840
  '<td><div class="test-name" title="' + t.test_name + '">' + t.test_name + '</div><div class="file-name">' + t.file_name + '</div></td>' +
771
841
  '<td><span class="pill pill-red">' + t.failure_count + ' failures</span></td>' +
772
842
  '<td>' + t.failure_label + '</td>' +
773
- '<td style="color:var(--muted);font-size:12px">' + (t.last_failed ? fmtDate(t.last_failed) : '') + '</td>' +
843
+ '<td style="color:var(--muted);font-size:12px">' + (t.last_failed ? fmtDate(t.last_failed) : '-') + '</td>' +
774
844
  '</tr>';
775
845
  }).join('');
776
- return '<table><thead><tr><th>Test</th><th>Failure Count</th><th>Cause</th><th>Last Failed</th></tr></thead><tbody>' + rows + '</tbody></table>';
846
+ const expandBtn = tests.length > limit
847
+ ? '<div class="table-expand-btn" onclick="toggleTableRows(this)">Show ' + (tests.length - limit) + ' more ▼</div>'
848
+ : '';
849
+ return '<table><thead><tr><th>Test</th><th>Failure Count</th><th>Cause</th><th>Last Failed</th></tr></thead><tbody>' + rows + '</tbody></table>' + expandBtn;
777
850
  }
778
851
 
779
852
  function renderSlowTable(tests) {
780
853
  if (tests.length === 0) return '<p class="empty-section" style="padding:20px">Not enough data yet.</p>';
854
+ const limit = 5;
781
855
  const rows = tests.map(function(t, i) {
782
- return '<tr class="anim-row" style="animation-delay:' + (i * 0.04) + 's">' +
856
+ const hiddenClass = i >= limit ? ' table-row-hidden' : '';
857
+ const clickAttr = t.last_run_id ? ' class="anim-row clickable-row' + hiddenClass + '" onclick="goToRun(\'' + t.last_run_id + '\')"' : ' class="anim-row' + hiddenClass + '"';
858
+ return '<tr' + clickAttr + ' style="animation-delay:' + (i * 0.04) + 's">' +
783
859
  '<td><div class="test-name" title="' + t.test_name + '">' + t.test_name + '</div><div class="file-name">' + t.file_name + '</div></td>' +
784
860
  '<td style="font-weight:700">' + fmtDur(t.avg_duration) + '</td>' +
785
861
  '<td style="color:var(--muted);font-size:12px">' + fmtDur(t.max_duration) + ' max</td>' +
786
862
  '</tr>';
787
863
  }).join('');
788
- return '<table><thead><tr><th>Test</th><th>Avg Duration</th><th>Max</th></tr></thead><tbody>' + rows + '</tbody></table>';
864
+ const expandBtn = tests.length > limit
865
+ ? '<div class="table-expand-btn" onclick="toggleTableRows(this)">Show ' + (tests.length - limit) + ' more ▼</div>'
866
+ : '';
867
+ return '<table><thead><tr><th>Test</th><th>Avg Duration</th><th>Max</th></tr></thead><tbody>' + rows + '</tbody></table>' + expandBtn;
789
868
  }
790
869
 
791
870
  function renderRegressionTable(tests) {
792
871
  if (tests.length === 0) return '<p class="empty-section" style="padding:20px">No performance regressions detected.</p>';
872
+ const limit = 5;
793
873
  const rows = tests.map(function(t, i) {
794
- return '<tr class="anim-row" style="animation-delay:' + (i * 0.04) + 's">' +
874
+ const hiddenClass = i >= limit ? ' table-row-hidden' : '';
875
+ const clickAttr = t.last_run_id ? ' class="anim-row clickable-row' + hiddenClass + '" onclick="goToRun(\'' + t.last_run_id + '\')"' : ' class="anim-row' + hiddenClass + '"';
876
+ return '<tr' + clickAttr + ' style="animation-delay:' + (i * 0.04) + 's">' +
795
877
  '<td><div class="test-name" title="' + t.test_name + '">' + t.test_name + '</div><div class="file-name">' + t.file_name + '</div></td>' +
796
878
  '<td style="color:var(--muted);font-size:12px">' + fmtDur(t.historical_avg) + '</td>' +
797
879
  '<td style="font-weight:700">' + fmtDur(t.recent_avg) + '</td>' +
798
880
  '<td><span class="pill pill-red">+' + t.percent_slower + '% slower</span></td>' +
799
881
  '</tr>';
800
882
  }).join('');
801
- return '<table><thead><tr><th>Test</th><th>Was</th><th>Now</th><th>Change</th></tr></thead><tbody>' + rows + '</tbody></table>';
883
+ const expandBtn = tests.length > limit
884
+ ? '<div class="table-expand-btn" onclick="toggleTableRows(this)">Show ' + (tests.length - limit) + ' more ▼</div>'
885
+ : '';
886
+ return '<table><thead><tr><th>Test</th><th>Was</th><th>Now</th><th>Change</th></tr></thead><tbody>' + rows + '</tbody></table>' + expandBtn;
802
887
  }
803
888
 
804
889
  /* ═══════════════════════════════════════════════════
@@ -835,7 +920,7 @@
835
920
  return '<tr>' +
836
921
  '<td>' + fmtDate(run.timestamp) + '</td>' +
837
922
  '<td><div style="font-weight:600;font-size:13px">' + run.file_name + '</div></td>' +
838
- '<td>' + (run.regression_tag || '') + '</td>' +
923
+ '<td>' + (run.regression_tag || '-') + '</td>' +
839
924
  '<td><span class="pill ' + pc + '">' + run.pass_rate.toFixed(0) + '%</span></td>' +
840
925
  '<td>' + run.total_tests + '</td>' +
841
926
  '<td style="color:var(--red)">' + run.failed + '</td>' +
@@ -850,7 +935,7 @@
850
935
  var canvas = document.getElementById(sid);
851
936
  if (!canvas) continue;
852
937
  var pts = sparklineIds[sid];
853
- if (pts.length < 2) { canvas.outerHTML = '<span class="sparkline-none-sm">—</span>'; continue; }
938
+ if (pts.length < 2) { canvas.outerHTML = '<span class="sparkline-none-sm">-</span>'; continue; }
854
939
  new Chart(canvas, {
855
940
  type: 'line',
856
941
  data: {
@@ -1041,7 +1126,7 @@
1041
1126
 
1042
1127
  document.getElementById('pageTitle').textContent = proj.name;
1043
1128
  document.getElementById('lastRun').textContent = 'Last run ' + relTime(proj.last_run);
1044
- document.title = proj.name + ' Test Dashboard';
1129
+ document.title = proj.name + ' - Test Dashboard';
1045
1130
 
1046
1131
  const analytics = proj.analytics || {};
1047
1132
 
@@ -6,7 +6,7 @@
6
6
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
7
7
  <meta http-equiv="Pragma" content="no-cache" />
8
8
  <meta http-equiv="Expires" content="0" />
9
- <title>Run Details Test Dashboard</title>
9
+ <title>Run Details - Test Dashboard</title>
10
10
  <style>
11
11
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
12
12
 
@@ -322,14 +322,14 @@
322
322
  var projectName = params.get('p') || '';
323
323
 
324
324
  function fmtDate(iso) {
325
- if (!iso) return '';
325
+ if (!iso) return '-';
326
326
  return new Date(iso).toLocaleString(undefined, {
327
327
  month:'short', day:'numeric', year:'numeric',
328
328
  hour:'2-digit', minute:'2-digit'
329
329
  });
330
330
  }
331
331
  function fmtDur(s) {
332
- if (!s || s === 0) return '';
332
+ if (!s || s === 0) return '-';
333
333
  if (s >= 60) return Math.floor(s/60) + 'm ' + Math.floor(s%60) + 's';
334
334
  return s.toFixed(1) + 's';
335
335
  }
@@ -374,13 +374,13 @@
374
374
  var cls = segClass(f.pass_rate);
375
375
  var label = pct > 8 ? f.file_name.replace(/\.py$/, '') : '';
376
376
  return '<div class="timeline-seg ' + cls + '" style="flex:' + pct.toFixed(2) + '"'
377
- + ' title="' + f.file_name + ' ' + f.pass_rate.toFixed(0) + '% passing (' + f.passed + '/' + f.total + ')"'
377
+ + ' title="' + f.file_name + ' - ' + f.pass_rate.toFixed(0) + '% passing (' + f.passed + '/' + f.total + ')"'
378
378
  + ' onclick="scrollToFile(\'' + f.file_name.replace(/'/g, '') + '\')">'
379
379
  + label + '</div>';
380
380
  }).join('');
381
381
 
382
382
  return '<div class="timeline-wrap">'
383
- + '<div class="timeline-label">Test File Map click to jump</div>'
383
+ + '<div class="timeline-label">Test File Map - click to jump</div>'
384
384
  + '<div class="timeline-bar">' + segs + '</div>'
385
385
  + '<div class="timeline-legend">'
386
386
  + '<div class="timeline-legend-item"><div class="legend-dot" style="background:#38a169"></div>Passing (≥95%)</div>'
@@ -410,7 +410,7 @@
410
410
  return;
411
411
  }
412
412
 
413
- document.title = (run.suite_name || 'Run') + ' Test Dashboard';
413
+ document.title = (run.suite_name || 'Run') + ' - Test Dashboard';
414
414
  document.getElementById('pageTitle').textContent =
415
415
  (run.suite_name || 'Run') + ' · ' + (run.project_name || '');
416
416
  document.getElementById('runTime').textContent = fmtDate(run.timestamp);
@@ -441,7 +441,7 @@
441
441
  /* Timeline */
442
442
  var timeline = buildTimeline(files, total);
443
443
 
444
- /* Hero ring card + metric cards */
444
+ /* Hero - ring card + metric cards */
445
445
  var rateColor2 = ringColor(rate);
446
446
  var ringHtml = buildRing(rate);
447
447
  var hero = '<div class="hero">'
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
9
9
 
10
10
  setup(
11
11
  name='test-reporting',
12
- version='3.2.0',
12
+ version='3.2.2',
13
13
  description='Multi-project test reporting dashboard — collect results locally or push to S3',
14
14
  long_description=long_description,
15
15
  long_description_content_type="text/markdown",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: test-reporting
3
- Version: 3.2.0
3
+ Version: 3.2.2
4
4
  Summary: Multi-project test reporting dashboard — collect results locally or push to S3
5
5
  Home-page: https://github.com/amahdy77/test-reporting.git
6
6
  Author: Ashfaqur Mahdy
File without changes
File without changes
File without changes