Running Cypress in CI is the foundation. Making it a reliable quality gate that the team trusts is the goal. This lesson covers the practices that transform a CI pipeline from “tests run somewhere” to “every merge is protected by automated quality verification”: PR-blocking status checks, smart test selection for faster feedback, artifact management, timeout tuning, and pipeline performance monitoring.
Production CI/CD Best Practices for Cypress
These practices come from teams running Cypress in production pipelines with 200+ tests, multiple daily deployments, and strict quality requirements.
// ── PRACTICE 1: PR-blocking quality gate ──
/*
GitHub: Settings → Branches → Branch protection rules
- Require status checks: "Cypress Tests" must pass before merging
- Require branches to be up to date before merging
Effect: PRs with failing Cypress tests CANNOT be merged to main.
This is the single most important CI practice — it makes test failures
impossible to ignore and ensures every merge is quality-verified.
*/
// ── PRACTICE 2: Two-tier test execution ──
const TWO_TIER_STRATEGY = {
'Tier 1 — Smoke (every PR push)': {
tests: 'Critical path only — login, checkout, core CRUD',
count: '20-30 tests',
duration: '2-3 minutes',
purpose: 'Fast feedback on every commit; blocks PR merge on failure',
config: 'npx cypress run --spec "cypress/e2e/smoke/**"',
},
'Tier 2 — Full regression (merge to main / nightly)': {
tests: 'Complete test suite — all features, edge cases, cross-browser',
count: '150-200 tests',
duration: '10-15 minutes (with parallelisation)',
purpose: 'Comprehensive coverage before deployment',
config: 'npx cypress run --record --parallel',
},
};
// ── PRACTICE 3: Smart test selection (affected tests only) ──
/*
When a PR changes only the checkout component, running all 200 tests
wastes 12 minutes on unrelated tests. Smart selection runs only the
specs that test the changed code.
Approaches:
1. Tag-based: @smoke, @checkout, @auth tags → filter by tag
npx cypress run --env grepTags=checkout
2. File-based: Map changed source files to relevant spec files
If src/components/Checkout/ changed → run cypress/e2e/checkout/*.cy.ts
3. Cypress Cloud Smart Orchestration:
Automatically prioritises specs that historically fail for the changed code
*/
// ── PRACTICE 4: Artifact and evidence management ──
const ARTIFACT_STRATEGY = {
'Screenshots': {
when: 'On failure only (screenshotOnRunFailure: true)',
where: 'cypress/screenshots/',
upload: 'Always upload with if: always() or when: always',
retention: '7 days (most failures are diagnosed within 24 hours)',
},
'Videos': {
when: 'Disabled by default in CI (video: false) — enable for full regression',
where: 'cypress/videos/',
upload: 'Only on failure to save storage',
retention: '3 days',
},
'JUnit XML': {
when: 'Every run — for CI dashboard integration',
where: 'results/cypress-results.xml',
upload: 'Always — CI platforms parse JUnit for test result display',
config: 'reporter: junit, reporterOptions: { mochaFile: "results/[hash].xml" }',
},
'Mochawesome HTML': {
when: 'Full regression runs — rich HTML report with embedded screenshots',
where: 'reports/mochawesome.html',
upload: 'Always — shareable with non-technical stakeholders',
config: 'npm install mochawesome mochawesome-merge mochawesome-report-generator',
},
};
// ── PRACTICE 5: Timeout and retry tuning for CI ──
const CI_TUNING = {
defaultCommandTimeout: '10000 (10s — up from 4s default, CI is slower)',
pageLoadTimeout: '60000 (60s — slow CI networks)',
responseTimeout: '30000 (30s — API calls in CI may be slower)',
retries_runMode: '2 (retry twice in CI to handle transient failures)',
retries_openMode: '0 (no retries locally — see failures immediately)',
video: 'false (disable by default — enable only for full regression)',
screenshotOnRunFailure: 'true (always capture failure evidence)',
};
// ── PRACTICE 6: Pipeline performance monitoring ──
const PERFORMANCE_TARGETS = [
{
metric: 'Smoke suite (PR check)',
target: '< 5 minutes',
action: 'If exceeded: reduce smoke test count or increase parallelism',
},
{
metric: 'Full regression',
target: '< 15 minutes',
action: 'If exceeded: increase parallelism, split slow specs, optimise setup',
},
{
metric: 'Flake rate',
target: '< 2% of test runs involve retries',
action: 'If exceeded: fix top 5 flaky tests; investigate environment stability',
},
{
metric: 'Pipeline reliability',
target: '< 1% infrastructure failures (non-test related)',
action: 'If exceeded: improve Docker image pinning, dependency caching, network stability',
},
];
console.log('CI/CD Best Practices Summary:');
console.log(' 1. PR-blocking: Cypress status check required for merge');
console.log(' 2. Two-tier: Smoke on every push, full regression on merge/nightly');
console.log(' 3. Smart selection: Run only affected tests when possible');
console.log(' 4. Artifacts: Screenshots + JUnit XML on every run; videos on failure');
console.log(' 5. CI tuning: Higher timeouts, retries in run mode only');
console.log(' 6. Monitoring: Track suite duration, flake rate, pipeline reliability');
reporter: 'junit' in cypress.config) for every CI run. Most CI platforms (GitHub Actions, GitLab, Jenkins) natively parse JUnit XML and display test results in their UI — individual test names, pass/fail status, durations, and error messages appear directly in the pipeline dashboard without opening Cypress Cloud. This gives immediate visibility without requiring a separate analytics platform.retries: 5 might pass on the 5th attempt every run, making the pipeline green while hiding a genuine timing bug. Keep retries at 1-2 maximum. If a test needs more than 2 retries to pass consistently, it has a root cause that must be fixed — not a transient issue that retries can handle.Common Mistakes
Mistake 1 — Not making Cypress a required PR status check
❌ Wrong: Cypress tests run in CI but are advisory — developers can merge PRs even when tests fail, defeating the purpose of automated quality gates.
✅ Correct: Configuring branch protection rules to require the Cypress status check before merging. Failed tests block the PR until they are fixed or the failure is explained.
Mistake 2 — Running the full test suite on every push
❌ Wrong: Every push triggers 200 tests taking 15 minutes — developers push 10 times a day, consuming 150 minutes of CI time and delaying feedback.
✅ Correct: Smoke tests (30 critical tests, 3 minutes) on every push. Full regression (200 tests) on merge to main and nightly. 90% faster feedback for 90% of pushes.