pytest-html's report lost its styling

The suite finishes, report.html exists, and the copy someone opens from Slack, email or a CI artifact looks like a 1996 web page — or has a summary line and no test rows at all. Two different causes; one fix covers both.

Cause 1: the assets/ folder stayed behind

pytest --html=report.html writes two things:

report.html
assets/style.css

The report links the stylesheet by relative path. Move, attach or upload report.html alone and that path points at nothing. The browser falls back to default styles: black serif text, borderless tables, no pass/fail colors.

The fix is one flag:

pytest --html=report.html --self-contained-html

The CSS is inlined, and so is anything you pass with --css custom.css. The file stands on its own.

Cause 2: the table needs JavaScript (pytest-html 4+)

pytest-html 4.0 rewrote the report. The results table is no longer static HTML. The test data is embedded in the page as JSON, and a script builds the rows, filters and sort order when the page loads. That makes the report smaller and faster for big suites. It also means no script, no rows.

Where it's opened What blocks the script What you see
Jenkins HTML Publisher Default CSP: sandbox; default-src 'none'; ... No styles, no rows
Outlook / Gmail preview Mail clients never run scripts Summary only, or a download
Jira / Confluence / Drive file preview Rendered as text or in a script-less sandbox Source code or an empty shell
GitHub Actions artifact Zip download; fine once unzipped and opened Works locally, not as a link
Your own <iframe sandbox> No allow-scripts Header and environment, no rows

If you pinned pytest-html<4 to get a static table back, that works, but you give up the v4 fixes. Better to open the report somewhere that runs its scripts.

Jenkins specifically

Jenkins serves archived files with a strict Content-Security-Policy to stop one job's HTML from attacking another user's session. It blocks the inline <style> and <script> that pytest-html relies on. Your options, best first:

  1. Set a Resource Root URL (Manage Jenkins → System). Reports are then served from a separate origin where the CSP isn't applied.
  2. Publish the report outside Jenkins and link it from the build.
  3. Relax the CSP with System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "") in the script console. It works, but it turns off the protection for every job on the controller, and it resets on restart unless you set it as a JVM flag.

More on that trade-off in Jenkins HTML reports.

Screenshots and attachments

Selenium and Playwright screenshots are the other thing that disappears.

# Linked — breaks when the report moves
extras.append(pytest_html.extras.image("screenshots/login.png"))

# Embedded — survives --self-contained-html
extras.append(pytest_html.extras.png(driver.get_screenshot_as_base64()))

When a path-based extra is left over, pytest-html warns Self-contained HTML report includes link to external resource. That warning is the list of things that will 404 for whoever opens the file. The same goes for videos and log files. Embed them, or host them next to the report. See images not showing in a sent HTML file for the general case.

Checklist

  • --self-contained-html on the pytest command line (or in addopts)
  • Screenshots added as base64, not file paths
  • No Self-contained HTML report includes link to external resource warnings in the run output
  • Opened somewhere that runs JavaScript, over https://
# pytest.ini — so nobody forgets the flag
[pytest]
addopts = --html=report.html --self-contained-html

Getting it to people who don't have Jenkins

A self-contained report is one file, which makes it easy to put behind a link. Comma stores the HTML as-is and renders it in a sandboxed frame with scripts allowed. The v4 table, filters and collapsible tracebacks work. QA leads, PMs and clients open one URL with no CI login. They can comment on a failing test, and the next run lands as a new revision of the same report. CI can publish it with one curl — see publish from CI.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Publish a pytest report →

Related