test-reporting 3.7.1__tar.gz → 3.7.3__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.
- {test_reporting-3.7.1/test_reporting.egg-info → test_reporting-3.7.3}/PKG-INFO +1 -1
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/classifier.py +47 -3
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/publisher.py +29 -14
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/templates/project.html +73 -44
- {test_reporting-3.7.1 → test_reporting-3.7.3}/setup.py +1 -1
- {test_reporting-3.7.1 → test_reporting-3.7.3/test_reporting.egg-info}/PKG-INFO +1 -1
- {test_reporting-3.7.1 → test_reporting-3.7.3}/LICENSE +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/README.md +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/__init__.py +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/cli.py +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/config.py +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/plugin.py +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/storage.py +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/templates/index.html +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/reporting/templates/run.html +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/setup.cfg +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/test_reporting.egg-info/SOURCES.txt +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/test_reporting.egg-info/dependency_links.txt +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/test_reporting.egg-info/entry_points.txt +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/test_reporting.egg-info/requires.txt +0 -0
- {test_reporting-3.7.1 → test_reporting-3.7.3}/test_reporting.egg-info/top_level.txt +0 -0
|
@@ -16,6 +16,10 @@ class FailureClassifier:
|
|
|
16
16
|
'API_TIMEOUT': 'Soft Failure - API Timeout',
|
|
17
17
|
'ENVIRONMENT': 'Environment Issue',
|
|
18
18
|
'FLAKY': 'Flaky Test - Passed on Retry',
|
|
19
|
+
'ELEMENT_NOT_FOUND': 'Element Not Found',
|
|
20
|
+
'NAVIGATION_ERROR': 'Navigation/Page Load Error',
|
|
21
|
+
'SCRIPT_ERROR': 'JavaScript/Script Error',
|
|
22
|
+
'VALIDATION_ERROR': 'Data Validation Error',
|
|
19
23
|
'UNKNOWN': 'Unknown Error',
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -44,6 +48,9 @@ class FailureClassifier:
|
|
|
44
48
|
is_soft_fail = False
|
|
45
49
|
category = 'functional'
|
|
46
50
|
|
|
51
|
+
exception_lower = exception_message.lower()
|
|
52
|
+
exception_type_lower = exception_type.lower()
|
|
53
|
+
|
|
47
54
|
# Check if passed on retry (flaky)
|
|
48
55
|
if retry_count > 0:
|
|
49
56
|
failure_type = 'FLAKY'
|
|
@@ -51,11 +58,48 @@ class FailureClassifier:
|
|
|
51
58
|
category = 'stability'
|
|
52
59
|
|
|
53
60
|
# Check for assertion errors (hard failures)
|
|
54
|
-
elif 'AssertionError' in exception_type or 'assert' in
|
|
61
|
+
elif 'AssertionError' in exception_type or 'assert' in exception_lower:
|
|
55
62
|
failure_type = 'ASSERTION'
|
|
56
63
|
is_soft_fail = False
|
|
57
64
|
category = 'functional'
|
|
58
65
|
|
|
66
|
+
# Check for element not found errors
|
|
67
|
+
elif any(pattern in exception_lower for pattern in [
|
|
68
|
+
'element not found', 'no such element', 'locator not found',
|
|
69
|
+
'selector not found', 'element is not attached', 'element not visible',
|
|
70
|
+
'waiting for selector', 'strict mode violation'
|
|
71
|
+
]):
|
|
72
|
+
failure_type = 'ELEMENT_NOT_FOUND'
|
|
73
|
+
is_soft_fail = False
|
|
74
|
+
category = 'functional'
|
|
75
|
+
|
|
76
|
+
# Check for navigation/page load errors
|
|
77
|
+
elif any(pattern in exception_lower for pattern in [
|
|
78
|
+
'navigation', 'page.goto', 'net::err', 'failed to load',
|
|
79
|
+
'page crash', 'target closed', 'page did not load'
|
|
80
|
+
]):
|
|
81
|
+
failure_type = 'NAVIGATION_ERROR'
|
|
82
|
+
is_soft_fail = True
|
|
83
|
+
category = 'infrastructure'
|
|
84
|
+
|
|
85
|
+
# Check for JavaScript/script errors
|
|
86
|
+
elif any(pattern in exception_lower for pattern in [
|
|
87
|
+
'javascript error', 'script error', 'evaluation failed',
|
|
88
|
+
'uncaught exception', 'referenceerror', 'typeerror'
|
|
89
|
+
]) or 'Error' in exception_type and 'script' in exception_lower:
|
|
90
|
+
failure_type = 'SCRIPT_ERROR'
|
|
91
|
+
is_soft_fail = False
|
|
92
|
+
category = 'functional'
|
|
93
|
+
|
|
94
|
+
# Check for validation/data errors
|
|
95
|
+
elif any(pattern in exception_lower for pattern in [
|
|
96
|
+
'validation', 'invalid', 'expected', 'does not match',
|
|
97
|
+
'valueerror', 'keyerror', 'attributeerror'
|
|
98
|
+
]) or any(exc in exception_type for exc in ['ValueError', 'KeyError', 'AttributeError']):
|
|
99
|
+
failure_type = 'VALIDATION_ERROR'
|
|
100
|
+
is_soft_fail = False
|
|
101
|
+
category = 'functional'
|
|
102
|
+
|
|
59
103
|
# Check for UI timeouts (soft failures)
|
|
60
104
|
elif any(pattern in exception_message for pattern in ReportingConfig.SOFT_FAIL_PATTERNS):
|
|
61
105
|
failure_type = 'UI_TIMEOUT'
|
|
@@ -63,13 +107,13 @@ class FailureClassifier:
|
|
|
63
107
|
category = 'performance'
|
|
64
108
|
|
|
65
109
|
# Check for environment issues
|
|
66
|
-
elif any(pattern in
|
|
110
|
+
elif any(pattern in exception_lower for pattern in ReportingConfig.ENVIRONMENT_FAIL_PATTERNS):
|
|
67
111
|
failure_type = 'ENVIRONMENT'
|
|
68
112
|
is_soft_fail = True
|
|
69
113
|
category = 'infrastructure'
|
|
70
114
|
|
|
71
115
|
# Check for API timeouts
|
|
72
|
-
elif 'timeout' in
|
|
116
|
+
elif 'timeout' in exception_lower and duration_ms > ReportingConfig.TIMEOUT_API_MAX:
|
|
73
117
|
failure_type = 'API_TIMEOUT'
|
|
74
118
|
is_soft_fail = True
|
|
75
119
|
category = 'performance'
|
|
@@ -404,12 +404,16 @@ class Publisher:
|
|
|
404
404
|
# Real bugs (ASSERTION) need code fixes.
|
|
405
405
|
# Infrastructure issues (ENVIRONMENT, TIMEOUT) may not reflect quality.
|
|
406
406
|
type_labels = {
|
|
407
|
-
'ASSERTION':
|
|
408
|
-
'
|
|
409
|
-
'
|
|
410
|
-
'
|
|
411
|
-
'
|
|
412
|
-
'
|
|
407
|
+
'ASSERTION': 'Real bugs (assertion errors)',
|
|
408
|
+
'ELEMENT_NOT_FOUND': 'Element not found',
|
|
409
|
+
'VALIDATION_ERROR': 'Data validation errors',
|
|
410
|
+
'SCRIPT_ERROR': 'JavaScript/Script errors',
|
|
411
|
+
'UI_TIMEOUT': 'UI timeouts',
|
|
412
|
+
'API_TIMEOUT': 'API timeouts',
|
|
413
|
+
'NAVIGATION_ERROR': 'Navigation/Page load errors',
|
|
414
|
+
'ENVIRONMENT': 'Infrastructure / connection issues',
|
|
415
|
+
'FLAKY': 'Passed on retry (unstable)',
|
|
416
|
+
'UNKNOWN': 'Uncategorized',
|
|
413
417
|
}
|
|
414
418
|
failure_type_breakdown = [
|
|
415
419
|
{
|
|
@@ -491,6 +495,10 @@ class Publisher:
|
|
|
491
495
|
})
|
|
492
496
|
|
|
493
497
|
# Regression grouping
|
|
498
|
+
# Strip [TAG] suffix to group reruns with original runs
|
|
499
|
+
import re
|
|
500
|
+
base_suite = re.sub(r'\s*\[.*?\]\s*$', '', suite)
|
|
501
|
+
|
|
494
502
|
if tag not in regressions:
|
|
495
503
|
regressions[tag] = {'tag': tag, 'date': tag[:10], 'suites': [],
|
|
496
504
|
'total_tests': 0, 'passed': 0, 'failed': 0, 'pass_rate': 0,
|
|
@@ -499,21 +507,28 @@ class Publisher:
|
|
|
499
507
|
# Track the latest timestamp for this regression
|
|
500
508
|
if ts > reg.get('timestamp', ''):
|
|
501
509
|
reg['timestamp'] = ts
|
|
502
|
-
existing = next((s for s in reg['suites'] if s['suite'] ==
|
|
510
|
+
existing = next((s for s in reg['suites'] if s['suite'] == base_suite), None)
|
|
503
511
|
if existing:
|
|
512
|
+
# Accumulate stats for multiple runs of the same test
|
|
513
|
+
existing['total_tests'] += run.get('total_tests', 0)
|
|
514
|
+
existing['passed'] += run.get('passed', 0)
|
|
515
|
+
existing['failed'] += run.get('failed', 0)
|
|
516
|
+
existing['run_count'] = existing.get('run_count', 1) + 1
|
|
517
|
+
# Update to latest timestamp and run_id
|
|
504
518
|
if ts > existing.get('timestamp', ''):
|
|
505
|
-
existing
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
519
|
+
existing['timestamp'] = ts
|
|
520
|
+
existing['run_id'] = run.get('run_id')
|
|
521
|
+
# Recalculate pass rate based on accumulated totals
|
|
522
|
+
if existing['total_tests'] > 0:
|
|
523
|
+
existing['pass_rate'] = round((existing['passed'] / existing['total_tests']) * 100, 1)
|
|
510
524
|
else:
|
|
511
|
-
reg['suites'].append({'suite':
|
|
525
|
+
reg['suites'].append({'suite': base_suite, 'timestamp': ts,
|
|
512
526
|
'run_id': run.get('run_id'),
|
|
513
527
|
'pass_rate': run.get('pass_rate', 0),
|
|
514
528
|
'total_tests': run.get('total_tests', 0),
|
|
515
529
|
'passed': run.get('passed', 0),
|
|
516
|
-
'failed': run.get('failed', 0)
|
|
530
|
+
'failed': run.get('failed', 0),
|
|
531
|
+
'run_count': 1})
|
|
517
532
|
|
|
518
533
|
# Compute regression combined stats
|
|
519
534
|
for reg in regressions.values():
|
|
@@ -280,6 +280,7 @@
|
|
|
280
280
|
border-radius: 6px; cursor: pointer; position: relative;
|
|
281
281
|
overflow: hidden; transition: transform .1s, box-shadow .1s; }
|
|
282
282
|
.treemap-cell:hover { transform: scale(1.05); z-index: 1; box-shadow: 0 4px 12px rgba(0,0,0,.3); }
|
|
283
|
+
.treemap-cell-hidden { display: none; }
|
|
283
284
|
.treemap-name { font-size: 11px; font-weight: 600; color: #fff; opacity: .9;
|
|
284
285
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
285
286
|
.treemap-rate { font-size: 14px; font-weight: 800; color: #fff; }
|
|
@@ -554,69 +555,53 @@
|
|
|
554
555
|
const rc2 = rateColor(reg.pass_rate);
|
|
555
556
|
const total = lastReg ? lastReg.total_tests || 1 : 1;
|
|
556
557
|
|
|
557
|
-
//
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
if (!suiteGroups[s.suite]) {
|
|
561
|
-
suiteGroups[s.suite] = [];
|
|
562
|
-
}
|
|
563
|
-
suiteGroups[s.suite].push(s);
|
|
558
|
+
// Sort suites alphabetically
|
|
559
|
+
const sortedSuites = (reg.suites || []).slice().sort(function(a, b) {
|
|
560
|
+
return a.suite.localeCompare(b.suite);
|
|
564
561
|
});
|
|
565
562
|
|
|
566
|
-
const fileRows =
|
|
567
|
-
const
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
let totalFailed = 0;
|
|
573
|
-
let totalSkipped = 0;
|
|
574
|
-
let totalPassed = 0;
|
|
575
|
-
let avgPassRate = 0;
|
|
576
|
-
|
|
577
|
-
runs.forEach(function(run) {
|
|
578
|
-
totalTests += run.total_tests;
|
|
579
|
-
totalFailed += run.failed;
|
|
580
|
-
totalSkipped += (run.skipped || 0);
|
|
581
|
-
totalPassed += (run.total_tests - run.failed - (run.skipped || 0));
|
|
582
|
-
avgPassRate += run.pass_rate;
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
avgPassRate = avgPassRate / runCount;
|
|
586
|
-
|
|
587
|
-
// Use first run's ID for navigation
|
|
588
|
-
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;
|
|
589
569
|
|
|
590
570
|
const failedPct = totalTests > 0 ? (totalFailed / totalTests * 100) : 0;
|
|
591
|
-
const
|
|
592
|
-
const
|
|
571
|
+
const passedPct = totalTests > 0 ? (totalPassed / totalTests * 100) : 0;
|
|
572
|
+
const skippedPct = Math.max(0, 100 - failedPct - passedPct);
|
|
593
573
|
|
|
594
574
|
const runBadge = runCount > 1
|
|
595
575
|
? '<span class="run-count-badge" title="Ran ' + runCount + ' times">×' + runCount + '</span>'
|
|
596
576
|
: '';
|
|
597
577
|
|
|
598
578
|
const metaText = runCount > 1
|
|
599
|
-
? totalTests + ' tests total · ' + totalFailed + ' failed ·
|
|
579
|
+
? totalTests + ' tests total · ' + totalFailed + ' failed · accumulated across ' + runCount + ' runs'
|
|
600
580
|
: totalTests + ' tests · ' + totalFailed + ' failed';
|
|
601
581
|
|
|
602
|
-
|
|
603
|
-
|
|
582
|
+
// If multiple runs, navigate to history with search; otherwise go to single run
|
|
583
|
+
const clickHandler = runCount > 1
|
|
584
|
+
? 'onclick="navigateToHistoryWithSearch(\'' + s.suite + '\')"'
|
|
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>' +
|
|
604
589
|
'<div class="suite-meta">' + metaText + '</div>' +
|
|
605
590
|
'<div class="suite-bar">' +
|
|
606
|
-
'<div class="stacked-bar" title="Passed: ' + totalPassed + ' | Failed: ' + totalFailed +
|
|
591
|
+
'<div class="stacked-bar" title="Passed: ' + totalPassed + ' | Failed: ' + totalFailed + (runCount > 1 ? ' (accumulated across ' + runCount + ' runs)' : '') + '">' +
|
|
607
592
|
'<div class="stacked-green" style="width:' + passedPct.toFixed(1) + '%"></div>' +
|
|
608
593
|
'<div class="stacked-red" style="width:' + failedPct.toFixed(1) + '%"></div>' +
|
|
609
594
|
'<div class="stacked-grey" style="width:' + skippedPct.toFixed(1) + '%"></div>' +
|
|
610
595
|
'</div>' +
|
|
611
596
|
'</div>' +
|
|
612
|
-
'<div class="suite-rate ' + rateColor(
|
|
597
|
+
'<div class="suite-rate ' + rateColor(passRate) + '">' + passRate.toFixed(0) + '%</div>' +
|
|
613
598
|
'</div>';
|
|
614
599
|
}).join('');
|
|
615
600
|
|
|
616
601
|
const isOpen = i === 0;
|
|
617
602
|
const regDateTime = reg.timestamp ? fmtDateTime(reg.timestamp) : '';
|
|
618
603
|
const regTitle = regDateTime ? reg.tag + ' · ' + regDateTime : reg.tag;
|
|
619
|
-
const uniqueSuiteCount =
|
|
604
|
+
const uniqueSuiteCount = sortedSuites.length;
|
|
620
605
|
return '<div class="regression">' +
|
|
621
606
|
'<div class="reg-head" onclick="toggleReg(this)">' +
|
|
622
607
|
'<div>' +
|
|
@@ -678,6 +663,44 @@
|
|
|
678
663
|
if (runId) location.href = 'run.html?r=' + encodeURIComponent(runId) + '&p=' + encodeURIComponent(projectName);
|
|
679
664
|
}
|
|
680
665
|
|
|
666
|
+
function navigateToHistoryWithSearch(fileName) {
|
|
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
|
+
|
|
676
|
+
// Wait for tab to render, then set search value and trigger filter
|
|
677
|
+
setTimeout(function() {
|
|
678
|
+
const searchInput = document.getElementById('historySearch');
|
|
679
|
+
if (searchInput) {
|
|
680
|
+
searchInput.value = fileName;
|
|
681
|
+
filterHistory();
|
|
682
|
+
// Scroll to the table
|
|
683
|
+
searchInput.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
684
|
+
}
|
|
685
|
+
}, 100);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function toggleTreemapCells(btn) {
|
|
689
|
+
const treemap = btn.previousElementSibling;
|
|
690
|
+
const hiddenCells = treemap.querySelectorAll('.treemap-cell-hidden');
|
|
691
|
+
const isExpanded = hiddenCells.length > 0 && hiddenCells[0].style.display === 'flex';
|
|
692
|
+
|
|
693
|
+
hiddenCells.forEach(function(cell) {
|
|
694
|
+
cell.style.display = isExpanded ? 'none' : 'flex';
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
if (isExpanded) {
|
|
698
|
+
btn.innerHTML = 'Show ' + hiddenCells.length + ' more ▼';
|
|
699
|
+
} else {
|
|
700
|
+
btn.innerHTML = 'Show less ▲';
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
681
704
|
/* ═══════════════════════════════════════════════════
|
|
682
705
|
ANALYSIS TAB
|
|
683
706
|
═══════════════════════════════════════════════════ */
|
|
@@ -725,7 +748,7 @@
|
|
|
725
748
|
'<h3>Test File Reliability (Treemap)</h3>' +
|
|
726
749
|
'<p class="section-explain">Each block = one file. Size ∝ test count. Color = pass rate. Worst files at top-left.</p>' +
|
|
727
750
|
'</div>' +
|
|
728
|
-
'<div class="section-body
|
|
751
|
+
'<div class="section-body">' +
|
|
729
752
|
renderTreemap(a.file_health || []) +
|
|
730
753
|
'</div>' +
|
|
731
754
|
'</div>' +
|
|
@@ -794,7 +817,7 @@
|
|
|
794
817
|
trend.forEach(function(d) { dataMap[d.date] = d.pass_rate; });
|
|
795
818
|
|
|
796
819
|
// Calculate 15 weeks back from today
|
|
797
|
-
const today = new Date(
|
|
820
|
+
const today = new Date();
|
|
798
821
|
const dayOfWeek = today.getDay(); // 0=Sun
|
|
799
822
|
const weeksBack = 15;
|
|
800
823
|
const totalDays = weeksBack * 7;
|
|
@@ -870,20 +893,26 @@
|
|
|
870
893
|
// Sort worst first
|
|
871
894
|
const sorted = files.slice().sort(function(a, b) { return a.pass_rate - b.pass_rate; });
|
|
872
895
|
const maxTests = Math.max.apply(null, sorted.map(function(f) { return f.total_runs || 1; }));
|
|
896
|
+
const limit = 12;
|
|
873
897
|
|
|
874
|
-
const cells = sorted.map(function(f) {
|
|
898
|
+
const cells = sorted.map(function(f, i) {
|
|
875
899
|
const ratio = (f.total_runs || 1) / maxTests;
|
|
876
900
|
const width = Math.round(80 + ratio * 160); // 80..240
|
|
877
901
|
var bg = f.pass_rate >= 95 ? '#276749' : f.pass_rate >= 80 ? '#975a16' : '#9b2c2c';
|
|
878
902
|
var bgLight = f.pass_rate >= 95 ? '#38a169' : f.pass_rate >= 80 ? '#d69e2e' : '#e53e3e';
|
|
879
|
-
const
|
|
880
|
-
|
|
903
|
+
const hiddenClass = i >= limit ? ' treemap-cell-hidden' : '';
|
|
904
|
+
const clickAttr = ' onclick="navigateToHistoryWithSearch(\'' + f.file_name + '\')" style="cursor:pointer;"';
|
|
905
|
+
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 + '>' +
|
|
881
906
|
'<div class="treemap-rate">' + f.pass_rate.toFixed(0) + '%</div>' +
|
|
882
907
|
'<div class="treemap-name">' + f.file_name + '</div>' +
|
|
883
908
|
'</div>';
|
|
884
909
|
}).join('');
|
|
885
910
|
|
|
886
|
-
|
|
911
|
+
const expandBtn = sorted.length > limit
|
|
912
|
+
? '<div class="table-expand-btn" onclick="toggleTreemapCells(this)">Show ' + (sorted.length - limit) + ' more ▼</div>'
|
|
913
|
+
: '';
|
|
914
|
+
|
|
915
|
+
return '<div class="treemap">' + cells + '</div>' + expandBtn;
|
|
887
916
|
}
|
|
888
917
|
|
|
889
918
|
function renderFlakyTable(tests) {
|
|
@@ -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.
|
|
12
|
+
version='3.7.3',
|
|
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",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|