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
- CI/CD automates testing to safeguard quality
- API-based setup and cy.session() improve speed
- Flaky tests should be fixed at the root cause, not ignored
- Parallel execution keeps large test suites efficient
- Reports make test results visible and shareable
Exercises
Basics
- Run tests with
cypress runin headless mode and verify that a video is generated. - Define test execution scripts in
package.json(cy:open,cy:run,test:e2e). - Configure the Mochawesome reporter in
cypress.config.js.
Intermediate
- Create a GitHub Actions workflow file that automatically runs Cypress tests on push.
- Create a custom command using
cy.session()to cache the login process. - Write a stable test that mocks external API responses using
cy.intercept().
Challenge
- Design a GitHub Actions workflow that runs tests across 3 parallel machines. Include configuration to upload screenshots and videos as artifacts.
References
- Cypress CI/CD Introduction
- Cypress GitHub Actions
- Cypress Cloud
- Cypress Component Testing
- Mochawesome Reporter
Congratulations!
You have completed 10 days of learning Cypress!
What You Learned
- Day 1: What Cypress is and why E2E testing matters
- Day 2: Setting up your environment and writing your first test
- Day 3: Core commands (visit, get, click, type)
- Day 4: Validating behavior with assertions
- Day 5: Form handling and validation
- Day 6: Testing network requests
- Day 7: Improving reusability with custom commands
- Day 8: Managing test data with fixtures
- Day 9: Debugging techniques and test strategy
- 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!