Automating CI/CD Pipelines with Cypress and GitHub Actions
CI/CD pipelines simplify software delivery by automating code integration, validation, and deployment. Incorporating end-to-end (E2E) tests ensures that changes function as expected in real user scenarios. Using GitHub Actions alongside Cypress helps automate testing workflows, reduce manual steps, and speed up feedback loops.
This guide outlines how to set up Cypress-based CI/CD pipelines using GitHub Actions. You’ll build a sample Next.js app and configure automated tests with Cypress.
Requirements
Before you begin, ensure the following:
- Create a GitHub repo, for example, cypress-dev-test, for your project and workflows.
- Use a Linux system like Ubuntu 24.04.
- Install and initialize Git on your local machine.
- Have an active Slack workspace for receiving test notifications.
Setting Up a Sample Next.js Project
Next.js is a React-based framework that supports features such as static generation, server-side rendering, and API routes. Follow these steps to create a sample project for Cypress testing:
Update Package Index
$ sudo apt update
Install Node.js and NPM
$ sudo apt install nodejs npm -y
Confirm Node.js Version
$ node -v
The output should show version 18.x or higher:
v18.19.1
If a lower version is installed, refer to the official Node.js and NPM installation guide for Ubuntu 24.04.
Navigate to Home Directory
$ cd
Create a New Next.js Project
$ npx create-next-app@latest my-project
Press Enter to accept default package installations and respond to setup prompts accordingly.
Sample setup output:
added 379 packages, and audited 380 packages in 1m
147 packages are looking for funding
found 0 vulnerabilities
Success! Created my-project at /root/example-project/my-project
Enter Project Directory
$ cd my-project
View Directory Structure
$ ls
Expected output includes:
app next.config.ts node_modules package-lock.json public
eslint.config.mjs next-env.d.ts package.json postcss.config.mjs README.md tailwind.config.ts tsconfig.json
Allow Port 3000 in Firewall
$ sudo ufw allow 3000
If UFW is not installed, use this command:
$ sudo apt install ufw -y && sudo ufw allow ssh
Apply UFW Changes
$ sudo ufw reload
Start Development Server
$ npm run dev
Example output:
▲ Next.js 15.1.6 (Turbopack)
- Local: http://localhost:3000
- Network: http://:3000
✓ Starting...
✓ Ready in 826ms
○ Compiling / ...
✓ Compiled / in 1906ms
Use your server’s IP to view the app in a browser: http://<SERVER-IP>:3000
.
Press Ctrl + C to stop the server when finished.
Building a Contact Form for Testing
Use the form below to simulate user input and detect UI issues during E2E testing. It contains fields for first name, last name, email, and message. Submitted data logs to the console.
Verify Project Path
$ pwd
Output should resemble:
/home/linuxuser/my-project
Backup Original Page File
$ mv app/page.tsx app/page.tsx.ORIG
Edit the File
$ nano app/page.tsx
Add Contact Form Code
"use client";
import { useState } from "react";
export default function Contact() {
const [data, setData] = useState({
firstName: "",
lastName: "",
email: "",
message: "",
});
const handleChange = (event: React.SyntheticEvent) => {
const target = event.target as HTMLInputElement;
setData((prev) => ({ ...prev, [target.name]: target.value }));
};
const handleSubmit = async (event: React.SyntheticEvent) => {
event.preventDefault();
console.log("Form Submitted:", data);
setData({ firstName: "", lastName: "", email: "", message: "" });
};
return (
<main className="w-[100%] h-[100%] absolute top-0 left-0 bg-[#8dd0fa] flex flex-col items-center justify-center">
<h1 className="text-[26px] font-bold mb-4 text-black uppercase">
CONTACT US
</h1>
<form
onSubmit={handleSubmit}
className="w-[450px] flex flex-col items-center text-black"
>
<div className="w-full flex justify-between my-2">
<input
type="text"
name="firstName"
placeholder="First Name"
onChange={handleChange}
value={data.firstName}
required
className="outline-none w-[48%] h-10 p-2 rounded shadow-sm"
/>
<input
type="text"
name="lastName"
placeholder="Last Name"
onChange={handleChange}
value={data.lastName}
required
className="outline-none w-[48%] h-10 p-2 rounded shadow-sm"
/>
</div>
<input
type="email"
name="email"
placeholder="Email"
onChange={handleChange}
value={data.email}
required
className="outline-none w-full h-10 p-2 rounded my-2 shadow-sm"
/>
<textarea
name="message"
id="message"
placeholder="Message"
rows={5}
onChange={handleChange}
value={data.message}
required
className="outline-none w-full p-2 rounded my-2 shadow-sm"
/>
<button type="submit" className="bg-black text-white m-2 w-full py-2">
Submit
</button>
</form>
</main>
);
}
</pre>
Save the file and close the editor.
Displaying the Contact Form and Running the Server in Background
The configured contact form adds several fields to validate user input and simulate interactive user behavior.
Launch the development server in the background to keep the app accessible on port 3000:
$ npm run dev &
Open the URL http://<SERVER-IP>:3000
in a web browser to view the form and confirm it is displayed correctly.
Installing and Configuring Cypress
Follow these steps to set up Cypress for end-to-end testing on your local environment.
Install Dependencies
$ sudo apt install libgtk2.0-0t64 libgtk-3-0t64 libgbm-dev libnotify-dev libnss3 libxss1 libasound2t64 libxtst6 xauth xvfb -y
Install Cypress
$ npm install cypress --save-dev
Initialize Cypress Configuration
$ npx cypress open
In the Cypress launchpad, follow these steps:
- Click Continue when prompted with release information.
- Select E2E Testing.
- Ensure configuration files are created, then click Continue.
- Select a preferred browser and click Start E2E Testing.
- Click Create new spec to add a test file.
- Rename the spec to
contact.cy.ts
. - After the prompt confirms the spec is created, click Okay, run the spec.
Verify Configuration Files
$ ls
Expected output:
app cypress.config.ts next.config.ts node_modules package-lock.json public tailwind.config.ts
cypress eslint.config.mjs next-env.d.ts package.json postcss.config.mjs README.md tsconfig.json
Modify the Cypress Configuration
Edit the configuration file to define the base URL for tests:
$ nano cypress.config.ts
Add this line within the e2e
block:
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
Updated Configuration Example
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});
This setting ensures a single source for the application URL used in all test cases, avoiding repetitive hardcoding.
Creating a Cypress Test
Structured tests should include three phases: setup, action, and assertion. The following example test opens the form, enters sample data, submits it, and checks the console log output.
Backup the Default Spec
$ mv cypress/e2e/contact.cy.ts cypress/e2e/contact.cy.ts.ORIG
Create a New Spec File
$ nano cypress/e2e/contact.cy.ts
Insert the Cypress Test Code
describe('Contact Page Tests', () => {
beforeEach(() => {
cy.visit('/');
});
it('Submits the form successfully', () => {
cy.window().then((win) => {
cy.spy(win.console, "log").as("consoleLog");
});
cy.get('input[name="firstName"]').type('John');
cy.get('input[name="lastName"]').type('Doe');
cy.get('input[name="email"]').type('john_doe@example.com');
cy.get('textarea[name="message"]').type('This is a test message.');
cy.get('button[type="submit"]').click();
cy.get("@consoleLog").should("be.calledWith", "Form Submitted:", {
firstName: "John",
lastName: "Doe",
email: "john_doe@example.com",
message: "This is a test message."
});
});
})
Run the Cypress Test
$ npx cypress run
Sample result output:
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: false │
│ Duration: 4 seconds │
│ Spec Ran: contact.cy.ts │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✔ contact.cy.ts 00:04 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! 00:04 1 1 - - -
If an error like Cypress failed to verify that your server is running occurs, restart the server with:
$ npm run dev
Then re-run the test.
Setting Up GitHub Actions for Cypress CI/CD
Once Cypress is tested locally, configure GitHub Actions to run those tests during your project’s CI/CD process. This integration ensures your application is validated automatically whenever you push new code.
Create the GitHub Actions Workflow File
Start by creating a new workflow file inside the project’s GitHub Actions directory.
$ mkdir -p .github/workflows
$ nano .github/workflows/cypress-e2e.yml
Insert Workflow Configuration
Add the following content to automate Cypress testing with every code push:
name: Cypress E2E Tests
on:
push:
branches:
- main
pull_request:
jobs:
cypress-run:
runs-on: ubuntu-latest
services:
web:
image: node:18
ports:
- 3000:3000
options: >-
--health-cmd "curl --fail http://localhost:3000 || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Dependencies
run: npm install
- name: Start Application
run: npm run dev &
env:
PORT: 3000
- name: Wait for Server to be Ready
run: npx wait-on http://localhost:3000
- name: Run Cypress Tests
run: npx cypress run
This YAML configuration does the following:
- Triggers tests on push or pull request to the
main
branch. - Sets up Node.js and installs all necessary packages.
- Launches the app and waits for the server to be ready.
- Executes all Cypress E2E tests.
Commit and Push the Workflow File
Save the file and commit your changes. Push the workflow to GitHub to activate it:
$ git add .
$ git commit -m "Add GitHub Actions workflow for Cypress E2E tests"
$ git push origin main
Review Workflow Execution
Go to the “Actions” tab in your GitHub repository to monitor the workflow. Once the CI process runs, you’ll see the Cypress E2E test job execute.
If everything is set up properly, your test results will show all passing cases. In case of a failure, GitHub provides detailed logs to help debug the issue.
Saving Failed Cypress Test Screenshots
Cypress automatically captures screenshots of failed tests, providing visual feedback for debugging. You can configure GitHub Actions to upload these screenshots as artifacts for centralized review. Here’s how to enable this feature in Cypress:
Enable Screenshot Capture
Open the Cypress configuration file:
$ nano cypress.config.ts
Add the following lines just below the baseUrl
entry:
screenshotOnRunFailure: true, // Enable screenshots
screenshotsFolder: "cypress/screenshots", // Save location
Full Configuration Example
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
screenshotOnRunFailure: true, // Enable screenshots
screenshotsFolder: "cypress/screenshots", // Save location
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});
Simulate a Failed Test
Back up your current test file:
$ cp cypress/e2e/contact.cy.ts cypress/e2e/contact.cy.ts.ORIG
Edit the test to produce a failure:
$ nano contact.cy.ts
Update the email field value to intentionally fail the test:
...
cy.get('input[name="email"]').type('john@example.com');
...
Execute the Test and Review the Failure
$ npx cypress run
The output should confirm a failed test and a saved screenshot:
(Screenshots)
- /home/linuxuser/my-project/cypress/screenshots/contact.cy.ts/Contact Page Tests -- Sub
mits the form successfully (failed).png
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✖ contact.cy.ts 00:06 1 - 1 - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✖ 1 of 1 failed (100%) 00:06 1 - 1 - -
Navigate to the cypress/screenshots/contact.cy.ts/
folder and view the generated screenshot named:
Contact Page Tests -- Submits the form successfully (failed).png
Configuring a CI/CD Pipeline with GitHub Actions
GitHub workflows are composed of jobs and steps to automate CI/CD processes. A job defines commands to be executed in an isolated runner, while steps define individual operations such as installing dependencies or running tests. Follow these instructions to build and validate a new workflow file:
Create Workflow File
$ mkdir -p .github/workflows
$ nano .github/workflows/cypress.yml
Insert CI/CD Workflow Configuration
name: CI/CD Pipeline
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18.x
- name: Install dependencies
run: npm install
- name: Build the Next.js app
run: npm run build
- name: Run Cypress Tests
uses: cypress-io/github-action@v6
with:
start: npm start
browser: chrome
- name: Upload Screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
path: cypress/screenshots
This pipeline configuration includes the following automated steps:
- Repository Checkout: Retrieves source files via
actions/checkout@v4
. - Node.js Setup: Configures Node.js version 18.
- Install & Build: Installs dependencies and builds the Next.js project.
- Cypress Test Execution: Runs E2E tests in Chrome.
- Artifact Upload: Automatically stores failed test screenshots.
Push to GitHub Repository
Initialize Git and push your local codebase to GitHub:
$ git init
$ git add .
$ git commit -m "Cypress E2E test workflow"
$ git remote add origin https://github.com/example-user/cypress-dev-test
$ git push -u origin main
Reviewing GitHub Actions Logs
GitHub provides detailed logs for monitoring workflows. These logs show each executed step, outputs, and diagnostics. To view them:
- Open the Actions tab in your repository.
- Select the latest run to view its summary.
- Review test results in the Cypress summary section.
- Download screenshots from the Artifacts section.
Analyze Job Execution
Click the test job in the workflow sidebar to view detailed execution logs. You’ll find:
- Commands: Shell commands used during CI/CD.
- Outputs: Terminal feedback and status messages.
- Timestamps: Duration of each action in the job.
Enabling Slack Notifications for Cypress Failures
Slack integration allows your team to receive instant notifications when Cypress tests fail during GitHub Actions runs. Follow these steps to set up Slack alerts and quickly address test issues.
Configure Slack Webhook
- Install the Incoming Webhooks app in your Slack workspace.
- Choose a channel for alerts (e.g.,
#ci-cd-alerts
) or create a new one. - Complete the setup and copy the generated Webhook URL.
Store Webhook in GitHub Secrets
- Go to your GitHub repository settings.
- Navigate to Settings > Secrets and Variables > Actions.
- Click New Repository Secret.
- Set the name to
SLACK_WEBHOOK_URL
. - Paste the copied Slack Webhook URL into the secret value field.
Edit GitHub Actions Workflow
Open your CI/CD workflow file to add the Slack notification configuration:
$ nano .github/workflows/cypress.yml
Add Slack Notification Step
Append the following step at the end of your workflow file:
- name: Notify Slack
if: failure()
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_COLOR: ${{ job.status }}
This setup uses the SLACK_WEBHOOK_URL
secret to send a message to your configured Slack channel whenever the CI workflow encounters a failure.
After enabling this integration, failed jobs will automatically trigger a Slack message containing a link to the failed GitHub Actions job log.
Conclusion
You have successfully built a CI/CD pipeline with GitHub Actions and Cypress by using a sample Next.js application. Your pipeline now includes automated test execution on every push to the main
branch, screenshot capture for failed test debugging, and Slack integration for real-time alerts.
With this automation in place, your development workflow becomes more efficient, reliable, and responsive. For advanced CI/CD implementations, such as deploying applications via Kubernetes, refer to the guide on setting up a pipeline using GitHub Actions and the Kubernetes Engine.