pytest-html-plus 0.4.5__py3-none-any.whl → 0.4.7__py3-none-any.whl
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.
- pytest_html_plus/compute_filter_counts.py +8 -1
- pytest_html_plus/generate_html_report.py +54 -4
- pytest_html_plus/plugin.py +23 -8
- {pytest_html_plus-0.4.5.dist-info → pytest_html_plus-0.4.7.dist-info}/METADATA +1 -1
- {pytest_html_plus-0.4.5.dist-info → pytest_html_plus-0.4.7.dist-info}/RECORD +8 -8
- {pytest_html_plus-0.4.5.dist-info → pytest_html_plus-0.4.7.dist-info}/LICENSE +0 -0
- {pytest_html_plus-0.4.5.dist-info → pytest_html_plus-0.4.7.dist-info}/WHEEL +0 -0
- {pytest_html_plus-0.4.5.dist-info → pytest_html_plus-0.4.7.dist-info}/entry_points.txt +0 -0
|
@@ -12,6 +12,8 @@ def compute_filter_count(results):
|
|
|
12
12
|
|
|
13
13
|
if status == "failed" and not flaky:
|
|
14
14
|
filters["failed"] += 1
|
|
15
|
+
if status == "error":
|
|
16
|
+
filters["error"] += 1
|
|
15
17
|
if flaky:
|
|
16
18
|
filters["flaky"] += 1
|
|
17
19
|
if status == "skipped":
|
|
@@ -24,7 +26,12 @@ def compute_filter_count(results):
|
|
|
24
26
|
|
|
25
27
|
total = len(results)
|
|
26
28
|
filters["total"] = total
|
|
27
|
-
filters["passed"] =
|
|
29
|
+
filters["passed"] = (
|
|
30
|
+
total
|
|
31
|
+
- filters["failed"]
|
|
32
|
+
- filters["skipped"]
|
|
33
|
+
- filters["error"]
|
|
34
|
+
)
|
|
28
35
|
|
|
29
36
|
filters["marker_counts"] = dict(marker_counts)
|
|
30
37
|
return dict(filters)
|
|
@@ -190,7 +190,11 @@ class JSONReporter:
|
|
|
190
190
|
.header.passed {{ background: #e6f4ea; color: #2f7a33; }}
|
|
191
191
|
.header.failed {{ background: #fdecea; color: #a83232; }}
|
|
192
192
|
.header.skipped {{ background: #fff8e1; color: #b36b00; }}
|
|
193
|
-
.header.error {{
|
|
193
|
+
.header.error {{
|
|
194
|
+
background: #fdecea; /* light red / pink */
|
|
195
|
+
color: #b71c1c; /* deep red */
|
|
196
|
+
border-left: 4px solid #d32f2f;
|
|
197
|
+
}}
|
|
194
198
|
.details {{ padding: 0.5rem 1rem; display: none; border-top: 1px solid #ddd; }}
|
|
195
199
|
.toggle::before {{ content: "▶"; display: inline-block; margin-right: 0.5rem; transition: transform 0.3s ease; }}
|
|
196
200
|
.header.expanded .toggle::before {{ transform: rotate(90deg); }}
|
|
@@ -364,21 +368,23 @@ class JSONReporter:
|
|
|
364
368
|
const flakyCheckbox = document.getElementById('flakyOnlyCheckbox');
|
|
365
369
|
const testsContainer = document.getElementById('tests-container');
|
|
366
370
|
const testElements = Array.from(testsContainer.querySelectorAll('.test'));
|
|
371
|
+
const errorCheckbox = document.getElementById('errorOnlyCheckbox');
|
|
367
372
|
|
|
368
373
|
if (longestCheckbox.checked) {{
|
|
369
374
|
failedCheckbox.checked = false;
|
|
370
375
|
skippedCheckbox.checked = false;
|
|
371
376
|
untrackedCheckbox.checked = false;
|
|
372
377
|
flakyCheckbox.checked = false;
|
|
378
|
+
errorCheckbox.checked = false;
|
|
373
379
|
// Re-enable all tests before sorting
|
|
374
380
|
testElements.forEach(el => el.style.display = 'block');
|
|
375
381
|
|
|
376
382
|
// Sort and reorder
|
|
377
383
|
testElements.sort((a, b) => {{
|
|
378
384
|
const aDuration = parseFloat(
|
|
379
|
-
a.querySelector('.timestamp').textContent.replace(/[
|
|
385
|
+
a.querySelector('.timestamp').textContent.replace(/[^\\d.]/g, '')
|
|
380
386
|
) || 0;
|
|
381
|
-
const bDuration = parseFloat(b.querySelector('.timestamp').textContent.replace(/[
|
|
387
|
+
const bDuration = parseFloat(b.querySelector('.timestamp').textContent.replace(/[^\\d.]/g, '')) || 0;
|
|
382
388
|
return bDuration - aDuration;
|
|
383
389
|
}});
|
|
384
390
|
|
|
@@ -395,11 +401,13 @@ class JSONReporter:
|
|
|
395
401
|
const skippedCheckbox = document.getElementById('skippedOnlyCheckbox');
|
|
396
402
|
const failedCheckbox = document.getElementById('failedOnlyCheckbox');
|
|
397
403
|
const flakyCheckbox = document.getElementById('flakyOnlyCheckbox');
|
|
404
|
+
const errorCheckbox = document.getElementById('errorOnlyCheckbox');
|
|
398
405
|
|
|
399
406
|
if (checkbox.checked) {{
|
|
400
407
|
longestCheckbox.checked = false;
|
|
401
408
|
skippedCheckbox.checked = false;
|
|
402
409
|
failedCheckbox.checked = false;
|
|
410
|
+
errorCheckbox.checked = false;
|
|
403
411
|
flakyCheckbox.checked = false
|
|
404
412
|
testCards.forEach(card => {{
|
|
405
413
|
const hasLink = card.querySelector('a[href]');
|
|
@@ -422,11 +430,13 @@ class JSONReporter:
|
|
|
422
430
|
const untrackedCheckbox = document.getElementById('untrackedOnlyCheckbox');
|
|
423
431
|
const skippedCheckbox = document.getElementById('skippedOnlyCheckbox');
|
|
424
432
|
const failedCheckbox = document.getElementById('failedOnlyCheckbox');
|
|
433
|
+
const errorCheckbox = document.getElementById('errorOnlyCheckbox');
|
|
425
434
|
const testElements = document.querySelectorAll('.test');
|
|
426
435
|
if (checkbox.checked) {{
|
|
427
436
|
longestCheckbox.checked = false;
|
|
428
437
|
skippedCheckbox.checked = false;
|
|
429
438
|
failedCheckbox.checked = false;
|
|
439
|
+
errorCheckbox.checked = false;
|
|
430
440
|
untrackedCheckbox.checked = false;
|
|
431
441
|
testElements.forEach(el => {{
|
|
432
442
|
const isFlaky = el.querySelector('.is-flaky') !== null;
|
|
@@ -444,12 +454,14 @@ class JSONReporter:
|
|
|
444
454
|
const untrackedCheckbox = document.getElementById('untrackedOnlyCheckbox');
|
|
445
455
|
const skippedCheckbox = document.getElementById('skippedOnlyCheckbox');
|
|
446
456
|
const flakyCheckbox = document.getElementById('flakyOnlyCheckbox');
|
|
457
|
+
const errorCheckbox = document.getElementById('errorOnlyCheckbox');
|
|
447
458
|
const testElements = document.querySelectorAll('.test');
|
|
448
459
|
if (failedCheckbox.checked) {{
|
|
449
460
|
longestCheckbox.checked = false;
|
|
450
461
|
untrackedCheckbox.checked = false;
|
|
451
462
|
skippedCheckbox.checked = false;
|
|
452
463
|
flakyCheckbox.checked = false;
|
|
464
|
+
errorCheckbox.checked = false;
|
|
453
465
|
testElements.forEach(el => {{
|
|
454
466
|
const header = el.querySelector('.header');
|
|
455
467
|
const isFailed = header.classList.contains('failed');
|
|
@@ -466,6 +478,7 @@ class JSONReporter:
|
|
|
466
478
|
const failedCheckbox = document.getElementById('failedOnlyCheckbox');
|
|
467
479
|
const untrackedCheckbox = document.getElementById('untrackedOnlyCheckbox');
|
|
468
480
|
const flakyCheckbox = document.getElementById('flakyOnlyCheckbox');
|
|
481
|
+
const errorCheckbox = document.getElementById('errorOnlyCheckbox');
|
|
469
482
|
const testElements = document.querySelectorAll('.test');
|
|
470
483
|
|
|
471
484
|
if (skippedCheckbox.checked) {{
|
|
@@ -473,6 +486,7 @@ class JSONReporter:
|
|
|
473
486
|
failedCheckbox.checked = false;
|
|
474
487
|
untrackedCheckbox.checked = false;
|
|
475
488
|
flakyCheckbox.checked = false;
|
|
489
|
+
errorCheckbox.checked = false;
|
|
476
490
|
testElements.forEach(el => {{
|
|
477
491
|
const header = el.querySelector('.header');
|
|
478
492
|
const isSkipped = header.classList.contains('skipped');
|
|
@@ -485,6 +499,32 @@ class JSONReporter:
|
|
|
485
499
|
filterByMarkers(); // Reapply marker filter
|
|
486
500
|
}}
|
|
487
501
|
|
|
502
|
+
function toggleErrorOnly(errorCheckbox) {{
|
|
503
|
+
const longestCheckbox = document.getElementById('longestOnlyCheckbox');
|
|
504
|
+
const failedCheckbox = document.getElementById('failedOnlyCheckbox');
|
|
505
|
+
const untrackedCheckbox = document.getElementById('untrackedOnlyCheckbox');
|
|
506
|
+
const flakyCheckbox = document.getElementById('flakyOnlyCheckbox');
|
|
507
|
+
const skippedCheckbox = document.getElementById('skippedOnlyCheckbox');
|
|
508
|
+
const testElements = document.querySelectorAll('.test');
|
|
509
|
+
|
|
510
|
+
if (errorCheckbox.checked) {{
|
|
511
|
+
longestCheckbox.checked = false;
|
|
512
|
+
failedCheckbox.checked = false;
|
|
513
|
+
untrackedCheckbox.checked = false;
|
|
514
|
+
flakyCheckbox.checked = false;
|
|
515
|
+
skippedCheckbox.checked = false;
|
|
516
|
+
testElements.forEach(el => {{
|
|
517
|
+
const header = el.querySelector('.header');
|
|
518
|
+
const isError = header.classList.contains('error');
|
|
519
|
+
el.style.display = isError ? 'block' : 'none';
|
|
520
|
+
}});
|
|
521
|
+
}} else {{
|
|
522
|
+
testElements.forEach(el => el.style.display = 'block');
|
|
523
|
+
}}
|
|
524
|
+
|
|
525
|
+
filterByMarkers();
|
|
526
|
+
}}
|
|
527
|
+
|
|
488
528
|
function initializeUniversalSearch() {{
|
|
489
529
|
const searchInput = document.getElementById('universal-search');
|
|
490
530
|
if (!searchInput) return;
|
|
@@ -506,19 +546,22 @@ class JSONReporter:
|
|
|
506
546
|
const selected = Array.from(document.querySelectorAll('.marker-filter input[type="checkbox"]:checked')).map(cb => cb.value);
|
|
507
547
|
const failedOnly = document.getElementById('failedOnlyCheckbox').checked;
|
|
508
548
|
const skippedOnly = document.getElementById('skippedOnlyCheckbox').checked;
|
|
549
|
+
const errorOnly = document.getElementById('errorOnlyCheckbox').checked;
|
|
509
550
|
|
|
510
551
|
document.querySelectorAll('.test').forEach(el => {{
|
|
511
552
|
const header = el.querySelector('.header');
|
|
512
553
|
const markers = el.getAttribute('data-markers').split(',');
|
|
513
554
|
const isFailed = header.classList.contains('failed');
|
|
514
555
|
const isSkipped = header.classList.contains('skipped');
|
|
556
|
+
const isError = header.classList.contains('error');
|
|
515
557
|
|
|
516
558
|
const showAllMarkers = selected.length === 0;
|
|
517
559
|
const matchesMarker = showAllMarkers || selected.some(m => markers.includes(m));
|
|
518
560
|
const matchesFailed = !failedOnly || isFailed;
|
|
519
561
|
const matchesSkipped = !skippedOnly || isSkipped;
|
|
562
|
+
const matchesError = !errorOnly || isError;
|
|
520
563
|
|
|
521
|
-
el.style.display = (matchesMarker && matchesFailed && matchesSkipped) ? 'block' : 'none';
|
|
564
|
+
el.style.display = (matchesMarker && matchesFailed && matchesSkipped && matchesError) ? 'block' : 'none';
|
|
522
565
|
}});
|
|
523
566
|
}}
|
|
524
567
|
|
|
@@ -529,9 +572,11 @@ class JSONReporter:
|
|
|
529
572
|
const skippedCheckbox = document.getElementById('skippedOnlyCheckbox');
|
|
530
573
|
const untrackedCheckbox = document.getElementById('untrackedOnlyCheckbox');
|
|
531
574
|
const flakyCheckbox = document.getElementById('flakyOnlyCheckbox');
|
|
575
|
+
const errorCheckbox = document.getElementById('errorOnlyCheckbox');
|
|
532
576
|
failedCheckbox.checked = true;
|
|
533
577
|
toggleFailedOnly(failedCheckbox);
|
|
534
578
|
failedCheckbox.addEventListener('change', () => toggleFailedOnly(failedCheckbox));
|
|
579
|
+
errorCheckbox.addEventListener('change', () => toggleErrorOnly(errorCheckbox));
|
|
535
580
|
longestCheckbox.addEventListener('change', () => toggleFilter(longestCheckbox));
|
|
536
581
|
skippedCheckbox.addEventListener('change', () => toggleSkippedOnly(skippedCheckbox));
|
|
537
582
|
untrackedCheckbox.addEventListener('change', () => toggleUntrackedOnly(untrackedCheckbox));
|
|
@@ -562,6 +607,10 @@ class JSONReporter:
|
|
|
562
607
|
<input type="checkbox" id="failedOnlyCheckbox" />
|
|
563
608
|
Show only failed tests (<span>{self.filters.get("failed", 0)}</span>)
|
|
564
609
|
</label>
|
|
610
|
+
<label>
|
|
611
|
+
<input type="checkbox" id="errorOnlyCheckbox" />
|
|
612
|
+
Show only error tests (<span>{self.filters.get("error", 0)}</span>)
|
|
613
|
+
</label>
|
|
565
614
|
<label>
|
|
566
615
|
<input type="checkbox" id="skippedOnlyCheckbox" />
|
|
567
616
|
Show only skipped tests (<span>{self.filters.get("skipped", 0)}</span>)
|
|
@@ -645,6 +694,7 @@ class JSONReporter:
|
|
|
645
694
|
status_class = (
|
|
646
695
|
'passed' if test['status'] == 'passed' else
|
|
647
696
|
'failed' if test['status'] == 'failed' else
|
|
697
|
+
'error' if test['status'] == 'error' else
|
|
648
698
|
'skipped'
|
|
649
699
|
)
|
|
650
700
|
screenshot_path = self.find_screenshot_and_copy(test['test'])
|
pytest_html_plus/plugin.py
CHANGED
|
@@ -48,21 +48,33 @@ def pytest_runtest_makereport(item, call):
|
|
|
48
48
|
error = extract_error_block(error=full_error)
|
|
49
49
|
trace = extract_trace_block(str(report.longrepr))
|
|
50
50
|
|
|
51
|
-
if
|
|
51
|
+
if (
|
|
52
|
+
report.when == "call"
|
|
53
|
+
or (report.when == "setup" and report.skipped)
|
|
54
|
+
or (report.when in ("setup", "teardown") and report.failed)
|
|
55
|
+
):
|
|
52
56
|
config = item.config
|
|
53
57
|
capture_option = config.getoption("--capture-screenshots")
|
|
54
58
|
|
|
55
59
|
caplog_text = None
|
|
56
|
-
if "
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
if report.when in ("call", "setup"):
|
|
61
|
+
if "caplog" in item.funcargs:
|
|
62
|
+
caplog = item.funcargs["caplog"]
|
|
63
|
+
try:
|
|
64
|
+
caplog_text = "\n".join(caplog.messages) if caplog.messages else None
|
|
65
|
+
except KeyError:
|
|
66
|
+
caplog_text = None
|
|
59
67
|
|
|
60
68
|
screenshot_path = config.getoption("--screenshots") or "screenshots"
|
|
61
69
|
|
|
62
70
|
should_capture_screenshot = (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
report.when in ("setup", "call") and
|
|
72
|
+
(
|
|
73
|
+
capture_option == "all" or
|
|
74
|
+
(capture_option == "failed" and report.outcome == "failed")
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
66
78
|
|
|
67
79
|
if should_capture_screenshot:
|
|
68
80
|
driver = resolve_driver(item)
|
|
@@ -75,10 +87,13 @@ def pytest_runtest_makereport(item, call):
|
|
|
75
87
|
reporter = config._json_reporter
|
|
76
88
|
worker_id = os.getenv("PYTEST_XDIST_WORKER") or "main"
|
|
77
89
|
test_name = "".join(c if c.isalnum() else "_" for c in item.name)
|
|
90
|
+
status = report.outcome
|
|
91
|
+
if report.when in ("setup", "teardown") and report.failed:
|
|
92
|
+
status = "error"
|
|
78
93
|
reporter.log_result(
|
|
79
94
|
test_name=test_name,
|
|
80
95
|
nodeid=item.nodeid,
|
|
81
|
-
status=
|
|
96
|
+
status=status,
|
|
82
97
|
duration=report.duration,
|
|
83
98
|
error=error,
|
|
84
99
|
trace=trace if report.failed else None,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pytest-html-plus
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.7
|
|
4
4
|
Summary: Generate Actionable, automatic screenshots, unified Pytest HTML report in less than 3 seconds — no hooks, merge plugins, no config, xdist-ready.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: pytest,pytest-html-plus,pytest-plugin,html-test-report,beautiful-test-report,shareable-test-results,test-report,test-results,unit-test-report,functional-test-report,test-summary,reporting,python-testing,automated-testing,test-runner,report-generator,continuous-integration,ci-cd,github-actions,jenkins,pytest-html,pytest-report
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
pytest_html_plus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
pytest_html_plus/compute_filter_counts.py,sha256=
|
|
2
|
+
pytest_html_plus/compute_filter_counts.py,sha256=FznjPos-bgYSi8bERG_WbaZWMQDIAfoYP4QTRbe8vHw,963
|
|
3
3
|
pytest_html_plus/compute_report_metadata.py,sha256=k35kCSCAc8Fj9zgzzM49kh0h1NV2Ope9Lr8JHG_1jlU,783
|
|
4
4
|
pytest_html_plus/extract_link.py,sha256=1XtqkbZkrl1YgPGtFc3Ss1XFWm4LNLuO3Hi407HNChk,340
|
|
5
|
-
pytest_html_plus/generate_html_report.py,sha256=
|
|
5
|
+
pytest_html_plus/generate_html_report.py,sha256=d7KHgwZ9BolItvk0sBzD4wWUA2-fJ56c49RxMmzLQII,36655
|
|
6
6
|
pytest_html_plus/json_merge.py,sha256=EQu23n8z4a0FxvFOlvYoDlX1jrZYjWwsCL-6oxfDREw,1868
|
|
7
7
|
pytest_html_plus/json_to_xml_converter.py,sha256=5E6BGMw4FaJhoYDUZQ6dF8NWqAeXrcuSrlySy81IrVE,3468
|
|
8
|
-
pytest_html_plus/plugin.py,sha256=
|
|
8
|
+
pytest_html_plus/plugin.py,sha256=u9CEf-A6W39LS3I5XulK3joRP87p_zQ7NK32mi-LkKs,11957
|
|
9
9
|
pytest_html_plus/resolver_driver.py,sha256=hehokRO5EuG4Ti8MyaSBW4xaHduUK3Ot2jbw1YpIsnk,1287
|
|
10
10
|
pytest_html_plus/send_email_report.py,sha256=qDnAPy1Jc4favOvXg6MT0ttNmSsG3MXGQX1Hu7wN1Go,2622
|
|
11
11
|
pytest_html_plus/utils.py,sha256=DkGzQDFG_SUQHo97_lBnqTyeNxlo3NlFBZ2X_ooZRdM,2205
|
|
12
|
-
pytest_html_plus-0.4.
|
|
13
|
-
pytest_html_plus-0.4.
|
|
14
|
-
pytest_html_plus-0.4.
|
|
15
|
-
pytest_html_plus-0.4.
|
|
16
|
-
pytest_html_plus-0.4.
|
|
12
|
+
pytest_html_plus-0.4.7.dist-info/LICENSE,sha256=8flU0ghLnuKK8qZv9pJ1xhXiKQdUncg0OvqMwYhGWzY,1090
|
|
13
|
+
pytest_html_plus-0.4.7.dist-info/METADATA,sha256=DOh06HMYRkIYFzmNJiWaQQXleQyb3-yBbZ5ZDiiNL2s,8504
|
|
14
|
+
pytest_html_plus-0.4.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
15
|
+
pytest_html_plus-0.4.7.dist-info/entry_points.txt,sha256=eSbV9G_n_XnR4wemMxKHSSuPMcBSoHbE1WuC2IQp4Zk,53
|
|
16
|
+
pytest_html_plus-0.4.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|