How to Generate a Pytest Code Coverage Report
Generate terminal, HTML, XML, and other pytest coverage reports with pytest-cov. Learn how to choose source files, configure CI thresholds, and fix common reporting issues.
Install pytest-cov, then run it with the package or source directory you want to measure:
python -m pip install pytest-cov
pytest --cov=YOUR_PACKAGE tests/
Replace YOUR_PACKAGE with your importable application package or source path, and tests/ with your test directory. This produces a terminal coverage summary. To see uncovered line numbers and save an HTML report in the same run, use:
pytest --cov=YOUR_PACKAGE --cov-report=term-missing --cov-report=html tests/
The HTML files go to htmlcov/ by default. Open htmlcov/index.html in a browser. pytest-cov documentation describes the plugin’s collection and reporting options.
1. Install pytest-cov and generate the first report
Run these commands from the project environment where pytest and your application dependencies are installed:
python -m pip install pytest-cov
python -m pytest --cov=YOUR_PACKAGE tests/
Using python -m pytest ensures pytest runs under the selected Python interpreter. If your project uses a virtual environment, activate it first. The default report is a terminal summary with statement counts, missed statements, and a coverage percentage.
For example, if the package is named myproj and tests are in tests/:
python -m pytest --cov=myproj tests/
Coverage measures which code ran during the test run. It does not establish that the code behaved correctly or that the tests checked every important outcome.
2. Choose report formats
You can produce multiple report formats from one test run. Choose based on who or what needs to read the result:
| Report | Option | Output | Useful for |
|---|---|---|---|
| Terminal summary | --cov-report=term |
Console output | A quick overview |
| Terminal with missing lines | --cov-report=term-missing |
Console output | Finding lines to cover |
| HTML | --cov-report=html |
htmlcov/ directory |
Browsing coverage by file and line |
| XML | --cov-report=xml |
coverage.xml |
Tools that consume XML |
| JSON | --cov-report=json |
coverage.json |
Scripts and JSON consumers |
| Markdown | --cov-report=markdown:coverage.md |
coverage.md |
Markdown summaries |
| LCOV | --cov-report=lcov:coverage.info |
coverage.info |
Consumers expecting LCOV |
| Annotated source | --cov-report=annotate:coverage-annotated |
coverage-annotated/ directory |
Reviewing annotated source output |
Specify a destination after a colon when you want to choose the output path. HTML and annotated source use directories; XML, JSON, Markdown, and LCOV use files.
Terminal with uncovered line numbers
pytest --cov=myproj --cov-report=term-missing tests/
To omit fully covered files from that terminal listing:
pytest --cov=myproj --cov-report=term-missing:skip-covered tests/
HTML report in a named directory
pytest --cov=myproj --cov-report=html:coverage-html tests/
Open coverage-html/index.html locally. The report lets you navigate files and inspect covered and missed lines.
Several reports in one run
pytest --cov=myproj \
--cov-report=term-missing \
--cov-report=html:coverage-html \
--cov-report=xml:coverage.xml \
tests/
Once you specify any --cov-report option, pytest-cov does not add the default terminal report automatically. Include --cov-report=term or --cov-report=term-missing explicitly if you want terminal output alongside saved reports. To collect coverage without generating a report in that invocation, set the report option empty:
pytest --cov=myproj --cov-report= tests/
3. Select the source you want to measure
The coverage source determines what counts in the report. Point --cov at the application package or source path, rather than relying on test discovery to define the scope:
pytest --cov=myproj tests/
You can pass multiple --cov values when measuring multiple packages or paths:
pytest --cov=service_a --cov=service_b tests/
A valued option such as --cov=myproj overrides the source setting in coverage configuration. If you maintain the source list in coverage configuration, use bare --cov to let that configuration provide it:
pytest --cov tests/
Use one source-selection approach intentionally. Mixing a configured source list with a different valued --cov=... can make the report cover a different set of files than expected.
4. Make coverage the project default
To run coverage whenever pytest runs, put the options in the pytest configuration. For pyproject.toml:
[tool.pytest.ini_options]
addopts = "--cov=myproj --cov-report=term-missing"
Then a regular test command generates the report:
pytest tests/
Because --cov accepts an optional value, avoid placing a bare --cov as the last token in addopts where it could consume a following command-line argument. If you intentionally want the option’s value empty, write --cov=.
Projects may have coverage settings in .coveragerc, tox.ini, setup.cfg, or pyproject.toml. When the wrong settings appear to apply, select a specific file with --cov-config:
pytest --cov=myproj --cov-config=path/to/.coveragerc tests/
The default .coveragerc name can trigger lookup across supported configuration files. Check for competing files, especially when tests change working directories or run subprocesses, and make the intended config path explicit where needed.
5. Measure branches and enforce a minimum
Line coverage records whether executable lines ran. Branch coverage also measures alternate control-flow paths. Enable it on the command line with --cov-branch:
pytest --cov=myproj --cov-branch --cov-report=term-missing tests/
Alternatively, enable branch measurement in the coverage configuration. Use one consistent setup for local and CI runs so the resulting percentages are comparable.
To fail the run when total coverage falls below a minimum percentage, use --cov-fail-under:
pytest --cov=myproj --cov-fail-under=85 tests/
Replace 85 with your project’s chosen threshold. A threshold is a quality gate on the reported total, not evidence that every important behavior has a test. Coverage tools can report execution; your tests still need assertions for expected behavior.
6. Repeated runs, failed tests, and test contexts
By default, pytest-cov starts with clean coverage data for a run. If you deliberately need to accumulate data across separate runs, use --cov-append:
pytest --cov=myproj --cov-append tests/
Appending can be useful when runs cover distinct test selections, but old data can make a later report include execution from an earlier run. Start clean when you need a report representing only the current test run.
By default, pytest-cov still reports coverage when tests fail. The --no-cov-on-fail option controls whether coverage is reported after failure; use it when you want to suppress the report in that case.
For test-level context in the collected data, use --cov-context=test. This records contexts such as test names and parametrization, which can help investigate which tests exercised code.
7. Use coverage reports in CI
In continuous integration, choose a report format consumed by your next step and set an explicit destination when another tool needs a known filename. For example:
pytest --cov=myproj \
--cov-report=term-missing \
--cov-report=xml:coverage.xml \
--cov-fail-under=85 \
tests/
This prints missing lines, writes coverage.xml, and returns a failing status if total coverage is below the selected threshold. XML is one option for downstream processors; pytest-cov documentation gives Coveralls as an example of a service that can process coverage data produced in CI. Check the service’s current documentation for its upload procedure and accepted format.
Markdown is another option when the CI environment supports a step summary:
pytest --cov=myproj --cov-report=markdown:coverage.md tests/
pytest-cov also documents Markdown append mode for workflows that add report content to a GitHub Actions step summary. Configure paths, thresholds, and report destinations consistently across CI jobs so reports remain interpretable.
8. Troubleshoot common coverage report problems
| Symptom | Likely cause | Fix |
|---|---|---|
| The report includes tests or unrelated files | The measured source scope is too broad or not set as intended. | Pass --cov=YOUR_PACKAGE, or define source in coverage configuration and use bare --cov. A valued --cov=... overrides configured source. |
| No terminal table appears | A report option was provided, so the default terminal report was disabled. | Add --cov-report=term or --cov-report=term-missing. |
| The report file is in an unexpected location | The default output path was used or the destination was assumed. | Set an explicit path, for example --cov-report=html:coverage-html or --cov-report=xml:coverage.xml. |
| Coverage configuration seems ignored | Another supported config file may be taking precedence, or the working directory changed. | Check .coveragerc, tox.ini, setup.cfg, and pyproject.toml; select the intended file with --cov-config=PATH. |
| Tests fail but a report still appears | Reporting on test failure is the default behavior. | Use --no-cov-on-fail if reports should be suppressed when tests fail. |
| Coverage is unexpectedly cumulative | --cov-append retained data from earlier runs. |
Remove append mode for a clean run, or clear stale coverage data using your project’s normal coverage workflow. |
| Coverage percentage is below the expected gate | The selected source, branch setting, or threshold differs from the intended policy. | Confirm the source scope and whether branch coverage is enabled; then review missing lines and the configured threshold. |
| A package appears missing from the report | The configured source path or package name may not match the project layout. | Use the importable package name or the correct source path, and verify which configuration file pytest-cov loaded. |
9. Performance, reliability, and cost considerations
Coverage collection adds work to a test run, while generating HTML or other saved reports adds output creation and storage. The exact effect depends on the project and workflow; the documentation provides report features, not a benchmark that predicts your project’s runtime.
For reliable comparisons, keep the measured source, branch setting, test selection, and configuration consistent. Avoid appending old data unless combining runs is intentional. In CI, use explicit report destinations and make the chosen threshold visible in the command or project configuration.
pytest-cov is an installable Python package. Its direct package installation and use do not require buying a physical product. If you send generated coverage files to another service, check that service’s current terms and pricing separately.
10. Or skip the browser setup
This guide is about pytest coverage reports, which are generated from Python test runs. If your project also needs website screenshots for visual checks or documentation, ScreenshotNeo can return an image or PDF from one GET request. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card.
11. Frequently asked questions
Does pytest-cov measure branch coverage by default?
Use --cov-branch or enable branches in coverage configuration when you want alternate control-flow paths measured.
Can one pytest run create both HTML and XML reports?
Yes. Pass multiple --cov-report options and include a terminal report explicitly if you also want console output.
What does a coverage percentage tell me?
It summarizes the measured code that ran under the selected source and coverage settings. It does not show whether assertions adequately check the behavior.
Why does my saved report differ from the terminal percentage?
Check that both outputs come from the same run and use the same source scope, branch configuration, and accumulated-data behavior.
Can pytest-cov produce data for downstream tools?
Yes. Select a format such as XML, JSON, or LCOV that the receiving tool supports, and use an explicit destination when its workflow expects a particular path.


