{"id":48755862,"url":"https://github.com/NullVoxPopuli/testem-code-coverage","last_synced_at":"2026-04-28T23:01:01.497Z","repository":{"id":343319339,"uuid":"1177119164","full_name":"NullVoxPopuli/testem-code-coverage","owner":"NullVoxPopuli","description":"Code coverage for the testem ecosystem","archived":false,"fork":false,"pushed_at":"2026-03-20T15:00:20.000Z","size":492,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-21T05:32:56.323Z","etag":null,"topics":["chrome","ci","coverage","quality","testem","testing"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NullVoxPopuli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-09T18:00:18.000Z","updated_at":"2026-03-20T14:59:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/NullVoxPopuli/testem-code-coverage","commit_stats":null,"previous_names":["nullvoxpopuli/ember-code-coverage","nullvoxpopuli/testem-code-coverage"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/NullVoxPopuli/testem-code-coverage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Ftestem-code-coverage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Ftestem-code-coverage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Ftestem-code-coverage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Ftestem-code-coverage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NullVoxPopuli","download_url":"https://codeload.github.com/NullVoxPopuli/testem-code-coverage/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Ftestem-code-coverage/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32402673,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T19:38:08.556Z","status":"ssl_error","status_checked_at":"2026-04-28T19:37:55.688Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chrome","ci","coverage","quality","testem","testing"],"created_at":"2026-04-13T02:00:31.728Z","updated_at":"2026-04-28T23:01:01.491Z","avatar_url":"https://github.com/NullVoxPopuli.png","language":"JavaScript","funding_links":[],"categories":["Testing"],"sub_categories":["Utilities"],"readme":"# testem-code-coverage\n\nGet _browser_ test coverage, without sus babel plugins, using a [chrome-specific feature](https://developer.chrome.com/docs/devtools/coverage), [`startPreciseCoverage`](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage).\n\nWorks with any test framework, but presently only provides an adapter for qunit.\n\n## Installation\n\n```bash\nnpm add testem-code-coverage\n# or from github\nnpm add \"github:NullVoxPopuli/testem-code-coverage#main\"\n```\n\n## Setup\n\nThis is assuming you are using testem and qunit.\n\n\u003e [!NOTE]\n\u003e While neither testem nor qunit are _new_, I consider them to be closer to finished than vitest is, and generally provide a better browser-based testing experience than vitest does (at least for now).\n\nSetup the testem middleware\n\n```js\n// testem.cjs\nmodule.exports = {\n  // ...\n  middleware: [\n    require(\"testem-code-coverage\").middleware({\n      /* optional config here */\n    }),\n  ],\n  // ...\n  browser_args: {\n    Chrome: {\n      ci: [\n        // ...\n        \"--remote-debugging-port=9222\",\n        // ...\n      ],\n    },\n  },\n};\n```\n\nSetup the runtime\n\n```js\n// tests/test-helper.js\nimport { setupCoverage } from \"testem-code-coverage/runtime\";\n\nexport async function start() {\n  // ... must come before tests are started\n  setupCoverage();\n  // ...\n  qunitStart();\n}\n```\n\n### Vite\n\nIf you are using Vite, source maps must be enabled for the build that serves your browser tests.\n\n```js\n// vite.config.mjs\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n  build: {\n    sourcemap: true,\n  },\n});\n```\n\n## Configuration\n\n### Testem\n\nonly the testem middleware is configurable, as it is what outputs the coverage report.\n\nHere are the default options:\n\n```js\nrequire(\"testem-code-coverage\").middleware({\n  /**\n   * If a non-absolute path, this defaults to CWD + /coverage\n   * and is the location where the coverage reports are output\n   * including: HTML, JSON, and TXT\n   */\n  outputFolder: \"coverage\",\n\n  /**\n   * Path to the built assets that Chrome loads during the test run.\n   * Defaults to \"dist\".\n   */\n  distDir: \"dist\",\n\n  /**\n   * Paths to include in the coverage report.\n   * By default, `node_modules` are excluded.\n   * But specifying library names here would allow you to track coverage\n   * of those libraries.\n   */\n  include: [],\n\n  /**\n   * Glob patterns for files to exclude from the coverage report.\n   * Matched against relative paths from the project root.\n   *\n   * Defaults to:\n   *   [\"**/tests/**\", \"**/node_modules/**\", \"**/.embroider/**\", \"**/embroider-implicit-modules/**\", \"**/-embroider-*\"]\n   *\n   * Setting this replaces the defaults entirely.\n   * Pass an empty array to disable all exclusions.\n   */\n  exclude: [\"**/tests/**\", \"**/node_modules/**\", \"**/.embroider/**\", \"**/embroider-implicit-modules/**\", \"**/-embroider-*\"],\n\n  /**\n   * Built-in Istanbul reporters to run.\n   *\n   * Defaults to [\"text\", \"html\", \"json-summary\"].\n   *\n   * Any reporter name supported by istanbul-reports can be used here,\n   * for example: \"lcov\", \"cobertura\", \"json\", or \"text-summary\".\n   *\n   * When omitted, the default behavior is preserved, including writing\n   * coverage/coverage-summary.txt via the text reporter.\n   */\n  reporters: [\"text\", \"html\", \"json-summary\"],\n\n  /**\n   * async callback that can be used to generate additional\n   * report formats.\n   *\n   * @type {(coverageReport: JSON[]) =\u003e Promise\u003cvoid\u003e}\n   */\n  handleReport: undefined,\n\n  /**\n   * Chrome-specific configuration for telling the middleware\n   * how to connect to and interact with Chrome\n   */\n  chrome: {\n    /**\n     * Amount of time to allow for Chrome to boot up.\n     *\n     * Default is 30 seconds.\n     * Units in milliseconds.\n     */\n    connectionTimeout: 30_000,\n\n    /**\n     * This is how we connect to and communicate with Chrome\n     */\n    remoteDebuggingPort: 9222,\n  },\n\n  /**\n   * When true, write middleware diagnostics to stderr and coverage/errors.log.\n   */\n  debug: false,\n});\n```\n\n### Reporter selection\n\nUse `reporters` when you want to choose which built-in Istanbul outputs are written.\n\n```js\nrequire(\"testem-code-coverage\").middleware({\n  reporters: [\"html\", \"json-summary\", \"lcov\"],\n});\n```\n\n- `reporters` accepts reporter names as strings.\n- Any reporter supported by `istanbul-reports` can be used.\n- Omitting `reporters` preserves the current default outputs: terminal `text`, `html`, `json-summary`, and `coverage-summary.txt`.\n- Setting `reporters` replaces the defaults entirely.\n- If `text` is included, the middleware also writes `coverage/coverage-summary.txt`.\n\nUse `handleReport` only when you need custom post-processing beyond Istanbul's built-in reporters.\n\n## Caveats about the implementation details\n\nThese are all internal things to this testem-code-coverage library\n\n### `Page.reload()` is required for accurate coverage\n\nAfter connecting to Chrome via CDP and calling `startPreciseCoverage`, this library reloads the page before the tests run. This is not optional — it is what makes function-level coverage correct.\n\n**Why:** testem launches Chrome with the test URL as a CLI argument, so Chrome navigates to the page _immediately on process start_. By the time CDP can connect (a page target only exists after Chrome has loaded the page), the test bundle has already been parsed and all module-level code has already executed — without any coverage tracking active.\n\nThe consequence of skipping the reload:\n\n- V8 emits **no top-level function entry** (`startOffset=0`) for the bundle, because the module never ran while coverage was active.\n- Functions that are **defined but never called** (e.g. an untested class method) produce **no V8 record at all**. They are invisible to the coverage snapshot.\n- `v8-to-istanbul` initialises every source line with `count = 1` (covered) and only zeroes lines that appear in the V8 snapshot with an explicit `count = 0`. Lines with no entry stay green.\n- Result: uncalled functions report **100% coverage** — a silent false positive.\n\nCalling `Page.reload()` after `startPreciseCoverage` ensures the scripts run while coverage is already armed. V8 then produces the top-level function entry and correct `count = 0` sub-ranges for every uncalled function, which `v8-to-istanbul` uses to zero those lines out. This is the same pattern used by Puppeteer and Playwright for browser coverage.\n\n### There is no Chrome launch flag equivalent to `startPreciseCoverage`\n\nThe CDP docs state: _\"Coverage data for JavaScript executed **before** enabling precise code coverage may be incomplete.\"_ There is no `--js-flags` or other Chrome launch flag that replicates what `Profiler.startPreciseCoverage` does, because:\n\n- `startPreciseCoverage` prevents V8 from running optimized/lazy compilation and resets execution counters — these are runtime behaviors controlled on a live isolate via CDP.\n- Chrome launch flags control how the browser process starts, not V8's internal coverage state machine.\n- Node.js has `NODE_V8_COVERAGE` because it wraps the entire process startup; Chrome has no equivalent since the browser starts before any test harness can intercept it.\n\nThe `Page.reload()` is the correct and only reliable approach for browser-based precise coverage via CDP.\n\n### testem has no hook between Chrome starting and the page loading\n\ntestem's lifecycle hooks (`on_start`, `before_tests`) run on the server side before Chrome launches — the CDP page target does not yet exist at that point. Chrome is spawned with the test URL as the last CLI argument and navigates immediately, leaving no gap to intercept. There is no built-in way to run code between \"Chrome process starts\" and \"Chrome loads the page\" without patching testem itself.\n\n### Branch counts from V8 are non-deterministic\n\nV8 uses tiered JIT compilation: functions start in the **Ignition** interpreter and may be promoted to **Maglev** or **TurboFan** optimising compilers if they become \"hot\". The coverage ranges reported by `Profiler.takePreciseCoverage` reflect whichever tier each function is in at the moment coverage is collected. TurboFan can split a single `if` into multiple tracked ranges or collapse branches it proves are unreachable, so the total number of branch ranges varies between runs depending on which background optimisation thread fires before `takePreciseCoverage` is called.\n\nIn practice, **line and function coverage for your own source files are stable** — those functions are called enough to consistently reach the same tier. The volatile numbers tend to appear in framework and vendor code (Ember internals, QUnit, test helpers) where tier-up is marginal. If you need deterministic snapshots, consider asserting only on `lines` and `functions` and omitting `branches`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNullVoxPopuli%2Ftestem-code-coverage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNullVoxPopuli%2Ftestem-code-coverage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNullVoxPopuli%2Ftestem-code-coverage/lists"}