pytest-html-plus 0.5.2__tar.gz → 1.0.0__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.
@@ -1,13 +1,14 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: pytest-html-plus
3
- Version: 0.5.2
3
+ Version: 1.0.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
+ License-File: LICENSE
6
7
  Keywords: pytest,pytest-plugin,html-report,test-report,screenshot,mobile-report,xdist,allure-alternative,playwright,selenium
7
8
  Author: reporterplus
8
9
  Maintainer: reporterplus
9
10
  Requires-Python: >=3.10,<4.0
10
- Classifier: Development Status :: 4 - Beta
11
+ Classifier: Development Status :: 5 - Production/Stable
11
12
  Classifier: Framework :: Pytest
12
13
  Classifier: Intended Audience :: Developers
13
14
  Classifier: License :: OSI Approved :: MIT License
@@ -17,9 +18,11 @@ Classifier: Programming Language :: Python :: 3.10
17
18
  Classifier: Programming Language :: Python :: 3.11
18
19
  Classifier: Programming Language :: Python :: 3.12
19
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
20
22
  Classifier: Topic :: Software Development :: Quality Assurance
21
23
  Classifier: Topic :: Software Development :: Testing
22
24
  Requires-Dist: keyring (>=25.6.0,<26.0.0)
25
+ Requires-Dist: tomli (>=2.0.1,<3.0.0) ; python_version < "3.11"
23
26
  Requires-Dist: yagmail (>=0.15.293,<0.16.0)
24
27
  Project-URL: Documentation, https://pytest-html-plus.readthedocs.io
25
28
  Project-URL: Homepage, https://github.com/reporterplus/pytest-html-plus
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pytest-html-plus"
3
- version = "0.5.2"
3
+ version = "1.0.0"
4
4
  description = "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
  readme = "README.md"
6
6
  authors = ["reporterplus"]
@@ -18,7 +18,7 @@ classifiers = [
18
18
  "Topic :: Software Development :: Testing",
19
19
  "Topic :: Software Development :: Quality Assurance",
20
20
  "Intended Audience :: Developers",
21
- "Development Status :: 4 - Beta",
21
+ "Development Status :: 5 - Production/Stable",
22
22
  ]
23
23
  keywords = [
24
24
  "pytest",
@@ -42,6 +42,7 @@ urls.Documentation = "https://pytest-html-plus.readthedocs.io"
42
42
  python = ">=3.10,<4.0"
43
43
  yagmail = ">=0.15.293,<0.16.0"
44
44
  keyring = ">=25.6.0,<26.0.0"
45
+ tomli = { version = ">=2.0.1,<3.0.0", python = "<3.11" }
45
46
 
46
47
  [tool.poetry.group.dev.dependencies]
47
48
  pytest-rerunfailures = "^15.1"
@@ -64,6 +65,15 @@ build-backend = "poetry.core.masonry.api"
64
65
  [tool.poetry.plugins."pytest11"]
65
66
  pytest_html_plus = "pytest_html_plus.plugin"
66
67
 
68
+ [tool.pytest-html-plus.profiles.ci]
69
+ generate-xml = true
70
+ xml-report = "final_xml_ci.xml"
71
+
72
+ [tool.pytest-html-plus.profiles.ci-warnings]
73
+ generate-xml = true
74
+ xml-report = "final_xml_ci_warnings.xml"
75
+ html-output = "report_output_warnings"
76
+
67
77
  [tool.ruff]
68
78
  line-length = 88
69
79
  target-version = "py39"
@@ -84,4 +94,4 @@ select = [
84
94
  "I", # isort
85
95
  "UP", # pyupgrade
86
96
  ]
87
- ignore = []
97
+ ignore = []
@@ -4,7 +4,6 @@ import json
4
4
  import os
5
5
  import shutil
6
6
  from datetime import datetime, timezone
7
- from sys import path
8
7
 
9
8
  from pytest_html_plus.compute_filter_counts import compute_filter_count
10
9
  from pytest_html_plus.utils import extract_error_block, extract_trace_block
@@ -9,6 +9,11 @@ from pathlib import Path
9
9
 
10
10
  import pytest
11
11
 
12
+ try:
13
+ import tomllib
14
+ except ModuleNotFoundError: # pragma: no cover - Python < 3.11
15
+ import tomli as tomllib
16
+
12
17
  from pytest_html_plus.compute_report_metadata import write_plus_metadata_if_main_worker
13
18
  from pytest_html_plus.extract_link import extract_links_from_item
14
19
  from pytest_html_plus.generate_html_report import JSONReporter
@@ -24,6 +29,22 @@ from pytest_html_plus.utils import (
24
29
 
25
30
  python_executable = shutil.which("python3") or shutil.which("python")
26
31
  test_screenshot_paths = {}
32
+ PROFILE_OPTION = "--plus-profile"
33
+ PROFILE_SECTION = ("tool", "pytest-html-plus", "profiles")
34
+ PROFILE_OPTION_MAP = {
35
+ "json-report": {"flag": "--json-report", "kind": "value"},
36
+ "capture-screenshots": {"flag": "--capture-screenshots", "kind": "value"},
37
+ "html-output": {"flag": "--html-output", "kind": "value"},
38
+ "screenshots": {"flag": "--screenshots", "kind": "value"},
39
+ "plus-email": {"flag": "--plus-email", "kind": "bool"},
40
+ "detect-flake": {"flag": "--detect-flake", "kind": "value"},
41
+ "should-open-report": {"flag": "--should-open-report", "kind": "value"},
42
+ "generate-xml": {"flag": "--generate-xml", "kind": "bool"},
43
+ "xml-report": {"flag": "--xml-report", "kind": "value"},
44
+ "git-branch": {"flag": "--git-branch", "kind": "value"},
45
+ "git-commit": {"flag": "--git-commit", "kind": "value"},
46
+ "rp-env": {"flag": "--rp-env", "kind": "value"},
47
+ }
27
48
 
28
49
 
29
50
  logger = logging.getLogger()
@@ -36,6 +57,136 @@ if not logger.handlers:
36
57
  logger.addHandler(handler)
37
58
 
38
59
 
60
+ def _normalize_profile_key(key):
61
+ return key.replace("_", "-")
62
+
63
+
64
+ def _find_pyproject_toml(start_path=None):
65
+ current = Path(start_path or Path.cwd()).resolve()
66
+ for candidate in (current, *current.parents):
67
+ pyproject_file = candidate / "pyproject.toml"
68
+ if pyproject_file.exists():
69
+ return pyproject_file
70
+ return None
71
+
72
+
73
+ def _read_profiles_from_pyproject(start_path=None):
74
+ pyproject_file = _find_pyproject_toml(start_path=start_path)
75
+ if pyproject_file is None:
76
+ raise pytest.UsageError(
77
+ f"{PROFILE_OPTION} requires a pyproject.toml file in the current "
78
+ "directory or a parent directory"
79
+ )
80
+
81
+ try:
82
+ with pyproject_file.open("rb") as fh:
83
+ pyproject_data = tomllib.load(fh)
84
+ except tomllib.TOMLDecodeError as exc:
85
+ raise pytest.UsageError(f"Invalid TOML in {pyproject_file}: {exc}") from exc
86
+
87
+ profiles = pyproject_data
88
+ for section in PROFILE_SECTION:
89
+ profiles = profiles.get(section, {})
90
+
91
+ if not isinstance(profiles, dict):
92
+ raise pytest.UsageError(
93
+ f"Expected [{'.'.join(PROFILE_SECTION)}] to be a TOML table"
94
+ )
95
+
96
+ return profiles
97
+
98
+
99
+ def _build_profile_args(profile_name, start_path=None):
100
+ profiles = _read_profiles_from_pyproject(start_path=start_path)
101
+ profile = profiles.get(profile_name)
102
+
103
+ if profile is None:
104
+ available_profiles = ", ".join(sorted(profiles)) or "none"
105
+ raise pytest.UsageError(
106
+ f"Unknown profile '{profile_name}' for {PROFILE_OPTION}. "
107
+ f"Available profiles: {available_profiles}"
108
+ )
109
+
110
+ if not isinstance(profile, dict):
111
+ raise pytest.UsageError(
112
+ f"Profile '{profile_name}' must be defined as a TOML table"
113
+ )
114
+
115
+ profile_args = []
116
+ invalid_keys = []
117
+
118
+ for raw_key, value in profile.items():
119
+ key = _normalize_profile_key(raw_key)
120
+ option = PROFILE_OPTION_MAP.get(key)
121
+
122
+ if option is None:
123
+ invalid_keys.append(raw_key)
124
+ continue
125
+
126
+ if option["kind"] == "bool":
127
+ if not isinstance(value, bool):
128
+ raise pytest.UsageError(
129
+ f"Profile '{profile_name}' option '{raw_key}' must be true or false"
130
+ )
131
+ if value:
132
+ profile_args.append(option["flag"])
133
+ continue
134
+
135
+ if not isinstance(value, (str, int, float)):
136
+ raise pytest.UsageError(
137
+ f"Profile '{profile_name}' option '{raw_key}' "
138
+ "must be a string-like value"
139
+ )
140
+
141
+ profile_args.append(f"{option['flag']}={value}")
142
+
143
+ if invalid_keys:
144
+ valid_keys = ", ".join(sorted(PROFILE_OPTION_MAP))
145
+ invalid = ", ".join(sorted(invalid_keys))
146
+ raise pytest.UsageError(
147
+ f"Invalid key(s) in profile '{profile_name}': {invalid}. "
148
+ f"Valid keys: {valid_keys}"
149
+ )
150
+
151
+ return profile_args
152
+
153
+
154
+ def apply_plus_profile_args(args, start_path=None):
155
+ profile_name = None
156
+ remaining_args = []
157
+ skip_next = False
158
+
159
+ for index, arg in enumerate(args):
160
+ if skip_next:
161
+ skip_next = False
162
+ continue
163
+
164
+ if arg == PROFILE_OPTION:
165
+ if profile_name is not None:
166
+ raise pytest.UsageError(f"{PROFILE_OPTION} can only be specified once")
167
+ if index + 1 >= len(args):
168
+ raise pytest.UsageError(f"{PROFILE_OPTION} requires a profile name")
169
+ profile_name = args[index + 1]
170
+ skip_next = True
171
+ continue
172
+
173
+ if arg.startswith(f"{PROFILE_OPTION}="):
174
+ if profile_name is not None:
175
+ raise pytest.UsageError(f"{PROFILE_OPTION} can only be specified once")
176
+ profile_name = arg.split("=", 1)[1]
177
+ if not profile_name:
178
+ raise pytest.UsageError(f"{PROFILE_OPTION} requires a profile name")
179
+ continue
180
+
181
+ remaining_args.append(arg)
182
+
183
+ if not profile_name:
184
+ return list(args)
185
+
186
+ profile_args = _build_profile_args(profile_name, start_path=start_path)
187
+ return profile_args + remaining_args
188
+
189
+
39
190
  @pytest.hookimpl(tryfirst=True)
40
191
  def pytest_runtest_setup(item):
41
192
  if "caplog" not in item.fixturenames:
@@ -263,6 +414,7 @@ def pytest_sessionstart(session):
263
414
 
264
415
 
265
416
  def pytest_load_initial_conftests(args):
417
+ args[:] = apply_plus_profile_args(args, start_path=Path.cwd())
266
418
  if not any(arg.startswith("--capture") for arg in args):
267
419
  args.append("--capture=tee-sys")
268
420
 
@@ -270,6 +422,12 @@ def pytest_load_initial_conftests(args):
270
422
  def pytest_addoption(parser):
271
423
  group = parser.getgroup("pytest-html-plus", "pytest-html-plus reporting options")
272
424
 
425
+ group.addoption(
426
+ PROFILE_OPTION,
427
+ action="store",
428
+ default=None,
429
+ help="Load pytest-html-plus options from a named profile in pyproject.toml",
430
+ )
273
431
  group.addoption(
274
432
  "--json-report",
275
433
  action="store",