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.
@@ -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"] = total - filters["failed"] - filters["skipped"] # estimate
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 {{ background: #f0f0f0; color: #f0f0f0; }}
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(/[^\d.]/g, '')
385
+ a.querySelector('.timestamp').textContent.replace(/[^\\d.]/g, '')
380
386
  ) || 0;
381
- const bDuration = parseFloat(b.querySelector('.timestamp').textContent.replace(/[^\d.]/g, '')) || 0;
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'])
@@ -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 report.when == "call" or (report.when == "setup" and report.skipped):
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 "caplog" in item.funcargs:
57
- caplog = item.funcargs["caplog"]
58
- caplog_text = "\n".join(caplog.messages) if caplog.messages else None
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
- capture_option == "all" or
64
- (capture_option == "failed" and report.outcome == "failed")
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=report.outcome,
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.5
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=A1rLdY9XWiIunpIs3VlmJsQ5JkKcWbVdj9r1Xg_4g6o,872
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=UFDX70ZIAnO9g75HszGKenjM0uvoM1rTNMwG2esLVP4,34253
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=sxCai3u018MRIhwYYsTjFv65Ss7OZOYM_xREU7XNn9E,11526
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.5.dist-info/LICENSE,sha256=8flU0ghLnuKK8qZv9pJ1xhXiKQdUncg0OvqMwYhGWzY,1090
13
- pytest_html_plus-0.4.5.dist-info/METADATA,sha256=vJ_eLd0-EtJlD-3MBIaIn4xfbk6Qe51L9Ao-tmkpFrc,8504
14
- pytest_html_plus-0.4.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
15
- pytest_html_plus-0.4.5.dist-info/entry_points.txt,sha256=eSbV9G_n_XnR4wemMxKHSSuPMcBSoHbE1WuC2IQp4Zk,53
16
- pytest_html_plus-0.4.5.dist-info/RECORD,,
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,,