Skip to main content
UI CoveragePremium Solution

Results API

UI Coverage reports are generated in Cypress Cloud after your run finishes, so nothing in your Cypress pipeline fails on its own when coverage drops. The Results API closes that loop: it lets you pull a run's UI Coverage results into your CI job and decide, in code, whether the coverage is good enough to merge.

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.

With the getUICoverageResults helper from the @cypress/extract-cloud-results module you can:

  • Fail a build or block a pull request when overall coverage falls below a threshold, or when a critical view regresses.
  • Enforce different standards per area of your app, such as requiring higher coverage on /checkout than on marketing pages.
  • Surface results where your team works by posting the coverage score and a deep link to the report on the pull request or in Slack.
  • Catch navigation gaps by flagging pages that are linked but never visited, using the tested and untested link counts.
  • Track regressions over time by comparing a run against a stored baseline, or by trending results in a dashboard.
  • Read and assert the applied configuration, including any Profile that produced the report.

See Examples for a worked script behind each of these.

Because reports are processed server-side from Test Replay data, using the Results API adds no overhead to your Cypress runs. The enforcement step is entirely opt-in and lives in your CI workflow.

How it works

When you call getUICoverageResults inside a CI job, the helper:

  1. Identifies the Cypress run for the current CI build by cross-referencing the CI environment variables present when the run was recorded. See Required CI environment variables for details.
  2. Waits for the UI Coverage report to finish processing. If the report is still processing, the helper polls Cypress Cloud (up to 30 attempts at 30-second intervals, roughly 15 minutes) before returning.
  3. Returns the results as a structured object you can assert against, or throws a descriptive error if no matching run or report can be found.

Because the helper matches the run from the current CI context, call it after cypress run --record completes within the same CI build, so the run it should report on already exists.

As it runs, the helper logs its progress to the console so you can follow along in your CI logs:

Cypress found run #68086 (https://cloud.cypress.io/projects/ypt4pf/runs/68086) associated with this CI workflow.
Cypress is fetching the UI Coverage report for run #68086.
Cypress found a UI Coverage report for run #68086 that is still processing. 8 of 12 specs have been processed. Cypress will fetch again in 30 seconds [Attempt 1 of 30]
Cypress found a UI Coverage report for run #68086 (https://cloud.cypress.io/projects/ypt4pf/runs/68086/ui-coverage).

A run must also meet a few conditions before a report can be found, including Test Replay being enabled and a supported Cypress version. See the Results API FAQ for the full list.

Supported CI providers at a glance

The Results API supports the following CI providers:

  • Azure
  • CircleCI
  • GitHub Actions
  • GitLab
  • Jenkins
  • AWS CodeBuild
  • Drone
  • Bitbucket
  • Buildkite

For other CI providers, contact Cypress Support at [email protected] to request support.

Installation

Install the @cypress/extract-cloud-results module in your install step in CI.

npm install --force https://cdn.cypress.io/extract-cloud-results/v1/extract-cloud-results.tgz
caution

Do not check this module in as a dependency. We recommend you install it separately, outside of your normal module installation, and use --force to get the latest version.

The v1 in the URL is the major-version line. Installing with --force always fetches the latest 1.x release, so you receive fixes and improvements without changing the URL.

Usage

Write your verification script

Write a script to fetch UI Coverage results and assert your coverage criteria. This script runs in CI.

Basic example

This snippet uses the getUICoverageResults() helper to log out the results. It assumes your Project ID and Record Key are set as environment variables. It works in any of the supported CI providers out of the box:

scripts/verifyUICoverageResults.js
const { getUICoverageResults } = require('@cypress/extract-cloud-results')

getUICoverageResults()
.then((results) => {
// use `console.dir` instead of `console.log` because the data is nested
console.dir(results, { depth: Infinity })
})
.catch((error) => {
console.error(error.message)
process.exit(1)
})

Once you can read the results, add your own logic to act on them. See Examples for a worked script behind each supported use case, from enforcing thresholds to reporting on the pull request.

getUICoverageResults reference

The arguments the helper accepts, the data it returns, and how it behaves on errors.

Arguments

getUICoverageResults accepts the following arguments:

getUICoverageResults({
// The Cypress project ID.
// Optional if the CYPRESS_PROJECT_ID env var is set.
projectId: string
// The project's record key.
// Optional if the CYPRESS_RECORD_KEY env var is set.
recordKey: string
// The run tags associated with the run.
// Required IF you record multiple Cypress runs from a single CI build.
// Pass the same tags you used when recording each run.
runTags: string[]
})

Result properties

getUICoverageResults resolves with the following data. A link is a navigational element (such as an anchor) that points at another view; elements are the interactive controls counted toward coverage.

{
runNumber: number
runUrl: 'https://cloud.cypress.io/projects/:project_id/runs/:run_number'
runStatus: 'passed' | 'failed' | 'errored' | 'timedOut' | 'cancelled' | 'noTests' | 'running' | 'overLimit'
uiCoverageReportUrl: 'https://cloud.cypress.io/[...]'
summary: {
// Whether a complete UI Coverage report was generated.
// For example, if a run was cancelled and the report was expected to
// cover 20 specs but only 10 ran, this would be a partial report.
isPartialReport: boolean
// The report coverage from 0-100 with 2-decimal precision (e.g. 92.45).
coverage: number
viewCount: number
testedElementsCount: number
untestedElementsCount: number
testedLinksCount: number
untestedLinksCount: number
}
views: [{
// The sanitized URL pattern shown in the report.
displayName: string
// The view coverage from 0-100 with 2-decimal precision (e.g. 92.45).
coverage: number
testedElementsCount: number
untestedElementsCount: number
testedLinksCount: number
untestedLinksCount: number
uiCoverageReportUrl: 'https://cloud.cypress.io/[...]'
}]
// The App Quality configuration that produced this report.
// Present when configuration exists for the project.
config: {
// ISO 8601 timestamp of when the configuration was last updated.
updatedAt: string
// The resolved configuration value, including any Profile applied
// to this run via run tags. See the example below.
value: object
}
}

An example of a returned result:

{
runNumber: 68086,
runUrl: 'https://cloud.cypress.io/projects/ypt4pf/runs/68086',
runStatus: 'passed',
uiCoverageReportUrl:
'https://cloud.cypress.io/projects/ypt4pf/runs/68086/ui-coverage',
summary: {
isPartialReport: false,
coverage: 87.42,
viewCount: 12,
testedElementsCount: 145,
untestedElementsCount: 21,
testedLinksCount: 34,
untestedLinksCount: 8,
},
views: [
{
displayName: '/checkout',
coverage: 94.12,
testedElementsCount: 32,
untestedElementsCount: 2,
testedLinksCount: 5,
untestedLinksCount: 0,
uiCoverageReportUrl:
'https://cloud.cypress.io/projects/ypt4pf/runs/68086/ui-coverage/view/checkout',
},
{
displayName: '/products/*',
coverage: 78.95,
testedElementsCount: 15,
untestedElementsCount: 4,
testedLinksCount: 9,
untestedLinksCount: 3,
uiCoverageReportUrl:
'https://cloud.cypress.io/projects/ypt4pf/runs/68086/ui-coverage/view/products',
},
],
config: {
updatedAt: '2026-05-14T18:22:03.000Z',
value: {
/* App Quality Config, see below */
},
},
}

Reading the applied configuration

The config property returns the App Quality Config that Cypress Cloud used to generate the report, resolved with any Profile that matched the run's tags. This lets your script confirm exactly which rules were in effect, which is useful when different runs use different profiles.

App Quality Config
{
"viewFilters": [{ "pattern": "/admin/*", "include": false }],
"uiCoverage": {
"elementFilters": [{ "selector": ".cookie-banner *", "include": false }]
},
"profiles": [
{
"name": "production",
"config": {
"viewFilters": [{ "pattern": "/internal/*", "include": false }]
}
}
]
}

Handling errors

getUICoverageResults returns a promise that rejects with a descriptive Error when it can't return results. For example, this happens when no matching run is found for the CI context, when the run was recorded with Test Replay disabled, or when the report is still processing after the poll window elapses. Decide whether that should fail your build (let the rejection surface, as in the examples above) or be tolerated (catch it and exit cleanly), depending on how strict you want the check to be.

scripts/verifyUICoverageResults.js
getUICoverageResults()
.then((results) => {
// assert against results
})
.catch((error) => {
console.error(`Could not verify UI Coverage results: ${error.message}`)
// Fail the build...
process.exit(1)
// ...or treat the check as non-blocking by returning without a non-zero exit.
})

Add the verification step to CI

In the CI workflow that runs your Cypress tests:

  1. Update your install step to install the @cypress/extract-cloud-results module.
  2. Pass any necessary arguments to getUICoverageResults.
  3. Add a step, after your Cypress run, that executes your verification script.

Example workflow updates

test_cypress.yaml
name: My Workflow
on: push

env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

jobs:
run-tests:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v7
- name: install
run: npm install
- name: Run Cypress tests
run: npx cypress run --record
+ - name: Get Cypress UI Coverage
+ run: |
+ npm install --force https://cdn.cypress.io/extract-cloud-results/v1/extract-cloud-results.tgz
+ node ./scripts/verifyUICoverageResults.js

Recording multiple runs in one CI build

If you record multiple runs in a single CI build, you must record each run with the --tag parameter and then call getUICoverageResults with the matching runTags argument for each run. This is how each run is uniquely identified so the correct results are returned. Tags can also activate Profiles that apply different configuration to different runs.

For example, imagine that within a single CI build you call cypress run --record twice, once against a staging environment and once against production.

Pass a different --tag to each Cypress run:

npx cypress run --record --tag staging
npx cypress run --record --tag production

When calling getUICoverageResults, pass the same tags to get the results for each run:

getUICoverageResults({ runTags: ['staging'] })
getUICoverageResults({ runTags: ['production'] })

Examples

Each example below assumes you have written a script that calls getUICoverageResults and wired it into CI.

Enforce coverage thresholds

Fail the build when overall coverage drops below a floor, and hold critical flows like login and checkout to a higher standard:

scripts/verifyUICoverageResults.js
const { getUICoverageResults } = require('@cypress/extract-cloud-results')

getUICoverageResults({
projectId: process.env.CYPRESS_PROJECT_ID, // optional if set from env
recordKey: process.env.CYPRESS_RECORD_KEY, // optional if set from env
runTags: ['production'], // required if recording multiple runs in one CI build
}).then((results) => {
const { runNumber, uiCoverageReportUrl, summary, views } = results

console.log(
`Received ${summary.isPartialReport ? 'partial ' : ''}results for run #${runNumber}.`
)
console.log(`See the full report at ${uiCoverageReportUrl}`)

// Fail the build if overall coverage is below 80%.
if (summary.coverage < 80) {
throw new Error(
`Project coverage is ${summary.coverage}%, below the minimum threshold of 80%.`
)
}

// Hold critical flows to a higher standard.
const criticalViews = [/login/, /checkout/]

views.forEach((view) => {
const { displayName, coverage, uiCoverageReportUrl } = view

if (
criticalViews.some((pattern) => pattern.test(displayName)) &&
coverage < 95
) {
throw new Error(
`Critical view "${displayName}" coverage is ${coverage}%, below the required 95%. See: ${uiCoverageReportUrl}`
)
}
})

console.log('UI Coverage meets all thresholds.')
})

Report coverage on the pull request

Every result carries the coverage numbers and deep-link report URLs, so you can surface a summary where your team reviews code instead of only passing or failing. This example writes a Markdown summary that GitHub Actions renders on the job:

scripts/reportUICoverage.js
const fs = require('fs')
const { getUICoverageResults } = require('@cypress/extract-cloud-results')

getUICoverageResults().then(({ summary, views, uiCoverageReportUrl }) => {
const rows = views
.map((view) => `| ${view.displayName} | ${view.coverage}% |`)
.join('\n')

const markdown = `## UI Coverage: ${summary.coverage}%

[View the full report](${uiCoverageReportUrl})

| View | Coverage |
| ---- | -------- |
${rows}`

fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, markdown)
})

UI Coverage counts links (navigational elements that point at another view) separately from interactive elements. Use the link counts to catch pages that are linked but never visited:

scripts/checkUntestedLinks.js
const { getUICoverageResults } = require('@cypress/extract-cloud-results')

getUICoverageResults().then(({ summary, views }) => {
if (summary.untestedLinksCount === 0) {
return
}

console.log(
`${summary.untestedLinksCount} linked page(s) were never visited:`
)
views
.filter((view) => view.untestedLinksCount > 0)
.forEach((view) => {
console.log(
` ${view.displayName}: ${view.untestedLinksCount} untested link(s)`
)
})

throw new Error(
'New navigation paths are untested. Add tests that follow them.'
)
})

A baseline compares one run to one prior run. To trend coverage across many runs and teams, push each run's numbers to a dashboard or data warehouse:

scripts/trackCoverageTrends.js
const { getUICoverageResults } = require('@cypress/extract-cloud-results')

getUICoverageResults().then(({ runNumber, summary }) => {
const metrics = {
runNumber,
coverage: summary.coverage,
testedElements: summary.testedElementsCount,
untestedElements: summary.untestedElementsCount,
viewCount: summary.viewCount,
}

// Send to your metrics service, warehouse, or observability tool.
return fetch('https://metrics.example.com/ui-coverage', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(metrics),
})
})

Skip enforcement for partial or non-passing runs

A run can be cancelled, time out, or produce a partial report. Check the run state first so an incomplete run warns instead of failing the build for the wrong reason:

scripts/skipIncompleteRuns.js
const { getUICoverageResults } = require('@cypress/extract-cloud-results')

getUICoverageResults().then((results) => {
const { runStatus, summary } = results

if (runStatus !== 'passed' && runStatus !== 'failed') {
console.warn(
`Run status is "${runStatus}". Skipping UI Coverage enforcement.`
)
return
}

if (summary.isPartialReport) {
console.warn('Report is partial. Skipping UI Coverage enforcement.')
return
}

// ...enforce your thresholds here.
})

Assert the applied configuration

The config property returns the App Quality Config that produced the report, resolved with any Profile that matched the run's tags. Assert that required rules are in effect so a misconfigured run doesn't silently change what's measured:

scripts/assertCoverageConfig.js
const { getUICoverageResults } = require('@cypress/extract-cloud-results')

getUICoverageResults().then(({ config }) => {
if (!config) {
throw new Error('No App Quality Config was applied to this run.')
}

const elementFilters = config.value.uiCoverage?.elementFilters ?? []
const hasCookieBannerFilter = elementFilters.some((filter) =>
filter.selector?.includes('cookie-banner')
)

if (!hasCookieBannerFilter) {
throw new Error('Expected the cookie-banner element filter to be applied.')
}

console.log(`Configuration last updated ${config.updatedAt}.`)
})

Raise the threshold as coverage improves

Store the last run's coverage and require the next run to meet or beat it, so the bar rises automatically as coverage improves. Commit the floor file so it travels with your code:

scripts/raiseCoverageFloor.js
const fs = require('fs')
const { getUICoverageResults } = require('@cypress/extract-cloud-results')

const FLOOR_FILE = 'ui-coverage-floor.json'
const floor = fs.existsSync(FLOOR_FILE)
? JSON.parse(fs.readFileSync(FLOOR_FILE, 'utf8')).coverage
: 0

getUICoverageResults().then(({ summary }) => {
if (summary.coverage < floor) {
throw new Error(
`Coverage dropped to ${summary.coverage}%, below the previous floor of ${floor}%.`
)
}

fs.writeFileSync(FLOOR_FILE, JSON.stringify({ coverage: summary.coverage }))
})

Compare against a baseline

Comparing untested-element counts against a stored baseline fails a build only when new gaps are introduced, letting you pay down existing gaps over time instead of blocking on them all at once. For complete code, the baseline structure, and best practices, see the Block pull requests and set policies guide.

Required CI environment variables

The @cypress/extract-cloud-results helper detects the correct Cloud run by matching environment variables from where it runs against those present when the run was recorded, which is why it works automatically from the same CI context (as in the examples above).

Knowing which variables it looks for helps with more complex setups and with local iteration: set them to match what was present in CI to pull a specific run (recorded within the last 7 days) from your machine.

Prerequisites

Two prerequisites apply to every provider:

  1. Record the Cypress run and run your validation script within the same CI run (the same build, workflow, or pipeline).
  2. Run the validation script after the run has been recorded, either in a separate job that depends on the recording job (using your provider's dependency option, such as needs, dependsOn, or requires), or serially after the recording step in the same job.

Environment variables by CI provider

Each CI provider has a unique combination of components, patterns, and environment variables that must be interpreted by this module. Expand a provider below for its essential environment variables.

GitHub Actions

References: Understanding GitHub Actions | GitHub Actions default environment variables

Essential environment variables

  • GITHUB_ACTIONS - Presence identifies the environment as a GitHub Actions environment.
  • GITHUB_RUN_ID - Value uniquely identifies a GitHub Actions workflow instance. Value does not change as jobs in the workflow are re-executed.
  • GITHUB_RUN_ATTEMPT - Value identifies the workflow instance's attempt index. Value is incremented each time jobs are re-executed.
GitLab

References: GitLab CI/CD pipelines | GitLab predefined variables

Essential environment variables

  • GITLAB_CI - Presence identifies the environment as a GitLab CI environment
  • CI_PIPELINE_ID - Value uniquely identifies a GitLab pipeline workflow. This value does not change as jobs in the pipeline are retried.
  • CI_JOB_NAME - Value uniquely identifies a single job name within a pipeline. Ex. run-e2e
  • CI_JOB_ID - Value uniquely identifies an execution instance of a job. This value will change each time a job is executed/re-executed.
Jenkins

References: Jenkins documentation | Jenkins environment variables

Jenkins is heavily customizable through the usage of plugins, which limits the amount of assumptions we can make about available environment variables and overall behavior.

We have implemented Jenkins support within this module using the broadest set of available default values. For the purposes of this documentation, though, we will discuss terms related to Jenkins Pipeline support.

Essential environment variables

  • JENKINS_HOME - Presence identifies the environment as a Jenkins environment
  • BUILD_URL - Value uniquely identifies a Jenkins job execution, including name and id characteristics.
Azure

References: Azure Pipelines key concepts | Azure Pipelines predefined variables

Note: Cypress v13.13.1 is the earliest Cypress release that records the environment variables necessary for this module to identify runs in an Azure environment. Previous Cypress versions are not supported in Azure pipelines.

Essential environment variables

  • TF_BUILD and AZURE_HTTP_USER_AGENT - Combined presence identifies the environment as an Azure pipeline environment.
  • SYSTEM_PLANID - Value uniquely identifies a pipeline run. Value does not change as jobs within the pipeline are retried from failure.
  • SYSTEM_JOBID - Value uniquely identifies a job execution. Value changes each time a job is retried from failure, in conjunction with the SYSTEM_JOBATTEMPT being incremented.
  • SYSTEM_JOBATTEMPT - Value identifies the pipelines shared attempt index. Value is incremented when jobs are retried from failure.
CircleCI

References: About CircleCI | CircleCI built-in environment variables

Note: Cypress v13.13.1 is the earliest Cypress release that records the environment variables necessary for this module to identify runs in an CircleCI environment. Previous Cypress versions are not supported in CircleCI pipelines.

Essential environment variables

  • CIRCLECI - Presence identifies the environment as a CircleCI environment
  • CIRCLE_PIPELINE_ID - Value uniquely identifies a CircleCI pipeline, created on push or manually triggered through the UI. This value does not change as workflows within the pipeline are re-executed.
  • CIRCLE_WORKFLOW_ID - Value uniquely identifies an instance of a workflow's execution within a pipeline. This value will be updated upon each workflow execution; in other words, retrying a workflow from failure from the Circle UI will create a new workflow with a new CIRCLE_WORKFLOW_ID value available to the jobs executed within it.
  • CIRCLE_WORKFLOW_JOB_ID - Value uniquely identifies an execution instance of a named job within a workflow instance.
AWS CodeBuild

References: AWS CodeBuild documentation | AWS CodeBuild environment variables

Essential environment variables

  • CODEBUILD_BUILD_ID - Presence identifies the environment as an AWS CodeBuild environment. Value uniquely identifies a build.
Drone

References: Drone pipeline overview | Drone environment reference

Essential environment variables

  • DRONE - Presence identifies the environment as a Drone environment.
  • DRONE_BUILD_NUMBER - Value uniquely identifies a Drone build.
Bitbucket

References: Bitbucket Cloud documentation | Bitbucket variables and secrets

Essential environment variables

  • BITBUCKET_BUILD_NUMBER - Presence identifies the environment as a Bitbucket environment. Value uniquely identifies a build.
  • BITBUCKET_STEP_RUN_NUMBER - Value indicates a build step execution index and increments when a step is retried. Initial value of 1.
Buildkite

References: Buildkite documentation | Buildkite environment variables

Essential environment variables

  • BUILDKITE - Presence identifies the environment as a Buildkite environment.
  • BUILDKITE_BUILD_ID - Value uniquely identifies a build. This value does not change across different jobs within the same build.
  • BUILDKITE_JOB_ID - Value uniquely identifies a job execution. Each job in a build has a unique job ID.
  • BUILDKITE_RETRY_COUNT - Value indicates the retry attempt for a job. Default value of 0.

Local development example

To iterate on your verification script and see everything working without putting code into your CI environment, simulate the CI context for a specific Cypress run locally. This can save a lot of time when getting started.

If you executed a run in GitHub Actions and it was recorded to Cypress Cloud, you would set these 4 environment variables to replicate the context of that run locally and execute your local handler script. This is a great way to iterate on your script and verify everything is working as expected, without having to integrate anything in CI. It's also useful for debugging.

CYPRESS_PROJECT_ID=AAA
CYPRESS_RECORD_KEY=BBB
GITHUB_ACTIONS=true
GITHUB_RUN_ID=111
GITHUB_RUN_ATTEMPT=1
node verifyResults.js

The Results API will then look for the Cypress Cloud run that matches this run ID. If there is more than one Cypress Cloud run found for that GitHub Actions Run, you can pass run tags to narrow down to one run's report.

See also