---
id: ui-coverage/troubleshooting
title: 'Troubleshooting: UI Coverage'
description: >-
  Fix common Cypress UI Coverage report problems: duplicate elements,
  mis-grouped elements, tested elements shown as untested, configuration that
  has no effect, and unexpected coverage scores.
section: ui-coverage
source_path: docs/ui-coverage/troubleshooting.mdx
version: 29f95bf8bb06f320986f3749f5bf09a35a409eab
updated_at: '2026-09-04T10:49:54.630Z'
---
# Troubleshooting UI Coverage

UI Coverage produces a report for every recorded run with no setup, and for most applications the defaults are accurate on their own. When a report doesn't match what you expect, the cause is usually one of a small, predictable set: an unstable attribute that splits one control into many, a generic identifier that merges distinct controls, an interaction UI Coverage doesn't recognize, or a configuration change that hasn't been applied to the run yet.

This page maps each symptom to its cause and the configuration that resolves it. Because you can [regenerate](/llm/markdown/ui-coverage/configuration/overview.md#Setting-configuration) any past run with your current configuration, you can confirm every fix in minutes without re-running your Cypress tests, so the fastest path to a coverage score you trust is to make one change, regenerate a recent run, and check the result.

Every run is processed with the configuration that was saved **at that moment**, so edits you make afterward don't change an existing report until you reprocess it. This timing is the single most common reason a rule "doesn't work."

After saving configuration in the **App Quality** tab of your project settings, open a run's **Properties** tab and use its "regenerate" button, or the "configuration updated" message shown on a report processed with older configuration. Every fix on this page assumes you regenerate the run after changing configuration. See [Setting configuration](/llm/markdown/ui-coverage/configuration/overview.md#Setting-configuration).

## One element is reported as many

A single control appears as several separate untested elements, inflating your element count and lowering your score.

**Cause.** UI Coverage recognizes the same element across [snapshots](/llm/markdown/ui-coverage/core-concepts/element-identification.md#Snapshots) by building an [identity](/llm/markdown/ui-coverage/core-concepts/element-identification.md) from its attributes and position in the DOM. When an identifying attribute changes on every render, such as a framework-generated `id`, a hashed class, or a per-request token, each snapshot looks like a new element and the one control is split apart.

UI Coverage already ignores the most common unstable values, including UUIDs and long hexadecimal hashes on any attribute, digit-only `id`, `name`, `for`, and `aria-*` values, and generated class names like `jss*` and `ng-tns-*`. Add configuration only when your application produces unstable identifiers these built-in filters don't cover.

**Fixes.**

*   **Ignore the dynamic attribute** with [`attributeFilters`](/llm/markdown/ui-coverage/configuration/attributefilters.md) so UI Coverage falls back to a stable attribute. When you filter a dynamic `id`, also filter the attributes that reference it, such as `for`, `aria-labelledby`, and `aria-describedby`, or the element can still be identified by those related values.
    
    App Quality Config
    
    ```
    {
      "attributeFilters": [
        {
          "attribute": "id|for|aria-labelledby|aria-describedby",
          "value": ":r.*",
          "include": false,
          "comment": "React useId() values like :r0: are regenerated on every render"
        }
      ]
    }
    ```
    
*   **Pin one specific element's identity** with an [`elements`](/llm/markdown/ui-coverage/configuration/elements.md) rule that identifies it by a selector that stays the same across snapshots. This is the right tool for a single control; if many elements share the same generated attribute, filter the attribute instead.
    
    App Quality Config
    
    ```
    {
      "uiCoverage": {
        "elements": [
          {
            "selector": "#checkout-form [id^='card-']",
            "name": "Card Number Field"
          }
        ]
      }
    }
    ```
    

Patterns in `attributeFilters` match the whole value (they're anchored as `^(...)$`, unless your pattern already starts with `^` or ends with `$`), and JSON requires backslashes to be doubled, so a token like `\d` must be written `\\d`. Note that `attribute` is matched **case-insensitively** while `value` is matched **case-sensitively**. See [How rules are applied](/llm/markdown/ui-coverage/configuration/attributefilters.md#How-rules-are-applied).

## Distinct elements are reported as one

Two or more controls that should each be tracked appear as a single element, so testing one marks all of them tested and real gaps are hidden.

**Cause.** The elements resolve to the same identity because their identifying attribute shares a generic value, such as several buttons that all carry `data-test="button"`, or because the attribute that would distinguish them isn't one UI Coverage uses by default.

**Fixes.**

*   **Add distinct identifiers** in your application, giving each control its own `data-cy` or `data-test` value. This is the most durable fix.
    
*   **Prefer a distinguishing attribute** your markup already has with [`significantAttributes`](/llm/markdown/ui-coverage/configuration/significantattributes.md). Attributes you list are checked before the defaults, so an attribute whose value differs between the elements gives each a distinct identity. Icon-only controls, for example, are often distinguishable by their `aria-label`.
    
    App Quality Config
    
    ```
    {
      "significantAttributes": ["aria-label"]
    }
    ```
    
    Listing `aria-label` also turns the report into an inventory of your label text, where a vague label that passes automated accessibility checks (`aria-label="button"`) stands out next to a clear one (`aria-label="Close dialog"`).
    

## Similar elements aren't grouped together

Repeated components, such as a row action rendered for every record, appear as many separate untested elements instead of one [group](/llm/markdown/ui-coverage/core-concepts/element-grouping.md), so a single table can dominate your untested list.

**Cause.** Each instance carries a per-instance value in its identifier, such as a database ID in an `id` attribute, so UI Coverage treats the instances as unrelated rather than as one repeated component.

**Fixes.**

*   **Ignore the per-instance value** with [`attributeFilters`](/llm/markdown/ui-coverage/configuration/attributefilters.md) so the otherwise-identical elements collapse into one group on their own.
    
    App Quality Config
    
    ```
    {
      "attributeFilters": [
        {
          "attribute": "id",
          "value": "delete-user-\\d+",
          "include": false,
          "comment": "Row action ids include the per-user database id"
        }
      ]
    }
    ```
    
*   **Force the grouping** with an [`elementGroups`](/llm/markdown/ui-coverage/configuration/elementgroups.md) rule that matches all the instances by a shared selector and names them, which also makes the report readable.
    
    App Quality Config
    
    ```
    {
      "uiCoverage": {
        "elementGroups": [
          {
            "selector": "[id^='delete-user-']",
            "name": "Delete User Button"
          }
        ]
      }
    }
    ```
    
*   **Group in your markup** by adding the [`data-cy-ui-group` attribute](/llm/markdown/ui-coverage/core-concepts/element-grouping.md#Grouping-with-markup-attributes) to the elements, or to a wrapper around them, when the team that owns the markup also owns the grouping decision. Groups defined this way take priority over `elementGroups` configuration.
    

## Unrelated elements are grouped together

Controls that do different things are combined into one group, so an interaction with one wrongly marks the others tested.

**Cause.** The elements share an overly generic selector or attribute value, so UI Coverage's automatic grouping or a shared identifying attribute treats them as instances of the same component.

**Fixes.**

*   **Add distinct identifiers** so each control resolves to its own identity, which prevents the shared-attribute grouping in the first place.
    
*   **Define the correct split** with [`elementGroups`](/llm/markdown/ui-coverage/configuration/elementgroups.md). A custom group overrides the automatic grouping, so a rule for each action separates controls the heuristics lumped together. List specific rules before broad ones, because the **first** matching rule wins.
    
    App Quality Config
    
    ```
    {
      "uiCoverage": {
        "elementGroups": [
          {
            "selector": "[data-cy='quick-view']",
            "name": "Quick View Button"
          },
          {
            "selector": "[data-cy='add-to-cart']",
            "name": "Add to Cart Button"
          }
        ]
      }
    }
    ```
    

## An element you tested is shown as untested

Your test interacts with an element on every run, but the report still lists it as untested.

**Cause.** UI Coverage marks an element tested only when a recognized [interaction command](/llm/markdown/ui-coverage/core-concepts/interactivity.md#Interaction-Commands) targets it. The built-in set is `blur`, `check`, `clear`, `click`, `dblclick`, `focus`, `rightclick`, `scrollIntoView`, `scrollTo`, `select`, `selectFile`, `submit`, `trigger`, `type`, and `uncheck`. Anything outside that set, including your own custom commands and plugin commands such as `cypress-real-events`' `realClick` and `realHover`, doesn't count until you declare it.

**Cause: you used `scrollIntoView`.** That command credits the element's nearest scrollable ancestor rather than the element you called it on, so it usually marks nothing tested. Use `click`, `focus`, or `trigger` on the element instead.

**Fixes.**

*   **Count a custom or plugin command everywhere** by adding it to [`additionalInteractionCommands`](/llm/markdown/ui-coverage/configuration/additionalinteractioncommands.md).
    
    App Quality Config
    
    ```
    {
      "uiCoverage": {
        "additionalInteractionCommands": ["realClick", "realHover"]
      }
    }
    ```
    
*   **Count a command for specific elements**, or **credit an interaction the defaults ignore** such as an `assert` on a read-only badge or total, with [`allowedInteractionCommands`](/llm/markdown/ui-coverage/configuration/allowedinteractioncommands.md).
    
    App Quality Config
    
    ```
    {
      "uiCoverage": {
        "allowedInteractionCommands": [
          {
            "selector": "[data-cy='order-total']",
            "commands": ["assert"],
            "comment": "This total is only ever validated, never interacted with"
          }
        ]
      }
    }
    ```
    

Two details often explain a command that still doesn't count. First, a custom command only produces coverage if it logs a [snapshot](/llm/markdown/ui-coverage/core-concepts/element-identification.md#Snapshots) that references the element it acts on, so register it with [`prevSubject`](/llm/markdown/api/cypress-api/custom-commands.md#Arguments) and log the subject as `$el` (see [Requirements for a custom command](/llm/markdown/ui-coverage/configuration/additionalinteractioncommands.md#Requirements-for-a-custom-command)). Second, command names you add are matched **case-sensitively**, exactly as you registered them.

## An element stops counting after you add an allowedInteractionCommands rule

An element that was tested is now reported as untested, right after you added an [`allowedInteractionCommands`](/llm/markdown/ui-coverage/configuration/allowedinteractioncommands.md) rule.

**Cause.** Once an element matches a rule, **only** the commands listed in the matching rules count for it, and the default commands no longer apply. If your test exercises the element with a command you didn't list, such as a plain `click`, it's now treated as untested.

**Fix.** Add the command your test actually uses to the rule's `commands` list, or tighten the `selector` so the rule matches only the elements you intended to restrict.

## A configuration rule has no effect

You saved a rule, but the report looks the same.

Work through these causes in order:

*   **The run predates the change.** Reports use the configuration saved when they were processed. [Regenerate](/llm/markdown/ui-coverage/configuration/overview.md#Setting-configuration) the run to apply your current configuration. This is by far the most common cause.
*   **An earlier rule matched first.** For [`elementFilters`](/llm/markdown/ui-coverage/configuration/elementfilters.md), [`viewFilters`](/llm/markdown/ui-coverage/configuration/viewfilters.md), [`elementGroups`](/llm/markdown/ui-coverage/configuration/elementgroups.md), and [`attributeFilters`](/llm/markdown/ui-coverage/configuration/attributefilters.md), the **first** matching rule wins, so a broad rule above your specific one prevents it from applying. List specific rules first, and place `include: true` exceptions before the `include: false` rule they should override. ([`elements`](/llm/markdown/ui-coverage/configuration/elements.md) is the exception: when several rules match one element, the **last** one wins.)
*   **A nested list replaced a root one.** Nesting a shared property such as `elementFilters` or `attributeFilters` under a `uiCoverage` or `accessibility` key **completely replaces** the root-level list for that product; the two are never merged. Repeat any root rules you still want in the nested list. See [Configuration scope](/llm/markdown/ui-coverage/configuration/overview.md#Configuration-scope).
*   **The property is in the wrong place.** `elementGroups`, `elements`, `additionalInteractionCommands`, and `allowedInteractionCommands` are UI Coverage–only and must live under the `uiCoverage` key, while `views` is set at the root. A misplaced property is rejected by the schema.
*   **The pattern or selector doesn't match.** `attributeFilters` patterns are anchored to the whole value, so `value: "user"` matches only the exact string `user`; use `value: "user-.*"` for a prefix. Selectors in `elementFilters`, `elements`, and `elementGroups` must match the interactive element itself, not a wrapper around it, so `footer` matches only the `<footer>`, while `footer *` matches the controls inside it.
*   **The configuration didn't save.** Cypress Cloud validates against a strict schema and rejects any property it doesn't recognize, including a misspelled name, a value of the wrong type, or a freeform note on a field that doesn't accept one. Keep notes in a [`comment`](/llm/markdown/ui-coverage/configuration/overview.md#Comments), and correct the flagged property so the configuration can save.

## The coverage score looks wrong

Your score is lower than the state of your tests suggests.

**Cause.** The score compares tested interactive elements to the total, and a few sources of untested elements commonly drag it down without pointing to a real gap in your suite:

*   **Third-party widgets.** Chat launchers, cookie banners, and analytics overlays are interactive elements your team doesn't own, and each counts as untested.
*   **Untested links.** A link to a page no test visits, including your own help center, marketing pages, and entirely external sites, counts against your score as an [untested link](/llm/markdown/ui-coverage/core-concepts/interactivity.md#Untested-Links), even though no view exists for it.
*   **Duplicated elements.** One control split into many by an unstable attribute (see [One element is reported as many](#One-element-is-reported-as-many)) adds untested elements that are really the same control.

**Fixes.**

*   **Remove third-party elements** with [`elementFilters`](/llm/markdown/ui-coverage/configuration/elementfilters.md), which drops them from the report and the score.
    
    App Quality Config
    
    ```
    {
      "elementFilters": [
        {
          "selector": ".intercom-launcher",
          "include": false,
          "comment": "Third-party chat widget, not part of our tested UI"
        }
      ]
    }
    ```
    
*   **Exclude destinations you don't intend to test** with [`viewFilters`](/llm/markdown/ui-coverage/configuration/viewfilters.md), which also removes the links that point to them.
    
    App Quality Config
    
    ```
    {
      "viewFilters": [
        {
          "pattern": "https://status.my-app.com/*",
          "include": false,
          "comment": "External status page, linked from the footer but out of scope"
        }
      ]
    }
    ```
    

To keep only your own application's URLs, list `include: true` rules for them followed by a catch-all `{ "pattern": "*", "include": false }`. Because the first matching rule wins, the catch-all must come last. See [Include only your application's URLs](/llm/markdown/ui-coverage/configuration/viewfilters.md#Include-only-your-applications-URLs).

## Still stuck?

*   The [UI Coverage FAQ](/llm/markdown/ui-coverage/faq.md) answers focused questions about scores, views, grouping, identification, interaction commands, and profiles.
*   Each configuration page has its own troubleshooting and "How rules are applied" section: [`attributeFilters`](/llm/markdown/ui-coverage/configuration/attributefilters.md), [`elementFilters`](/llm/markdown/ui-coverage/configuration/elementfilters.md), [`elementGroups`](/llm/markdown/ui-coverage/configuration/elementgroups.md), [`elements`](/llm/markdown/ui-coverage/configuration/elements.md), [`viewFilters`](/llm/markdown/ui-coverage/configuration/viewfilters.md), [`views`](/llm/markdown/ui-coverage/configuration/views.md), and [`profiles`](/llm/markdown/ui-coverage/configuration/profiles.md).
*   The [Configuration overview](/llm/markdown/ui-coverage/configuration/overview.md) covers where configuration lives, how rules are applied, and how to regenerate reports.
*   If a report still looks wrong after working through this page, reach out to your Cypress point of contact with a link to the run.
