From b41485bb7cc2fca8e84e572c6d9e331cd0840e41 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 24 May 2026 23:17:40 +0200 Subject: [PATCH 1/6] feat: pluggable HTML themes via Jinja2 template inheritance Add a theme system using entry_points ("pytest_html.themes") and an html_theme_path ini option for local overrides. Themes provide a layout.jinja2 that extends base.jinja2 and optionally a style.css. Ships two built-in themes: classic (current look, default) and modern (card-based dashboard). External packages can register additional themes via the same entry point group. Closes #1029 Co-authored-by: Cursor AI Co-authored-by: Anthropic Claude Sonnet 4 --- docs/user_guide.rst | 109 +++- package.json | 4 +- pyproject.toml | 2 + src/.gitattributes | 3 +- src/layout/css/{style.scss => classic.scss} | 0 src/layout/css/modern.scss | 520 ++++++++++++++++++ src/pytest_html/plugin.py | 48 +- src/pytest_html/resources/__init__.py | 0 .../resources/{index.jinja2 => base.jinja2} | 13 + src/pytest_html/resources/classic/__init__.py | 0 .../resources/classic/layout.jinja2 | 1 + .../resources/{ => classic}/style.css | 0 src/pytest_html/resources/modern/__init__.py | 0 .../resources/modern/layout.jinja2 | 93 ++++ src/pytest_html/resources/modern/style.css | 480 ++++++++++++++++ src/pytest_html/util.py | 2 +- testing/test_themes.py | 131 +++++ 17 files changed, 1400 insertions(+), 6 deletions(-) rename src/layout/css/{style.scss => classic.scss} (100%) create mode 100644 src/layout/css/modern.scss create mode 100644 src/pytest_html/resources/__init__.py rename src/pytest_html/resources/{index.jinja2 => base.jinja2} (91%) create mode 100644 src/pytest_html/resources/classic/__init__.py create mode 100644 src/pytest_html/resources/classic/layout.jinja2 rename src/pytest_html/resources/{ => classic}/style.css (100%) create mode 100644 src/pytest_html/resources/modern/__init__.py create mode 100644 src/pytest_html/resources/modern/layout.jinja2 create mode 100644 src/pytest_html/resources/modern/style.css create mode 100644 testing/test_themes.py diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 8415bfb0..4862606e 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -51,10 +51,117 @@ The plugin will issue a warning when adding files or links to the standalone rep Enhancing reports ----------------- +Themes +~~~~~~ + +pytest-html supports pluggable themes via Jinja2 template inheritance. Two +built-in themes are included: **classic** (the default, preserving the +traditional appearance) and **modern** (a card-based dashboard style). + +Selecting a theme +^^^^^^^^^^^^^^^^^ + +Set the ``html_theme`` option in your pytest configuration: + +Using ``pyproject.toml``: + +.. code-block:: toml + + [tool.pytest.ini_options] + html_theme = "modern" + +Using ``pytest.ini``: + +.. code-block:: ini + + [pytest] + html_theme = modern + +Using a local theme directory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can point to a custom theme directory using ``html_theme_path``. This +overrides entry point-based theme discovery and does not require packaging: + +.. code-block:: toml + + [tool.pytest.ini_options] + html_theme_path = "./my_theme" + +The theme directory must contain at least a ``layout.jinja2`` file that extends +the base template: + +.. code-block:: jinja + + {% extends "base.jinja2" %} + + {% block header %} +

{{ title }}

+

Custom header content here

+ {% endblock header %} + +Optionally include a ``style.css`` to replace the default stylesheet entirely. +If no ``style.css`` is present, the classic theme's CSS is used as a fallback. + +The ``--css`` flag continues to work and appends additional CSS on top of +whatever the theme provides. + +Creating a theme package +^^^^^^^^^^^^^^^^^^^^^^^^ + +Theme packages register via the ``pytest_html.themes`` entry point group. The +entry point value must be a Python package (directory with ``__init__.py``) +containing the theme resources. + +Example package structure: + +.. code-block:: text + + pytest-html-dark/ + src/ + pytest_html_dark/ + __init__.py + theme/ + __init__.py + layout.jinja2 + style.css + pyproject.toml + +Register the entry point in ``pyproject.toml``: + +.. code-block:: toml + + [project.entry-points."pytest_html.themes"] + dark = "pytest_html_dark.theme" + +Users can then select the theme: + +.. code-block:: toml + + [tool.pytest.ini_options] + html_theme = "dark" + +Available template blocks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The base template (``base.jinja2``) provides the following overridable blocks: + +- ``styles`` -- CSS delivery (inline ``