Skip to content

Feature: pluggable HTML themes via Jinja2 template inheritance #1029

Description

@RonnyPfannschmidt

Motivation

There is clear user demand for a modernised report UI (see #913, #904, #884). Currently, anyone who wants a different visual style has to override CSS after the fact or maintain a separate fork. pytest-html could instead offer first-class theme support, making it trivial to ship alternative report styles and for the community to contribute new ones.

Proposed design

1. Jinja2 template inheritance with {% extends %}

Factor the current index.jinja2 into a base template and per-theme overrides:

resources/
  base.jinja2              # shared HTML skeleton with {% block %} placeholders
  classic/
    layout.jinja2          # {% extends "base.jinja2" %} — current look
    style.css
  modern/
    layout.jinja2          # {% extends "base.jinja2" %} — card-based dashboard
    style.css

Only 4 blocks actually differ between visual styles:

  • {% block header %} — page title / navbar
  • {% block summary %} — run stats presentation (flat text vs stat cards with percentages)
  • {% block filters %} — checkboxes vs toggle switches
  • {% block footer %} — generation info placement

Everything else is shared in base.jinja2: the results table, the <template> for result rows, the media viewer, the JS data container, and the script include.

2. Config option

# pytest.ini / pyproject.toml
[tool.pytest.ini_options]
html_theme = "modern"   # default: "classic"
# plugin.py
parser.addini(
    "html_theme",
    type="string",
    default="classic",
    help="HTML report theme ('classic' or 'modern').",
)

3. Template loader change

Pass [theme_path, resources_path] to Jinja2's FileSystemLoader so each theme's layout.jinja2 can do {% extends "base.jinja2" %} and the loader resolves base.jinja2 from the parent resources directory. Minimal change to _read_template.

4. Extra template variables

A modern card-based theme benefits from a few additional pre-computed template variables. These are cheap to compute and can be passed unconditionally so all themes have them available:

  • start_time / end_time — formatted timestamps for the suite
  • duration_display — human-readable total duration
  • total_count — total tests excluding reruns

This is ~15 lines in _generate_report().

Benefits

  • Backward compatibleclassic is the default; no one's custom CSS breaks
  • Extensible — new themes are just a directory with layout.jinja2 + style.css
  • Low risk — the JS logic, data model, hooks, and Python backend are untouched
  • Community-friendly — lowers the bar for contributing visual improvements

What this does NOT change

  • No new features or hooks — purely a presentation-layer refactor
  • No change to the default appearance (classic remains default)
  • No JS modifications required

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions