Getting Started with awesome Cypress Automation Framework

Niluka Sripali Monnankulama
Many Minds
Published in
17 min readFeb 26, 2022

--

We know that when working with different test automation frameworks we may experience some form of pain and difficulty in many areas 😫, We try to avoid this pain by bringing in different coding practices within these tools.

But a tool like Cypress, which helps to overcome this pain point and many of the problems we face, and with the experience of working with Cypress, it seemed to me that the potential of Cypress is really good.🥰

Cypress runs on NodeJS server, which communicates with the test runner(Browser) instrumentalized by Cypress to run the application (one of the iframe) and test code(another iframe) in the same event loop.

This in turn allows the Cypress code to mock and even change the JavaScript object on the fly.

This is one of the primary reasons why Cypress tests are expected to execute faster than corresponding Selenium tests.

🌲 Key Features of Cypress:

  • Mocking — By mocking the server response, it has the ability to test edge cases.
  • Time Travel — It takes snapshots as your tests run, allowing users to go back and forth in time during test scenarios.
  • Flake Resistant — It automatically waits for commands and assertions before moving on.
  • Spies, Stubs, and Clocks — It can verify and control the behavior of functions, server responses, or timers.
  • Real-Time Reloads — It automatically reloads whenever you make changes to your tests.
  • Consistent Results — It gives consistent and reliable tests that aren’t flaky.
  • Network Traffic Control — Easily control, stub, and test edge cases without involving your server.
  • Automatic Waiting — It automatically waits for commands and assertions without ever adding waits or sleeps to your tests. No more async hell.
  • Screenshots and Videos — View screenshots taken automatically on failure, or videos of your entire test suite when it has run smoothly.
  • Debuggability — Readable error messages help you to debug quickly.

~~💃 So let’s see how to install and set up Cypress on our machine.~~

💻 System Prerequisites

Cypress is a desktop application that is installed on your computer. The desktop application supports the following operating systems:

  • macOS 10.9 and above (64-bit only)
  • Linux Ubuntu 12.04 and above, Fedora 21 and Debian 8 (64-bit only)
  • Windows 7 and above

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

✅ Create a new npm project:

As the first step, use the npm init command to initialize a project and to create a package.json file for application. (As a general rule, any project that’s using Node.js will need to have a package.json)

This command prompts you for a number of things, including the name and version of your application and the name of the initial entry point file (by default this is index.js). For now, just accept the defaults:

cd /your/project/pathnpm init

✅ Install Cypress:

As the document states, only this command is required for installation.

npm install cypress --save-dev

This will download and unzip the Cypress desktop client and install all the necessary packages. And also this command will install the latest stable version.

👀 Note : You must have NPM installed on your Windows or Mac and execute this, it does not require any other dependencies.

Cypress installation is now complete.

once you have done these steps, You can those files via Visual Studio code.

inside package.json you can see the “scripts” section

"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},

This will be used to run our test scripts from the command line.

package.json is very useful in managing all the dependencies .it also initializes the npm project. Now we can run npm commands directly on this project, If we have added dependencies/libraries or if we have to send this project to any operating system it manages all dependencies.

Using Package.json all dependencies will be downloaded and nothing will have to be done manually.

So once everything is done, then we need to create Cypress packages. We already installed Cypress dependency but things related to Cypress do not appear here. So for that, we can use a command,

npx cypress open

npx is a different way of executing npm package binaries.

Once you enter this, you can see some magic happening,

This will open the playground, and this playground has already implemented test specifications, and with the help of these tests, we can get more ideas on implementing our own test scripts.

Basically, this command will open up the Cypress app and list all the initial pre-built tests

Also in the folder structure, we see a new folder called

Within this folder, we can see four subfolders,

fixtures, integration, plugins, and support.

And in the integration folder, we can see several * .spec.js files, which are the same files we can see in the playground window as well.

So what really happens after executing the above command

“npx cypress open”, Some magical things came up automatically.

Well, this is the cypress playground setup. With that command, it will create the folder structure for us, which is the most industrially recommended folder structure, and they have already created a template for us.

These folders content will reflect the following

├── cypress|   ├── fixtures/ ........................ Fixtures files|   ├── integration/ ...................... Test files are located in cypress/integration|   ├── plugins/ ....................... Automatic plugins file|   ├── support/ ........................... Automatic Support File
├── cypress.json

📁 Cypress Folder Structure

  • fixtures
    Here is where you’ll find fixed test data, which have no relation to the other entities. So, no IDs are stored here, which can change according to the local state.
  • integration
    You will find the actual tests here.
  • plugins
    Here, you can extend Cypress, whether with existing Cypress plugins or your own.
  • support
    Here, you can extend Cypress itself. Your own commands and helpers are located here.
  • cypress.json
    Modify configurations here, including for the environment.

And any test of the extension of *.spec.* is considered as a test file within cypress.

🏃 Run tests in Cypress Test Runner

Once you refer to any of this example test spec, ex: cypress/integration/1-getting-started/todo.spec.js

There you can see,

/// <reference types="cypress" />

With this, we provide IntelliSense much more efficiently.

We can also execute selected test specification files through the playground and select one of them and click on it.

I will click on “todo.spec.js”

Then it will open a magical window for us and it doing some process there very faster. In fact, it executes certain tests. This window is called Test runner in cypress.

The Command Log UI (on the left) runs alongside your front-end application (on the right).

“What does it mean? What are the main Command Log UI features?

  • You have direct feedback for what Cypress is doing. Every time you ask Cypress to interact with the page through its commands (cy.click , cy.type, etc.), Cypress adds a log to the Test Runner. This verbose automatic logging is really helpful both while writing your test and while debugging it. It seriously improves your productivity, both because it is automatic and because it is side-by-side with your application.

But, as I told you, the lack of retroactive debuggability is a big missing while writing UI tests… Let me introduce you…

  • Interactive time-traveling: not sure how the app reached a particular command or how the test failed? Would you take a look at the UI at a previous step? That is why the Command Log is interactive! You can hover over the various logged steps and see how the app looks at a particular step! Or, obviously, you can pin a step and inspect the DOM, check how the app looks before/after the step, etc. This is another life-saver feature, both in the first approach (debugging a test when you do not know the testing tool could be a nightmare) and in the day-by-day testing work. It makes test inspection so handy that you completely forget how was testing without it. Watch it in action”

Others Command Log utilities are:

  • Command’ rich log: clicking on command shows a more detailed log into the browser DevTools
  • Assertion inspection: clicking on an assertion shows both the expected value and the result in the browser DevTools. You do not need to relaunch the test with more verbose logging
  • If you spy XHR calls the Command Log shows a resume of the spied/stubbed calls and how many times they have been called

… and more, take a look at its capabilities in the official Cypress docs.

We can also view the configuration in the Test Runner GUI under the settings button.

👻 Run Test via Command line /Headless Mode

If you don’t need the interactive test runner or maybe you just want to automate things (CI/CD). You can run the test runner from the command line in a headless fashion. This allows for complete customization of how things are run.

This is how we run tests using only the command line/Headless mode:

cd /your/project/pathnpx cypress run

When running from the command line you will see additional insights about your tests. You also get the ability to generate stats/reports for external code analysis and also Cypress automatically generates screenshots for failed test cases and videos for all running test specifications.

Whenever Cypress Runs in your CI environment, it will create a report of the tests and how they ran. This is very similar to reports that you see with other frameworks like Karma.

📊📈 Cypress Dashboard

Cypress takes it a step farther, however, and offers a Dashboard service where you can view all of the runs (and output) of your tests. This is particularly helpful if you want to see history or compare failing test results from your pipeline.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Let’s move to another important part of this blog, Test Writting

🔄🔃 Typical test execution lifecycle

⚙️ How to Write Simple UI Tests Using Cypress

With Cypress end-to-end testing, we can replicate user behavior on application and cross-check whether everything is working as expected. In this section, we’ll check useful ways to write tests on the front-end using Cypress.

Test Suite & Test case Structure in Cypress

// type definitions for Cypress object "cy"
/// <reference types="cypress" />

describe('Cypress Basic Test structure', function() {
before(function() {
// runs once before all tests in the it block
cy.log("I am in before")
})

after(function() {
// runs once after all tests in the it block
cy.log("I am in after")
})

beforeEach(function() {
// runs before each test in the it block
cy.log("I am in beforeEach")
})

afterEach(function() {
// runs after each test in the it block
cy.log("I am in afterEach")
})

it('Cypress Test Case 1', function() {
// Test Steps/Commands for Test Case 1
cy.log("I am in Test Case 1 ")
})

specify('Cypress Test Case 2', function() {
// Test Steps/Commands for Test Case 2
cy.log("I am in Test Case 2")
})
})

Here is an example of how to write UI test in Cypress:

If you try with command line/Headless mode, Cypress support you to execute the selected test file.

npx cypress run --spec path/to/file.spec.js

or using glob patterns:

npx cypress run --spec 'path/to/files/*.spec.js'

⚙️ REST API Testing Using Cypress

Testing the API along with UI test, is important, as it also helps to stabilize the API and process data to interact with third-party servers.

Cypress provides the functionality to make an HTTP request.

Using Cypress’s Request() method, we can validate GET, POST, PUT, and DELETE API Endpoints.

describe("Testing API Endpoints Using Cypress", () => { it("Test GET Request", () => {  cy.request("<endpoint>").then((response) => {   expect(response.body).to.have.property("<validation-entity>", "  <Expected_Result>");   }) }) it("Test POST Request", () => {  cy.request({   method: 'POST',   url: '<endpoint>',   body: "<payload>"  }).then((response) => {  expect(response.body).has.property("<validation-entity>", " <Expected_Result>");  }) })  it("Test PUT Request", () => {   cy.request({    method: 'PUT',    url: '<endpoint>',    body: "<payload>"  }).then((response) => {   expect(response.body).has.property("<validation-entity>", " <Expected_Result>");   }) })  it("Test DELETE Request", () => {   cy.request({    method: 'DELETE',    url: '<endpoint' }).then((response) => {    expect(response.body).to.be.empty;   }) })})

📸 Screenshots

  • Cypress has the in-built feature to capture the screenshots of failed tests.
  • Screenshots will not be captured automatically taken on test failure during cypress open.
  • To turn this behavior off, you will need to set screenshotOnRunFailure to false in the Cypress.Screenshot.defaults().

Ex: I change assertion in the example test spec, cypress/integration/1-getting-started/todo.spec.js

Here I execute sample test using command line/Headless mode with chrome browser.

👀 Note: By default, when running cypress run from the CLI, it will launch Electron browser headlessly. But we can try with any other supported browser as well, You can refer to the official documentation for further details.

npx cypress run --browser chrome --spec 'cypress/integration/1-getting-started/todo.spec.js'

Okay, after changing the assertion in the test file, we can see the test failure in the test results.

The screenshots will be stored in the screenshots folder which is by default set to cypress/screenshots.

By default, cypress will clear any screenshot that is in the folder before cypress run. If you don’t want to clear your screenshots folder before a run, you should set trashAssetsBeforeRuns to false.

If you want to take a manual screenshot, you can use the cy.screenshot() command.

  • We can capture both the complete page and particular element screenshots with the screenshot command in Cypress.
  • Note: `cy.screenshot()` only works for a single element. You attempted to screenshot 2 elements/more.

📷 Screenshot Implementation

The implementation of the screenshot commands in Cypress is as follows −

/// <reference types="cypress" />context('Viewport', () => {beforeEach(() => {cy.visit('https://example.cypress.io/commands/viewport')//complete page screenshot with filename - CompletePagecy.screenshot('CompletePage')})it('cy.viewport() - set the viewport size and dimension', () => {// https://on.cypress.io/viewportcy.get('#navbar').should('be.visible')//screenshot of particular elementcy.get('#navbar').screenshot();cy.viewport(320, 480)})})

These screenshots got captured inside the screenshots folder (in the plugins folder) within the project. The location where the screenshots got captured, can be modified by changing the Global configurations.

Execution Results

The output is given below −

When you try with test runner, the execution logs show that complete full-page screenshot captured and also screenshot a particular element.

CompletePage.png file created for full-page image.

Screenshot of a particular element

In the Test Runner Settings tab, the parameter screenshotOnRunFailure, is set to true value by default. Due to this, the screenshots are always captured for failure tests.

Also, the screenshotsFolder parameter has the value cypress/screenshots value. So, the screenshots are captured within the screenshots folder.

To disable the feature of capturing failed screenshots, we have to add the below values in the cypress.json file −

Cypress.Screenshot.defaults({
screenshotOnRunFailure: false
})

🎥📽 Videos

  • Cypress will record a video for each spec file when running tests during cypress run. Videos will not be recorded automatically during cypress open.
  • The videos will be stored in the videosFolder which is set to cypress/videos by default.
  • Once cypress run completes, Cypress will automatically compress the video so as reduce the file size. By default, Cypress will compress the video to a 32 CRF, but you can configure this using the videoCompression property.
  • Whenever you are using the –record flag to run your tests, the videos will be processed, compressed and uploaded to the Dashboard Service after every spec file runs, whether it is successful or not. If you want to change this behavior to only process videos in the case that the tests fail, you will need to set the videoUploadOnPasses configuration option to false.
  • Cypress will clear any existing video before a cypress run. If you don’t want to clear your videos folder before a run, you should set trashAssetsBeofreRuns to false.

When you try to execute Cypress test with CLI/Headless mode,

We get the corresponding video in the same location within the project.

To disable the video capture feature, we have to add the below value in the cypress.json file −

{
"video": false
}

👁 General tips

Now that we have an idea of ​​how to set up and execute tests with Cypress,

Let me share some guidelines and best practices, including test scripts, when writing tests and troubleshooting and independent investigations.

1. Ensure & Reset before test

This is true for both server and user settings. In the past, we set test requirements only in the test file. During that time, changes in settings are easier to monitor and have minimal UI effect.

However, it did not work well. As we develop more and more features, we ensure that the global before hook that server and other test data are in a predetermined state before being tested.

2. Isolate test Data

When using known test data, if there are feature changes, test cases make changes based on those change data, which is fine.

However, this caused a lot of pain to prepare the situation for the next test,

So it is better to manage specific common place keep test data that will avoid these painful situations.

3. Organize custom commands

Cypress custom commands are beneficial for automating workflows that are repeated in tests over and over again. You may use it to override or extend the behavior of built-in commands or to create a new one and take advantage of Cypress internals it comes with.

  • Do specific things as the name suggests.
  • Organize by folder and file
  • Standard naming convention by adding a prefix to denote something.
  • Example: cy.apiLogin(user)means user login is done directly via REST API, and cy.uiChangeMessageDisplaySetting(display)means changing the message display setting is done via UI workflow.
  • Make it discoverable through autocompletion and IntelliSense by adding type definitions for each custom command with comments for in-code documentation.

4. Avoid dependency of test block from another test block

In cases where a test file has several test blocks, each test block it() should be independent of each other. Appending exclusivity (.only()) or inclusivity (.skip) should work normally and should not rely on state generated from other tests.

This will make individual test verification faster and deterministic. Cypress has a section that explains it in detail.

5. Avoid unnecessary waiting

Cypress has a section that explains this in detail and lists workarounds when you find yourself needing it. Explicit wait makes the test flaky or longer than usual.

we can use cypress-wait-until under the hood which makes it easier to wait for a certain subject. and Use Timeout, with that, Cypress will wait for a definite time for the command to get executed.

6. Add comments for each action and verification

During pull request (PR) review, test scripts are normally reviewed by someone else.

Though the code is readable and comprehensible, the convention for adding comments helps everyone align on what is going on in the test script.

beforeEach(() => {cy.visit('https://example.cypress.io/commands/local-storage')})// Although local storage is automatically cleared// in between tests to maintain a clean state// sometimes we need to clear the local storage manuallyit('cy.clearLocalStorage() - clear all data in local storage', () => {
// https://on.cypress.io/clearlocalstoragecy.get('.ls-btn').click().should(() => {expect(localStorage.getItem('prop1')).to.eq('red')expect(localStorage.getItem('prop2')).to.eq('blue')expect(localStorage.getItem('prop3')).to.eq('magenta')})

6. Selectively run tests based on metadata

There are cases where we don’t need to run the entire test suite. The test environment, browser, or release version might not be supported by a certain test case, for example. Or simply, the written test is not stable enough for production.

Unfortunately, Cypress doesn’t have this capability. With that, we implemented a node script so we can run tests selectively. Start by adding metadata, as we call it, in a test file:

// Stage: @prod
// Group: @accessibility

Then, simply initiating node run_tests.js --stage='@prod' --group='@accessibility will run production tests for accessibility groups.

Undoubtedly, there are many of the best practices not mentioned here. But I hope these guidelines and best practices will help anyone reading this — especially for those who want to get started with Cyprus or set up an automated testing framework in general.

Most of basic testing behavior we’ve covered now……..

Let’s beiefly check something addition two componants

⭐️ Cypress Commands and Tasks

Cyprus custom command is useful for automating a recurring workflow during your tests.

Each command returns a Chainable type that allows to further interact with web application by making querying and assertions.

Commands are executed serially but are enqueued immediately in the same event loop tick.

This is why you cannot assign values and use them later with other commands. They simply will not exist.

// Bad example let value; 
cy.get(#input-field).invoke('val').then((input) => { value = input; });
cy.get(#another-input).type(value); // undefined

— — —

// Another bad example let value; 
cy.request({ method: 'GET', url: `/api/username`, }).then((result) => { value = result.body; }); cy.get(#input-field).type(value); // undefined

— — —

// Bad example let value; 
await cy.request({ method: 'GET', url: `/api/username`, }).then((result) => { value = result.body; }); cy.get(#input-field).type(value); // undefined

— — -

// Another bad example 
const value = await cy.get(#input-field).invoke('val'); cy.get(#input-field).type(value); // undefined
  • Commands are also not promises.
  • Until a previous command finishes, the next command doesn’t execute.
  • Cypress guarantees to run all of its commands deterministically and identically every time they are run.
  • Cypress serially runs these commands to create consistency and prevent flakey tests!

Commands always work best written serially as Cypress intended:

// Login with cy commands in test cy.get(#username).type('username'); cy.get(#password).type('password'); cy.get('button').contains('Login').click(); cy.get('body').contains('Welcome Home');

Custom commands for pre-defined workflows that might repeat in your tests. A good example is the previous login flow:

// Create a custom command 
Cypress.Commands.add('login', (username, pw) => { cy.get(#username).type('username'); cy.get(#password).type('password'); cy.get('button').contains('Login').click(); cy.get('body').contains('Welcome Home'); }); // Login with the command in your test cy.login('username', 'password');

🤖 Cypress Tasks

  • In contrast, a Cypress task is a function defined and executed in Node.
  • It allows your tests to “jump” from the Cypress browser process to the Cypress Node process.
  • A task is enqueued like a regular command via cy.task.
  • However, when the command takes its turn to execute, the backend process will run the code asynchronously.
  • Data is then returned to the browser serialized, and you can access the value via a .then(callback).

Tasks are best used for functionality such as seeding a database, communicating with your backend, storing state in Node, and running external processes. If you are looking to run a promise in Cypress, tasks are what you want.

// Custom task in plugins/index.js
import axios from 'axios';
module.exports = (on, config) => {
on('task', {
getVerificationCode(args: { userId: string; password: string; }) {
const result = await axios.request({
method: 'GET',
url: '/api/getVerification'
params: {
userId: args.userId
},
body: {
password: args.password
}
});
expect(result.status).to.eq(200); // Can make assertions too!
return result.data;
},
});
}
// Using custom task in test code
cy.task('getVerificationCode', { userId, password }).then((value) => {
// Can use the value in commands.
cy.get('#verification-field').type(value);
});

📜 Cypress Commands and Tasks

Hopefully, now you know the difference between Cypress commands and tasks. The key takeaways are:

  • If you need to run a promise or interact with your backend, go with a task. Remember: Commands are not promises.
  • If you are interacting with the DOM and making assertions, go with a command.
  • When a series of commands are repeated multiple times, create a custom command.

Thanks and I really hope this helps you.😊

--

--

Niluka Sripali Monnankulama
Many Minds

An IT professional with over 7+ years of experience. Member of the WSO2 Identity & Access Management Team.