Pytest plugin for JavaScript coverage via Playwright CDP
Project description
pytest-jscov
A pytest plugin that 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. More precisely, we use Chrome's profiler to track which lines of code were executed.
Get a single, unified coverage report for your full-stack Python + JS/TS application.
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) - Zero-config when coverage is not active: the
jscovfixture is a no-op unless--covis passed - Full VS Code integration
Limitations
You must implement your tests in Python using the async Playwright API with the Chrome
browser for this to work, you need to configure both pytest and coverage to use this
plugin, and you need to use the jscov context manager in your page fixture. For more
details, see the installation section.
Even then, coverage collection is not perfect. Coverage collection 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 is gone with
it.
The current implementation uses a partial workaround. While the jscov(...)
context is active, it temporarily wraps the active Playwright Page object's
reload, goto, go_back, and go_forward methods and flushes coverage
immediately before those navigations run.
This improves coverage retention for navigations initiated through those page methods in tests, but it does not catch every way a page can navigate or be replaced. In particular, navigations triggered from inside page JavaScript, such as
window.location.assign(...)
Those cases need lower-level page lifecycle hooks or browser events rather than only
method wrapping on the Playwright Page object.
Coverage reporting for files that were never observed by V8 also has a limitation.
The file reporter can still discover .js and .ts files on disk under
static_root, but Chrome can only provide coverage ranges for scripts that were
actually loaded into the browser. For files that were never loaded during the
test run, pytest-jscov infers executable lines heuristically from the source
text so they still appear in the report. As a result, the reported Stmts and
Miss counts for those not-yet-covered files are approximate and may not match
V8's exact range model line-for-line.
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.
Use the jscov fixture in your page fixture
import pytest
from collections.abc import AsyncIterator
from playwright.async_api import Browser, Page
@pytest.fixture
async def page(browser: Browser, jscov) -> AsyncIterator[Page]:
context = await browser.new_context()
page = await context.new_page()
async with jscov(context, page, "http://localhost:8000"):
await page.goto("http://localhost:8000")
yield page
await page.close()
await context.close()
The jscov(context, page, base_url) call returns an async context manager
that:
- On enter: opens a CDP session and starts V8 precise coverage
- During the context: flushes coverage before
page.reload(),page.goto(),page.go_back(), andpage.go_forward() - On exit: collects coverage, fetches script sources, and records everything
When --cov is not passed to pytest, jscov is a no-op context manager, so
you don't need any if guards.
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%
How it works
-
The pytest plugin (
pytest_jscov.plugin) provides thejscovfixture and apytest_runtestloophook. During each test, coverage entries from V8 are accumulated in memory. After all tests complete, the accumulated data is written as a.coverage.jscovfile that pytest-cov'scombine()step picks up automatically. -
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.
Configuration
static_root (required)
This is the filesystem path to your static files directory. Can be set in two ways:
-
coverage.py config (recommended):
[tool.coverage.pytest_jscov.covplugin] static_root = "src/myapp/static"
-
CLI option:
pytest --cov=src --jscov=src/myapp/static
The CLI option takes precedence over the config file.
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
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.6.0.tar.gz.
File metadata
- Download URL: pytest_jscov-0.6.0.tar.gz
- Upload date:
- Size: 18.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a7b56ae3942032c6bd4080f469e4c296ac1dc190c1ee863c3b57b6dc934e2cc4
|
|
| MD5 |
a5dc924b525716b22419bb454ecf6485
|
|
| BLAKE2b-256 |
10ea99937f3044e45e77ed7d5ba588a6c5648bccc589a4a4129529450c1f23eb
|
Provenance
The following attestation bundles were made for pytest_jscov-0.6.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.6.0.tar.gz -
Subject digest:
a7b56ae3942032c6bd4080f469e4c296ac1dc190c1ee863c3b57b6dc934e2cc4 - Sigstore transparency entry: 1224892550
- Sigstore integration time:
-
Permalink:
HDembinski/pytest-jscov@7f2477ffde6ea2e6c04204be3f54807d45cdc698 -
Branch / Tag:
refs/tags/0.6.0 - Owner: https://github.com/HDembinski
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7f2477ffde6ea2e6c04204be3f54807d45cdc698 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pytest_jscov-0.6.0-py3-none-any.whl.
File metadata
- Download URL: pytest_jscov-0.6.0-py3-none-any.whl
- Upload date:
- Size: 14.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 |
fce6b88bd507b9b15f83b5b3c784c04c16305ec0c1bf784244185bdb2c528792
|
|
| MD5 |
3c6ba3671a191606d2dba61318be8b55
|
|
| BLAKE2b-256 |
9f2552951d0dd3fd487bea9fffabdb85010a6b80bc06247bd5bbdee0322a4795
|
Provenance
The following attestation bundles were made for pytest_jscov-0.6.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.6.0-py3-none-any.whl -
Subject digest:
fce6b88bd507b9b15f83b5b3c784c04c16305ec0c1bf784244185bdb2c528792 - Sigstore transparency entry: 1224892970
- Sigstore integration time:
-
Permalink:
HDembinski/pytest-jscov@7f2477ffde6ea2e6c04204be3f54807d45cdc698 -
Branch / Tag:
refs/tags/0.6.0 - Owner: https://github.com/HDembinski
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7f2477ffde6ea2e6c04204be3f54807d45cdc698 -
Trigger Event:
push
-
Statement type: