Jest's HTML report is missing, blank, or says nothing useful

There are two popular packages with almost the same name, and most of the confusion starts there:

Package Output by default Single file?
jest-html-reporter (singular) ./test-report.html Yes, CSS inlined
jest-html-reporters (plural) ./jest_html_reporters.html plus a *-attach folder Only with inlineSource: true

Work out which one is in package.json before reading further. The fixes differ.

Symptom → cause

What you see Most likely cause
No HTML file at all Reporter never ran: CLI --reporters, projects, wrong config
HTML file exists, terminal output vanished reporters array without "default"
File is there locally, missing from the CI artifact outputPath relative to a different working directory
Plural package: blank page once moved or uploaded -attach folder left behind; inlineSource is off
Singular package: "failed" with no message includeFailureMsg defaults to false
Vitest html/index.html blank Page fetches its data; needs an HTTP server
Report only from the last CI step / partial suite Job killed (timeout, OOM, process.exit) before Jest finished

1. The reporter never ran

Jest calls reporters at the end of the run. If nothing was written, Jest didn't load the reporter. Three ways that happens:

The command line replaced it. --reporters on the CLI overrides the reporters key in your config, it doesn't add to it. A CI script with jest --ci --reporters=default --reporters=jest-junit silently drops the HTML reporter you configured.

It's inside projects. reporters is a global option. In a monorepo config with projects: [...], a reporters key inside a project entry is ignored. Put it at the top level of the root config.

Jest read a different config. A jest key in package.json and a jest.config.js in the same folder is an error (Multiple configurations found). A --config flag in CI, or running from a subfolder, can pick up a file you didn't edit.

Check what Jest actually resolved:

npx jest --showConfig | grep -A5 '"reporters"'

If your reporter isn't in that list, nothing below matters yet.

2. You lost the terminal output

Setting reporters replaces Jest's defaults rather than adding to them. Keep the console summary with "default":

// jest.config.js
module.exports = {
  reporters: [
    "default",
    [
      "jest-html-reporter",
      {
        pageTitle: "Test report",
        outputPath: "reports/test-report.html",
        includeFailureMsg: true,
        includeSuiteFailure: true,
      },
    ],
  ],
};

Older setups used testResultsProcessor: "./node_modules/jest-html-reporter". That still exists, but the reporters form is the one to use; don't configure both.

3. The file is local-only

outputPath is resolved from the directory Jest runs in. In a monorepo where CI runs npm test -w packages/web, the report lands in packages/web/reports/, while the artifact step uploads reports/ at the repo root and finds nothing. Use an absolute path in CI, or point the upload at the package folder. jest-html-reporter also reads environment variables such as JEST_HTML_REPORTER_OUTPUT_PATH, which is handy when only CI needs a different location.

4. jest-html-reporters opens blank

The plural package writes the page and a sibling folder (*-attach) that holds the run data and any attachments. The page loads that folder when it opens. Email the HTML, upload it alone, or open it from inside a zip preview, and you get the app shell with no tests in it.

reporters: [
  "default",
  [
    "jest-html-reporters",
    {
      publicPath: "./reports",
      filename: "jest-report.html",
      inlineSource: true, // one file, no -attach folder needed for the data
      expand: false,
    },
  ],
];

Attachments added with addAttach (screenshots from Puppeteer or Playwright-in-Jest) are still files on disk. If you need them in the shared copy, pass them as base64 or keep the folder with the report. The general rule is the same one behind most broken reports: a report is the directory, not the file.

5. "Failed" with no reason

In jest-html-reporter, includeFailureMsg defaults to false. The report shows a red row and no assertion message, so whoever opens it still has to go and find the CI log. Turn it on, along with:

  • includeSuiteFailure: true — suites that fail to even load (a syntax error, a bad import) otherwise show up as an empty suite.
  • includeConsoleLog: true — console.log output per suite.
  • executionTimeWarningThreshold — highlights slow tests, useful when hunting flaky tests.

6. Vitest's HTML report is blank

vitest --reporter=html (it needs @vitest/ui installed) writes an html/ directory. Its index.html fetches the results at view time, so opening it from disk hits the file:// origin rules and renders an empty UI. The Vitest docs serve it instead:

npx vite preview --outDir ./html

That works on your machine but not as something to send. For a shareable view of a Vitest run, emit JUnit XML and render it:

npx vitest run --reporter=default --reporter=junit --outputFile.junit=junit.xml

Paste junit.xml into the JUnit XML viewer to get a static HTML report with failures grouped by suite — one file, no fetches.

7. Half a report, or none, after a CI timeout

Reporters write when the run completes. A job killed by the CI step timeout, the OOM killer, or a test calling process.exit() never gets there. Signs: the HTML file is missing only on the runs where the job was red for "cancelled" or "exit code 137". Give the job more headroom, shard with --shard=1/3, or use --maxWorkers=50% on small runners.

And make the upload step unconditional (if: always() in GitHub Actions), or the report never leaves the runner on exactly the runs that failed.

Checklist

  • Reporter shows up in npx jest --showConfig
  • "default" is still in reporters
  • Single file: singular package, or inlineSource: true on the plural
  • includeFailureMsg: true
  • outputPath matches the path your artifact step uploads
  • Upload step runs on failure

Sending it to someone

Once the report is one file, it can live behind a link. Comma stores the HTML as-is and renders it in a sandboxed frame with scripts allowed, so the jest-html-reporters React UI works when it's built with inlineSource: true. Teammates open one URL without a CI login; commenting needs a free sign-in. Each run can land as a new revision of the same report with one curl from CI — see publish from CI. One limit to know: a report that loads its data from sibling files at view time (the default plural setup, Vitest html) won't load it inside Comma either. Make it a single file first.

Try it

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

Publish a Jest report →

Related