Skip to content

Enable variable expansion for CSS addons. #676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 3 additions & 46 deletions src/pytest_html/basereport.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,21 @@
from pathlib import Path

import pytest
from jinja2 import Environment
from jinja2 import FileSystemLoader
from jinja2 import select_autoescape

from pytest_html import __version__
from pytest_html import extras
from pytest_html.table import Header
from pytest_html.table import Row
from pytest_html.util import _ansi_styles
from pytest_html.util import cleanup_unserializable


class BaseReport:
def __init__(self, report_path, config, report_data, default_css="style.css"):
def __init__(self, report_path, config, report_data, template, css):
self._report_path = Path(os.path.expandvars(report_path)).expanduser()
self._report_path.parent.mkdir(parents=True, exist_ok=True)
self._resources_path = Path(__file__).parent.joinpath("resources")
self._config = config
self._template = _read_template([self._resources_path])
self._css = _process_css(
Path(self._resources_path, default_css), self._config.getoption("css")
)
self._template = template
self._css = css
self._max_asset_filename_length = int(
config.getini("max_asset_filename_length")
)
Expand Down Expand Up @@ -224,32 +217,6 @@ def pytest_runtest_logreport(self, report):
self._generate_report()


def _process_css(default_css, extra_css):
with open(default_css, encoding="utf-8") as f:
css = f.read()

# Add user-provided CSS
for path in extra_css:
css += "\n/******************************"
css += "\n * CUSTOM CSS"
css += f"\n * {path}"
css += "\n ******************************/\n\n"
with open(path, encoding="utf-8") as f:
css += f.read()

# ANSI support
if _ansi_styles:
ansi_css = [
"\n/******************************",
" * ANSI2HTML STYLES",
" ******************************/\n",
]
ansi_css.extend([str(r) for r in _ansi_styles])
css += "\n".join(ansi_css)

return css


def _is_error(report):
return report.when in ["setup", "teardown"] and report.outcome == "failed"

Expand Down Expand Up @@ -282,13 +249,3 @@ def _process_outcome(report):
return "XFailed"

return report.outcome.capitalize()


def _read_template(search_paths, template_name="index.jinja2"):
env = Environment(
loader=FileSystemLoader(search_paths),
autoescape=select_autoescape(
enabled_extensions=("jinja2",),
),
)
return env.get_template(template_name)
23 changes: 18 additions & 5 deletions src/pytest_html/plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import warnings
from pathlib import Path

Expand All @@ -11,6 +12,8 @@
from pytest_html.report import Report
from pytest_html.report_data import ReportData
from pytest_html.selfcontained_report import SelfContainedReport
from pytest_html.util import _process_css
from pytest_html.util import _read_template


def pytest_addhooks(pluginmanager):
Expand Down Expand Up @@ -68,10 +71,14 @@ def pytest_addoption(parser):
def pytest_configure(config):
html_path = config.getoption("htmlpath")
if html_path:
extra_css = [
Path(os.path.expandvars(css)).expanduser()
for css in config.getoption("css")
]
missing_css_files = []
for css_path in config.getoption("css"):
if not Path(css_path).exists():
missing_css_files.append(css_path)
for css_path in extra_css:
if not css_path.exists():
missing_css_files.append(str(css_path))

if missing_css_files:
os_error = (
Expand All @@ -82,11 +89,17 @@ def pytest_configure(config):

if not hasattr(config, "workerinput"):
# prevent opening html_path on worker nodes (xdist)
resources_path = Path(__file__).parent.joinpath("resources")
default_css = Path(resources_path, "style.css")
template = _read_template([resources_path])
processed_css = _process_css(default_css, extra_css)
report_data = ReportData(config)
if config.getoption("self_contained_html"):
html = SelfContainedReport(html_path, config, report_data)
html = SelfContainedReport(
html_path, config, report_data, template, processed_css
)
else:
html = Report(html_path, config, report_data)
html = Report(html_path, config, report_data, template, processed_css)

config.pluginmanager.register(html)

Expand Down
4 changes: 2 additions & 2 deletions src/pytest_html/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@


class Report(BaseReport):
def __init__(self, report_path, config, report_data):
super().__init__(report_path, config, report_data)
def __init__(self, report_path, config, report_data, template, css):
super().__init__(report_path, config, report_data, template, css)
self._assets_path = Path(self._report_path.parent, "assets")
self._assets_path.mkdir(parents=True, exist_ok=True)
self._css_path = Path(self._assets_path, "style.css")
Expand Down
4 changes: 2 additions & 2 deletions src/pytest_html/selfcontained_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@


class SelfContainedReport(BaseReport):
def __init__(self, report_path, config, report_data):
super().__init__(report_path, config, report_data)
def __init__(self, report_path, config, report_data, template, css):
super().__init__(report_path, config, report_data, template, css)

@property
def css(self):
Expand Down
39 changes: 39 additions & 0 deletions src/pytest_html/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
from typing import Any
from typing import Dict

from jinja2 import Environment
from jinja2 import FileSystemLoader
from jinja2 import select_autoescape

try:
from ansi2html import Ansi2HTMLConverter, style
Expand All @@ -30,3 +33,39 @@ def cleanup_unserializable(d: Dict[str, Any]) -> Dict[str, Any]:
v = str(v)
result[k] = v
return result


def _read_template(search_paths, template_name="index.jinja2"):
env = Environment(
loader=FileSystemLoader(search_paths),
autoescape=select_autoescape(
enabled_extensions=("jinja2",),
),
)
return env.get_template(template_name)


def _process_css(default_css, extra_css):
with open(default_css, encoding="utf-8") as f:
css = f.read()

# Add user-provided CSS
for path in extra_css:
css += "\n/******************************"
css += "\n * CUSTOM CSS"
css += f"\n * {path}"
css += "\n ******************************/\n\n"
with open(path, encoding="utf-8") as f:
css += f.read()

# ANSI support
if _ansi_styles:
ansi_css = [
"\n/******************************",
" * ANSI2HTML STYLES",
" ******************************/\n",
]
ansi_css.extend([str(r) for r in _ansi_styles])
css += "\n".join(ansi_css)

return css
88 changes: 88 additions & 0 deletions testing/test_unit.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import importlib.resources
import os
import sys

import pkg_resources
import pytest
from assertpy import assert_that

pytest_plugins = ("pytester",)


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


def file_content():
try:
return (
importlib.resources.files("pytest_html")
.joinpath("assets", "style.css")
.read_bytes()
.decode("utf-8")
.strip()
)
except AttributeError:
# Needed for python < 3.9
return pkg_resources.resource_string(
"pytest_html", os.path.join("assets", "style.css")
).decode("utf-8")


def test_duration_format_deprecation_warning(pytester):
pytester.makeconftest(
"""
Expand Down Expand Up @@ -44,3 +68,67 @@ def pytest_html_results_summary(prefix, summary, postfix, session):
pytester.makepyfile("def test_pass(): pass")
result = run(pytester)
result.assert_outcomes(passed=1)


@pytest.fixture
def css_file_path(pytester):
css_one = """
h1 {
color: red;
}
"""
css_two = """
h2 {
color: blue;
}
"""
css_dir = pytester.path / "extra_css"
css_dir.mkdir()
file_path = css_dir / "one.css"
with open(file_path, "w") as f:
f.write(css_one)

pytester.makefile(".css", two=css_two)
pytester.makepyfile("def test_pass(): pass")

return file_path


@pytest.fixture(params=[True, False])
def expandvar(request, css_file_path, monkeypatch):
if request.param:
monkeypatch.setenv("EXTRA_CSS", str(css_file_path))
return "%EXTRA_CSS%" if sys.platform == "win32" else "${EXTRA_CSS}"
return css_file_path


def test_custom_css(pytester, css_file_path, expandvar):
result = run(
pytester, "report.html", cmd_flags=["--css", expandvar, "--css", "two.css"]
)
result.assert_outcomes(passed=1)

path = pytester.path.joinpath("assets", "style.css")

with open(str(path)) as f:
css = f.read()
assert_that(css).contains("* " + str(css_file_path)).contains("* two.css")


def test_custom_css_selfcontained(pytester, css_file_path, expandvar):
result = run(
pytester,
"report.html",
cmd_flags=[
"--css",
expandvar,
"--css",
"two.css",
"--self-contained-html",
],
)
result.assert_outcomes(passed=1)

with open(pytester.path / "report.html") as f:
html = f.read()
assert_that(html).contains("* " + str(css_file_path)).contains("* two.css")