{
  "doc": {
    "id": "ui-coverage/guides/block-pull-requests",
    "title": "Block pull requests with UI Coverage policies",
    "description": "Block pull requests with Cypress UI Coverage: use the Results API to fail a build when coverage drops below a threshold or a run introduces new untested elements.",
    "section": "ui-coverage",
    "source_path": "/llm/markdown/ui-coverage/guides/block-pull-requests.md",
    "version": "e6c8d867c21227247f14714fb5690c7c018983c5",
    "updated_at": "2026-08-08T12:39:37.868Z",
    "headings": [
      {
        "id": "ui-coverage/guides/block-pull-requests#block-pull-requests-and-set-policies",
        "text": "Block pull requests and set policies",
        "level": 1
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#choose-a-policy",
        "text": "Choose a policy",
        "level": 2
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#before-you-begin",
        "text": "Before you begin",
        "level": 2
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#enforce-a-coverage-threshold",
        "text": "Enforce a coverage threshold",
        "level": 2
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#block-only-on-new-gaps-with-a-baseline",
        "text": "Block only on new gaps with a baseline",
        "level": 2
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#why-compare-untested-element-counts-instead-of-the-coverage-score",
        "text": "Why compare untested-element counts instead of the coverage score?",
        "level": 3
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#baseline-structure",
        "text": "Baseline structure",
        "level": 3
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#create-your-first-baseline",
        "text": "Create your first baseline",
        "level": 3
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#key-concepts",
        "text": "Key concepts",
        "level": 3
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#new-untested-elements",
        "text": "New untested elements",
        "level": 4
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#resolved-gaps",
        "text": "Resolved gaps",
        "level": 4
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#view-level-comparison",
        "text": "View-level comparison",
        "level": 4
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#wire-the-check-into-ci",
        "text": "Wire the check into CI",
        "level": 2
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#use-profiles-for-pr-specific-configuration",
        "text": "Use Profiles for PR-specific configuration",
        "level": 2
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#best-practices",
        "text": "Best practices",
        "level": 2
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#when-to-update-the-baseline",
        "text": "When to update the baseline",
        "level": 3
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#skip-incomplete-or-non-passing-runs",
        "text": "Skip incomplete or non-passing runs",
        "level": 3
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#manage-baselines-across-branches",
        "text": "Manage baselines across branches",
        "level": 3
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#where-to-store-the-baseline",
        "text": "Where to store the baseline",
        "level": 3
      },
      {
        "id": "ui-coverage/guides/block-pull-requests#see-also",
        "text": "See also",
        "level": 2
      }
    ]
  },
  "chunks": [
    {
      "id": "ui-coverage/guides/block-pull-requests#choose-a-policy",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Choose a policy",
      "heading_level": 2,
      "content_markdown": "## Choose a policy\n\nThe Results API gives you the run's coverage numbers; _how_ you gate on them is the policy. Two models cover most teams, and you can combine them.\n\n| Policy | Blocks when… | Best for |\n| --- | --- | --- |\n| [Coverage threshold](#Enforce-a-coverage-threshold) | Overall or per-view coverage drops below a fixed percentage | A codebase whose coverage is already where you want it, or a critical flow you hold to a high bar |\n| [New gaps vs. a baseline](#comparing-against-a-baseline) | A run introduces untested elements that weren't there before | An existing app with known coverage debt you want to stop growing |\n\nA fixed threshold is simple but blunt: if your app sits at 62% coverage, a threshold of 80% blocks _every_ pull request until you've paid down all the existing debt. A **baseline** compares each run against a known-good state and fails only on _newly_ introduced gaps, which lets you hold the line today and improve incrementally. Most teams with an established suite start here.\n",
      "section": "ui-coverage",
      "anchors": [
        "choose-a-policy"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 235
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#before-you-begin",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Before you begin",
      "heading_level": 2,
      "content_markdown": "## Before you begin\n\nThis guide assumes you've completed the [Results API setup](/llm/markdown/ui-coverage/results-api.md#Installation): your CI install step adds the `@cypress/extract-cloud-results` module, and a verification script runs **after** `cypress run --record` in the **same** CI build. A run also needs Test Replay enabled and must have been recorded within the last 7 days for the helper to find it. See the [Results API FAQ](/llm/markdown/ui-coverage/faq.md#Results-API-and-CI) for the full prerequisite list.\n\nEvery example below is a script that calls `getUICoverageResults()` and, when your policy isn't met, exits with a non-zero status to fail the CI step. See [Wire the check into CI](#Wire-the-check-into-CI) for how that failed step becomes a required status check that blocks the merge.\n",
      "section": "ui-coverage",
      "anchors": [
        "before-you-begin"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 151
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#enforce-a-coverage-threshold",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Enforce a coverage threshold",
      "heading_level": 2,
      "content_markdown": "## Enforce a coverage threshold\n\nThe simplest policy fails the build when overall coverage drops below a floor, and holds critical flows like login and checkout to a higher standard:\n\nscripts/verifyUICoverageResults.js\n\n```\nconst { getUICoverageResults } = require('@cypress/extract-cloud-results')getUICoverageResults()  .then((results) => {    const { summary, views } = results    // 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) => {      if (        criticalViews.some((pattern) => pattern.test(view.displayName)) &&        view.coverage < 95      ) {        throw new Error(          `Critical view \"${view.displayName}\" coverage is ${view.coverage}%, below the required 95%. See: ${view.uiCoverageReportUrl}`        )      }    })    console.log('UI Coverage meets all thresholds.')  })  .catch((error) => {    console.error(error.message)    process.exit(1)  })\n```\n\nThe [Results API examples](/llm/markdown/ui-coverage/results-api.md#Examples) cover more threshold variations, including reporting the score on the pull request, failing on untested navigation links, and ratcheting the floor up as coverage improves.\n",
      "section": "ui-coverage",
      "anchors": [
        "enforce-a-coverage-threshold"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 220
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#block-only-on-new-gaps-with-a-baseline",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Block only on new gaps with a baseline",
      "heading_level": 2,
      "content_markdown": "## Block only on new gaps with a baseline\n\nA threshold treats all untested elements the same, whether they've been untested for a year or arrived in the pull request under review. Comparing a run against a stored **baseline** lets you ignore existing gaps and fail only when a change introduces _new_ untested elements. This is what makes UI Coverage enforceable on a real codebase: you stop the debt from growing today, and pay it down on your own schedule without blocking every merge.\n\n### Why compare untested-element counts instead of the coverage score?\n\nThe coverage score is the share of your app's interactive elements that your tests exercise (tested ÷ total). It's the right number for tracking direction over time, but a poor signal for a per-run gate: adding a single test can reveal a whole page of previously unseen elements and _lower_ the score even though your coverage improved.\n\nThe **untested-element count per view** is a more direct signal for \"did this change add gaps?\" If a view's `untestedElementsCount` rises above its baseline, or a brand-new view arrives carrying untested elements, the change introduced gaps, regardless of what the percentage did. That's the number the script below compares.\n\n### Baseline structure\n\nThe baseline is a small JSON file you commit alongside your code. It records, for a known-good run, how many untested elements each view had:\n\n*   **runNumber**: the run the baseline was captured from, for reference.\n*   **runUrl**: a link back to that run in Cypress Cloud.\n*   **views**: a map of each view's `displayName` to its `untestedElementsCount`.\n\nui-coverage-baseline.json\n\n```\n{  \"runNumber\": 68086,  \"runUrl\": \"https://cloud.cypress.io/projects/ypt4pf/runs/68086\",  \"views\": {    \"/\": 0,    \"/login\": 2,    \"/products/*\": 4,    \"/checkout\": 1  }}\n```\n\n### Create your first baseline\n\nYou need a known-good run to anchor the baseline against. Pick a recent run whose coverage reflects the state you want to hold the line at, typically a run on your base branch, then capture its per-view untested counts one of two ways.\n\n**From the comparison script.** The script below is the same one you wire into CI. Run it once with no committed baseline and it writes the current run's numbers to `ui-coverage-baseline.next.json` without failing the build. Review that file, then commit it as `ui-coverage-baseline.json` so every later run compares against it. A CI runner is discarded after the job, so to capture that first candidate either run the script locally by replaying a recent run's CI context (see [Required CI environment variables](/llm/markdown/ui-coverage/results-api.md#Required-CI-environment-variables)), or upload `ui-coverage-baseline.next.json` as a build artifact from the CI run.\n\nView the comparison script\n\nThis script reads the committed baseline, fails the build when any view has _more_ untested elements than its baseline (or when a new view ships untested elements), and writes the current run's numbers to a separate candidate file so you can promote them when you're ready to accept a new state. Running it the first time with no baseline just writes the candidate for you.\n\nscripts/compareUICoverageBaseline.js\n\n```\nconst fs = require('fs')const { getUICoverageResults } = require('@cypress/extract-cloud-results')const BASELINE_FILE = 'ui-coverage-baseline.json'// Written on every run; promote it to BASELINE_FILE when you accept a new state.const CANDIDATE_FILE = 'ui-coverage-baseline.next.json'const hasBaseline = fs.existsSync(BASELINE_FILE)const baseline = hasBaseline  ? JSON.parse(fs.readFileSync(BASELINE_FILE, 'utf8'))  : { runNumber: null, runUrl: null, views: {} }getUICoverageResults()  .then((results) => {    const { runNumber, runUrl, uiCoverageReportUrl, summary, views } = results    // A cancelled or incomplete run can under-report views, which would look    // like a regression. Skip enforcement instead of failing for the wrong reason.    if (summary.isPartialReport) {      console.warn('Report is partial. Skipping baseline comparison.')      return    }    const newGaps = []    const improvements = []    views.forEach((view) => {      const current = view.untestedElementsCount      const previous = baseline.views[view.displayName]      if (previous === undefined) {        // A view with no baseline entry is new. Only flag it if it ships gaps.        if (current > 0) {          newGaps.push({            view: view.displayName,            previous: 0,            current,            url: view.uiCoverageReportUrl,          })        }      } else if (current > previous) {        newGaps.push({          view: view.displayName,          previous,          current,          url: view.uiCoverageReportUrl,        })      } else if (current < previous) {        improvements.push({ view: view.displayName, previous, current })      }    })    // Save the current run's numbers as the next candidate baseline.    fs.writeFileSync(      CANDIDATE_FILE,      JSON.stringify(        {          runNumber,          runUrl,          views: Object.fromEntries(            views.map((view) => [view.displayName, view.untestedElementsCount])          ),        },        null,        2      )    )    // On the first run there's no committed baseline to compare against, so seed    // the candidate and exit cleanly. Commit it to start gating later runs.    if (!hasBaseline) {      console.log(        `No committed baseline yet. Wrote ${CANDIDATE_FILE}; commit it as ${BASELINE_FILE} to start gating.`      )      return    }    improvements.forEach(({ view, previous, current }) =>      console.log(`✅ ${view}: untested elements ${previous} → ${current}`)    )    if (newGaps.length > 0) {      console.error('\\n❌ New untested elements were introduced:\\n')      newGaps.forEach(({ view, previous, current, url }) =>        console.error(`   ${view}: ${previous} → ${current} untested (${url})`)      )      console.error(        '\\nAdd tests for the new elements, or exclude them with App Quality configuration.'      )      console.error(`Full report: ${uiCoverageReportUrl}`)      process.exit(1)    }    console.log('\\n✅ No new untested elements against the baseline.')  })  .catch((error) => {    console.error(`Could not compare UI Coverage results: ${error.message}`)    process.exit(1)  })\n```\n\n**From your editor with an AI assistant.** If you use an agent that supports [Cypress Cloud MCP](/llm/markdown/cloud/integrations/cloud-mcp.md), you can build the baseline without wiring up CI first by having it pull the report and write the file:\n\nBuild a UI Coverage baseline with your AI assistant\n\nPulls the UI Coverage report from Cypress Cloud and writes a baseline file you can commit.\n\nUsing Cypress Cloud, pull the UI Coverage report for the latest run on main. Write a ui-coverage-baseline.json file with runNumber, runUrl, and a views object that maps each view's display name to its untested element count.\n\nReview the generated file the same way, then commit it. See [Work with AI agents](/llm/markdown/ui-coverage/work-with-ai-agents.md) for more on using Cloud MCP with UI Coverage.\n\n### Key concepts\n\n#### New untested elements\n\nA view has **new untested elements** when its `untestedElementsCount` is higher than the baseline, or when a view absent from the baseline appears with untested elements. This is a coverage regression: the change added interactive elements no test exercises. The script fails the build so the pull request can't merge until you cover them or exclude them.\n\n#### Resolved gaps\n\nA view has **resolved gaps** when its `untestedElementsCount` is lower than the baseline. This is an improvement: a change added coverage. The script reports these but doesn't fail the build, so progress is visible without blocking anyone.\n\n#### View-level comparison\n\nUntested elements are compared per [view](/llm/markdown/ui-coverage/core-concepts/views.md) (a URL pattern for end-to-end tests, or a spec file for component tests), so the failure message points at the exact page or component where the new gap appeared, rather than a single project-wide number. Views are matched by `displayName`, so renaming a route or changing how views are grouped changes the key: an unchanged page can then look like a new view and trip the gate. Re-promote the baseline after changes like that.\n",
      "section": "ui-coverage",
      "anchors": [
        "block-only-on-new-gaps-with-a-baseline"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 1519
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#why-compare-untested-element-counts-instead-of-the-coverage-score",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Why compare untested-element counts instead of the coverage score?",
      "heading_level": 3,
      "content_markdown": "### Why compare untested-element counts instead of the coverage score?\n\nThe coverage score is the share of your app's interactive elements that your tests exercise (tested ÷ total). It's the right number for tracking direction over time, but a poor signal for a per-run gate: adding a single test can reveal a whole page of previously unseen elements and _lower_ the score even though your coverage improved.\n\nThe **untested-element count per view** is a more direct signal for \"did this change add gaps?\" If a view's `untestedElementsCount` rises above its baseline, or a brand-new view arrives carrying untested elements, the change introduced gaps, regardless of what the percentage did. That's the number the script below compares.\n",
      "section": "ui-coverage",
      "anchors": [
        "why-compare-untested-element-counts-instead-of-the-coverage-score"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 155
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#baseline-structure",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Baseline structure",
      "heading_level": 3,
      "content_markdown": "### Baseline structure\n\nThe baseline is a small JSON file you commit alongside your code. It records, for a known-good run, how many untested elements each view had:\n\n*   **runNumber**: the run the baseline was captured from, for reference.\n*   **runUrl**: a link back to that run in Cypress Cloud.\n*   **views**: a map of each view's `displayName` to its `untestedElementsCount`.\n\nui-coverage-baseline.json\n\n```\n{  \"runNumber\": 68086,  \"runUrl\": \"https://cloud.cypress.io/projects/ypt4pf/runs/68086\",  \"views\": {    \"/\": 0,    \"/login\": 2,    \"/products/*\": 4,    \"/checkout\": 1  }}\n```\n",
      "section": "ui-coverage",
      "anchors": [
        "baseline-structure"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 107
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#create-your-first-baseline",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Create your first baseline",
      "heading_level": 3,
      "content_markdown": "### Create your first baseline\n\nYou need a known-good run to anchor the baseline against. Pick a recent run whose coverage reflects the state you want to hold the line at, typically a run on your base branch, then capture its per-view untested counts one of two ways.\n\n**From the comparison script.** The script below is the same one you wire into CI. Run it once with no committed baseline and it writes the current run's numbers to `ui-coverage-baseline.next.json` without failing the build. Review that file, then commit it as `ui-coverage-baseline.json` so every later run compares against it. A CI runner is discarded after the job, so to capture that first candidate either run the script locally by replaying a recent run's CI context (see [Required CI environment variables](/llm/markdown/ui-coverage/results-api.md#Required-CI-environment-variables)), or upload `ui-coverage-baseline.next.json` as a build artifact from the CI run.\n\nView the comparison script\n\nThis script reads the committed baseline, fails the build when any view has _more_ untested elements than its baseline (or when a new view ships untested elements), and writes the current run's numbers to a separate candidate file so you can promote them when you're ready to accept a new state. Running it the first time with no baseline just writes the candidate for you.\n\nscripts/compareUICoverageBaseline.js\n\n```\nconst fs = require('fs')const { getUICoverageResults } = require('@cypress/extract-cloud-results')const BASELINE_FILE = 'ui-coverage-baseline.json'// Written on every run; promote it to BASELINE_FILE when you accept a new state.const CANDIDATE_FILE = 'ui-coverage-baseline.next.json'const hasBaseline = fs.existsSync(BASELINE_FILE)const baseline = hasBaseline  ? JSON.parse(fs.readFileSync(BASELINE_FILE, 'utf8'))  : { runNumber: null, runUrl: null, views: {} }getUICoverageResults()  .then((results) => {    const { runNumber, runUrl, uiCoverageReportUrl, summary, views } = results    // A cancelled or incomplete run can under-report views, which would look    // like a regression. Skip enforcement instead of failing for the wrong reason.    if (summary.isPartialReport) {      console.warn('Report is partial. Skipping baseline comparison.')      return    }    const newGaps = []    const improvements = []    views.forEach((view) => {      const current = view.untestedElementsCount      const previous = baseline.views[view.displayName]      if (previous === undefined) {        // A view with no baseline entry is new. Only flag it if it ships gaps.        if (current > 0) {          newGaps.push({            view: view.displayName,            previous: 0,            current,            url: view.uiCoverageReportUrl,          })        }      } else if (current > previous) {        newGaps.push({          view: view.displayName,          previous,          current,          url: view.uiCoverageReportUrl,        })      } else if (current < previous) {        improvements.push({ view: view.displayName, previous, current })      }    })    // Save the current run's numbers as the next candidate baseline.    fs.writeFileSync(      CANDIDATE_FILE,      JSON.stringify(        {          runNumber,          runUrl,          views: Object.fromEntries(            views.map((view) => [view.displayName, view.untestedElementsCount])          ),        },        null,        2      )    )    // On the first run there's no committed baseline to compare against, so seed    // the candidate and exit cleanly. Commit it to start gating later runs.    if (!hasBaseline) {      console.log(        `No committed baseline yet. Wrote ${CANDIDATE_FILE}; commit it as ${BASELINE_FILE} to start gating.`      )      return    }    improvements.forEach(({ view, previous, current }) =>      console.log(`✅ ${view}: untested elements ${previous} → ${current}`)    )    if (newGaps.length > 0) {      console.error('\\n❌ New untested elements were introduced:\\n')      newGaps.forEach(({ view, previous, current, url }) =>        console.error(`   ${view}: ${previous} → ${current} untested (${url})`)      )      console.error(        '\\nAdd tests for the new elements, or exclude them with App Quality configuration.'      )      console.error(`Full report: ${uiCoverageReportUrl}`)      process.exit(1)    }    console.log('\\n✅ No new untested elements against the baseline.')  })  .catch((error) => {    console.error(`Could not compare UI Coverage results: ${error.message}`)    process.exit(1)  })\n```\n\n**From your editor with an AI assistant.** If you use an agent that supports [Cypress Cloud MCP](/llm/markdown/cloud/integrations/cloud-mcp.md), you can build the baseline without wiring up CI first by having it pull the report and write the file:\n\nBuild a UI Coverage baseline with your AI assistant\n\nPulls the UI Coverage report from Cypress Cloud and writes a baseline file you can commit.\n\nUsing Cypress Cloud, pull the UI Coverage report for the latest run on main. Write a ui-coverage-baseline.json file with runNumber, runUrl, and a views object that maps each view's display name to its untested element count.\n\nReview the generated file the same way, then commit it. See [Work with AI agents](/llm/markdown/ui-coverage/work-with-ai-agents.md) for more on using Cloud MCP with UI Coverage.\n",
      "section": "ui-coverage",
      "anchors": [
        "create-your-first-baseline"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 896
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#key-concepts",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Key concepts",
      "heading_level": 3,
      "content_markdown": "### Key concepts\n\n#### New untested elements\n\nA view has **new untested elements** when its `untestedElementsCount` is higher than the baseline, or when a view absent from the baseline appears with untested elements. This is a coverage regression: the change added interactive elements no test exercises. The script fails the build so the pull request can't merge until you cover them or exclude them.\n\n#### Resolved gaps\n\nA view has **resolved gaps** when its `untestedElementsCount` is lower than the baseline. This is an improvement: a change added coverage. The script reports these but doesn't fail the build, so progress is visible without blocking anyone.\n\n#### View-level comparison\n\nUntested elements are compared per [view](/llm/markdown/ui-coverage/core-concepts/views.md) (a URL pattern for end-to-end tests, or a spec file for component tests), so the failure message points at the exact page or component where the new gap appeared, rather than a single project-wide number. Views are matched by `displayName`, so renaming a route or changing how views are grouped changes the key: an unchanged page can then look like a new view and trip the gate. Re-promote the baseline after changes like that.\n",
      "section": "ui-coverage",
      "anchors": [
        "key-concepts"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 249
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#new-untested-elements",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "New untested elements",
      "heading_level": 4,
      "content_markdown": "#### New untested elements\n\nA view has **new untested elements** when its `untestedElementsCount` is higher than the baseline, or when a view absent from the baseline appears with untested elements. This is a coverage regression: the change added interactive elements no test exercises. The script fails the build so the pull request can't merge until you cover them or exclude them.\n",
      "section": "ui-coverage",
      "anchors": [
        "new-untested-elements"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 81
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#resolved-gaps",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Resolved gaps",
      "heading_level": 4,
      "content_markdown": "#### Resolved gaps\n\nA view has **resolved gaps** when its `untestedElementsCount` is lower than the baseline. This is an improvement: a change added coverage. The script reports these but doesn't fail the build, so progress is visible without blocking anyone.\n",
      "section": "ui-coverage",
      "anchors": [
        "resolved-gaps"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 53
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#view-level-comparison",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "View-level comparison",
      "heading_level": 4,
      "content_markdown": "#### View-level comparison\n\nUntested elements are compared per [view](/llm/markdown/ui-coverage/core-concepts/views.md) (a URL pattern for end-to-end tests, or a spec file for component tests), so the failure message points at the exact page or component where the new gap appeared, rather than a single project-wide number. Views are matched by `displayName`, so renaming a route or changing how views are grouped changes the key: an unchanged page can then look like a new view and trip the gate. Re-promote the baseline after changes like that.\n",
      "section": "ui-coverage",
      "anchors": [
        "view-level-comparison"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 111
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#wire-the-check-into-ci",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Wire the check into CI",
      "heading_level": 2,
      "content_markdown": "## Wire the check into CI\n\nAdd a step to the CI workflow that runs your Cypress tests, after the `cypress run --record` step, that installs the Results API module and executes your verification script:\n\ntest\\_cypress.yaml\n\n```\nname: My Workflowon: pushjobs:  run-tests:    runs-on: ubuntu-24.04    steps:      - uses: actions/checkout@v7      - run: npm install      - name: Run Cypress tests        run: npx cypress run --record        env:          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}+     - name: Enforce UI Coverage policy+       run: |+          npm install --force https://cdn.cypress.io/extract-cloud-results/v1/extract-cloud-results.tgz+          node ./scripts/compareUICoverageBaseline.js+       env:+         CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}+         CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}\n```\n\nThis example runs the baseline script. If you chose the [threshold policy](#Enforce-a-coverage-threshold) instead, run `verifyUICoverageResults.js` in that step, or run whichever script matches the policy you settled on.\n\nWhen your script exits non-zero, the CI step fails, which surfaces as a failed **status check** on the pull request. To make that check _block_ the merge rather than just show red, mark it as required in your version control provider's branch protection settings (for example, [GitHub's required status checks](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging)). Because enforcement lives entirely in your CI job, you control exactly when a coverage gap is advisory versus blocking.\n\nSee the [Results API CI setup](/llm/markdown/ui-coverage/results-api.md#Add-the-verification-step-to-CI) for the equivalent step in GitLab, Jenkins, Azure, CircleCI, and other providers.\n",
      "section": "ui-coverage",
      "anchors": [
        "wire-the-check-into-ci"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 275
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#use-profiles-for-pr-specific-configuration",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Use Profiles for PR-specific configuration",
      "heading_level": 2,
      "content_markdown": "## Use Profiles for PR-specific configuration\n\nA pull request check should be fast and focused, while your nightly regression run tracks everything. [Profiles](/llm/markdown/ui-coverage/configuration/profiles.md) let you apply different app quality configuration to different runs based on their [run tags](/llm/markdown/app/references/command-line.md#cypress-run-tag-lt-tag-gt), so both can share one Cypress Cloud project.\n\nFor example, this configuration defines a `pr-critical` profile that narrows PR runs to just the flows you gate merges on, while the base configuration still reports on everything for regression tracking:\n\nApp Quality Config\n\n```\n{  \"profiles\": [    {      \"name\": \"pr-critical\",      \"comment\": \"Narrow, fast config for PR runs: only the flows we block merges on\",      \"config\": {        \"viewFilters\": [          { \"pattern\": \"/checkout/*\", \"include\": true },          { \"pattern\": \"/login\", \"include\": true },          { \"pattern\": \"*\", \"include\": false }        ]      }    }  ]}\n```\n\nRecord your PR runs with the matching tag, and Cypress Cloud applies the profile automatically:\n\n```\ncypress run --record --tag pr-critical\n```\n\nWhen you record more than one run in a single CI build, pass the same tag to the helper so it returns the right run's results:\n\nFetch results for a tagged run\n\n```\ngetUICoverageResults({ runTags: ['pr-critical'] })\n```\n\nYou can confirm which profile produced a report by reading the [`config`](/llm/markdown/ui-coverage/results-api.md#Reading-the-applied-configuration) property on the result, which is useful for asserting that a PR run actually used the narrow configuration you expect.\n",
      "section": "ui-coverage",
      "anchors": [
        "use-profiles-for-pr-specific-configuration"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 293
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#best-practices",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Best practices",
      "heading_level": 2,
      "content_markdown": "## Best practices\n\n### When to update the baseline\n\nPromote the candidate baseline (`ui-coverage-baseline.next.json`) to your committed `ui-coverage-baseline.json` when you:\n\n*   Add tests that cover previously untested elements and want to lock in the gain.\n*   Deliberately accept a new gap as known debt that shouldn't block deployments.\n*   Want a fresh reference point after a large change to the application.\n\nCommit the baseline to version control so it's versioned alongside your code and available to every CI run.\n\n### Skip incomplete or non-passing runs\n\nA run can be cancelled, time out, or produce a partial report. The example already skips comparison when `summary.isPartialReport` is `true`; you can also check [`runStatus`](/llm/markdown/ui-coverage/results-api.md#Result-properties) and skip enforcement for statuses like `cancelled` or `timedOut`, so an incomplete run warns instead of failing the build for the wrong reason. See [Skip enforcement for partial or non-passing runs](/llm/markdown/ui-coverage/results-api.md#Skip-enforcement-for-partial-or-non-passing-runs).\n\n### Manage baselines across branches\n\nYou may want different baselines for different branches. Store branch-specific baseline files, or use an environment variable to choose which baseline to load, so a long-lived feature branch doesn't fail against `main`'s numbers.\n\n### Where to store the baseline\n\n*   **Version control** (recommended): commit the JSON file so it's reviewed and versioned with the code that changed the coverage.\n*   **CI artifacts**: store baselines as build artifacts retrieved on later runs.\n*   **External storage**: use a database if you need richer versioning across many projects.\n\nThis programmatic baseline complements the [Branch Review](/llm/markdown/ui-coverage/guides/compare-reports.md) UI, which visually compares two runs. Use the script to gate merges automatically in CI, and Branch Review for manual investigation during code review.\n",
      "section": "ui-coverage",
      "anchors": [
        "best-practices"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 351
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#when-to-update-the-baseline",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "When to update the baseline",
      "heading_level": 3,
      "content_markdown": "### When to update the baseline\n\nPromote the candidate baseline (`ui-coverage-baseline.next.json`) to your committed `ui-coverage-baseline.json` when you:\n\n*   Add tests that cover previously untested elements and want to lock in the gain.\n*   Deliberately accept a new gap as known debt that shouldn't block deployments.\n*   Want a fresh reference point after a large change to the application.\n\nCommit the baseline to version control so it's versioned alongside your code and available to every CI run.\n",
      "section": "ui-coverage",
      "anchors": [
        "when-to-update-the-baseline"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 101
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#skip-incomplete-or-non-passing-runs",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Skip incomplete or non-passing runs",
      "heading_level": 3,
      "content_markdown": "### Skip incomplete or non-passing runs\n\nA run can be cancelled, time out, or produce a partial report. The example already skips comparison when `summary.isPartialReport` is `true`; you can also check [`runStatus`](/llm/markdown/ui-coverage/results-api.md#Result-properties) and skip enforcement for statuses like `cancelled` or `timedOut`, so an incomplete run warns instead of failing the build for the wrong reason. See [Skip enforcement for partial or non-passing runs](/llm/markdown/ui-coverage/results-api.md#Skip-enforcement-for-partial-or-non-passing-runs).\n",
      "section": "ui-coverage",
      "anchors": [
        "skip-incomplete-or-non-passing-runs"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 84
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#manage-baselines-across-branches",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Manage baselines across branches",
      "heading_level": 3,
      "content_markdown": "### Manage baselines across branches\n\nYou may want different baselines for different branches. Store branch-specific baseline files, or use an environment variable to choose which baseline to load, so a long-lived feature branch doesn't fail against `main`'s numbers.\n",
      "section": "ui-coverage",
      "anchors": [
        "manage-baselines-across-branches"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 51
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#where-to-store-the-baseline",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "Where to store the baseline",
      "heading_level": 3,
      "content_markdown": "### Where to store the baseline\n\n*   **Version control** (recommended): commit the JSON file so it's reviewed and versioned with the code that changed the coverage.\n*   **CI artifacts**: store baselines as build artifacts retrieved on later runs.\n*   **External storage**: use a database if you need richer versioning across many projects.\n\nThis programmatic baseline complements the [Branch Review](/llm/markdown/ui-coverage/guides/compare-reports.md) UI, which visually compares two runs. Use the script to gate merges automatically in CI, and Branch Review for manual investigation during code review.\n",
      "section": "ui-coverage",
      "anchors": [
        "where-to-store-the-baseline"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 111
    },
    {
      "id": "ui-coverage/guides/block-pull-requests#see-also",
      "doc_id": "ui-coverage/guides/block-pull-requests",
      "heading": "See also",
      "heading_level": 2,
      "content_markdown": "## See also\n\n*   [Results API](/llm/markdown/ui-coverage/results-api.md): the full `getUICoverageResults` reference and more enforcement examples.\n*   [Monitor changes](/llm/markdown/ui-coverage/guides/monitor-changes.md): track coverage trends and catch regressions over time.\n*   [Compare reports](/llm/markdown/ui-coverage/guides/compare-reports.md): diff two runs visually with Branch Review.\n*   [Profiles](/llm/markdown/ui-coverage/configuration/profiles.md): apply different configuration to runs using run tags.\n*   [Identify coverage gaps](/llm/markdown/ui-coverage/guides/identify-coverage-gaps.md) and [Address coverage gaps](/llm/markdown/ui-coverage/guides/address-coverage-gaps.md): find the gaps a policy will block, and close them.\n*   [UI Coverage FAQ](/llm/markdown/ui-coverage/faq.md#Results-API-and-CI): quick answers to common Results API, baseline, and CI questions.\n",
      "section": "ui-coverage",
      "anchors": [
        "see-also"
      ],
      "path": "/llm/json/chunked/ui-coverage/guides/block-pull-requests.json",
      "token_estimate": 103
    }
  ]
}