test-reporting 3.7.2__tar.gz → 3.7.4__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.7.2 → test_reporting-3.7.4}/PKG-INFO +1 -1
  2. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/publisher.py +35 -17
  3. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/templates/project.html +45 -52
  4. {test_reporting-3.7.2 → test_reporting-3.7.4}/setup.py +1 -1
  5. {test_reporting-3.7.2 → test_reporting-3.7.4}/test_reporting.egg-info/PKG-INFO +1 -1
  6. {test_reporting-3.7.2 → test_reporting-3.7.4}/LICENSE +0 -0
  7. {test_reporting-3.7.2 → test_reporting-3.7.4}/README.md +0 -0
  8. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/__init__.py +0 -0
  9. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/classifier.py +0 -0
  10. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/cli.py +0 -0
  11. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/config.py +0 -0
  12. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/plugin.py +0 -0
  13. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/storage.py +0 -0
  14. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/templates/index.html +0 -0
  15. {test_reporting-3.7.2 → test_reporting-3.7.4}/reporting/templates/run.html +0 -0
  16. {test_reporting-3.7.2 → test_reporting-3.7.4}/setup.cfg +0 -0
  17. {test_reporting-3.7.2 → test_reporting-3.7.4}/test_reporting.egg-info/SOURCES.txt +0 -0
  18. {test_reporting-3.7.2 → test_reporting-3.7.4}/test_reporting.egg-info/dependency_links.txt +0 -0
  19. {test_reporting-3.7.2 → test_reporting-3.7.4}/test_reporting.egg-info/entry_points.txt +0 -0
  20. {test_reporting-3.7.2 → test_reporting-3.7.4}/test_reporting.egg-info/requires.txt +0 -0
  21. {test_reporting-3.7.2 → test_reporting-3.7.4}/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.7.2
3
+ Version: 3.7.4
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
@@ -473,7 +473,7 @@ class Publisher:
473
473
  tag = run.get('regression_tag') or self._date_tag(run.get('timestamp'))
474
474
  ts = run.get('timestamp', '')
475
475
 
476
- # Suite summary
476
+ # Suite summary (kept for backwards compatibility)
477
477
  if suite not in suites:
478
478
  suites[suite] = {'name': suite, 'last_run': ts,
479
479
  'last_pass_rate': 0, 'runs': []}
@@ -494,7 +494,7 @@ class Publisher:
494
494
  'duration_seconds': run.get('duration_seconds', 0),
495
495
  })
496
496
 
497
- # Regression grouping
497
+ # Regression grouping - group by actual test files, not suite names
498
498
  if tag not in regressions:
499
499
  regressions[tag] = {'tag': tag, 'date': tag[:10], 'suites': [],
500
500
  'total_tests': 0, 'passed': 0, 'failed': 0, 'pass_rate': 0,
@@ -503,21 +503,39 @@ class Publisher:
503
503
  # Track the latest timestamp for this regression
504
504
  if ts > reg.get('timestamp', ''):
505
505
  reg['timestamp'] = ts
506
- existing = next((s for s in reg['suites'] if s['suite'] == suite), None)
507
- if existing:
508
- if ts > existing.get('timestamp', ''):
509
- existing.update({'timestamp': ts, 'run_id': run.get('run_id'),
510
- 'pass_rate': run.get('pass_rate', 0),
511
- 'total_tests': run.get('total_tests', 0),
512
- 'passed': run.get('passed', 0),
513
- 'failed': run.get('failed', 0)})
514
- else:
515
- reg['suites'].append({'suite': suite, 'timestamp': ts,
516
- 'run_id': run.get('run_id'),
517
- 'pass_rate': run.get('pass_rate', 0),
518
- 'total_tests': run.get('total_tests', 0),
519
- 'passed': run.get('passed', 0),
520
- 'failed': run.get('failed', 0)})
506
+
507
+ # Process each test file in this run
508
+ for test_file in run.get('test_files', []):
509
+ file_name = test_file.get('file_name', 'unknown')
510
+ # Strip .py extension for consistency
511
+ base_file_name = file_name.replace('.py', '')
512
+
513
+ existing = next((s for s in reg['suites'] if s['suite'] == base_file_name), None)
514
+ if existing:
515
+ # Accumulate stats for multiple runs of the same file
516
+ existing['total_tests'] += test_file.get('total', 0)
517
+ existing['passed'] += test_file.get('passed', 0)
518
+ existing['failed'] += test_file.get('failed', 0)
519
+ existing['run_count'] = existing.get('run_count', 1) + 1
520
+ # Update to latest timestamp and run_id
521
+ if ts > existing.get('timestamp', ''):
522
+ existing['timestamp'] = ts
523
+ existing['run_id'] = run.get('run_id')
524
+ # Recalculate pass rate based on accumulated totals
525
+ if existing['total_tests'] > 0:
526
+ existing['pass_rate'] = round((existing['passed'] / existing['total_tests']) * 100, 1)
527
+ else:
528
+ file_total = test_file.get('total', 0)
529
+ file_passed = test_file.get('passed', 0)
530
+ file_failed = test_file.get('failed', 0)
531
+ file_pass_rate = round((file_passed / file_total * 100) if file_total > 0 else 0, 1)
532
+ reg['suites'].append({'suite': base_file_name, 'timestamp': ts,
533
+ 'run_id': run.get('run_id'),
534
+ 'pass_rate': file_pass_rate,
535
+ 'total_tests': file_total,
536
+ 'passed': file_passed,
537
+ 'failed': file_failed,
538
+ 'run_count': 1})
521
539
 
522
540
  # Compute regression combined stats
523
541
  for reg in regressions.values():
@@ -555,75 +555,53 @@
555
555
  const rc2 = rateColor(reg.pass_rate);
556
556
  const total = lastReg ? lastReg.total_tests || 1 : 1;
557
557
 
558
- // Group suites by name to detect duplicates
559
- // Strip [TAG] suffix to group reruns with original runs
560
- const suiteGroups = {};
561
- (reg.suites || []).forEach(function(s) {
562
- // Remove [TAG] suffix like [RERUN], [RETRY], etc.
563
- const baseName = s.suite.replace(/\s*\[.*?\]\s*$/, '');
564
- if (!suiteGroups[baseName]) {
565
- suiteGroups[baseName] = [];
566
- }
567
- suiteGroups[baseName].push(s);
558
+ // Sort suites alphabetically
559
+ const sortedSuites = (reg.suites || []).slice().sort(function(a, b) {
560
+ return a.suite.localeCompare(b.suite);
568
561
  });
569
562
 
570
- // Sort suite names alphabetically
571
- const sortedSuiteNames = Object.keys(suiteGroups).sort();
572
-
573
- const fileRows = sortedSuiteNames.map(function(suiteName) {
574
- const runs = suiteGroups[suiteName];
575
- const runCount = runs.length;
576
-
577
- // Calculate accumulated averages
578
- let totalTests = 0;
579
- let totalFailed = 0;
580
- let totalSkipped = 0;
581
- let totalPassed = 0;
582
- let avgPassRate = 0;
583
-
584
- runs.forEach(function(run) {
585
- totalTests += run.total_tests;
586
- totalFailed += run.failed;
587
- totalSkipped += (run.skipped || 0);
588
- totalPassed += (run.total_tests - run.failed - (run.skipped || 0));
589
- avgPassRate += run.pass_rate;
590
- });
591
-
592
- avgPassRate = avgPassRate / runCount;
593
-
594
- // Use first run's ID for navigation
595
- const firstRunId = runs[0].run_id;
563
+ const fileRows = sortedSuites.map(function(s) {
564
+ const runCount = s.run_count || 1;
565
+ const totalTests = s.total_tests;
566
+ const totalFailed = s.failed;
567
+ const totalPassed = s.passed;
568
+ const passRate = s.pass_rate;
596
569
 
597
570
  const failedPct = totalTests > 0 ? (totalFailed / totalTests * 100) : 0;
598
- const skippedPct = totalTests > 0 ? (totalSkipped / totalTests * 100) : 0;
599
- const passedPct = Math.max(0, 100 - failedPct - skippedPct);
571
+ const passedPct = totalTests > 0 ? (totalPassed / totalTests * 100) : 0;
572
+ const skippedPct = Math.max(0, 100 - failedPct - passedPct);
600
573
 
601
574
  const runBadge = runCount > 1
602
- ? '<span class="run-count-badge" title="Ran ' + runCount + ' times">×' + runCount + '</span>'
575
+ ? '<span class="run-count-badge" title="Ran ' + runCount + ' times in this regression">×' + runCount + '</span>'
603
576
  : '';
604
577
 
605
578
  const metaText = runCount > 1
606
- ? totalTests + ' tests total · ' + totalFailed + ' failed · avg across ' + runCount + ' runs'
579
+ ? totalTests + ' tests total · ' + totalFailed + ' failed · accumulated across ' + runCount + ' runs'
607
580
  : totalTests + ' tests · ' + totalFailed + ' failed';
608
581
 
609
- return '<div class="suite-row" onclick="goToRun(\'' + firstRunId + '\')">' +
610
- '<div class="suite-name"><span>' + suiteName + '</span>' + runBadge + '</div>' +
582
+ // If multiple runs, navigate to history with search; otherwise go to single run
583
+ const clickHandler = runCount > 1
584
+ ? 'onclick="navigateToHistoryWithSearch(\'' + s.suite + '\', \'' + reg.tag + '\')"'
585
+ : 'onclick="goToRun(\'' + s.run_id + '\')"';
586
+
587
+ return '<div class="suite-row" ' + clickHandler + '>' +
588
+ '<div class="suite-name"><span>' + s.suite + '</span>' + runBadge + '</div>' +
611
589
  '<div class="suite-meta">' + metaText + '</div>' +
612
590
  '<div class="suite-bar">' +
613
- '<div class="stacked-bar" title="Passed: ' + totalPassed + ' | Failed: ' + totalFailed + ' | Skipped: ' + totalSkipped + (runCount > 1 ? ' (accumulated)' : '') + '">' +
591
+ '<div class="stacked-bar" title="Passed: ' + totalPassed + ' | Failed: ' + totalFailed + (runCount > 1 ? ' (accumulated across ' + runCount + ' runs)' : '') + '">' +
614
592
  '<div class="stacked-green" style="width:' + passedPct.toFixed(1) + '%"></div>' +
615
593
  '<div class="stacked-red" style="width:' + failedPct.toFixed(1) + '%"></div>' +
616
594
  '<div class="stacked-grey" style="width:' + skippedPct.toFixed(1) + '%"></div>' +
617
595
  '</div>' +
618
596
  '</div>' +
619
- '<div class="suite-rate ' + rateColor(avgPassRate) + '">' + avgPassRate.toFixed(0) + '%</div>' +
597
+ '<div class="suite-rate ' + rateColor(passRate) + '">' + passRate.toFixed(0) + '%</div>' +
620
598
  '</div>';
621
599
  }).join('');
622
600
 
623
601
  const isOpen = i === 0;
624
602
  const regDateTime = reg.timestamp ? fmtDateTime(reg.timestamp) : '';
625
603
  const regTitle = regDateTime ? reg.tag + ' · ' + regDateTime : reg.tag;
626
- const uniqueSuiteCount = Object.keys(suiteGroups).length;
604
+ const uniqueSuiteCount = sortedSuites.length;
627
605
  return '<div class="regression">' +
628
606
  '<div class="reg-head" onclick="toggleReg(this)">' +
629
607
  '<div>' +
@@ -685,14 +663,23 @@
685
663
  if (runId) location.href = 'run.html?r=' + encodeURIComponent(runId) + '&p=' + encodeURIComponent(projectName);
686
664
  }
687
665
 
688
- function navigateToHistoryWithSearch(fileName) {
689
- // Switch to Run History tab
690
- switchTab('history');
666
+ function navigateToHistoryWithSearch(fileName, tag) {
667
+ // Find and click the Run History tab button
668
+ const historyBtn = Array.from(document.querySelectorAll('.tab-btn')).find(function(btn) {
669
+ return btn.textContent.trim() === 'Run History';
670
+ });
671
+
672
+ if (historyBtn) {
673
+ switchTab('history', historyBtn);
674
+ }
675
+
691
676
  // Wait for tab to render, then set search value and trigger filter
692
677
  setTimeout(function() {
693
678
  const searchInput = document.getElementById('historySearch');
694
679
  if (searchInput) {
695
- searchInput.value = fileName;
680
+ // Combine file name and tag for search
681
+ const searchValue = tag ? fileName + ' ' + tag : fileName;
682
+ searchInput.value = searchValue;
696
683
  filterHistory();
697
684
  // Scroll to the table
698
685
  searchInput.scrollIntoView({ behavior: 'smooth', block: 'start' });
@@ -916,7 +903,9 @@
916
903
  var bg = f.pass_rate >= 95 ? '#276749' : f.pass_rate >= 80 ? '#975a16' : '#9b2c2c';
917
904
  var bgLight = f.pass_rate >= 95 ? '#38a169' : f.pass_rate >= 80 ? '#d69e2e' : '#e53e3e';
918
905
  const hiddenClass = i >= limit ? ' treemap-cell-hidden' : '';
919
- const clickAttr = ' onclick="navigateToHistoryWithSearch(\'' + f.file_name + '\')" style="cursor:pointer;"';
906
+ // Strip .py extension for search to match display format
907
+ const searchName = f.file_name.replace(/\.py$/, '');
908
+ const clickAttr = ' onclick="navigateToHistoryWithSearch(\'' + searchName + '\')" style="cursor:pointer;"';
920
909
  return '<div class="treemap-cell' + hiddenClass + '" style="width:' + width + 'px;background:' + bgLight + ';" title="' + f.file_name + ': ' + f.pass_rate.toFixed(1) + '% pass rate (' + f.total_runs + ' runs) - Click to view all runs"' + clickAttr + '>' +
921
910
  '<div class="treemap-rate">' + f.pass_rate.toFixed(0) + '%</div>' +
922
911
  '<div class="treemap-name">' + f.file_name + '</div>' +
@@ -1048,7 +1037,7 @@
1048
1037
  '<span style="font-size:13px;color:var(--muted)"><span id="visibleRunCount">' + recentRuns.length + '</span> of ' + recentRuns.length + ' runs across ' + names.length + ' test ' + plural(names.length, 'file') + '</span>' +
1049
1038
  '</div>' +
1050
1039
  '<div class="search-wrap">' +
1051
- '<input type="text" id="historySearch" class="search-input" placeholder="Search by test file name..." oninput="filterHistory()" />' +
1040
+ '<input type="text" id="historySearch" class="search-input" placeholder="Search by test file name or tag..." oninput="filterHistory()" />' +
1052
1041
  '</div>' +
1053
1042
  '<div class="section-body-flush">' +
1054
1043
  '<table class="hist-table" id="historyTable">' +
@@ -1126,9 +1115,13 @@
1126
1115
 
1127
1116
  rows.forEach(function(row) {
1128
1117
  const fileNameCell = row.querySelector('td:nth-child(3)');
1118
+ const tagCell = row.querySelector('td:nth-child(4)');
1129
1119
  if (fileNameCell) {
1130
1120
  const fileName = fileNameCell.textContent.toLowerCase();
1131
- if (fileName.includes(searchTerm)) {
1121
+ const tag = tagCell ? tagCell.textContent.toLowerCase() : '';
1122
+ // Match if search term is in file name OR tag
1123
+ const combinedText = fileName + ' ' + tag;
1124
+ if (combinedText.includes(searchTerm)) {
1132
1125
  row.style.display = '';
1133
1126
  visibleCount++;
1134
1127
  } else {
@@ -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.7.2',
12
+ version='3.7.4',
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.7.2
3
+ Version: 3.7.4
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