Reduce test duplication
Most test suites spend real time repeating work that no test is actually checking. Every test that clicks through the same login, onboarding, or cookie banner just to reach what it means to verify repeats that setup on every run, and those minutes add up across a suite and across CI.
UI Coverage turns each recorded run into a ranked list of the elements your tests exercise, with a count of how many times and how many separate tests touched each one. The controls at the top of that list are usually shared setup steps you can consolidate: collapse them into one programmatic step and you cut CI time and make the report easier to read, without losing any coverage.
This workflow takes you from a recorded run to a leaner, faster suite:
- Record a run so Cypress Cloud can build a UI Coverage report.
- Find your most-tested elements in Cypress Cloud or from your editor.
- Tell intentional coverage from pass-through by drilling into an element.
- Consolidate the repeated setup with a programmatic step instead of UI clicks.
- Confirm the duplication dropped on your next run.
- Keep duplication from creeping back.
Step 1: Record a run to Cypress Cloud
UI Coverage needs a recorded run to analyze. Any run recorded to Cypress Cloud with Test Replay produces a report automatically, with no configuration or code changes. Use a run that reflects your suite as it normally executes, such as a full regression run, so the interaction counts are representative.
Step 2: Find your most-tested elements
However you prefer to work, the goal of this step is the same: a list of the elements your tests exercise, ranked so the most-tested rise to the top.
- In Cypress Cloud, open the run's UI Coverage tab and go to the Tested elements section. It lists every interactive element your tests exercised, one row per element or group, sorted by Interactions with the highest first.
- From your editor, ask an AI agent connected to Cypress Cloud MCP to pull the tested elements for the run and rank the most-exercised controls. It reads the same data, so you get the same ranked list without opening a browser tab. See Work with AI agents for prompts.
Each element carries a few counts, and two of them tell you what you need:
- Interactions: how many times a recognized command targeted the element. A high number can come from one test hitting it in a loop, which usually isn't duplication.
- Tests: how many separate tests interacted with it. A high number here means many separate tests all touch the same control, which is the signature of shared setup repeated across your suite.

The Tests count is the clearer duplication signal, so look for controls that a large share of your suite touches. A "Submit" button covered by a handful of form tests is expected. A "Continue" button on a welcome screen, or a "Log in" button, that shows up in hundreds of tests is a strong signal those tests are passing through a flow to reach something else.
Find duplication candidates with MCP
Have an agent rank your most-tested elements and flag the ones that look like shared setup.
Using Cypress Cloud, pull the UI Coverage tested elements for the latest run on this branch. Rank them by how many separate tests interact with each element, then by total interactions, and for the highest-ranked ones tell me which views they appear in. Flag the ones that look like shared setup a test passes through rather than verifies — logging in, onboarding, cookie or consent banners — and give me a shortlist of consolidation candidates with the element name, its test and interaction counts, and where it appears.
Step 3: Tell intentional coverage from pass-through
A high count isn't automatically a problem. Before changing anything, confirm the interactions are incidental rather than the point of the tests. Expand the element to break its counts down by view, with a Test Replay link for each occurrence:

Open Test Replay on a few of the tests. Ask:
- Is any assertion about this element? If tests interact with it but never assert on its behavior, they're passing through, not testing it.
- Do the tests share the same purpose? Dozens of unrelated tests clicking the same "Continue" button are all paying for setup that belongs somewhere cheaper.
- Does the interaction happen before the test's real work begins? Setup that runs at the start of nearly every test is the prime candidate to consolidate.
In the example above, the "Continue" button on the welcome screen is interacted with by most of the suite, but Test Replay shows almost none of those tests care about the welcome screen. They're just getting past it to reach the projects page. That's the duplication to remove.
Step 4: Consolidate the repeated setup
Once you've confirmed a control is pass-through setup, replace the repeated UI interaction with a single programmatic step. Which technique fits depends on what the setup does:
| When the pass-through is… | Reach for |
|---|---|
| Logging in through a form | cy.session() to cache and restore the login |
| A modal, banner, or onboarding step gated by a flag | Set the cookie or storage value directly with cy.setCookie() |
| Building up data your test needs to exist first | Seed it through your API with cy.request(), or run backend setup with cy.task() |
| Waiting on a network response to reach a state | Stub it with cy.intercept() |
Cache authentication with cy.session()
Logging in through the UI is the most common pass-through in any suite. Wrap it
in cy.session() so the login runs once and every
later test restores the cached session instead of driving the form again:
- JavaScript
- TypeScript
cy.session(username, () => {
cy.visit('/login')
cy.get('[data-cy="email"]').type(username)
cy.get('[data-cy="password"]').type('super-secret')
cy.get('[data-cy="submit"]').click()
cy.url().should('include', '/projects')
})
})
declare global {
namespace Cypress {
interface Chainable {
login(username?: string): Chainable<void>
}
}
}
cy.session(username, () => {
cy.visit('/login')
cy.get('[data-cy="email"]').type(username)
cy.get('[data-cy="password"]').type('super-secret')
cy.get('[data-cy="submit"]').click()
cy.url().should('include', '/projects')
})
})
Keep one dedicated test that drives the login form through the UI so the flow
itself stays covered, then call cy.login() everywhere else. UI Coverage still
marks the form as tested from that one test, while the interaction count on it
drops from hundreds to one. When your login lives on a separate identity
provider, pair cy.session() with cy.origin() to
drive the form across the origin boundary.
Set state directly instead of clicking through it
When the pass-through is a modal, banner, or onboarding step gated by a cookie or stored flag, set that state before the page loads rather than dismissing it in the UI each time:
- JavaScript
- TypeScript
// Skip the first-run welcome screen by pre-setting the flag it checks
Cypress.Commands.add('skipWelcome', () => {
cy.setCookie('welcome-dismissed', 'true')
})
declare global {
namespace Cypress {
interface Chainable {
skipWelcome(): Chainable<void>
}
}
}
// Skip the first-run welcome screen by pre-setting the flag it checks
Cypress.Commands.add('skipWelcome', () => {
cy.setCookie('welcome-dismissed', 'true')
})
Moving the setup into a beforeEach hook is what multiplies the interaction
count in the first place, so it's also where the fix belongs: the projects specs
skip the welcome screen once per test in setup, while one dedicated test still
covers the welcome flow on purpose.
describe('Projects', () => {
it('shows the welcome screen to first-time users', () => {
cy.visit('/')
cy.contains('Welcome')
cy.get('[data-cy="continue"]').click()
cy.contains('Projects')
})
describe('for returning users', () => {
beforeEach(() => {
cy.skipWelcome()
cy.visit('/')
})
it('lists the current user projects', () => {
cy.contains('Projects')
})
// Every other test starts past the welcome screen without clicking Continue
})
})
The same approach works for seeding data with
cy.request() against your API, or stubbing a
response with cy.intercept(), instead of building
that state by hand through the UI in every test.
An agent can do this consolidation for you, combining the Cloud data about which tests touch the control with your local specs and support files:
Consolidate a pass-through flow with MCP
Have an agent replace repeated UI setup with a single programmatic step across your specs.
Take the login flow that most of my suite passes through. Using Cypress Cloud UI Coverage for the latest run on this branch, find the specs that exercise it, then consolidate that setup in my project: add a cy.session() wrapper in my support files (or set the cookie/flag directly, or seed with cy.request()/cy.task() where that fits better), following the patterns already in my codebase. Keep one test that still drives the flow through the UI so its coverage is preserved, and update the other specs to call the new command instead of repeating the UI steps.
Step 5: Confirm the duplication dropped
Record a new run after consolidating, then reopen the Tested elements section. The control you consolidated should sit lower in the list: its Interactions and Tests counts fall to reflect the one test that still exercises it on purpose, and its coverage is unchanged because that test still marks it tested.

To see the change directly, use Branch Review to compare the two runs: the before-and-after makes it easy to confirm the element's interactions dropped while your overall coverage score held steady.
The time you saved shows up outside UI Coverage, in how long the suite takes to run. Watch the run duration on the runs list or in Test Replay fall as the redundant UI steps come out, so you can put a number on the payoff.
Step 6: Keep duplication from creeping back
Consolidation sticks only if new tests reuse the shared setup instead of re-introducing the UI steps. A few habits keep the list clean:
- Reach for shared commands first. Make
cy.login(),cy.skipWelcome(), and similar setup commands the default way to get a test to its starting point, so no one hand-rolls the flow again. - Recheck the top of the list periodically. Revisit the Tested elements section after big changes, or on a regular cadence, and treat a control climbing back toward the top as a prompt to consolidate again.
- Write down the boundary. Note which flows are covered once on purpose (the login form, the welcome screen) and which should always be reached programmatically, so the intent survives beyond the person who set it up.
Time you get back from a leaner suite is best spent on the flows you haven't covered yet. See Identify coverage gaps to find them.
See also
- Identify coverage gaps: find the untested elements and pages to spend your reclaimed time on.
- Reduce noise: sharpen a report that over-counts elements, the report-accuracy counterpart to this test-effort guide.
- Compare reports with Branch Review: confirm interactions dropped and coverage held between your before-and-after runs.
- Monitor changes: track the report over time so duplication doesn't creep back.
- Cypress Cloud MCP: query a run's UI Coverage from your editor, as in the prompts above.
- Work with AI agents: more prompts and patterns for pointing an agent at UI Coverage data.
- Test Replay: the recorded run data UI Coverage is built from, and where you inspect each interaction.
cy.session(): cache and restore login so authentication runs once instead of in every test.- UI Coverage FAQ: common questions about over-tested elements and consolidating setup.