Skip to content

Commit 316246e

Browse files
authored
Enable variable expansion for CSS addons. (#676)
1 parent 7992d49 commit 316246e

File tree

6 files changed

+152
-55
lines changed

6 files changed

+152
-55
lines changed

src/pytest_html/basereport.py

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,21 @@
99
from pathlib import Path
1010

1111
import pytest
12-
from jinja2 import Environment
13-
from jinja2 import FileSystemLoader
14-
from jinja2 import select_autoescape
1512

1613
from pytest_html import __version__
1714
from pytest_html import extras
1815
from pytest_html.table import Header
1916
from pytest_html.table import Row
20-
from pytest_html.util import _ansi_styles
2117
from pytest_html.util import cleanup_unserializable
2218

2319

2420
class BaseReport:
25-
def __init__(self, report_path, config, report_data, default_css="style.css"):
21+
def __init__(self, report_path, config, report_data, template, css):
2622
self._report_path = Path(os.path.expandvars(report_path)).expanduser()
2723
self._report_path.parent.mkdir(parents=True, exist_ok=True)
28-
self._resources_path = Path(__file__).parent.joinpath("resources")
2924
self._config = config
30-
self._template = _read_template([self._resources_path])
31-
self._css = _process_css(
32-
Path(self._resources_path, default_css), self._config.getoption("css")
33-
)
25+
self._template = template
26+
self._css = css
3427
self._max_asset_filename_length = int(
3528
config.getini("max_asset_filename_length")
3629
)
@@ -224,32 +217,6 @@ def pytest_runtest_logreport(self, report):
224217
self._generate_report()
225218

226219

227-
def _process_css(default_css, extra_css):
228-
with open(default_css, encoding="utf-8") as f:
229-
css = f.read()
230-
231-
# Add user-provided CSS
232-
for path in extra_css:
233-
css += "\n/******************************"
234-
css += "\n * CUSTOM CSS"
235-
css += f"\n * {path}"
236-
css += "\n ******************************/\n\n"
237-
with open(path, encoding="utf-8") as f:
238-
css += f.read()
239-
240-
# ANSI support
241-
if _ansi_styles:
242-
ansi_css = [
243-
"\n/******************************",
244-
" * ANSI2HTML STYLES",
245-
" ******************************/\n",
246-
]
247-
ansi_css.extend([str(r) for r in _ansi_styles])
248-
css += "\n".join(ansi_css)
249-
250-
return css
251-
252-
253220
def _is_error(report):
254221
return report.when in ["setup", "teardown"] and report.outcome == "failed"
255222

@@ -282,13 +249,3 @@ def _process_outcome(report):
282249
return "XFailed"
283250

284251
return report.outcome.capitalize()
285-
286-
287-
def _read_template(search_paths, template_name="index.jinja2"):
288-
env = Environment(
289-
loader=FileSystemLoader(search_paths),
290-
autoescape=select_autoescape(
291-
enabled_extensions=("jinja2",),
292-
),
293-
)
294-
return env.get_template(template_name)

src/pytest_html/plugin.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# This Source Code Form is subject to the terms of the Mozilla Public
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
import os
45
import warnings
56
from pathlib import Path
67

@@ -11,6 +12,8 @@
1112
from pytest_html.report import Report
1213
from pytest_html.report_data import ReportData
1314
from pytest_html.selfcontained_report import SelfContainedReport
15+
from pytest_html.util import _process_css
16+
from pytest_html.util import _read_template
1417

1518

1619
def pytest_addhooks(pluginmanager):
@@ -68,10 +71,14 @@ def pytest_addoption(parser):
6871
def pytest_configure(config):
6972
html_path = config.getoption("htmlpath")
7073
if html_path:
74+
extra_css = [
75+
Path(os.path.expandvars(css)).expanduser()
76+
for css in config.getoption("css")
77+
]
7178
missing_css_files = []
72-
for css_path in config.getoption("css"):
73-
if not Path(css_path).exists():
74-
missing_css_files.append(css_path)
79+
for css_path in extra_css:
80+
if not css_path.exists():
81+
missing_css_files.append(str(css_path))
7582

7683
if missing_css_files:
7784
os_error = (
@@ -82,11 +89,17 @@ def pytest_configure(config):
8289

8390
if not hasattr(config, "workerinput"):
8491
# prevent opening html_path on worker nodes (xdist)
92+
resources_path = Path(__file__).parent.joinpath("resources")
93+
default_css = Path(resources_path, "style.css")
94+
template = _read_template([resources_path])
95+
processed_css = _process_css(default_css, extra_css)
8596
report_data = ReportData(config)
8697
if config.getoption("self_contained_html"):
87-
html = SelfContainedReport(html_path, config, report_data)
98+
html = SelfContainedReport(
99+
html_path, config, report_data, template, processed_css
100+
)
88101
else:
89-
html = Report(html_path, config, report_data)
102+
html = Report(html_path, config, report_data, template, processed_css)
90103

91104
config.pluginmanager.register(html)
92105

src/pytest_html/report.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111

1212
class Report(BaseReport):
13-
def __init__(self, report_path, config, report_data):
14-
super().__init__(report_path, config, report_data)
13+
def __init__(self, report_path, config, report_data, template, css):
14+
super().__init__(report_path, config, report_data, template, css)
1515
self._assets_path = Path(self._report_path.parent, "assets")
1616
self._assets_path.mkdir(parents=True, exist_ok=True)
1717
self._css_path = Path(self._assets_path, "style.css")

src/pytest_html/selfcontained_report.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010

1111
class SelfContainedReport(BaseReport):
12-
def __init__(self, report_path, config, report_data):
13-
super().__init__(report_path, config, report_data)
12+
def __init__(self, report_path, config, report_data, template, css):
13+
super().__init__(report_path, config, report_data, template, css)
1414

1515
@property
1616
def css(self):

src/pytest_html/util.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
from typing import Any
77
from typing import Dict
88

9+
from jinja2 import Environment
10+
from jinja2 import FileSystemLoader
11+
from jinja2 import select_autoescape
912

1013
try:
1114
from ansi2html import Ansi2HTMLConverter, style
@@ -30,3 +33,39 @@ def cleanup_unserializable(d: Dict[str, Any]) -> Dict[str, Any]:
3033
v = str(v)
3134
result[k] = v
3235
return result
36+
37+
38+
def _read_template(search_paths, template_name="index.jinja2"):
39+
env = Environment(
40+
loader=FileSystemLoader(search_paths),
41+
autoescape=select_autoescape(
42+
enabled_extensions=("jinja2",),
43+
),
44+
)
45+
return env.get_template(template_name)
46+
47+
48+
def _process_css(default_css, extra_css):
49+
with open(default_css, encoding="utf-8") as f:
50+
css = f.read()
51+
52+
# Add user-provided CSS
53+
for path in extra_css:
54+
css += "\n/******************************"
55+
css += "\n * CUSTOM CSS"
56+
css += f"\n * {path}"
57+
css += "\n ******************************/\n\n"
58+
with open(path, encoding="utf-8") as f:
59+
css += f.read()
60+
61+
# ANSI support
62+
if _ansi_styles:
63+
ansi_css = [
64+
"\n/******************************",
65+
" * ANSI2HTML STYLES",
66+
" ******************************/\n",
67+
]
68+
ansi_css.extend([str(r) for r in _ansi_styles])
69+
css += "\n".join(ansi_css)
70+
71+
return css

testing/test_unit.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import importlib.resources
2+
import os
3+
import sys
4+
5+
import pkg_resources
6+
import pytest
7+
from assertpy import assert_that
8+
19
pytest_plugins = ("pytester",)
210

311

@@ -7,6 +15,22 @@ def run(pytester, path="report.html", cmd_flags=None):
715
return pytester.runpytest("--html", path, *cmd_flags)
816

917

18+
def file_content():
19+
try:
20+
return (
21+
importlib.resources.files("pytest_html")
22+
.joinpath("assets", "style.css")
23+
.read_bytes()
24+
.decode("utf-8")
25+
.strip()
26+
)
27+
except AttributeError:
28+
# Needed for python < 3.9
29+
return pkg_resources.resource_string(
30+
"pytest_html", os.path.join("assets", "style.css")
31+
).decode("utf-8")
32+
33+
1034
def test_duration_format_deprecation_warning(pytester):
1135
pytester.makeconftest(
1236
"""
@@ -44,3 +68,67 @@ def pytest_html_results_summary(prefix, summary, postfix, session):
4468
pytester.makepyfile("def test_pass(): pass")
4569
result = run(pytester)
4670
result.assert_outcomes(passed=1)
71+
72+
73+
@pytest.fixture
74+
def css_file_path(pytester):
75+
css_one = """
76+
h1 {
77+
color: red;
78+
}
79+
"""
80+
css_two = """
81+
h2 {
82+
color: blue;
83+
}
84+
"""
85+
css_dir = pytester.path / "extra_css"
86+
css_dir.mkdir()
87+
file_path = css_dir / "one.css"
88+
with open(file_path, "w") as f:
89+
f.write(css_one)
90+
91+
pytester.makefile(".css", two=css_two)
92+
pytester.makepyfile("def test_pass(): pass")
93+
94+
return file_path
95+
96+
97+
@pytest.fixture(params=[True, False])
98+
def expandvar(request, css_file_path, monkeypatch):
99+
if request.param:
100+
monkeypatch.setenv("EXTRA_CSS", str(css_file_path))
101+
return "%EXTRA_CSS%" if sys.platform == "win32" else "${EXTRA_CSS}"
102+
return css_file_path
103+
104+
105+
def test_custom_css(pytester, css_file_path, expandvar):
106+
result = run(
107+
pytester, "report.html", cmd_flags=["--css", expandvar, "--css", "two.css"]
108+
)
109+
result.assert_outcomes(passed=1)
110+
111+
path = pytester.path.joinpath("assets", "style.css")
112+
113+
with open(str(path)) as f:
114+
css = f.read()
115+
assert_that(css).contains("* " + str(css_file_path)).contains("* two.css")
116+
117+
118+
def test_custom_css_selfcontained(pytester, css_file_path, expandvar):
119+
result = run(
120+
pytester,
121+
"report.html",
122+
cmd_flags=[
123+
"--css",
124+
expandvar,
125+
"--css",
126+
"two.css",
127+
"--self-contained-html",
128+
],
129+
)
130+
result.assert_outcomes(passed=1)
131+
132+
with open(pytester.path / "report.html") as f:
133+
html = f.read()
134+
assert_that(html).contains("* " + str(css_file_path)).contains("* two.css")

0 commit comments

Comments
 (0)