Setting up Cypress.js in gitlab CI, the complete journey (Part 1/2)

Pierre Cavalet
Kaliop
Published in
8 min readJun 22, 2020

This guide is divided into two parts. The first part shows how to setup Cypress end-to-end tests in a simple application with some cool features along the way. The second part (still in writing) will show you how to integrate these tests in Gitlab CI, from setting up your first jobs and pipelines to the different test workflows you can use.

Cypress.js has been a revolution for us. We had been using Nightwatch.js for a long time. It did the job back then, and we were happy about it, but when we tried out Cypress.js, we knew we had to make the switch for two reasons:

  • Stability: we have experienced multiple weird behaviors with end-to-end testing frameworks, and I am not saying that Cypress.js is perfectly stable, but it is much much more stable than what we have tested so far. The fact that it does not use selenium is probably what makes such a difference.
  • Developer experience: video recording, test progression, logger, debugger, automatic waiting, time travel, automatic reloads and so on. Cypress.js has a complete developer tool set and it is a real pleasure to work with.

I like to work on small projects to try things out, so we will be working on the following (fake) website for this article:

https://workshop-cypress-kaliop.netlify.app/

Let’s pretend we are helping a developer named Jordan to test his application. Jordan is a great developer but never had the chance to get his hands dirty on Cypress.js.

Cypress.js

So Jordan has his website ready to test, already in (kind of) production. The first thing he needs to do is simply to install Cypress.

npm install cypress --save-dev

He adds the following script to his package.json file:

"scripts": {
"cypress:open": "cypress open"
}

He can then run

npm run cypress:open

And voilà! Jordan is ready.

Note: Cypress generates a lot of tests. If you want to check them out, you can but for this tutorial we will just get rid of them. And so Jordan deletes all the generated tests file in the integration folder.

This is just the setup so let’s get into the real stuff.

Writing your first test

So Jordan asks himself:

What do I want to test?

Some people have tons of ideas, while others have none. So let’s break down what this website does and see what we need to test.

Jordan knows that most of his traffic lands directly on the homepage, because he uses analytics to his advantage. Then, a user might try to log in or sign up. That’s it. Pretty simple website.

I first need to test my homepage, because it’s where most of my traffic lands.

Alright Jordan. Good call.

With some help, Jordan creates a new file named homepage.spec.js and fills it with the following code:

  • cy.visit() is used to navigate to a specific url.
  • cy.get() is used to get an element in the DOM and .should('exist') is pretty self-explanatory.

Note: cy.get() uses query selectors. data-cy is a data attribute (data-*) that you can put on any html tag. It’s best to use data attribute because if you use tags, ids or classes, your tests would not be as stable: any modification in the code could break the selector. And bonus points because we know exactly what it is used for (cf. Cypress.js Best practices).

Now Jordan can run his test directly from the Cypress UI.

So far so good.

Tests with user interaction

Ok. I can test if my home page is displayed correctly. Now let’s move to my login.

When it comes to testing features (like the login) which are data dependant, we want to be sure that what we are testing exists. For example, Jordan is going to try to log in to an account specifically created for this purpose. Another example would be if you had articles. You would want to test a specific stable article and not pick randomly the first one on your home page. Ideally you would want to work on fixtures. Let’s move back to Jordan’s login.

Note: this login accepts every email address and password (yes it is fake).

So Jordan needs to find which login workflow(s) he wants to test.

My users mainly land on my homepage and navigate to the login page when they try to log in, so I want to test the following workflow:

- the user lands on the home page
- the user navigates to the login page with the menu link
- the user enters his credentials
- the user is logged in

Commands in cypress are pretty easy to understand but let’s break down the new ones.

cy.url().should('eq', 'XXX')

This one is used to retrieve the current URL and then the should asserts that the URL is equal ('eq') to the second parameter.

type and click can be used with get to either type something in an input or click on an element.

It just works. 😃

I want to stop at this point and ask a simple question. Looking at the code above which (I think) is not that difficult to produce, how valuable do you think it is for your website and how difficult it would be to implement such test suites on your core functionalities ?

I personally think that it is very valuable for the time invested. I am going to continue on cypress functionalities because I want to keep this article structured, but at this point, if I were Jordan, I would stop developing test suites and try to integrate them in my CI.

The most important thing is not to cover all your website functionalities but rather to cover the most important workflows for your product to make sure it does not break. You can add other test suites later on. Moving back to Jordan’s website.

Cypress custom command

I don’t really like to hard code my URL. What if I want to have different configuration (dev, UAT, production), what if my url changes in my router?

Don’t worry Jordan, we got you. Now let’s say we are using a router. We might want to type something like this:

cy.goTo('home')
cy.goTo('login')
cy.goTo('signup')

We can create a custom command goTo for this. We will create a fake router that returns the url when we pass the route name. Obviously the implementation will vary depending on the router you use but the logic is always the same. Let’s create a new file services/router.js :

Now that we have a router we can use it in the support/commands.js file.

And we can now use the command in our test suites (you don’t need to require it).

This example is pretty simple because it does not handle path parameters, query parameters, etc. The idea is still the same even if you add more parameters. Just pass them through the command’s parameters.

Cypress tasks

Well now that my log in process is tested, let’s do the same for the sign up process.

My users mainly lands on my homepage and navigates to the sign up page when they try to sign up, so I want to test the following workflow:

- the user lands on the home page
- the user navigates to the signup page with the menu link
- the user enters his information
- he fills the code he received by e-mail
- the user is signed up

Hold on. This process looks like the login process but there is a catch.

he fills the code he received by e-mail

Now we could just open up a gmail client in the browser and retrieve the code. Right ? Right ? No.

First of all, Cypress won’t let you change the domain. You can still bypass it but the point here is that we do not want to test the gmail interface because it may change and break your test.

What we want to do instead, is use the gmail API (or another provider API) to retrieve the email, parse the content to retrieve the code and inject it in our test suite. This can be achieve using cypress tasks.

A cypress task is just a function that will be executed when it is triggered. But what is so special about a task is that it is not executed in the browser. Instead, it is executed in a Node.js environment, which means you can do whatever you want (more or less), because you have access to the Node.js API. You can for example read a file with the fs package…

…or you could install a library which allows you to read your email through an API and retrieve the code you want in the e-mail. 😃

Note: Because it is a bit long to set up, we won’t actually be fetching the code from gmail. Instead we will fake it with an API endpoint that directly returns the code (always the same).

If you want to do it, you can go through the setup of this library and do pretty much the same. Retrieve the code through the library instead of the API call and the rest is the same.

After reading the documentation, Jordan writes his task in the plugins folder, making sure to install axios in order to call the API.

npm install axios

Since we returned a promise, Cypress will wait for this promise to be resolved (or rejected).

Now he can use it in his test suite:

And yes, Cypress will type the code before clicking on the submit button.

This was just an introduction to Cypress.js. You can do a lot of things with this tool. Don’t fall into the trap of creating something too big. Start small, make it work and then try to move on to something bigger. Remember that the most important thing is to test what has the biggest value in your website.

In the next article we will reuse the code we produced and integrate our tests in gitlab CI. Stay tuned.

--

--

Pierre Cavalet
Kaliop
Editor for

A web developer passionate about education. Technical Expert @ Kaliop.