pytest-jscov
Get a single, unified coverage report for your full-stack Python + JS/TS application.
This pytest plugin collects JavaScript and TypeScript code coverage from Playwright browser tests via Chrome's DevTools Protocol (CDP) and merges it into pytest-cov's combined report. In other words, we use Chrome's profiler to track which lines of code were executed.
Using pytest-jscov is perfect if you write your frontend tests in Python with Playwright already. Then you get coverage measurements on top with almost no extra effort.
Features
- Collects V8 precise coverage from Chromium-based browsers via CDP
- Resolves inline sourcemaps (e.g. from esbuild) to map coverage back to
original
.tssource files - Merges JS/TS line hits into pytest-cov so everything appears in one report
- Works with
--cov-branch, but no branch coverage is collected - Full VS Code integration: coverage gutters work for JS/TS files just for Python
Limitations
Requirements for use
You must implement your tests in Python using the async Playwright API with the Chrome browser for this to work. For details, see the installation section.
Loss of coverage data due to page navigation
Coverage collection is not perfect when doing page navigation. Coverage data is attached
to the currently executing page context. If that page reloads or navigates away before
pytest-jscov reads the coverage data, the old execution context is gone and its coverage
data with it.
We implement a partial workarounds for this issue. When the plugin is active,
Playwright browser contexts created via browser.new_context() and pages
created via browser.new_page() are automatically instrumented so that
reload, goto, go_back, and go_forward flush coverage immediately
before those navigations run.
In addition, the plugin installs CDP function-call breakpoints for a small set of common browser navigation APIs and flushes coverage when they are called. This currently covers direct calls to:
window.location.assign(...)window.location.replace(...)location.assign(...)location.replace(...)HTMLAnchorElement.prototype.click()HTMLFormElement.prototype.submit()HTMLFormElement.prototype.requestSubmit()
This improves coverage retention for both Playwright-driven navigations and some JavaScript-triggered navigations, but it still does not catch every way a page can transition, such as
window.location.href = ...window.location.pathname = ...- History API changes like
history.pushState(...)orhistory.replaceState(...) - navigations that do not go through one of the tracked function calls
For those cases, you can call save_coverage(page) just before the action that would
replace the page context:
from pytest_jscov import save_coverage
await save_coverage(page)
await page.evaluate("window.location.assign('about:blank')")
Detection of executable lines
We use a custom code to detect executable lines in JS/TS files to keep this project lightweight. This code may not be perfect, if you encounter issues, drop an issue.
Installation
pip install pytest-jscov
The plugin requires pytest, pytest-cov, and playwright (with Chromium installed).
Register the coverage.py plugin
In your pyproject.toml:
[tool.coverage.run]
plugins = ["pytest_jscov.covplugin"]
[tool.coverage.pytest_jscov.covplugin]
static_root = "src/myapp/static"
static_root tells the plugin where your JS/TS source files live on disk, so
it can match coverage data to real files.
The plugin automatically switches coverage.py to the plugin-compatible tracing
ctrace core.
If you run pytest --cov, any page created from Playwright in Python will have its
coverage recorded. The methods browser.new_context() and browser.new_page()
return instrumented objects so that:
- On each
context.new_page(): opens a CDP session and starts V8 precise coverage for the new page - During the page lifetime: flushes coverage before
page.reload(),page.goto(),page.go_back(),page.go_forward(), andpage.close() - During the page lifetime: flushes coverage when selected browser navigation
functions such as
location.assign()andlocation.replace()are called - During the page lifetime: lets you call
await save_coverage(page)to persist coverage before JS-triggered navigation - On
page.close()orcontext.close(): collects coverage from all pages before closing the context
When --cov is not passed to pytest, Playwright is not instrumented.
If you want to use manual coverage saving, import it explicitly:
from pytest_jscov import save_coverage
When --cov is not passed to pytest, save_coverage does nothing.
Run your tests
pytest --cov=src --cov-report=term
JS and TS files appear alongside Python in the coverage report:
Name Stmts Miss Cover
----------------------------------------------------------
src/myapp/main.py 180 90 50%
src/myapp/static/app.js 441 144 67%
src/myapp/static/modules/foo.ts 194 14 93%
src/myapp/static/modules/bar.js 103 10 90%
----------------------------------------------------------
TOTAL 918 258 72%
Filtering
Just like with Python sources, you can restrict the coverage report to individual files or directories by passing a path to --cov, for example:
pytest --cov=src/myapp/static/foo.js
How it works
-
The pytest plugin (
pytest_jscov.plugin) patches Playwright when--covis active and uses apytest_runtestloophook to inject the accumulated JS/TS line hits directly into pytest-cov's activeCoverageobject before pytest-cov finalises the report. -
The coverage.py plugin (
pytest_jscov.covplugin) registers a file tracer and file reporter for JS/TS files. This teaches coverage.py how to find, read, and report on JavaScript and TypeScript source files.
Sourcemap support
When a script contains an inline sourcemap
(//# sourceMappingURL=data:application/json;base64,...), the plugin decodes
the VLQ mappings and attributes coverage to the original source files. This
means if you use a bundler like esbuild to transpile TypeScript with
--sourcemap=inline, coverage is reported against your .ts files, not the
generated .js.
IDE integration
When using the
Python Testing
extension in VS Code, coverage gutters work for JS/TS files just like they do
for Python. VS Code runs pytest with --cov --cov-branch automatically, and
the merged report includes your frontend files — you'll see green/red line
markers directly in your .ts and .js sources.
Metadata
Release files for pytest-jscov 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pytest_jscov-0.8.0.tar.gz | 21.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_jscov-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.3 kB
Release files / pytest_jscov-0.8.0.tar.gz
| Download URL | pytest_jscov-0.8.0.tar.gz |
|---|---|
| Size | 21.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2d29ec979113fc09eb7c2eceb4be117af01310e54fd507a110d29e6c5b6c2f16
|
|
BLAKE2b-256 checksum How to use checksums |
d01ecc86941fafa121e81118dce6ea50d61643cd2f6a8cd1bac196efe499fe32
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 4, 2026.
Transparency logRelease files / pytest_jscov-0.8.0-py3-none-any.whl
| Download URL | pytest_jscov-0.8.0-py3-none-any.whl |
|---|---|
| Size | 18.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
24ed87f739654b78d1afe653f1c9b81a4efed0f38d29750ee55b44ae9cfe24f9
|
|
BLAKE2b-256 checksum How to use checksums |
bc922c728b9c2cb7d2df9432d05daa997dc2af8bfeb9ccaaa925d4f94f5d872f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 4, 2026.
Transparency logSecurity for pytest-jscov 0.8.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pytest_jscov-0.8.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.