Cypress E2E Testing: From Zero to a Real-World CI/CD Pipeline

Development tutorial - IT technology blog
Development tutorial - IT technology blog

5 minutes to a working demo

Instead of getting bogged down in dry theory, let’s jump right in to see immediate results. Cypress is incredibly accessible. It doesn’t require complex driver installations or cumbersome configurations like Selenium did in the past.

First, create a new directory and initialize the project with a few simple commands:

mkdir cypress-demo && cd cypress-demo
npm init -y
npm install cypress --save-dev

Once installed, start the Cypress Test Runner:

npx cypress open

An intuitive window will appear. Select E2E Testing and click Continue to let Cypress automatically initialize the necessary configuration files. Next, pick your favorite browser (like Chrome or Edge) and click Start E2E Testing.

Now, create your first test file at cypress/e2e/first_test.cy.js:

describe('My First Test', () => {
  it('Visit itfromzero.com and check the title', () => {
    cy.visit('https://itfromzero.com')
    cy.contains('Development').should('be.visible')
  })
})

Go back to the Cypress interface and click on the file you just created. You’ll see the browser open automatically, visit the site, and check the element in an instant. You’ve just completed your first E2E test!

Why is Cypress superior to legacy tools?

I once participated in refactoring a legacy system with over 50,000 lines of code. The most valuable lesson I learned was that without good test coverage, modifying old logic is like walking a tightrope. Back when we used Selenium, my team frequently struggled with “flaky tests”—tests that passed or failed unpredictably because elements hadn’t finished loading.

Cypress completely changed the game thanks to three invaluable features:

  • Automatic Waiting: You no longer need to use time.sleep(5) based on guesswork. Cypress automatically waits until the element appears or reaches the desired state.
  • Real-time Reloads: As soon as you hit Ctrl+S to save your test code, Cypress automatically reruns the tests. This makes the debugging process incredibly fast.
  • Time Travel: This feature allows you to hover over each executed command to see the UI state at that exact moment. It’s like having a detailed video recording of your test.

Structure of a professional test suite

As a project scales to hundreds of test cases, scientific directory organization becomes vital. A standard structure usually includes:

  • cypress/e2e/: Where the test scenarios live. This is the heart of the entire process.
  • cypress/fixtures/: Stores static data like JSON. For example, mock user info or product lists for API mocking.
  • cypress/support/: Where Custom Commands are defined. Instead of repeating login code in 100 different files, you can just write a shared cy.login() command.

Real-world scenario: Testing the Login flow

Don’t just test the “happy path” where users enter correct information. A quality test suite needs to cover edge cases like incorrect inputs or empty fields.

describe('Login Feature', () => {
  beforeEach(() => {
    cy.visit('/login') // Base URL is centrally configured in cypress.config.js
  })

  it('Displays error message when password is empty', () => {
    cy.get('input[name="email"]').type('[email protected]')
    cy.get('button[type="submit"]').click()
    cy.get('.error-message').should('contain', 'Password cannot be empty')
  })

  it('Redirects successfully with valid credentials', () => {
    cy.get('input[name="email"]').type('[email protected]')
    cy.get('input[name="password"]').type('password123')
    cy.get('button[type="submit"]').click()
    
    cy.url().should('include', '/dashboard')
    cy.get('h1').should('contain', 'Welcome Admin')
  })
})

API Mocking Techniques with cy.intercept()

This is the secret weapon to keep your tests 100% stable. Sometimes the Backend is under maintenance or a third-party API (like a payment gateway) responds very slowly. Cypress allows you to “intercept” these requests and return mock data immediately.

it('Mock article list from API', () => {
  cy.intercept('GET', '/api/posts', { fixture: 'posts.json' }).as('getPosts')
  
  cy.visit('/blog')
  cy.wait('@getPosts')
  
  cy.get('.post-item').should('have.length', 3)
})

This approach reduces test execution time from minutes to seconds because it doesn’t have to wait for actual server processing.

Automation with GitHub Actions

To ensure no one accidentally merges buggy code, you need to integrate Cypress into your CI/CD pipeline. This allows the system to automatically check code whenever someone creates a Pull Request.

Below is a simple .github/workflows/main.yml configuration file:

name: E2E Tests
on: [push]
jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

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

If any test case fails, GitHub will flag it with a red status and block the deployment, helping protect your Production environment.

Real-world experience to avoid “disaster”

After more than 6 months of applying Cypress to real projects, I’ve distilled 4 golden rules:

  1. Prioritize data-cy: Don’t use CSS classes (like .btn-blue) to select elements because they change easily during UI refactoring. Use data-cy="submit-button" for robust tests.
  2. Absolutely avoid cy.wait(seconds): Forcing a script to pause for 3-5 seconds is the fastest way to slow down a project. Use flexible timeouts or wait for API aliases.
  3. Isolate State: Each test case should be an independent entity. Reset the database or clear cookies in beforeEach to ensure previous tests don’t affect subsequent ones.
  4. Optimize CI resources: When running on a server, always use Headless mode. This significantly saves RAM and CPU for the CI system.

Cypress is more than just a testing tool. It’s a solution that helps developers sleep better before every release. If you’re building web applications, try integrating it today.

Are you having trouble configuring Cypress on Docker or Next.js? Share your issues in the comments below!

Share: