Pytest plugin for JavaScript coverage via Playwright CDP
Project description
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_jscov-0.8.0.tar.gz.
File metadata
- Download URL: pytest_jscov-0.8.0.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d29ec979113fc09eb7c2eceb4be117af01310e54fd507a110d29e6c5b6c2f16
|
|
| MD5 |
adaaf8083c3b9d60d0be54039f10c948
|
|
| BLAKE2b-256 |
d01ecc86941fafa121e81118dce6ea50d61643cd2f6a8cd1bac196efe499fe32
|
Provenance
The following attestation bundles were made for pytest_jscov-0.8.0.tar.gz:
Publisher:
release.yml on HDembinski/pytest-jscov
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_jscov-0.8.0.tar.gz -
Subject digest:
2d29ec979113fc09eb7c2eceb4be117af01310e54fd507a110d29e6c5b6c2f16 - Sigstore transparency entry: 1234157908
- Sigstore integration time:
-
Permalink:
HDembinski/pytest-jscov@f07c1e90482c5c500107a3f896d90f82593ddc06 -
Branch / Tag:
refs/tags/0.8.0 - Owner: https://github.com/HDembinski
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f07c1e90482c5c500107a3f896d90f82593ddc06 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pytest_jscov-0.8.0-py3-none-any.whl.
File metadata
- Download URL: pytest_jscov-0.8.0-py3-none-any.whl
- Upload date:
- Size: 18.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
24ed87f739654b78d1afe653f1c9b81a4efed0f38d29750ee55b44ae9cfe24f9
|
|
| MD5 |
d162a468fa74035fcebc0941ef9b6c77
|
|
| BLAKE2b-256 |
bc922c728b9c2cb7d2df9432d05daa997dc2af8bfeb9ccaaa925d4f94f5d872f
|
Provenance
The following attestation bundles were made for pytest_jscov-0.8.0-py3-none-any.whl:
Publisher:
release.yml on HDembinski/pytest-jscov
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_jscov-0.8.0-py3-none-any.whl -
Subject digest:
24ed87f739654b78d1afe653f1c9b81a4efed0f38d29750ee55b44ae9cfe24f9 - Sigstore transparency entry: 1234158348
- Sigstore integration time:
-
Permalink:
HDembinski/pytest-jscov@f07c1e90482c5c500107a3f896d90f82593ddc06 -
Branch / Tag:
refs/tags/0.8.0 - Owner: https://github.com/HDembinski
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f07c1e90482c5c500107a3f896d90f82593ddc06 -
Trigger Event:
push
-
Statement type: