pytest-html-plus 0.4.9__py3-none-any.whl → 0.5.0__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.
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import os
2
3
  from datetime import datetime
3
4
 
4
5
  from pytest_html_plus.utils import is_main_worker, get_env_marker, get_report_title, \
@@ -8,6 +9,7 @@ from pytest_html_plus.utils import is_main_worker, get_env_marker, get_report_ti
8
9
  def write_plus_metadata_if_main_worker(config, report_path, output_path="plus_metadata.json", **kwargs):
9
10
  if not is_main_worker():
10
11
  return
12
+ metadata_path = os.path.join(report_path, output_path)
11
13
  branch = kwargs.get("git_branch", "Pass --git-branch to populate git metadata")
12
14
  commit = kwargs.get("git_commit", "Pass --git-commit to populate git metadata")
13
15
  metadata = {
@@ -18,5 +20,7 @@ def write_plus_metadata_if_main_worker(config, report_path, output_path="plus_me
18
20
  "python_version": get_python_version(),
19
21
  "generated_at": datetime.now().isoformat()
20
22
  }
21
- with open(output_path, "w") as f:
23
+ os.makedirs(report_path, exist_ok=True)
24
+
25
+ with open(metadata_path, "w") as f:
22
26
  json.dump(metadata, f, indent=2)
@@ -149,21 +149,6 @@ class JSONReporter:
149
149
  return os.path.join("screenshots", file)
150
150
  return None
151
151
 
152
- def copy_json_report(self):
153
- """
154
- Copies the source JSON report into the report output directory.
155
- """
156
- if not os.path.exists(self.report_path):
157
- return
158
-
159
- os.makedirs(self.output_dir, exist_ok=True)
160
-
161
- dest_path = os.path.join(
162
- self.output_dir,
163
- os.path.basename(self.report_path)
164
- )
165
-
166
- shutil.copyfile(self.report_path, dest_path)
167
152
 
168
153
  def generate_copy_button(self, content, label):
169
154
  if isinstance(content, list):
@@ -186,7 +171,6 @@ class JSONReporter:
186
171
 
187
172
  def generate_html_report(self):
188
173
  # Extract all unique markers
189
- self.copy_json_report()
190
174
  ignore_markers = {"link"}
191
175
  all_markers = set()
192
176
  for test in self.results:
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
 
6
6
  import pytest
7
7
  import json
8
+ import sys
8
9
 
9
10
  from pytest_html_plus.compute_report_metadata import write_plus_metadata_if_main_worker
10
11
  from pytest_html_plus.extract_link import extract_links_from_item
@@ -36,6 +37,19 @@ def pytest_runtest_setup(item):
36
37
  if "caplog" not in item.fixturenames:
37
38
  item.fixturenames.append("caplog")
38
39
 
40
+ import warnings
41
+
42
+ def _warn_python_39_deprecation():
43
+ if sys.version_info[:2] == (3, 9):
44
+ warnings.warn(
45
+ "pytest-html-plus is not actively tested in Python 3.9 and support will be dropped in v0.5.1. "
46
+ "Please upgrade to Python 3.10+.",
47
+ DeprecationWarning,
48
+ stacklevel=2,
49
+ )
50
+
51
+ _warn_python_39_deprecation()
52
+
39
53
 
40
54
  @pytest.hookimpl(hookwrapper=True)
41
55
  def pytest_runtest_makereport(item, call):
@@ -113,73 +127,117 @@ import subprocess
113
127
 
114
128
 
115
129
  def pytest_sessionfinish(session, exitstatus):
116
- reporter = session.config._json_reporter
117
-
118
- json_path = session.config.getoption("--json-report") or "final_report.json"
119
- html_output = session.config.getoption("--html-output") or "report_output"
120
- screenshots_path = session.config.getoption("--screenshots") or "screenshots"
121
- xml_path = session.config.getoption("--xml-report") or "final_xml.xml"
122
-
123
- is_worker = os.getenv("PYTEST_XDIST_WORKER") is not None
124
- try:
125
- is_xdist = bool(session.config.getoption("-n"))
126
- except ValueError:
127
- is_xdist = False
128
-
129
- if is_worker:
130
- reporter.write_report()
131
- print(f"Worker {os.getenv('PYTEST_XDIST_WORKER')} finished – skipping merge.")
132
- return
133
-
134
- if is_xdist:
135
- merge_json_reports(directory=".pytest_worker_jsons", output_path=json_path)
136
- else:
137
- reporter.results = mark_flaky_tests(reporter.results)
138
- reporter.write_report()
139
-
140
- script_path = os.path.join(os.path.dirname(__file__), "generate_html_report.py")
141
-
142
- if not os.path.exists(script_path):
143
- logger.warning(f"Report generation script not found at {script_path}. Skipping HTML report generation.")
144
- return
130
+ reporter = session.config._json_reporter
145
131
 
146
- try:
147
- subprocess.run([
148
- sys.executable,
149
- script_path,
150
- "--report", json_path,
151
- "--screenshots", screenshots_path,
152
- "--output", html_output
153
- ], check=True)
154
- except Exception as e:
155
- raise RuntimeError(f"Exception during HTML report generation: {e}") from e
156
-
157
- if session.config.getoption("--plus-email"):
158
- print("📬 --plus-email enabled. Sending report...")
159
- try:
160
- config = load_email_env()
161
- config["report_path"] = f"{html_output}"
162
- sender = EmailSender(config, report_path=config["report_path"])
163
- sender.send()
164
- except Exception as e:
165
- raise RuntimeError(f"Failed to send email: {e}") from e
132
+ raw_json_report = session.config.getoption("--json-report")
133
+ html_output = session.config.getoption("--html-output") or "report_output"
134
+ screenshots_path = session.config.getoption("--screenshots") or "screenshots"
135
+ raw_xml_report = session.config.getoption("--xml-report")
136
+
137
+ # ---- XML filename validation ----
138
+ if raw_xml_report:
139
+ if os.path.basename(raw_xml_report) != raw_xml_report:
140
+ raise pytest.UsageError("--xml-report must be a filename, not a path")
141
+ xml_filename = raw_xml_report
142
+ else:
143
+ xml_filename = "final_xml.xml"
144
+
145
+ xml_path = os.path.join(html_output, xml_filename)
146
+
147
+ os.makedirs(html_output, exist_ok=True)
148
+
149
+ # ---- JSON filename validation ----
150
+ if raw_json_report:
151
+ if os.path.basename(raw_json_report) != raw_json_report:
152
+ raise pytest.UsageError("--json-report must be a filename, not a path")
153
+ json_filename = raw_json_report
154
+ else:
155
+ json_filename = "final_report.json"
156
+
157
+ json_path = os.path.join(html_output, json_filename)
158
+ reporter.report_path = json_path
159
+
160
+ is_worker = os.getenv("PYTEST_XDIST_WORKER") is not None
161
+ try:
162
+ is_xdist = bool(session.config.getoption("-n"))
163
+ except ValueError:
164
+ is_xdist = False
165
+
166
+ # ---- Worker behavior ----
167
+ if is_worker:
168
+ worker_id = os.getenv("PYTEST_XDIST_WORKER")
169
+ worker_dir = ".pytest_worker_jsons"
170
+ os.makedirs(worker_dir, exist_ok=True)
171
+
172
+ reporter.report_path = os.path.join(
173
+ worker_dir,
174
+ f"{worker_id}.json"
175
+ )
166
176
 
167
- open_html_report(report_path=f"{html_output}/report.html",json_path=json_path, config=session.config)
177
+ reporter.write_report()
178
+ return
168
179
 
169
- if session.config.getoption("--generate-xml"):
170
- try:
171
- json_path = reporter.report_path
172
- convert_json_to_junit_xml(json_path, xml_path)
173
- print(f"XML report generated: {xml_path}")
174
- except Exception as e:
175
- raise RuntimeError(f"Failed to generate XML report: {e}") from e
180
+ # ---- Controller behavior ----
181
+ if is_xdist:
182
+ merge_json_reports(directory=".pytest_worker_jsons", output_path=json_path)
183
+ else:
184
+ reporter.results = mark_flaky_tests(reporter.results)
185
+ reporter.write_report()
176
186
 
187
+ script_path = os.path.join(os.path.dirname(__file__), "generate_html_report.py")
188
+ if not os.path.exists(script_path):
189
+ logger.warning(
190
+ f"Report generation script not found at {script_path}. Skipping HTML report generation."
191
+ )
192
+ return
193
+
194
+ try:
195
+ subprocess.run([
196
+ sys.executable,
197
+ script_path,
198
+ "--report", json_path,
199
+ "--screenshots", screenshots_path,
200
+ "--output", html_output
201
+ ], check=True)
202
+ except Exception as e:
203
+ raise RuntimeError(f"Exception during HTML report generation: {e}") from e
204
+
205
+ # ---- Generate XML ----
206
+ if session.config.getoption("--generate-xml"):
207
+ try:
208
+ convert_json_to_junit_xml(json_path, xml_path)
209
+ print(f"XML report generated: {xml_path}")
210
+ except Exception as e:
211
+ raise RuntimeError(f"Failed to generate XML report: {e}") from e
212
+
213
+ if not os.getenv("PYTEST_XDIST_WORKER"):
214
+ if os.path.exists(screenshots_path):
215
+ try:
216
+ shutil.rmtree(screenshots_path)
217
+ except Exception:
218
+ logger.warning("Could not clean up screenshots directory")
219
+
220
+ if session.config.getoption("--plus-email"):
221
+ try:
222
+ config = load_email_env()
223
+ config["report_path"] = html_output
224
+ sender = EmailSender(config, report_path=html_output)
225
+ sender.send()
226
+ except Exception as e:
227
+ raise RuntimeError(f"Failed to send email: {e}") from e
228
+
229
+ # ---- Open report (controller only) ----
230
+ open_html_report(
231
+ report_path=os.path.join(html_output, "report.html"),
232
+ json_path=json_path,
233
+ config=session.config
234
+ )
177
235
 
178
236
  def pytest_sessionstart(session):
179
237
  html_output = session.config.getoption("--html-output") or "report_output"
180
238
  git_branch = session.config.getoption("--git-branch") or "Pass --git-branch to populate git metadata"
181
239
  git_commit = session.config.getoption("--git-commit") or "Pass --git-commit to populate git metadata"
182
- rp_env = session.config.getoption("--rp-env") or "Pass --env or --environment or --rp-env <name> to populate environment"
240
+ rp_env = session.config.getoption("--rp-env") or "Pass --rp-env <name> to populate environment"
183
241
  configure_logging()
184
242
  session.config.addinivalue_line(
185
243
  "markers", "link(url): Add a link to external test case or documentation."
@@ -194,68 +252,74 @@ def pytest_load_initial_conftests(args):
194
252
 
195
253
 
196
254
  def pytest_addoption(parser):
197
- parser.addoption(
198
- "--json-report",
199
- action="store",
200
- default="final_report.json",
201
- help="Directory to save individual JSON test reports"
202
- )
203
- parser.addoption(
255
+ group = parser.getgroup(
256
+ "pytest-html-plus",
257
+ "pytest-html-plus reporting options"
258
+ )
259
+
260
+
261
+ group.addoption(
262
+ "--json-report",
263
+ action="store",
264
+ default="final_report.json",
265
+ help="Name of the JSON report file generated alongside the HTML report"
266
+ )
267
+ group.addoption(
204
268
  "--capture-screenshots",
205
269
  action="store",
206
270
  default="failed",
207
271
  choices=["failed", "all", "none"],
208
272
  help="Capture screenshots: failed (default), all, or none"
209
273
  )
210
- parser.addoption("--html-output", default="report_output")
211
- parser.addoption("--screenshots", default="screenshots")
212
- parser.addoption(
274
+ group.addoption("--html-output", default="report_output")
275
+ group.addoption("--screenshots", default="screenshots")
276
+ group.addoption(
213
277
  "--plus-email",
214
278
  action="store_true",
215
279
  default=False,
216
280
  help="Send HTML test report via email after test run"
217
281
  )
218
- parser.addoption(
282
+ group.addoption(
219
283
  "--detect-flake",
220
284
  action="store",
221
285
  default=False,
222
286
  help="Helps capture flaky tests in the last n number of builds"
223
287
  )
224
- parser.addoption(
288
+ group.addoption(
225
289
  "--should-open-report",
226
290
  action="store",
227
291
  default="failed",
228
292
  choices=["always", "failed", "never"],
229
293
  help="When to open the HTML report: always, failed, or never (default: failed)",
230
294
  )
231
- parser.addoption(
295
+ group.addoption(
232
296
  "--generate-xml",
233
297
  action="store_true",
234
298
  default=False,
235
299
  help="Generate JUnit-style XML from the final JSON report"
236
300
  )
237
- parser.addoption(
301
+ group.addoption(
238
302
  "--xml-report",
239
303
  action="store",
240
304
  default=None,
241
- help="Path to output the XML report (used with --generatexml)"
305
+ help="Name of the XML report file generated alongside the HTML report (used with --generate-xml)"
242
306
  )
243
- parser.addoption(
307
+ group.addoption(
244
308
  "--git-branch",
245
309
  action="store",
246
310
  default="Pass --git-branch to populate git metadata",
247
311
  help="Helps show branch information on the report"
248
312
  )
249
- parser.addoption(
313
+ group.addoption(
250
314
  "--git-commit",
251
315
  action="store",
252
316
  default="Pass --git-commit to populate git metadata",
253
317
  help="Helps show commitId information on the report"
254
318
  )
255
- parser.addoption(
319
+ group.addoption(
256
320
  "--rp-env",
257
321
  action="store",
258
- default="Pass --env or --environment or --rp-env <name> to populate environment",
322
+ default="Pass --rp-env to populate environment",
259
323
  help="Helps show env information on the report"
260
324
  )
261
325
 
@@ -356,6 +420,7 @@ def open_html_report(report_path: str, json_path: str, config) -> None:
356
420
 
357
421
  results = report_data.get("results", [])
358
422
 
423
+
359
424
  has_failures = any(
360
425
  t.get("status") == "failed" or t.get("error")
361
426
  for t in results
pytest_html_plus/utils.py CHANGED
@@ -7,7 +7,7 @@ def get_env_marker(config):
7
7
  for arg in ("--env", "--environment"):
8
8
  if config.getoption(arg.lstrip("-").replace("-", "_"), default=None):
9
9
  return config.getoption(arg.lstrip("-").replace("-", "_"))
10
- return "Pass --env or --rp-env or --environment <name> to populate environment"
10
+ return "Pass --rp-env to populate environment"
11
11
 
12
12
  def get_report_title(output_path):
13
13
  report_path = output_path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pytest-html-plus
3
- Version: 0.4.9
3
+ Version: 0.5.0
4
4
  Summary: Generate Actionable, automatic screenshots, unified Mobile friendly 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
@@ -20,7 +20,7 @@ Project-URL: Source, https://github.com/reporterplus/pytest-html-plus
20
20
  Project-URL: Tracker, https://github.com/reporterplus/pytest-html-plus/issues
21
21
  Description-Content-Type: text/markdown
22
22
 
23
- ⚡ **Plug. Play. Debug without delay.**
23
+ ⚡ **Test your code, not your reporting setup.**
24
24
  > _Get started with rich pytest reports in under 3 seconds. Just install — no setup required. The simplest, fastest reporter for pytest._
25
25
 
26
26
  ## Get a self-contained, actionable, easy-to-read single page HTML unified reports summarizing all your test results — no hassle, just clarity. Detect **flaky tests**, **attach screenshots** automatically without hooks and optionally send reports via email**. Works beautifully with or without `xdist`.
@@ -46,6 +46,12 @@ If you don’t want the burden of installing pytest-html-plus manually and your
46
46
  [![🚀 Checkout on GitHub Marketplace](https://img.shields.io/badge/Marketplace-Pytest%20HTML%20Plus-blue?logo=github)](https://github.com/marketplace/actions/pytest-html-plus-action)
47
47
  [![Documentation](https://img.shields.io/badge/docs-readthedocs.io-brightgreen)](https://pytest-html-plus.readthedocs.io/en/main/marketplace/usage.html)
48
48
 
49
+ ## Pytest HTML Plus VSCode
50
+
51
+ [![VS Code Marketplace](https://img.shields.io/visual-studio-marketplace/v/reporterplus.pytest-html-plus-vscode)]
52
+ [![Installs](https://img.shields.io/visual-studio-marketplace/i/reporterplus.pytest-html-plus-vscode)]
53
+ [![Docs](https://img.shields.io/badge/docs-online-blue)](https://pytest-html-plus.readthedocs.io/en/main/extensions/vscode/usage.html)
54
+
49
55
  ## ✨ Features
50
56
 
51
57
  #### 🧩 Seamless Combined XML Export to your favourite test management tools — No Plugins Needed
@@ -1,16 +1,16 @@
1
1
  pytest_html_plus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  pytest_html_plus/compute_filter_counts.py,sha256=FznjPos-bgYSi8bERG_WbaZWMQDIAfoYP4QTRbe8vHw,963
3
- pytest_html_plus/compute_report_metadata.py,sha256=XnpmTGGSpuXJvyFnxIWs5I1QlWte6qSHdCbgLMgTxYA,863
3
+ pytest_html_plus/compute_report_metadata.py,sha256=ShZTxZofFQH5I9_sb2C30vUlfIk21-bblJM7pkDOfS0,979
4
4
  pytest_html_plus/extract_link.py,sha256=1XtqkbZkrl1YgPGtFc3Ss1XFWm4LNLuO3Hi407HNChk,340
5
- pytest_html_plus/generate_html_report.py,sha256=i-OUnakUAEwvbm6Pb8ZjoauoVrbW7Ia-tNFzBERCAI0,37646
5
+ pytest_html_plus/generate_html_report.py,sha256=wIANaL_DNKrJvXe6hop6EkbGDjzLJ1_CnJizeUnGBm0,37190
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=9ofatZswVZEOJ4P3SZgv4B4j_RsVAEa1mU_73aOOW3s,12470
8
+ pytest_html_plus/plugin.py,sha256=stnjupg0BBvVouDaliTYoftqRrqChaIJwwrBvE_-AIU,14310
9
9
  pytest_html_plus/resolver_driver.py,sha256=hehokRO5EuG4Ti8MyaSBW4xaHduUK3Ot2jbw1YpIsnk,1287
10
10
  pytest_html_plus/send_email_report.py,sha256=qDnAPy1Jc4favOvXg6MT0ttNmSsG3MXGQX1Hu7wN1Go,2622
11
- pytest_html_plus/utils.py,sha256=bSCxZGq6g_A6LVZvPfZ3xEQUmW6Ferhhj4fbjQAWgD0,2273
12
- pytest_html_plus-0.4.9.dist-info/LICENSE,sha256=8flU0ghLnuKK8qZv9pJ1xhXiKQdUncg0OvqMwYhGWzY,1090
13
- pytest_html_plus-0.4.9.dist-info/METADATA,sha256=tFZ7LsK8U5-MlsoYSewJxMRKljQ1JYvL0Q4jdBCCvnE,8520
14
- pytest_html_plus-0.4.9.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
15
- pytest_html_plus-0.4.9.dist-info/entry_points.txt,sha256=eSbV9G_n_XnR4wemMxKHSSuPMcBSoHbE1WuC2IQp4Zk,53
16
- pytest_html_plus-0.4.9.dist-info/RECORD,,
11
+ pytest_html_plus/utils.py,sha256=5ePnU_sf-jRs0YYX3sH3alyvrnKr8xS7gJE0gaZ_P30,2240
12
+ pytest_html_plus-0.5.0.dist-info/LICENSE,sha256=8flU0ghLnuKK8qZv9pJ1xhXiKQdUncg0OvqMwYhGWzY,1090
13
+ pytest_html_plus-0.5.0.dist-info/METADATA,sha256=0gqiIN5_JLNhHTKGuvrO4kFWg9qC9-UGk2cfl-sxzRg,8910
14
+ pytest_html_plus-0.5.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
15
+ pytest_html_plus-0.5.0.dist-info/entry_points.txt,sha256=eSbV9G_n_XnR4wemMxKHSSuPMcBSoHbE1WuC2IQp4Zk,53
16
+ pytest_html_plus-0.5.0.dist-info/RECORD,,