Skip to content

coverage.json reference (schema version 1) ​

This page is the field-by-field reference for coverage.json, the machine-readable result of a run. The reporter and the playwright-ui-coverage report CLI write coverage.json next to index.html in the report folder (ui-coverage-report/ by default). It's the integration point for CI gates, dashboards, baselines and PR comments.

  • TypeScript types: import type { CoverageReport } from 'playwright-ui-coverage' (source: src/coverage-report.ts).
  • JSON Schema (draft 2020-12): schema/coverage.schema.json, shipped in the package and importable as playwright-ui-coverage/coverage.schema.json.
  • The raw data folder (data/ui-coverage-data.json, formatVersion) is a different file: input for rebuilding the report, not part of this contract.

Compatibility ​

  • schemaVersion is an integer, currently 1. It changes only on breaking changes: a field removed or renamed, its type changed, or its meaning changed.

  • New fields can be added in any release without a version change. Readers must ignore fields they don't know. The JSON Schema doesn't set additionalProperties: false for that reason.

  • Fields marked internal ("x-internal": true in the schema) are written for the HTML report and may change in any release. In v1 that's views[].screenshots.

  • Some values are heuristics whose content may change between releases while the field stays: suggestedLocator text, a11yHints[].message wording, the set of a11yHints[].rule ids, element labels.

  • Check the version before reading:

    js
    if (report.schemaVersion !== 1) throw new Error(`unsupported coverage.json schemaVersion ${report.schemaVersion}`);

What is counted (the denominator) ​

Coverage is measured in units: interactive elements and link units.

coverage = (testedElementsCount + testedLinksCount)
         / (testedElementsCount + untestedElementsCount + testedLinksCount + untestedLinksCount) × 100

rounded to one decimal; 100 when there are no units.

  • An element is an interactive element of a view (see Which elements count), identified by its key within the view. Elements sharing a key (a "Delete" button on every row) are one unit, whatever instances says, and so is a group (see Element grouping): the "Add to cart" button of every product card, a data-uicov-group. A group is tested when any member is.
  • An element is tested when a counted test interacted with it in that view. By default only each test's last passing attempt counts (countFailedTests: true counts every attempt). Elements touched only by attempts that didn't pass are untested and also counted in failingOnlyElementsCount.
  • A link unit is the set of <a href> elements of a view that lead to one other view (destination URLs mapped to views by the same function as page URLs, normalizeUrl), identified by the destination view's name (key). It is tested when a counted test visited the destination view, by any path. An <a> in a link unit is not also counted as an element.
  • Links that stay elements (tested when clicked): links within the same view (#fragment, #, the page itself or another page of the same view), javascript: and empty hrefs, links inside iframes, all links in component tests (viewBy: 'file'), and links to other origins (unless links: { external: 'ignore' }, which drops them). links: false makes every link an element, as in reports written before link units.
  • Untested views (untestedViews): destination views of untested links that no recorded test visited. They are not in views and don't add units of their own; their incoming links are the untested link units.
  • a11yHintsCount is informational and never affects coverage.
  • The overall summary sums the views' counts; coverage is computed from the sums (not an average of the views' percents).

Top level: CoverageReport ​

FieldType
schemaVersion1See Compatibility.
generatedAtstringISO 8601 time the report was built.
generator{ name, version }name is "playwright-ui-coverage", version the package version that wrote the file.
summaryReportSummaryOverall counts.
viewsViewCoverage[]One per view, sorted by name.
untestedViewsUntestedView[]Destination views of untested links that no recorded test visited, sorted by name.
testsTestRef[]Every test with recorded data, sorted by title.
thresholdsThresholdCheck[]Results of the threshold option; empty when it isn't set.
insights{ mostRepeated }See Insights.
playwrightReport{ path, attachmentsBaseURL }, optionalPlaywright's HTML report, when linked: path is its folder relative to the folder of coverage.json (/ separators), attachmentsBaseURL where tests[].trace files are, relative to path.
configobject, optionalThe UI coverage config that built the report: source (file: the config file relative to the working directory, or null; inline: keys set in the reporter options), profile (name or null) and resolved (the merged config; informational, its keys follow the config file, not this schema's versioning).
baselineBaselineCheck, optionalResult of the baseline option; absent when it isn't set.

summary: ReportSummary / CoverageCounts ​

views[].summary is a CoverageCounts; the top-level summary is a ReportSummary, the same plus viewCount and untestedViewCount.

FieldType
viewCountintegerTop level only: number of entries in views.
untestedViewCountintegerTop level only: number of entries in untestedViews.
coveragenumberPercent, 0–100, one decimal. See the denominator.
testedElementsCountinteger
untestedElementsCountintegerIncludes failingOnlyElementsCount.
testedLinksCountintegerLink units whose destination view a counted test visited.
untestedLinksCountintegerLink units whose destination view no counted test visited.
failingOnlyElementsCountintegerUntested elements that only attempts that didn't pass touched.
a11yHintsCountintegerAccessibility hints on these elements.

views[]: ViewCoverage ​

A view is a group of page URLs: named by the first matching views pattern (/users/:username), else by normalizeUrl or the automatic rules (/users/42?tab=x → /users/:id); URLs excluded by viewFilters are in no view. Or, with viewBy: 'file' (component tests), a spec file path.

FieldType
namestringAs shown in the report and matched by threshold.views.
reportUrlstringThis view in the HTML report, relative to the folder of coverage.json: index.html# + encodeURIComponent(name).
summaryCoverageCounts
elementsElementCoverage[]Interactive elements, not the <a> elements of links. Untested first, then by label.
linksLinkCoverage[]Link units: this view's links grouped by destination view. Untested first, then by key.
screenshotsViewScreenshot[]Internal. The screenshots the HTML report shows: { file, dpr, testId?, boxes: [{ key, x, y, w, h }] }, file relative to the folder of coverage.json, boxes in CSS pixels. Which screenshots are picked depends on timing.

views[].elements[]: ElementCoverage ​

(view name, key) identifies an element across runs, as long as the identity rules (significantAttributes, attributeFilters, …) don't change. This is the identity to use for baselines and run-to-run comparisons.

FieldType
keystringStructural selector, unique within the view. Inside an iframe or open shadow root, prefixed by the host's key and >> (iframe[data-testid="payments"] >> [data-testid="card"], promo-banner >> button:nth-of-type(2)).
labelstringrole "name", with (+N variants) when instances sharing the key had different labels. For display.
tagstringLowercase tag name; ? for an element only seen in an interaction, never in an inventory.
instancesintegerMax number of instances seen at once.
group{ name?, reason, members }, optionalSet when the element stands for several. reason: markup (data-uicov-group), config (elementGroups), same-key (instances sharing the key), row (the same column of a table or grid), repeated (the same control in repeated structures) or label (a <label> merged into its control); more may be added. name: the group's name, explicit groups only. members: distinct elements (keys before grouping) in it. Absent for a single element.
testedboolean
testIdsstring[]Ids (into tests) of the tests whose interactions counted. Empty when untested.
failingTestIdsstring[]Ids of tests that touched it only in attempts that didn't pass. Non-empty with tested: false = "failing tests only".
interactionCountsRecord<string, integer>Counted interactions by type, e.g. { "click": 3, "input": 1 }. Empty when untested. Sum it for a total.
rolestring, optionalRecorded ARIA role.
namestring, optionalRecorded accessible name.
nameFromstring, optionalWhere name came from: aria-labelledby, aria-label, label, placeholder, title, alt, value, text or name.
attrsRecord<string, string>, optionalIdentifying attributes (id, name, role, type, aria-label, href, tabindex, contenteditable, data-*, your significantAttributes).
framesstring[], optionalKeys of the <iframe> elements the element is inside, outermost first; each is a prefix of key. Absent in the top document.
suggestedLocatorstring, optionalOne line of Playwright code that interacts with the element. Heuristic.
a11yHints{ rule, message }[], optionalAbsent when there are none. rule is one of no-name, placeholder-only, duplicate-name, not-focusable, no-role; more may be added.

Optional fields are absent (not null) when unknown, e.g. for data recorded by older versions.

The links of a view that go to one other view: one unit. (view name, key) identifies it across runs (baselines list untested ones under untestedLinks).

FieldType
keystringThe destination view's name, e.g. /users/:id. Unique within the view.
labelstringlink "name" of the first referrer by label, with (+N more) when several <a> elements go there. For display.
testedbooleanA counted test visited the destination view (not necessarily through these links).
destinationsstring[]Distinct destination URLs, sorted, at most 20: path, query and hash when on the page's origin, else the full URL.
destinationCountintegerNumber of distinct destination URLs (may exceed destinations.length).
referrersElementCoverage[]The <a> elements of the view that link there, with suggestedLocator etc. Their tested/testIds say whether a test interacted with them: informational, not part of coverage.

untestedViews[]: UntestedView ​

FieldType
namestringView name, from the destination URL the same way page URLs are named.
destinationsstring[]Distinct destination URLs, sorted, at most 20.
destinationCountintegerNumber of distinct destination URLs.
referrers{ view, label, keys }[]The views linking to it (view, a name in views), with their link unit's label and the <a> elements' keys (views[].links[].referrers[].key).

A view seen only in attempts that don't count (failing tests) is in views, so it isn't an untested view, but links to it are untested.

tests[]: TestRef ​

FieldType
idstringTestCase.id: stable across runs for the same file, title and project.
titlestringSpec file and title path: users.spec.ts › add a user.
projectstring, optionalPlaywright project name.
passedbooleanWhether the attempt the report used passed (had the expected status, so test.fail() tests pass when they fail).
retryintegerTestResult.retry of that attempt: the last passing one, else the last one.
tracestring, optionalThat attempt's trace file in the HTML report's attachments folder, when linked: <playwrightReport.path>/<attachmentsBaseURL><trace>.

thresholds[]: ThresholdCheck ​

FieldType
scope"overall" | "view"
viewstring, when scope is viewThe view name; for a pattern that matched no view, the pattern.
patternstring, optionalThe threshold.views glob that set this requirement.
requirednumberRequired coverage.
actualnumber | nullActual coverage; null when the view wasn't in this run.
status"passed" | "failed" | "missing"missing only warns; the run fails when any check is failed.

baseline: BaselineCheck ​

Present when the baseline reporter option is set (and not mode: 'off'). Units are identified by (view, kind, key).

FieldType
filestringThe baseline file, relative to the folder of coverage.json (/ separators).
mode"check" | "update"
status"passed" | "failed" | "missing" | "updated"failed: newGaps is non-empty and the run failed. missing: no baseline file yet (warning only). updated: mode: 'update' rewrote the file; never fails.
nextFilestring, optionalCheck mode: where the current state was written (<file> → <name>.next.json), relative like file.
allowNewViewsbooleanWhether untested units of views not in the baseline were allowed.
newGaps{ view, kind, key, label, newView }[]Untested units the baseline doesn't list, by view, kind, key. kind is element or link; newView is true when the whole view is new. In update mode: the units added to the file.
resolved{ view, kind, key, reason, label? }[]Units the baseline lists as untested that aren't any more: reason tested (with label) or removed (no longer in the view).
addedViewsstring[]Views not in the baseline (renames excluded).
removedViewsstring[]Baseline views not in this run (renames excluded). Never fail.
renamedViews{ from, to }[]A baseline view that disappeared and a new view with similar untested keys (Jaccard index ≥ 0.5), compared as one.

The baseline file itself (ui-coverage-baseline.json) is { "baselineVersion": 1, "views": { "<view>": { "untestedElements": [keys], "untestedLinks"?: [keys] } } }, keys sorted.

Insights ​

insights.mostRepeated: up to 10 elements used by at least 2 tests, most first. Each is { view, key, label, testCount, interactionCount, testShare }, where testShare is testCount as a percent of tests.length. These are often setup steps repeated in every test.

Using it in a script ​

A custom gate, e.g. failing when any view under /admin is below 60%:

js
import fs from 'node:fs';

const report = JSON.parse(fs.readFileSync('ui-coverage-report/coverage.json', 'utf8'));
if (report.schemaVersion !== 1) throw new Error(`unsupported coverage.json schemaVersion ${report.schemaVersion}`);
const low = report.views.filter((v) => v.name.startsWith('/admin') && v.summary.coverage < 60);
if (low.length) {
  console.error(low.map((v) => `${v.name}: ${v.summary.coverage}%`).join('\n'));
  process.exit(1);
}

For the common cases there are built-in gates: thresholds and the baseline.

Example ​

json
{
  "schemaVersion": 1,
  "generatedAt": "2026-01-01T12:00:00.000Z",
  "generator": { "name": "playwright-ui-coverage", "version": "0.1.0" },
  "summary": {
    "viewCount": 1,
    "coverage": 50,
    "testedElementsCount": 1,
    "untestedElementsCount": 1,
    "testedLinksCount": 0,
    "untestedLinksCount": 0,
    "failingOnlyElementsCount": 0,
    "a11yHintsCount": 0
  },
  "views": [
    {
      "name": "/users/:id",
      "reportUrl": "index.html#%2Fusers%2F%3Aid",
      "summary": {
        "coverage": 50,
        "testedElementsCount": 1,
        "untestedElementsCount": 1,
        "testedLinksCount": 0,
        "untestedLinksCount": 0,
        "failingOnlyElementsCount": 0,
        "a11yHintsCount": 0
      },
      "elements": [
        {
          "key": "[data-action=\"delete-user\"]",
          "label": "button \"Delete\"",
          "tag": "button",
          "instances": 1,
          "tested": false,
          "testIds": [],
          "failingTestIds": [],
          "interactionCounts": {},
          "role": "button",
          "name": "Delete",
          "nameFrom": "text",
          "attrs": { "data-action": "delete-user" },
          "suggestedLocator": "await page.getByRole('button', { name: 'Delete' }).click();"
        },
        {
          "key": "main > div:nth-of-type(1) > a",
          "label": "link \"← Back to users\"",
          "tag": "a",
          "instances": 1,
          "tested": true,
          "testIds": ["7d08affc2ce055c80dfd-96667847379d7d7361ae"],
          "failingTestIds": [],
          "interactionCounts": { "click": 1 },
          "role": "link",
          "name": "← Back to users",
          "nameFrom": "text",
          "attrs": { "href": "/users" }
        }
      ],
      "screenshots": []
    }
  ],
  "tests": [
    { "id": "7d08affc2ce055c80dfd-96667847379d7d7361ae", "title": "users.spec.ts › open a user", "project": "chromium", "passed": true, "retry": 0 }
  ],
  "thresholds": [{ "scope": "overall", "required": 30, "actual": 50, "status": "passed" }],
  "insights": { "mostRepeated": [] }
}

Changes from the unversioned format (before schema version 1) ​

BeforeVersion 1
total, tested, percent, failingOnly, a11yHints at the top levelsummary.{testedElementsCount, untestedElementsCount, coverage, failingOnlyElementsCount, a11yHintsCount}, plus viewCount and link counts
routes[] with route, total, tested, percent, failingOnly, a11yHintsviews[] with name, reportUrl and summary
element testedBy / testedByFailing (titles)testIds / failingTestIds (always present; titles in tests)
element interactionTotal, testCountremoved: sum interactionCounts, use testIds.length
screenshots[].source, .test (title)screenshots[].file, .testId (internal)
tests[].runtests[].retry, plus passed
htmlReportplaywrightReport
mostRepeated[] with route, tests, interactions, shareinsights.mostRepeated[] with view, testCount, interactionCount, testShare
thresholds[] only when configured, with target, passedalways present, with scope and view; use status

Released under the MIT License.