pytestHTML Reporter
Home Docs Features
Overview

Features

One command writes one HTML file. Inside it are a dashboard, a searchable metrics table, pictures of what broke, the payloads a test attached, the steps it ran, its coverage, and an analytics tab read across every build you have kept. This page is the full list — what each part shows, what it costs, and where the detail lives.

The report itself

The output is one static HTML file. There is no server to run, no asset folder that has to travel with it, and no build step: you can mail it, publish it as a CI artifact, or open it off a stick. Everything below lives inside that one file.

shell No flags at all — writes ./pytest_html_report.html
$ pip3 install pytest-html-reporter
$ pytest tests/
The Dashboard tab of a generated report: summary card, doughnut, Trends line, per-suite bars and Highlights The Dashboard tab of a generated report: summary card, doughnut, Trends line, per-suite bars and Highlights
The whole Dashboard in one frame — summary, Trends, the per-suite bars and Highlights.

Light and dark themes

A switch at the foot of the side nav. It is remembered per reader, and follows the operating system until somebody touches it. There is no flag and no ini key for it — the theme belongs to whoever opens the report, not to the run that produced it.

It opens itself on a local run

When the run finishes, the report opens in your browser. Nothing has to be added to get this. It only happens on a run somebody is sat in front of, and a build agent fails every one of the three checks: the output has to be a terminal, no CI variable may be set (CI, GITHUB_ACTIONS, JENKINS_URL and the rest of the usual set), and on anything that is not macOS or Windows there has to be a DISPLAY or WAYLAND_DISPLAY to open into.

shell The three modes
$ pytest tests/ --report-open=none      # never open it
$ pytest tests/ --report-open=always    # open it whatever the run looks like
$ pytest tests/ --report-open=auto      # the default, as described above

The browser is asked for a tab rather than a window, so a suite run over and over does not bury the desktop. A machine with no browser on it is not an error: the report is written either way, and a run that could not open it still passes or fails on its tests alone.

--report-link adds an entry to the report's side nav pointing at any page you like — the annotated coverage source, a CI job, a Grafana board, an internal wiki page. Repeat it as often as you need. Relative paths are resolved from wherever the report is written, so linking a folder that ships beside it works, and links open in a new tab.

shell Two custom nav entries
$ pytest tests/ --report-link "Coverage=htmlcov/index.html" \
                --report-link "CI job=https://ci.example.com/job/42"
NoteAnything carrying a scheme other than http, https or mailto is dropped rather than rendered. A report is a build artifact that gets published and passed round, and a nav entry has no business being able to run something in whoever opens it.

Reading a run

The Dashboard answers how did this run go? — the counts, the shape of the failures, and whether the suite is moving in the right direction.

Overview

The donut and the PASS / FAIL / SKIP / XPASS / XFAIL / ERROR counts, with the time the run took beside them.

Passed, failed and skipped plotted across this run and the five builds before it. Failed here counts failures and errors together.

Highlights

The suite that failed most, and — once a second build exists — which way the failure count has moved since the last one.

Test suite details

Every suite in the run with its own counts, so a failure concentrated in one file is visible without opening the table.

Environment panel

What the run was, where it ran, which pipeline and commit it came from, and what it kept of the output — opened from a chip on the summary card. Most of it needs no flag.

The Test Metrics table

The table is a DataTable: search, sort, and export buttons for copy, CSV, Excel, print and column visibility. It already carries ten columns, which is why markers ended up as badges on the Test Steps tab rather than as an eleventh.

ColumnWhat it holds
LogsHow many lines of captured output the test produced. Clicking it opens the output section by section, with a Copy button. A test that produced nothing shows a dash.
DataHow many payloads the test attached. Clicking it crosses to the API Logs tab with the list already narrowed to that one test.
ScreensA thumbnail of the screenshot taken for that test, next to the error it explains, opening full size when clicked.
StepsCrosses to the test's step tree on the Test Steps tab.
RerunHow many attempts the test took. From 0.4.2 a non-zero count is a button, opening the trail of what each attempt did. A test that ran once shows a plain, disabled 0.
Error MessageThe failure, truncated in the cell and expandable in place, with the copy buttons beside it.
Why they are not in the tableCaptured logs, attachments and step trees are all held outside the metrics table, so they are never swept into its search box or into the CSV, Excel and print exports. That is the reason all three live in overlays and tabs of their own.

Environment and build details

--environment names what was under test and shows it as a badge beside the report title. --build-info takes any key=value you like and adds it to the Environment panel; repeat it as often as you want. Unlike most of the flags, --build-info entries are added to the ones set in the ini file rather than replacing them.

shell Environment plus arbitrary key=value build details
$ pytest tests/ --environment=prod --build-info branch=main --build-info sha=$GITHUB_SHA

The same panel states what the run kept of the output and from which log level — for example all tests: stdout, stderr and logging, logging from WARNING — so there is always somewhere to check which mode you are in.

New in 0.4.2Most of the panel now needs no flag at all. A report is read a week later by somebody who cannot re-run it, so everything answerable without being asked is answered: the CI system and a Pipeline link back to the build, the branch and commit under test, the operating system named the way its own users name it, the interpreter that actually ran, and — under -n — how many xdist workers reported results. Nothing you have already named through --build-info is overruled. See CI integrations.

A run built with a named profile gets one more row, Profile, saying which one — so the panel answers "where did the logs go" as well as "where did this run happen". New in 0.4.3.

One row is opt-in: --report-packages adds a Packages row listing every installed distribution and its version, the way pip freeze reads. It is off by default because it is a few hundred entries nobody reads until the day the report is the only surviving record of what was installed — and it publishes a full dependency inventory into a file that gets passed around.

The failure delta

Once there is a build to compare against, the Highlights card gains a second entry saying which way the suite is moving: ▲ +3 failures over SINCE LAST BUILD, red when there are more failures than last time and green with a when there are fewer. Nothing to configure — it appears as soon as a second build has been archived.

The absolute count tells you how bad this build is; the delta tells you whether it is getting better, which is the one you act on. Hovering it gives the two counts behind it — 12 failures this build, 9 in the build before it — because +3 reads very differently against 3 than against 300. No change is written ±0 failures rather than 0 failures, which beside SINCE LAST BUILD would say the opposite of what it means, and a first build leaves the entry out entirely rather than comparing against a build that does not exist.

Reading a failure

A status column tells you a test failed. Everything in this section exists to shorten the distance between seeing that and doing something about it.

Copy the error, the rerun command, or the link

Four buttons sit beside a failure message, folded behind a ... until pressed:

  1. Expand the error in place.
  2. Copy the full error — it opens in the same overlay the Logs column uses, which gives the dialog a Copy button and a readable body.
  3. Copy the rerun commandpytest path/to/test.py::test_name, built from the test's node id rather than by joining the suite and test names in the row, because a test inside a class is listed under its own name where pytest wants the class in front of it.
  4. Copy the link to that row (see Deep links below).
shell What the rerun button puts on the clipboard
$ pytest "tests/test_cart.py::TestCart::test_add[sku-A-12]"

A node id a shell would not read as one word is quoted, since a parametrised test's brackets are a glob pattern to zsh.

A click also puts a word in the middle of the page — Error copied, Command copied, Link copied — faded in and gone 2.2 seconds later, because 22 pixels of button at the end of a long row is not legible from wherever the pointer is, and because it says which of the three landed. It takes no clicks, so the row underneath stays live while it is up, and it shows four lines of what went onto the clipboard with anything past that faded out. Only one row is open at a time; a click anywhere else, or Escape, shuts it.

Opened off the file systemSome browsers refuse the clipboard on a file:// page. When that happens the panel says Press Ctrl+C to copy rather than leaving a button that did nothing and no reason why.
Screenshot: assets/img/shots/failure-copy-buttons.gif A short loop on the Test Metrics table, light theme: hovering a failure row, pressing the ... to unfold the four buttons, clicking Copy link, and the words "Link copied" fading in mid-page with the four-line clipboard preview underneath, then fading out.

Status filter chips

The PASS / FAIL / SKIP / XPASS / XFAIL / ERROR count pills over the Test Metrics table are clickable, and drive a column search on the table under them. One click gets you "show me only the failures", which is the most common thing anybody does with a report like this.

From 0.4.3 the seven figures under the Dashboard's ring are the other end of the same filter. Each is a button that opens Test Metrics with the table already filtered to what it counted — Rerun to every test that ran more than once, since rerun is a count rather than an outcome — and a counter standing at zero is disabled rather than offering rows that are not there. The filter is in the address as #test-metrics?status=FAIL, so "the failures in last night's build" is a link somebody can be sent, and a Clear filter control stands beside the chips for the reader who arrived on one.

Truncation that says so

A cut error message ends by fading its last eight characters out rather than in an ellipsis — the same answer the report title gives to the same question. The ellipsis is still in the cell, hidden: the CSV, Excel and print exports are built from cell text, they cannot carry a gradient, and a message cut short with nothing to say so is a trap in a file read a week later.

Every test row has an address of its own, copied from a button beside the failure, opening the report on that row whatever page of the table it has ended up on. Three decisions make the link worth sending:

A node id that appears twice — pytest-repeat, or a rerun collected rather than merged — has its repeats numbered, so the two rows keep separate addresses.

Analytics: how a test behaves, not how a run went

One run cannot tell you whether a failure is new, whether a test has been flipping for a fortnight, or whether the suite is quietly getting slower. The Analytics tab reads every build you have kept and lines them up per test. Nothing to install, nothing to configure, and nothing extra is collected: the archives already hold a status per test per build.

Stability score

One number, 0–100. It starts at the mean per-test pass rate and is charged half the mean flip rate, because a test that alternates pass, fail, pass is the more expensive of the two to live with.

Flaky tests

Tests that have flipped between passing and failing, or that needed a retry to pass — kept apart from the ones that have never passed.

Always failing

Tests that have failed every build they were in, two builds running or more. A test that only ever fails is a bug with an owner, not a race worth hunting.

Why this run failed

This run's failures grouped by the exception each came out of — 12 failures, 9 are TimeoutException — with the tests in each group named rather than only counted.

Who owns what

One row per owner, worst first: the tests they hold, the share of the suite that is, their mean pass rate, what is failing now, what is flaky and where their minutes go. Forty failures over six teams and forty in one team read identically everywhere else.

How much it matters

One row per severity level in ladder order, Unrated last, led by what somebody came to the tab to find out — 1 critical test failing. Forty failures at trivial and two at blocker are not the same run.

Pass rate across builds

The drift, on an axis that is deliberately not pinned to 0–100: a suite that lives between 96% and 99% is exactly the one whose two-point drops matter.

Where the time goes

This run's tests bucketed by duration, which a slowest-tests list cannot tell you: two thousand tests at 300ms each is a different problem from ten tests at a minute.

Under the figures, four cards name what changed since the previous build — newly failing, newly fixed, new tests and no longer run — each opening its full list in a searchable dialog. Then a sortable row per test: its verdict, its recent outcomes as a strip of one block per build, its pass rate, how many times it has flipped, its retries, how long its current streak has run and its duration. It opens worst-behaved first, so the list to work through is already the list on screen.

The top of the Analytics tab: six stat tiles above the The top of the Analytics tab: six stat tiles above the
The top of Analytics — six figures for the suite, and the run’s failures grouped by exception underneath.
NoteSkips are excluded from the arithmetic rather than counted against a test, and xfail / xpass count as passes — they are outcomes the suite declared in advance. How far back the tab reads is whatever --archive-count, --archive-days and --archive-since have kept; the charts draw the most recent twenty builds so the axis stays readable, while the tables count every build on disk.

Analytics in full — every figure, every panel, and what the tab shows on a first run.

Test steps, Gherkin and markers

A status column tells you a test failed. Steps tell you where, and how long it had been running when it got there. Name the pieces a test is made of and they are timed, nested, and shown on a Test Steps tab of its own, with the suite drilling down to the test and the test to what it did.

python step() as a context manager, with arbitrary keyword data kept beside it
from pytest_html_reporter import step

def test_checkout():
    with step("Add to cart", sku="A-12"):
        cart.add("A-12")

    with step("Charge the card"):
        assert gateway.charge(cart).ok

The same function works as a decorator, which is how the methods of a page object or an API client — already the steps of every test that calls them — get named once for all of those tests. The arguments of the call fill in the {placeholders} of the title.

python step() as a decorator
@step("Log in as {user}")
def login(user):
    page.fill("#user", user)
    page.click("#submit")

login("amy")        # the tab shows: Log in as amy, with user=amy kept beside it
The Test Steps tab with a failing scenario open, showing its phases, per-step durations, the error and the screenshot on the failed step The Test Steps tab with a failing scenario open, showing its phases, per-step durations, the error and the screenshot on the failed step
Test Steps, drilled into one test — the phases, the named steps with their durations, and the screenshot on the step that failed.

Gherkin scenarios, with nothing to do

A pytest-bdd scenario is already a list of named steps, so its Given / When / Then arrive on their own — each timed, each carrying what its parser pulled out of the line, and badged as Gherkin so a specification never reads as somebody's plumbing. The feature, the scenario and the feature file are named above the tree, an Outline's <placeholders> are shown filled in with the row that actually ran, and the scenario's tags arrive as markers. pytest-bdd does not have to be installed: the hooks are declared optional, so a run without it is untouched.

Markers, and where they were written

Markers are shown in full, including the ones a test never mentions: a module-level pytestmark, a marker on the class, one added by request.node.add_marker while the test ran. Each says which scope it came from, which is the answer when nobody remembers applying it. pytest's own markers are coloured apart from yours, because skipif changes how a test runs and @smoke only names it. Two are cut down deliberately: parametrize shows its argument names rather than every row the test will ever run with, and a skipif condition — evaluated at import, so it reaches any reporter as a bare False — is shown as its reason instead.

Traceability: owners, severities and markers that link

A marker holding an id is already collected and already shown, but as a flat badge — nothing in the report knows that PROJ-123 is an issue rather than a word. A pattern is the missing half. Name a marker in report_link_pattern, put {} where its argument goes, and every test carrying it gets a badge that opens the ticket.

ini pytest.ini — one MARKER=URL per line
[pytest]
report_link_pattern =
    jira = https://acme.atlassian.net/browse/{}
    testcase = https://acme.testrail.io/index.php?/cases/view/{}
    owner = https://github.com/orgs/acme/teams/{}
python The markers those patterns read
@pytest.mark.owner("payments-team")
@pytest.mark.severity("blocker")
@pytest.mark.jira("PROJ-123")
@pytest.mark.jira("PROJ-987")          # two tickets, two badges, one row
def test_refund_is_idempotent():
    ...

Test steps, Gherkin and markers in full — including --report-steps, --report-step-limit, and what a retry reports.

Evidence: screenshots, attachments and captured output

Three different kinds of "what actually happened", each kept against the test that produced it.

Screenshots, taken for you

A test that fails while holding a Selenium driver or a Playwright page is photographed. No hook to write, no fixture to add, nothing to import. The picture is taken at the very end of the test, before the fixture that quits the browser has run. What makes something a browser is that it can hand over a PNG, so Selenium, Playwright, appium, splinter and a driver wrapper of your own are all covered, whatever the fixture is called — page, driver, browser, selenium or anything else. A test driving two browsers at once gets a picture of each.

python An ordinary browser test — nothing about screenshots in it at all
def test_checkout(page):
    page.goto("/cart")
    assert page.locator("h1").inner_text() == "Cart"     # fails, and is photographed

Every screenshot lands in two places: the Screenshots gallery, and the Screens column of the row it belongs to. --report-screenshots decides which tests are photographed — failed by default, all if a screenshot of a pass is a baseline worth having, none to switch it off. When the moment matters, attach takes the PNG bytes yourself, from anything that can produce one; an image you attached is always kept whatever that flag says, and a test that attaches its own is not photographed again on the way out.

Attachments and API logs

A picture is no use when the thing under test is an API. attach_text, attach_json, attach_api and attach_file take the payloads instead, and everything a test hands over is opened from the API Logs tab.

python The four helpers at a glance
from pytest_html_reporter import attach_api, attach_file, attach_json, attach_text

attach_api(requests.get(url))                         # the whole call
attach_json({"expected": order, "got": response})     # pretty-printed, secrets blanked
attach_text(query, name="Query", format="sql")        # any text at all
attach_file("payloads/order.json")                    # a small file from disk
TipAttach on failure, not on every call. Keeping every response buries the one that matters and grows the report for no reason. Attach from a fixture's teardown and let the outcome decide — the reporter builds a test's record after the finalizers have run, which is what makes that work.

Captured stdout, stderr and logging

Everything pytest captures while a test runs — from setup, call and teardown alike — is kept against that test, and this is on by default. The Logs column shows how many lines a test produced; clicking it opens the output section by section with a Copy button.

What the test doesWhere it shows up
print(...)Captured stdout section
sys.stderr.write(...)Captured stderr section
log.info(...), log.warning(...)Captured log section, subject to --log-level
an assertion failure or tracebacknot here — the Error Message column already has it
warnings.warn(...)not here — pytest keeps its own warnings summary

So a test that only asserts has nothing to show and correctly gets a dash, even when it fails. Two of pytest's own options decide what reaches the column at all: -s (that is, --capture=no) sends stdout and stderr straight to the terminal so nothing is captured, and logging is captured from WARNING up unless --log-level lowers it.

NoteIf you are on -s for the live output, --capture=tee-sys gives you both: it streams to the terminal and still keeps the output for the report.

Screenshots in full covers the automatic capture, the async and unittest cases, and taking the picture yourself. The Python API documents all six functions — attach, attach_text, attach_json, attach_api, attach_file and step — with every argument each one takes.

Coverage

Run with pytest-cov and the report grows a Test Coverage tab: the overall percentage as a ring, the counts beside it, a row per file with its missing lines, and the percentage plotted across this run and the five builds before it. A chip on the Dashboard shows the figure and crosses to the tab. Nothing needs configuring — if coverage was measured, it is there.

shell Line coverage, and line + branch coverage
$ pytest tests/ --cov=my_package --html-report=./report
$ pytest tests/ --cov=my_package --cov-branch --html-report=./report

Coverage that was measured somewhere else

The tab does not need pytest-cov to have run in this session — useful in CI, where coverage is often produced by an earlier step. The kind of file is worked out from its contents, not its name, and a coverage.json or Cobertura coverage.xml sitting beside the report or at the project root is found without being named at all.

shell Three file formats, detected by content
$ pytest tests/ --report-coverage-file=coverage.xml     # Cobertura, from `coverage xml`
$ pytest tests/ --report-coverage-file=coverage.json    # from `coverage json`
$ pytest tests/ --report-coverage-file=.coverage        # coverage.py's own data file
NoteA .coverage data file is not picked up automatically — one is usually left over from an earlier run, and quietly publishing a number from last Tuesday is worse than publishing none. Name it if you want it. Reading a Cobertura coverage.xml needs no coverage package installed at all, which makes it the useful one when the reporting job is not the job that ran the tests.

The annotated source is linked, never embedded: generate htmlcov/ with --cov-report=html and the tab links to it, and only when that folder was written by this run. Framing it into the page would break the one property the whole reporter is built on — one file you can mail — and it would break silently, showing an empty frame wherever the folder did not travel with it.

The Test Coverage tab: percentage ring, statement counts, coverage trend and the per-file table with missing line ranges The Test Coverage tab: percentage ring, statement counts, coverage trend and the per-file table with missing line ranges
The Coverage tab after a run with --cov — the ring, the trend and the per-file table with its missing lines.

History and archives

Every run archives its own record, and the Archives section lists them. That file is what Trends, Highlights, the failure delta, the coverage trend and the whole Analytics tab are built from: it holds the test counts, a status and a duration per test, and the coverage percentage.

A build is dated by the moment its run started, which is kept in the name of its archive file — not by the file's mtime, which in CI is the moment of the checkout. That is what lets an age limit still measure the right thing after the reports have been copied into a fresh workspace.

shell The three retention limits
$ pytest tests/ --archive-count 7                  # keep the last N builds
$ pytest tests/ --archive-days 30                  # a rolling window; fractions allowed
$ pytest tests/ --archive-since '2026-06-01 09:00'  # a one-off cut

The three intersect: a build has to satisfy every limit you set to be kept. Set none of them and every build is kept for ever, which is what eventually makes a report slow to open — a retained build costs roughly 5KB of the page, so an hourly run reaches a multi-megabyte report inside a couple of months. How far back Analytics reads is whatever these three have kept.

TipRetention is a property of the job rather than of one run, so the ini file is usually the better place for it: set archive_count, archive_days or archive_since once and every invocation, however it is started, keeps the same window.

One name for a whole shape of a run

New in 0.4.3A suite is run in more than one shape, and each shape used to be a command line. A profile is a shape written down under a name — local, ci — and selected in one word.

On a laptop you want the browser to open, every log kept and a handful of builds archived. On CI you want no browser, logs only where something failed, a JUnit xml beside the report and a month of history. Kept as two long invocations — one in a Makefile target, one in a workflow file — they drift the moment either is edited, and the drift stays invisible until somebody reads a report missing the thing they went looking for.

toml Two shapes, written down once
[tool.pytest-html-reporter.profiles.local]
open = "auto"
logs = "all"
archive_count = 10

[tool.pytest-html-reporter.profiles.ci]
open = "none"
logs = "failed"
junit = "report/junit.xml"
archive_days = 30

Both files are read, so a repository that keeps a pytest.ini does not have to grow a pyproject.toml to say ci in a single word. A profile can be pinned as the default, so a bare pytest is already the shape the repository agreed on; a PYTEST_HTML_REPORTER_* variable overrides one setting for one job without editing a file everybody else reads; and a flag typed on the command line still beats all of it.

See Configuration for where profiles are written, what the 27 settings are called, and the six layers in order.

Running at scale

Parallel runs with pytest-xdist

Runs distributed with pytest-xdist are gathered into a single report. Every worker sends its results back to the controller, which merges them and writes one report — one build in Archives, one set of totals, one row per test — whichever way the tests were distributed. Tests are listed in collection order rather than the order the workers happened to finish them in, so a parallel report reads the same as a serial one. Nothing needs configuring, and running without -n is unaffected.

shell Both distribution modes produce one merged report
$ pytest tests/ -n 2 --html-report=./report
$ pytest tests/ -n auto --dist loadfile --html-report=./report

The strftime placeholders in --html-report are expanded once, when the run starts, which is what lets a parallel run — and a run that crosses a minute boundary — still write a single report.

CarefulResults are handed over when a worker finishes, so tests from a worker that crashes outright — rather than failing — are not in the report. pytest reports the crash itself.

Reruns

The plugin reacts to pytest-rerunfailures; there is no rerun-specific flag to set. A retried test is one row carrying the outcome that stuck, with the attempts it took in the Rerun column.

New in 0.4.2That count is also a button, and the panel behind it lists every attempt in order — what each one did, how long it took and the full error it raised — ending on the attempt the row itself is showing, marked kept. A test that failed twice and then passed used to read PASS  2 with nothing anywhere in the report saying what it had failed with; two failures for two different reasons is a different bug report from the same failure twice. The panel's Copy button hands the trail over in the shape somebody pastes into an issue. See the attempt trail.

Two more behaviours are worth knowing, and they deliberately differ:

Analytics counts retries too: a test that needed one to pass is flaky by definition, and the per-test row carries a retries column.

Matrix runs

pytest-xdist merges within one run, so a CI matrix — several processes on several machines — is out of its reach. Name each leg with --report-shard instead: it writes a bundle to <report>/shards/<id> and no report of its own, and the standalone pytest-html-reporter merge command turns every bundle into one build once the legs have finished. Legs that run one after another on a single machine can skip the extra command — give the last one --report-shard-merge and it merges as it ends.

Each leg still needs its own artifact name to get its bundle off the runner. The GitHub Action takes an artifact-name input for exactly this, and a build-info entry is the place to record which leg of the matrix a record came from. The CLI reference has the four shard flags and the merge command's own options.

CI integrations covers the rest: keeping the archive folder between runs so the trend charts have something to draw, and publishing the report where people will actually open it.

The ecosystem

The GitHub Action

A composite action that installs the plugin, runs pytest and publishes what comes out. Its inputs mirror the plugin's flags one for one — report-path, title, environment, build-info, archive-days, report-logs, report-coverage and the rest — plus the parts that only make sense on a runner.

yaml The whole quick start
name: tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: prashanth-sams/pytest-html-reporter-action@v1
        with:
          python-version: '3.12'
          requirements: requirements.txt
          tests: tests/
          title: Nightly regression

The GitHub Action in full — every input, the recipes for a pull-request comment, a coverage gate and a Pages deploy, and the outputs a later step can read.

The VS Code extension

The same results in the sidebar, while you are still in the file that broke. It reads output.json rather than the generated HTML, finds the report on its own, and reloads when a run finishes.

The VS Code extension in full — where it looks for your report, what each setting does, and the layout it expects on disk.

Where to go next