Cypress E2E Tests for Applications hosted on Vercel

4min read
Tags: Testing, E2E, CI/CD, GitHub, Vercel, Cypress

Introduction

Personally, I am a big fan of having any sort of (well-written) tests in all of my programming projects. They give me the confidence to push changes to my production build while knowing that all my core features are still functioning properly. Especially if they are fully integrated into your work pipeline. I recently switched to Vercel as my personal choice as a deployment platform and I was looking for a way to automate my E2E test runs whenever I added new features to my codebase. There are a couple tutorials already out there which explain in great detail on how to do that [1][2]. But unlike explained there, I did not want to run my E2E tests on every commit but rather on every pull request since I have a protected main branch and only allow changes coming in through a PR. Setting it up this way would also save some bandwidth since the number of PRs is smaller or equal to the number of incoming commits. So here is a walkthrough on how I manage to do that using GitHub actions:

Implementation

Let’s start by initializing a new Next.js project and add cypress as a devdependency:

npx create-next-app cypress-vercel-github --use-npm
npm install -D cypress

Add two new npm scripts for running cypress (UI and headless mode):

// package.json

{
  "name": "cypress-vercel-github",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "e2e": "cypress open",
    "e2e:headless": "cypress run"
  },
  "dependencies": {
    "next": "12.1.5",
    "react": "18.0.0",
    "react-dom": "18.0.0"
  },
  "devDependencies": {
    "cypress": "^9.5.4",
    "eslint": "8.13.0",
    "eslint-config-next": "12.1.5"
  }
}

After running npm run e2e for the first time, we should end up with a cypress folder in our root directory. Let’s add a very basic E2E test:

// cypress/integration/example.spec.js

describe("Example", () => {
  it("should have correct title", () => {
    cy.visit("/");

    cy.get("h1").contains("Welcome to Next.js!");
  });
});

Don’t forget to set/adjust the baseUrl in your cypress configuration file:

// cypress.json

{
  "baseUrl": "http://localhost:3000"
}

We should now have a first passing test. Feel free to add as many tests as you need. If we want, we can also execute a headless run with npm run e2e:headless to simulate the test run on our CI/CD pipeline.

Successful Cypress E2E test runSuccessful Cypress E2E test run

Having our local code in place, we push our code to our GitHub repository and connect it with a new project on Vercel. This should automatically trigger a deployment and create a production build hosted at <project-name>.vercel.app. Similarly, Vercel also automatically creates a preview build for every Git branch other than our production branch (hosted at project-name>-<unique-hash>-<scope-slug>.vercel.app). We can take advantage of this feature by having our E2E tests run against these preview builds before merging our changes to main. If our tests turn green, we then have the confidence that our new changes do not impede our current production build. To do so, we add a new GitHub workflow file so our tests run automatically whenever we create a new pull request against our main branch:

// .github/workflows/pull_request.yaml

name: Pull request

on:
  pull_request:
    branches:
      - main

jobs:
  e2e-cypress:
    name: E2E Cypress
    runs-on: ubuntu-latest
    steps:
      - name: Wait for Vercel Preview
        uses: patrickedqvist/wait-for-vercel-preview@v1.2.0
        id: waitForVercelPreview
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          max_timeout: 60

      - name: Checkout
        uses: actions/checkout@v3

      - name: Run Cypress Tests
        uses: cypress-io/github-action@v2
        env:
          CYPRESS_BASE_URL: ${{ steps.waitForVercelPreview.outputs.url }}

Here is a short description of each step:

  • Since we need the URL of our preview build we have to wait until the deployment on Vercel is finished. Fortunately, there exists a GitHub Action that not only waits until the deployment is successful but also provides the resulting URL as an output for later steps.
  • Checkout the code on the local runner.
  • Use the official Cypress GitHub Action to run our E2E tests. By providing a URL through the respective environment variable, our tests automatically run against our preview build.

And that’s it! If we now create a new branch to add a new feature and eventually create a pull request against our main branch, the GitHub action should automatically trigger and report its result as an additional check:

Pull request with integrated E2E test run checkPull request with integrated E2E test run check

If we click on Details we can see the logs generated by Cypress:

E2E test run logsE2E test run logs

Bonus point: If you have a GitHub Pro membership, you can additionally setup branch protection rules so pull requests can only be merged if our E2E test run is successful.

For more details, check my example repository on GitHub: https://github.com/dangpg/cypress-vercel-github

Next.js
Mantine
Vercel