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

Install Node.js and NPM

$ sudo apt install nodejs npm -y

Confirm Node.js Version

The output should show version 18.x or higher:

If a lower version is installed, refer to the official Node.js and NPM installation guide for Ubuntu 24.04.

Navigate to Home Directory

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

View Directory Structure

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

If UFW is not installed, use this command:

$ sudo apt install ufw -y && sudo ufw allow ssh

Apply UFW Changes

Start Development Server

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

Output should resemble:

/home/linuxuser/my-project

Backup Original Page File

$ mv app/page.tsx app/page.tsx.ORIG

Edit the File

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:

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

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

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:

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

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:

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:

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:

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

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

  1. Go to your GitHub repository settings.
  2. Navigate to Settings > Secrets and Variables > Actions.
  3. Click New Repository Secret.
  4. Set the name to SLACK_WEBHOOK_URL.
  5. 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.

Source: vultr.com

Create a Free Account

Register now and get access to our Cloud Services.

Posts you might be interested in: