Skip to main content

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

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

pytest_jscov-0.8.0.tar.gz (21.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_jscov-0.8.0-py3-none-any.whl (18.1 kB view details)

Uploaded Python 3

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

Hashes for pytest_jscov-0.8.0.tar.gz
Algorithm Hash digest
SHA256 2d29ec979113fc09eb7c2eceb4be117af01310e54fd507a110d29e6c5b6c2f16
MD5 adaaf8083c3b9d60d0be54039f10c948
BLAKE2b-256 d01ecc86941fafa121e81118dce6ea50d61643cd2f6a8cd1bac196efe499fe32

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_jscov-0.8.0.tar.gz:

Publisher: release.yml on HDembinski/pytest-jscov

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for pytest_jscov-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 24ed87f739654b78d1afe653f1c9b81a4efed0f38d29750ee55b44ae9cfe24f9
MD5 d162a468fa74035fcebc0941ef9b6c77
BLAKE2b-256 bc922c728b9c2cb7d2df9432d05daa997dc2af8bfeb9ccaaa925d4f94f5d872f

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_jscov-0.8.0-py3-none-any.whl:

Publisher: release.yml on HDembinski/pytest-jscov

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page