Skip to main content

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 .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)
  • Zero-config when coverage is not active: the jscov fixture is a no-op unless --cov is 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(), and page.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

  1. The pytest plugin (pytest_jscov.plugin) provides the jscov fixture and a pytest_runtestloop hook. During each test, coverage entries from V8 are accumulated in memory. After all tests complete, the accumulated data is written as a .coverage.jscov file that pytest-cov's combine() step picks up automatically.

  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.

Configuration

static_root (required)

This is the filesystem path to your static files directory. Can be set in two ways:

  1. coverage.py config (recommended):

    [tool.coverage.pytest_jscov.covplugin]
    static_root = "src/myapp/static"
    
  2. 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

pytest_jscov-0.6.0.tar.gz (18.4 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.6.0-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

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

Hashes for pytest_jscov-0.6.0.tar.gz
Algorithm Hash digest
SHA256 a7b56ae3942032c6bd4080f469e4c296ac1dc190c1ee863c3b57b6dc934e2cc4
MD5 a5dc924b525716b22419bb454ecf6485
BLAKE2b-256 10ea99937f3044e45e77ed7d5ba588a6c5648bccc589a4a4129529450c1f23eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_jscov-0.6.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.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

Hashes for pytest_jscov-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fce6b88bd507b9b15f83b5b3c784c04c16305ec0c1bf784244185bdb2c528792
MD5 3c6ba3671a191606d2dba61318be8b55
BLAKE2b-256 9f2552951d0dd3fd487bea9fffabdb85010a6b80bc06247bd5bbdee0322a4795

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_jscov-0.6.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