Learn Cypress in 10 DaysDay 10: CI/CD and Best Practices
Chapter 10Learn Cypress in 10 Days

Day 10: CI/CD and Best Practices

What You Will Learn Today

  • Running Tests in Headless Mode
  • Configuring GitHub Actions
  • Parallel Test Execution
  • Introduction to Cypress Cloud
  • Generating Test Reports
  • Performance Optimization
  • Dealing with Flaky Tests
  • Project Directory Structure
  • 10-Day Recap

Running Tests in Headless Mode

Cypress offers two modes for running tests.

flowchart LR
    subgraph Open["cypress open"]
        O1["Browser GUI"]
        O2["Interactive"]
        O3["Used during development"]
    end

    subgraph Run["cypress run"]
        R1["Headless execution"]
        R2["Automated"]
        R3["Used in CI/CD"]
    end

    style Open fill:#3b82f6,color:#fff
    style Run fill:#22c55e,color:#fff

Basic Commands

# Open the browser GUI for testing (development)
npx cypress open

# Run all tests in headless mode (CI/CD)
npx cypress run

# Run a specific spec file
npx cypress run --spec "cypress/e2e/login.cy.js"

# Run with a specific browser
npx cypress run --browser chrome
npx cypress run --browser firefox
npx cypress run --browser electron

# Use a glob pattern to run multiple files
npx cypress run --spec "cypress/e2e/auth/**/*.cy.js"

Defining Scripts in package.json

{
  "scripts": {
    "cy:open": "cypress open",
    "cy:run": "cypress run",
    "cy:run:chrome": "cypress run --browser chrome",
    "cy:run:auth": "cypress run --spec 'cypress/e2e/auth/**/*.cy.js'",
    "test:e2e": "start-server-and-test dev http://localhost:3000 cy:run"
  }
}

The start-server-and-test package waits for the server to be ready before running tests.

npm install --save-dev start-server-and-test

Configuring GitHub Actions

Basic Workflow

# .github/workflows/cypress.yml
name: Cypress Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  cypress-run:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Cypress run
        uses: cypress-io/github-action@v6
        with:
          build: npm run build
          start: npm start
          wait-on: 'http://localhost:3000'
          wait-on-timeout: 120

      - name: Upload screenshots
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: cypress-screenshots
          path: cypress/screenshots

      - name: Upload videos
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: cypress-videos
          path: cypress/videos

Workflow Overview

flowchart TB
    subgraph CI["GitHub Actions Workflow"]
        A["Checkout code"] --> B["Set up Node.js"]
        B --> C["Install dependencies"]
        C --> D["Build application"]
        D --> E["Start server"]
        E --> F["Run Cypress tests"]
        F --> G{"Pass?"}
        G -->|"Yes"| H["Done"]
        G -->|"No"| I["Save screenshots"]
        I --> J["Report failure"]
    end

    style CI fill:#8b5cf6,color:#fff
    style H fill:#22c55e,color:#fff
    style J fill:#ef4444,color:#fff

Key Options for the Official GitHub Action

Option Description Example
build Build command npm run build
start Server start command npm start
wait-on URL to wait for http://localhost:3000
browser Browser selection chrome
spec Test file specification cypress/e2e/**/*.cy.js
record Record to Cypress Cloud true
parallel Parallel execution true

Parallel Test Execution

As the number of tests grows, execution time increases. Parallel execution can dramatically reduce it.

flowchart TB
    subgraph Sequential["Sequential: 30 min"]
        S1["Test Suite A: 10 min"] --> S2["Test Suite B: 10 min"] --> S3["Test Suite C: 10 min"]
    end

    subgraph Parallel["Parallel: 10 min"]
        P1["Machine 1: Test Suite A 10 min"]
        P2["Machine 2: Test Suite B 10 min"]
        P3["Machine 3: Test Suite C 10 min"]
    end

    style Sequential fill:#f59e0b,color:#000
    style Parallel fill:#22c55e,color:#fff

Parallel Execution with GitHub Actions

# .github/workflows/cypress-parallel.yml
name: Cypress Parallel Tests

on: [push]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        containers: [1, 2, 3]  # Run on 3 machines in parallel

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Cypress run
        uses: cypress-io/github-action@v6
        with:
          start: npm start
          wait-on: 'http://localhost:3000'
          record: true
          parallel: true
          group: 'CI Parallel'
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

Parallel execution requires a Cypress Cloud record key.


Introduction to Cypress Cloud

Cypress Cloud (formerly Cypress Dashboard) is a cloud service for managing and analyzing test results.

Key Features

Feature Description
Test Recording Store results, screenshots, and videos in the cloud
Parallel Optimization Automatic distribution based on test duration
Flaky Test Detection Automatically identify unstable tests
Analytics Dashboard Trend analysis for pass rates and execution times
GitHub Status Checks Display test results on pull requests

Setup

// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  projectId: 'your-project-id',  // Obtained from Cypress Cloud
  e2e: {
    // ...
  },
});
# Record test results
npx cypress run --record --key YOUR_RECORD_KEY

Generating Test Reports

Mochawesome Reporter

Generate visually rich HTML reports.

npm install --save-dev mochawesome mochawesome-merge mochawesome-report-generator
// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    reporter: 'mochawesome',
    reporterOptions: {
      reportDir: 'cypress/reports',
      overwrite: false,
      html: false,
      json: true,
    },
  },
});

Report Generation Scripts

// package.json
{
  "scripts": {
    "cy:run": "cypress run",
    "report:merge": "mochawesome-merge cypress/reports/*.json > cypress/reports/merged.json",
    "report:generate": "marge cypress/reports/merged.json --reportDir cypress/reports/html",
    "test:report": "npm run cy:run && npm run report:merge && npm run report:generate"
  }
}
# Run tests and generate report
npm run test:report

JUnit Reporter (for CI/CD)

JUnit format is supported by many CI tools including Jenkins, GitLab CI, and CircleCI.

npm install --save-dev cypress-multi-reporters mocha-junit-reporter
// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    reporter: 'cypress-multi-reporters',
    reporterOptions: {
      configFile: 'reporter-config.json',
    },
  },
});
// reporter-config.json
{
  "reporterEnabled": "mochawesome, mocha-junit-reporter",
  "mochawesomeReporterOptions": {
    "reportDir": "cypress/reports/mochawesome",
    "overwrite": false,
    "html": false,
    "json": true
  },
  "mochaJunitReporterReporterOptions": {
    "mochaFile": "cypress/reports/junit/results-[hash].xml"
  }
}

Performance Optimization

Techniques to Improve Test Speed

flowchart TB
    subgraph Optimization["Speed Improvement Techniques"]
        A["API-based setup"]
        B["Eliminate unnecessary waits"]
        C["Session management"]
        D["Parallel execution"]
        E["Selective test execution"]
    end

    A --> F["Skip UI login"]
    B --> G["Reduce cy.wait() calls"]
    C --> H["Use cy.session()"]
    D --> I["Run on multiple machines"]
    E --> J["Only run tests related to changes"]

    style Optimization fill:#f59e0b,color:#000

1. API-Based Setup

// Slow: Log in via UI (5 seconds)
beforeEach(() => {
  cy.visit('/login');
  cy.get('#email').type('test@example.com');
  cy.get('#password').type('password');
  cy.get('#submit').click();
  cy.url().should('include', '/dashboard');
});

// Fast: Log in via API (0.5 seconds)
beforeEach(() => {
  cy.request('POST', '/api/login', {
    email: 'test@example.com',
    password: 'password',
  }).then((response) => {
    window.localStorage.setItem('token', response.body.token);
  });
  cy.visit('/dashboard');
});

2. Cache Sessions with cy.session()

// Cache and reuse sessions
Cypress.Commands.add('login', (email, password) => {
  cy.session([email, password], () => {
    cy.request('POST', '/api/login', { email, password })
      .then((response) => {
        window.localStorage.setItem('token', response.body.token);
      });
  });
});

// Use in tests
beforeEach(() => {
  cy.login('test@example.com', 'password');
  cy.visit('/dashboard');
});

cy.session() reuses the cache on subsequent calls with the same arguments.

3. Eliminate Unnecessary Waits

// BAD: Fixed-time wait
cy.wait(3000);
cy.get('.result').should('be.visible');

// GOOD: Assertion-based wait
cy.get('.result', { timeout: 10000 }).should('be.visible');

// GOOD: Wait for a network request
cy.intercept('GET', '/api/data').as('getData');
cy.get('#load-btn').click();
cy.wait('@getData');
cy.get('.result').should('be.visible');

Speed Improvement Comparison

Technique Before After Improvement
API login 5s/test 0.5s/test 90% faster
cy.session() 0.5s/test 0.05s/test 90% faster
Eliminate cy.wait() 3s fixed wait 0.1-3s ~50% faster on average
Parallel execution (3x) 30 min 10 min 66% faster

Dealing with Flaky Tests

A flaky test is an unstable test that sometimes passes and sometimes fails with the same code.

flowchart TB
    subgraph Causes["Causes of Flakiness"]
        C1["Timing dependencies"]
        C2["Inter-test dependencies"]
        C3["External service dependencies"]
        C4["Animations"]
        C5["Dynamic data"]
    end

    subgraph Solutions["Solutions"]
        S1["Assertion-based waits"]
        S2["Ensure test isolation"]
        S3["Mock with cy.intercept()"]
        S4["Disable animations"]
        S5["Use fixed test data"]
    end

    C1 --> S1
    C2 --> S2
    C3 --> S3
    C4 --> S4
    C5 --> S5

    style Causes fill:#ef4444,color:#fff
    style Solutions fill:#22c55e,color:#fff

1. Disable Animations

// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    // Disable CSS animations during tests
    setupNodeEvents(on, config) {
      // ...
    },
  },
});
// cypress/support/e2e.js
// CSS to disable animations during tests
Cypress.on('window:before:load', (win) => {
  const style = win.document.createElement('style');
  style.innerHTML = `
    *, *::before, *::after {
      transition-duration: 0s !important;
      animation-duration: 0s !important;
    }
  `;
  win.document.head.appendChild(style);
});

2. Mock External APIs

// Stable tests that don't depend on external APIs
beforeEach(() => {
  cy.intercept('GET', '/api/weather', {
    statusCode: 200,
    body: { temp: 25, condition: 'sunny' },
  }).as('getWeather');
});

it('displays weather information', () => {
  cy.visit('/dashboard');
  cy.wait('@getWeather');
  cy.get('.weather').should('contain', '25');
});

3. Flaky Test Detection Checklist

Check What to Look For
Using cy.wait(ms) Are you using fixed-time waits?
Test order dependency Does the test pass when run in isolation?
External API calls Are you using mocks?
Date/time dependency Are you using cy.clock()?
Random data Are you using fixed test data?

Project Directory Structure

Recommended Structure

project-root/
β”œβ”€β”€ cypress/
β”‚   β”œβ”€β”€ e2e/                    # E2E test files
β”‚   β”‚   β”œβ”€β”€ auth/               # Authentication
β”‚   β”‚   β”‚   β”œβ”€β”€ login.cy.js
β”‚   β”‚   β”‚   β”œβ”€β”€ logout.cy.js
β”‚   β”‚   β”‚   └── register.cy.js
β”‚   β”‚   β”œβ”€β”€ dashboard/          # Dashboard
β”‚   β”‚   β”‚   β”œβ”€β”€ overview.cy.js
β”‚   β”‚   β”‚   └── settings.cy.js
β”‚   β”‚   └── products/           # Product management
β”‚   β”‚       β”œβ”€β”€ list.cy.js
β”‚   β”‚       β”œβ”€β”€ detail.cy.js
β”‚   β”‚       └── cart.cy.js
β”‚   β”œβ”€β”€ fixtures/               # Test data (JSON)
β”‚   β”‚   β”œβ”€β”€ users.json
β”‚   β”‚   β”œβ”€β”€ products.json
β”‚   β”‚   └── api-responses/
β”‚   β”‚       β”œβ”€β”€ login-success.json
β”‚   β”‚       └── login-error.json
β”‚   β”œβ”€β”€ pages/                  # Page Objects
β”‚   β”‚   β”œβ”€β”€ LoginPage.js
β”‚   β”‚   β”œβ”€β”€ DashboardPage.js
β”‚   β”‚   └── ProductPage.js
β”‚   β”œβ”€β”€ support/                # Support files
β”‚   β”‚   β”œβ”€β”€ commands.js         # Custom commands
β”‚   β”‚   └── e2e.js             # Global configuration
β”‚   β”œβ”€β”€ downloads/              # For download tests
β”‚   └── reports/                # Test reports
β”œβ”€β”€ cypress.config.js           # Cypress configuration
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       └── cypress.yml         # CI/CD configuration
└── package.json

Structure Guidelines

Directory Role Key Point
e2e/ Test files Organize by feature in folders
fixtures/ Test data Manage in JSON format
pages/ Page Objects One file per page
support/ Shared utilities Custom commands, global config

10-Day Learning Recap

flowchart TB
    subgraph Journey["Your 10-Day Learning Journey"]
        D1["Day 1<br/>What is Cypress"]
        D2["Day 2<br/>Setup"]
        D3["Day 3<br/>Basic Commands"]
        D4["Day 4<br/>Assertions"]
        D5["Day 5<br/>Form Handling"]
        D6["Day 6<br/>Networking"]
        D7["Day 7<br/>Custom Commands"]
        D8["Day 8<br/>Fixtures & Data"]
        D9["Day 9<br/>Debugging & Strategy"]
        D10["Day 10<br/>CI/CD"]

        D1 --> D2 --> D3 --> D4 --> D5
        D5 --> D6 --> D7 --> D8 --> D9 --> D10
    end

    style D1 fill:#3b82f6,color:#fff
    style D2 fill:#3b82f6,color:#fff
    style D3 fill:#8b5cf6,color:#fff
    style D4 fill:#8b5cf6,color:#fff
    style D5 fill:#8b5cf6,color:#fff
    style D6 fill:#f59e0b,color:#000
    style D7 fill:#f59e0b,color:#000
    style D8 fill:#f59e0b,color:#000
    style D9 fill:#22c55e,color:#fff
    style D10 fill:#22c55e,color:#fff
Day Topic What You Learned
1 What is Cypress Overview of E2E testing, Cypress features
2 Setup Installation, configuration, first test
3 Basic Commands visit, get, click, type, should
4 Assertions Using should, expect, and assert
5 Form Handling Input, selection, validation testing
6 Networking intercept, wait, API mocking
7 Custom Commands Creating reusable commands
8 Fixtures & Data Test data management with fixtures
9 Debugging & Strategy Debugging tools, Page Object, test design
10 CI/CD Automated testing, parallel execution, best practices

Next Steps

Now that you have a solid foundation in Cypress, it is time to explore more advanced topics.

Component Testing

Since Cypress 12, component testing is officially supported.

// React component test example
import { mount } from 'cypress/react';
import Button from './Button';

describe('Button', () => {
  it('fires a click event', () => {
    const onClick = cy.stub().as('onClick');
    mount(<Button onClick={onClick}>Click me</Button>);
    cy.get('button').click();
    cy.get('@onClick').should('have.been.calledOnce');
  });
});

Visual Testing

Compare screenshots to detect unintended UI changes.

npm install --save-dev cypress-visual-regression
it('has the correct login page layout', () => {
  cy.visit('/login');
  cy.compareSnapshot('login-page', 0.1);  // Allow up to 0.1% difference
});

Accessibility Testing

npm install --save-dev cypress-axe
import 'cypress-axe';

it('has no accessibility violations', () => {
  cy.visit('/');
  cy.injectAxe();
  cy.checkA11y();
});

Summary

Concept Description
cypress run Run tests in headless mode
GitHub Actions Automate test execution in CI/CD
Parallel execution Distribute tests across multiple machines
Cypress Cloud Cloud service for recording and analyzing results
Mochawesome Generate HTML test reports
cy.session() Speed up tests with session caching
Flaky tests Detect and fix unstable tests
Directory structure Organize by feature using folders

Key Takeaways

  1. CI/CD automates testing to safeguard quality
  2. API-based setup and cy.session() improve speed
  3. Flaky tests should be fixed at the root cause, not ignored
  4. Parallel execution keeps large test suites efficient
  5. Reports make test results visible and shareable

Exercises

Basics

  1. Run tests with cypress run in headless mode and verify that a video is generated.
  2. Define test execution scripts in package.json (cy:open, cy:run, test:e2e).
  3. Configure the Mochawesome reporter in cypress.config.js.

Intermediate

  1. Create a GitHub Actions workflow file that automatically runs Cypress tests on push.
  2. Create a custom command using cy.session() to cache the login process.
  3. Write a stable test that mocks external API responses using cy.intercept().

Challenge

  1. Design a GitHub Actions workflow that runs tests across 3 parallel machines. Include configuration to upload screenshots and videos as artifacts.

References


Congratulations!

You have completed 10 days of learning Cypress!

What You Learned

  1. Day 1: What Cypress is and why E2E testing matters
  2. Day 2: Setting up your environment and writing your first test
  3. Day 3: Core commands (visit, get, click, type)
  4. Day 4: Validating behavior with assertions
  5. Day 5: Form handling and validation
  6. Day 6: Testing network requests
  7. Day 7: Improving reusability with custom commands
  8. Day 8: Managing test data with fixtures
  9. Day 9: Debugging techniques and test strategy
  10. Day 10: CI/CD and best practices

The Road Ahead

You now have a solid grasp of Cypress fundamentals. From here, the most important thing is to apply what you have learned in real projects and build experience through practice.

Writing tests does more than protect software quality -- it gives you the confidence to make changes and ship faster. Take the CI/CD knowledge you gained today and integrate Cypress into your team's development workflow.

Your testing journey starts here. Keep learning, and build web applications you can trust!