Hello, I hope you are doing well. Today I would like to tell you about how to set up an end-to-end test for you front-end application. I used Cypress because it is currently the best tool to run an end-to-end test. The question is, why not unit tests? In my opinion, for a small part of the code, you could run unit tests. But if you want to ensure a happy path always succeeds, end-to-end tests are better to simulate the user’s journey. You can use both, and I encourage it. So, Without further ado, let’s get started.

Install Cypress

First, you need to install Cypress in your project. Run npm i -D cypress or yarn add -D cypress. After that, add the following scripts to your package.json file.

{
  // ...the rest of the package.json file
  "scripts": {
    "e2e": "cypress open",
    "e2e:headless": "cypress run"
  }
  // ...the rest of the package.json file
}

The e2e script is for running with the browser, and e2e:headless is for running in headless mode.

Write Your Test

Open Cypress by running npm run e2e. Cypress UI will pop up, known as Launchpad. Click on E2E Testing, and it will generate the files you need to run the test.

Once everything is set, add a sample test for you project. Create a file called cypress/e2e/login.cy.ts.

// cypress/e2e/login.cy.ts
describe("Login page", () => {
  it("should open the login page", () => {
    // arrange
    cy.visit("http://localhost:3000/auth/login");

    // act
    cy.contains("Username").click().type("Hari");
    cy.contains("Password").click().type("random-string");
    cy.contains("Log in").click();

    // assert
    cy.contains("Hi, Hari");
  });
});

Here is the sample of my application code:

<!-- ... the rest of the pages/login.tsx -->
<div class="name">
  <label for="name">Username</label>
  <input name="name" id="name" />
</div>
<div class="password">
  <label for="password">Password</label>
  <input name="password" id="password" />
</div>
<button>Log in</button>
<!-- ... the rest of the pages/login.tsx -->

You need too learn the Cypress methods from its documentation. The documentation is great, you are not supposeed to have difficulty exploring it. In my opinion, the learning curve is shallow, as you can see in my example. You could read it easily. It basically do the follows these steps:

  1. Visit http://localhost:3000/auth/login" from browser
  2. Find something that contains Username (this is a label). Click it. Then, type Hari
  3. Find something that contains Password (this is a label). Click it. Then, type random-string
  4. Find something that contains Log in (this is a button). Then click it
  5. Find something that contains Hi, Hari

A little bit advice: I’ve put three comments - arrange, act, assert. That is a testing pattern where you prepare the test (arrange), perform the test (act), evaluate the test result (assert). I’ve added the comments to make it more readable.

Another piece of advice is that when you write and end-to-end test, make sure you put yourself in the user’s perspective. For instance, you can replace cy.contains("Username").click().type("Hari") with cy.get("#name").type("Hari"). However, users won’t find elements by their ID, they will find elements by readable things like Username then perform the action on that element.

After you’ve added your test, run it. Run npm run e2e or npm run e2e:headless.

Run it in GitHub Actions

After everything is okay, you just need to run you test in your CI/CD. Here is what the ./.github/workflows/code-quality.yaml looks like:

name: 🌟 Code Quality

on: push

jobs:
  code-quality:
    name: 🌟 Code quality
    runs-on: ubuntu-latest
    steps:
      - name: ⬇️ Checkout repo
        uses: actions/checkout@v4

      - name: ⎔ Setup node
        uses: actions/setup-node@v3
        with:
          node-version: "18"
          cache: "yarn"

      - name: 📥 Install deps
        run: yarn --immutable

      - name: 🛠️ Build the application
        run: yarn build

      - name: 💻 Run the application
        run: yarn start &

      - name: ⏳ Wait the application to start
        run: npx wait-on http://localhost:3000/auth/login

      - name: 🤖 End-to-end test
        run: yarn e2e:headless

The pipeline will run the following steps:

  1. Check out the repository to the CI/CD server.
  2. Set up Node.js.
  3. Install the application dependencies.
  4. Build the application.
  5. Run the application.
  6. Wait the application to run.
  7. Run the end-to-end test.

One more piece of advice here is that you need to build the application in production mode if you can. This ensures that you test the same build as your production.

In the sixth step, I wait for the application to run because the test will fail if the application is not running.

That’s all from me. I hope you can get some ideas on how to set up end-to-end tests and run them in GitHub Actions.

Thank you!