Skip to main content
UI CoveragePremium Solution

How UI Coverage measures interactivity

Your UI Coverage score is built from the interactive parts of your app: the buttons, inputs, links, and controls a user can actually touch. To read your report with confidence, it helps to know the three rules that shape it:

  1. An element is counted when it is interactive, which forms the denominator of your score.
  2. An interactive element is marked as tested when a recognized Cypress command targets it, which forms the numerator.
  3. A link is flagged as untested when its destination is never visited during your run.

Put simply, your score is the share of interactive elements your tests exercise: tested ÷ total, where grouped elements count once. See how the score is calculated for the details.

This page covers all three, along with the configuration and markup overrides you can use to make each one match your application. All three are computed from Test Replay data, so they work automatically with no setup or instrumentation. Reach for the overrides only when the defaults don't fit.

note

UI Coverage turns your runs into a visual map of the interactive elements your tests exercise and the ones they miss, with no code changes or instrumentation. Schedule a demo.

Flow diagram: Test Replay captures DOM snapshots, UI Coverage analyzes each one to detect interactive elements (the total) and the interaction commands that mark them tested (the numerator), and flags untested links, all feeding the coverage score and report.

How UI Coverage turns a recorded run into a coverage score.

Interactive Elements

UI Coverage follows the WHATWG definition of interactive content, extended with a few Cypress-specific rules. An element is counted as interactive when it falls into any of these categories:

  • Natively interactive elements: elements the browser already treats as interactive, such as <a> links, <button>, form controls like <input> and <select>, and any element that is contenteditable.
  • Elements with an interactive ARIA role: an explicit role such as button, checkbox, tab, or menuitem, or an aria-haspopup value that opens a menu or dialog.
  • Tab-navigable elements: any element made keyboard-focusable with a tabindex of 0 or greater.

Contact links are the one deliberate exception: anchors whose href uses tel:, mailto:, or sms: are not treated as interactive. See Untested Links for how the rest of an anchor's href is handled.

Because detection is based on semantics rather than event handlers, a real <button> is recognized automatically, while a <div> wired up with a click handler is not, until you give it interactive semantics.

A UI Coverage report drilldown showing an interactive button highlighted in a snapshot, with its interactions listed alongside

UI Coverage finds each interactive element in a snapshot and tracks whether your tests exercise it.

Overriding interactive elements with data-cy-ui-interactive

When the automatic rules don't match your app, the data-cy-ui-interactive attribute lets you override them directly in your markup. It accepts four values, matched case-insensitively:

ValueEffect
includeTreats the element as interactive even when it wouldn't be otherwise.
excludeRemoves the element from UI Coverage, even when it's normally interactive.
exclude-allRemoves the element and every element nested inside it.
resetRe-enables tracking for a subtree nested inside an element marked exclude-all.

Include a custom control the browser doesn't recognize:

<!-- A custom drag handle built from a div -->
<div role="presentation" data-cy-ui-interactive="include">Reorder</div>

Exclude a whole section while keeping one control inside it tracked:

<section data-cy-ui-interactive="exclude-all">
<!-- Nothing in this experimental panel is tracked... -->
<button>Beta action</button>
<div data-cy-ui-interactive="reset">
<!-- ...except controls inside here -->
<button>Save feedback</button>
</div>
</section>

Excluding an element removes it from UI Coverage entirely: it no longer counts as an interactive element, and if it's a link, it also stops appearing as an untested link, so neither affects your score.

In most cases we recommend not using include, and instead updating the HTML to be something the browser already treats as interactive, such as a real <button> in place of a clickable <div>. That produces better overall behavior, including for accessibility, since the same semantics that let assistive technology operate a control are what UI Coverage reads. data-cy-ui-interactive is a fallback for situations where that isn't possible.

To exclude elements without editing your application's markup, elementFilters does the same job from Cypress Cloud configuration. Prefer it when the team that manages configuration isn't the team that owns the markup:

App Quality Config
{
"elementFilters": [
{
"selector": ".intercom-launcher, .intercom-launcher *",
"include": false,
"comment": "Third-party chat widget, not owned by our tests"
}
]
}

How elements get marked tested

An interactive element is marked as tested when a recognized Cypress command targets it. UI Coverage recognizes this built-in set of commands:

  • blur
  • check
  • clear
  • click
  • dblclick
  • focus
  • rightclick
  • scrollIntoView
  • scrollTo
  • select
  • selectFile
  • submit
  • trigger
  • type
  • uncheck

Using at least one of them on every interactive element is what drives your coverage score, so a test flow naturally marks the elements it exercises as tested:

// Each command marks its target element as tested
cy.get('[data-cy="signup-email"]').type('[email protected]')
cy.get('[data-cy="signup-plan-pro"]').check()
cy.get('[data-cy="signup-submit"]').click()
The Tested elements list in a UI Coverage report, with Interactions and Tests columns counting how often each element was exercised

An element moves to the Tested list once a recognized command interacts with it, with the interaction count shown per element.

A command only counts when it logs a snapshot that highlights the element it acts on, which is how UI Coverage knows which element to credit. Built-in commands do this automatically; a custom command must do it explicitly (see Requirements for a custom command).

Customizing interaction commands

Two configuration options let you change which commands count:

  • additionalInteractionCommands extends the built-in set everywhere, so custom commands or ones from third-party libraries (such as cypress-real-events' realClick) count as coverage on any element.
  • allowedInteractionCommands works per element: for elements matching a selector, only the commands you list count. This lets you both require a meaningful interaction (a chart that must be hovered, not just clicked) and credit commands the defaults ignore (an assert on a read-only badge).

For example, to require a hover rather than a plain click on a chart before it counts as tested:

App Quality Config
{
"uiCoverage": {
"allowedInteractionCommands": [
{
"selector": "[data-cy='revenue-chart']",
"commands": ["trigger"],
"comment": "Only a hover (dispatched via trigger) proves the tooltip works"
}
]
}
}

See Which option do I need? for a side-by-side comparison.

UI Coverage tracks links (<a> elements) whose destination URLs are never visited during testing. A link to a page no test navigates to is a coverage gap even when no view exists for that page, so untested links surface the flows your suite is missing, not just the elements it skipped.

A UI Coverage snapshot with an untested link highlighted in red in the rendered page, showing it had no interactions during the run

An untested link highlighted in the report: its destination was never visited during the run.

The href value determines how UI Coverage treats each anchor. At a glance:

href exampleInteractive element?Can be an untested link?
/settings, #/settings, https://external.com, blob:YesYes
#pricing (page fragment)YesNo
# or javascript:YesNo
Empty or missing hrefYesNo
tel:, mailto:, sms:NoNo

The three cases in detail:

An href that resolves to a URL is treated as a link to a destination, and the anchor is also an ordinary interactive element. This covers:

  • URLs: relative paths like /settings, hash routes like #/settings, and absolute URLs, including links to entirely external websites.
  • Other schemes: any other scheme, such as ftp:, file:, data:, blob:, or a custom app scheme, treated the same as an http(s) URL.

The anchor counts as tested once a test either interacts with it or visits its destination. If the destination is never visited, the link appears as an untested link and counts against your score, unless a viewFilters rule excludes the destination URL.

2. Counted only as interactive elements

These anchors are ordinary interactive elements that must be interacted with to count as tested. They point to no separate destination, so they never appear as untested links:

  • Document fragments: an href pointing to a fragment on the current page, such as #pricing.
  • # and javascript: values: anchors with href="#" or a javascript: value.
  • Empty or missing href: an anchor with no usable href value.

3. Not counted in UI Coverage

  • tel:, mailto:, and sms: values: contact links are not considered interactive elements. They don't appear in reports and don't count toward your coverage score. To track one anyway, add data-cy-ui-interactive="include" to the anchor.

To keep destinations you'll never test, such as external sites, your marketing pages, or a status page, from counting against your score, exclude them by URL:

App Quality Config
{
"viewFilters": [
{
"pattern": "https://status.example.com/*",
"include": false,
"comment": "External status page, linked from the footer but not owned by our team"
}
]
}

Excluding a destination with viewFilters also removes the links that point to it, so they stop appearing as untested links. See Ignore views and links for the full workflow. To exclude a single link from your markup instead, add data-cy-ui-interactive="exclude" to the anchor.

Expanding an untested link in the report reveals where it came from and which destinations it covers, so you can decide whether to test it or exclude it. That detail is organized into two tabs, Referrers and URLs.

The Untested links list in Cypress Cloud with one link expanded to show its Referrers and URLs tabs

Referrers

The Referrers tab lists the views that contain a link to the untested destination, so you can trace it back to the pages that link to it and the flows that lead there.

URLs

The URLs tab groups similar destinations for dynamic routing. For instance, links to /users/1, /users/2, and /users/3 are grouped as /users/*. Only path segments that are numbers or UUID-like IDs are treated as dynamic, so /users/alice and /users/bob stay separate. The grouping shows the scope and variations of a dynamic route, and the pattern it surfaces is the one to use when writing views and viewFilters rules.

See also