Skip to main content

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 .ts source 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(...) or history.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(), and page.close()
  • During the page lifetime: flushes coverage when selected browser navigation functions such as location.assign() and location.replace() are called
  • During the page lifetime: lets you call await save_coverage(page) to persist coverage before JS-triggered navigation
  • On page.close() or context.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

  1. The pytest plugin (pytest_jscov.plugin) patches Playwright when --cov is active and uses a pytest_runtestloop hook to inject the accumulated JS/TS line hits directly into pytest-cov's active Coverage object before pytest-cov finalises the report.

  2. 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)

Source distribution for pytest-jscov 0.8.0
File Size Uploaded Provenance
pytest_jscov-0.8.0.tar.gz 21.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-jscov 0.8.0
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page