DEV Community

Bushra Alam
Bushra Alam

Posted on • Updated on

Cypress - End to End Testing Framework

Cypress is the new kid in the front-end testing market which is gaining popularity amongst testers and developers alike. It can test anything that runs in a browser. Cypress is open source and its community is very active. Cypress tests are written in JavaScript but let that not scare you, in case you are unfamiliar with JavaScript, majority of the time you would be working with cypress commands which are pretty intuitive to work with.

But having an understanding of JavaScript would be really beneficial.. and so I have written a blog and created a YouTube series: JS for Testers to help you with that as well.

Cypress could be used for:

  • Unit Testing
  • Integration Testing
  • End-to-End Testing

These are some of the outstanding features that Cypress boasts of:
CypressFeatures

We will be discussing all of them as we explore cypress and reap the benefits.


Table of Contents


Cypress and Selenium

Cypress and Selenium exist to serve a very similar purpose but they are as different as they can be. If you are familiar with Selenium, you will be amazed by how simple setting up and getting things running in Cypress is.

Installation is hassle free, writing test is easy, tests are not flaky and tests run really fast.

All of this has been possible because Cypress isn't built on top of Selenium unlike most end-to-end testing tools. In fact, Cypress' architecture is very different from that of Selenium. Selenium executes remote commands through the network, whereas Cypress runs in the same run-loop as your application.

Back to table of contents


If you prefer to watch and learn, hop on to our Cypress series on Youtube
Subscribe to my Youtube Channel - QA Camp!


Cypress Installation

Cypress is all in one. Perhaps this will help you understand:

Cypress Installation

And cypress installs all of this with a single command:

CypressInstallCommand

Let's setup the IDE, create a project and install cypress.

Install IDE

It's a good idea to have an IDE. Visual Studio Code is the preferred IDE for Cypress projects.
You can download Visual Studio Code from here: https://code.visualstudio.com/download

Install Node.js

We need to install Node.js because we need to use npm and npx which get downloaded along with node.js.
You can download node.js from here: https://nodejs.org/en/download/
Once download is complete, launch and run through the installer.
To verify successful installation check the version:

   node -v
   npm -v

Create Project

  1. Create a project folder.
  2. In the terminal go to the project directory and run the following command:

    npm init
    

    This will create a package.json file inside your project folder.

  3. You can open this project in Visual Studio Code.

Install Cypress

As promised, Cypress will install in a single command:

   npm install cypress --save-dev

This will install Cypress desktop app and Cypress CLI. Cypress desktop app is GUI that can be used to run the test in browser. Cypress CLI helps you run cypress tests headlessly.

Launch Cypress

To launch the Cypress desktop app (GUI), run the following command:

   npx cypress open

Cypress GUI

The .js files that you see under examples folder are sample cypress tests downloaded to give you a jump start in the world of Cypress.

Back to table of contents


Commands

Now, I have been saying Cypress is easy but I don't want you to take my word for it. See for yourself. Read the below code line by line and see if it makes sense to you.

Cypress Basic Test

Cypress is intuitive because its English-like.

describe and it come from Mocha, which is a JavaScript test framework.

In the above code we are performing four operations that are most common and would be used in almost all the tests that you write. These are:

  1. Visit a page
  2. Query for an element
  3. Perform an action on the element
  4. Make an assertion

Now let's dive deep and explore the different commands cypress provides to perform these four tasks.

Back to table of contents


1. Launching / Navigating the application

visit

Purpose: Visit a URL.

Syntax:

  • cy.visit(url)
  • cy.visit(url,options)
  • cy.visit(options)

Examples:

// Visit a local server running on http://localhost:8000/
cy.visit('http://localhost:8000/')    

// Visit an application
cy.visit('https://www.acme.com/')  

// Visit an application and wait for 30 seconds for the application to launch
cy.visit('https://www.acme.com/', { timeout: 30000 })

// Submit a form
cy.visit({            
   url: 'http://localhost:3000/cgi-bin/newsletterSignup',
   method: 'POST',
   body: {
      name: 'George Burdell',
      email: 'burdell@microsoft.com'
   }
})

url

Purpose: Get the current URL of the active page.

Syntax:

  • cy.url()
  • cy.url(options)

Examples:

// Yield the current URL as a string
cy.url()

// verifies the curent url is equal to the given string
cy.url().should('eq', 'http://localhost:3000/cgi-bin/newsletterSignup')    

// verifies the current url includes the given string
cy.url().should('include', '/newsletterSignup')

go

Purpose: Navigate back or forward to the previous or next URL in the browser’s history.

Syntax:

  • cy.go(direction)
  • cy.go(direction, options)

Examples:

// Go back in browser’s history
cy.go('back')
cy.go(-1)

// Go forward in browser’s history
cy.go('forward')
cy.go(1)

reload

Purpose: Reload the page.

Syntax:

  • cy.reload()
  • cy.reload(forceReload)
  • cy.reload(options)
  • cy.reload(forceReload, options)

forceReload: Whether to reload the current page without using the cache. 'true' forces the reload without cache.

Examples:

// Reload the page as if the user clicked ‘Refresh’
cy.visit('http://localhost:3000/admin')
cy.get('#undo-btn').click().should('not.be.visible')
cy.reload()
cy.get('#undo-btn').click().should('not.be.visible')

// Reload the page without using the cache
cy.visit('http://localhost:3000/admin')
cy.reload(true)

Back to table of contents


2. Accessing UI Elements

get

Purpose: Get one or more DOM elements.

Syntax:

  • cy.get(selector)
  • cy.get(alias)
  • cy.get(selector, options)
  • cy.get(alias, options)

Selector: property of an element like id, class etc to filter matching DOM elements.
Alias: giving DOM element a name by which it could be referred later. Defined using the .as() command and referenced with the @ character and the name of the alias.

Examples:

// Find the dropdown-menu with the given class name
cy.get('.dropdown-menu')

// Find element(s) with the given data attribute
cy.get('[data-test-id="test-example"]')

// Create and use an alias
cy.get('button[type=submit]').as('submitBtn')
//...hack hack hack...
cy.get('@submitBtn')     // later retrieve the submitBtn

contains

Purpose: Get the DOM element containing the text.

Syntax:

  • .contains(content)
  • .contains(content, options)
  • .contains(selector, content)
  • .contains(selector, content, options)

Things to note:

  • contains() could start a series of commands or could be chained to an existing series of command
  • content could be: String, Number, RegExp

Examples:

<ul>
    <li>apples</li>
    <li>oranges</li>
    <li>bananas</li>
</ul>
// Find the first element containing some text
cy.contains('apples')       // yields <li>apples</li>

// Find the first element with text matching the regular expression
cy.contains(/^b\w+/)       // yields <li>bananas</li>

// Specify a selector to return a specific element
cy.contains('ul', 'apples')       // yields <ul>...</ul>

// When chained to an existing series of commands
cy.get('#checkout-container').contains('Buy Now')
//This will query inside of the <#checkout-container> element.

Access element by index

You can get the first, last or an element at a specific index in an array of elements using first(), last() and eq() respectively.

Examples:

<ul>
    <li>one</li>
    <li>two</li>
    <li>three</li>
    <li>four</li>
    <li>five</li>
</ul>
// Get the first element
cy.get('li').first()      // yield <li>one</li>

// Get the last element
cy.get('li').last()      // yield <li>five</li>

// Get the second element
cy.get('li').eq(1)      // yield <li>two</li>

// Get the second last element
cy.get('li').eq(-2)      // yields <li>four</li>

Access element by relation

You can access parents, children, siblings of an element.

parent- Get the parent DOM element (single level up) of a set of DOM elements.

parents- Get the parent DOM elements (multiple level up) of a set of DOM elements.

parentsUntil- Get all ancestors of each DOM element in a set of matched DOM elements up to, but not including, the element provided.

children- Get the children of each DOM element within a set of DOM elements.

siblings- Get sibling DOM elements.

prev- Get the immediately preceding sibling of each element in a set of the elements.

prevAll- Get all previous siblings of each DOM element in a set of matched DOM elements.

prevUntil- Get all previous siblings of each DOM element in a set of matched DOM elements up to, but not including, the element provided.

next- Get the immediately following sibling of each DOM element within a set of DOM elements.

nextAll- Get all following siblings of each DOM element in a set of matched DOM elements.

nextUntil- Get all following siblings of each DOM element in a set of matched DOM elements up to, but not including, the element provided.

Examples:

<ul class='main-nav'>
    <li>Overview</li>
    <li>Getting started
        <ul class='sub-nav'>
            <li>Install</li>
            <li class='active'>Build</li>
            <li>Test</li>
        </ul>
    </li>
</ul>
// parent 
cy.get('li.active').parent()           // yields .sub-nav

// parents
cy.get('li.active').parents()           // yields [.sub-nav, li, .main-nav]

// parentsUntil
cy.get('li.active').parentsUntil('.main-nav')           // yields [.sub-nav, li]

// children
cy.get('ul.sub-nav').children()              // yields [<li>Install</li>,
                                             //         <li class='active'>Build</li>,
                                             //         <li>Test</li>]

cy.get('ul.sub-nav').children('.active')      // yields [<li class='active'>Build</li>]

// siblings
cy.get('.active').siblings()              // yields [<li>Install</li>, <li>Test</li>]

cy.get('li').siblings('.active')          // yields [<li class='active'>Build</li>]
<ul>
    <li id="fruits" class="header">Fruits</li>
    <li>apples</li>
    <li>oranges</li>
    <li>bananas</li>
    <li id="veggies" class="header">Vegetables</li>
    <li>cucumbers</li>
    <li>carrots</li>
    <li>corn</li>
    <li id="nuts" class="header">Nuts</li>
    <li>walnuts</li>
    <li>cashews</li>
    <li>almonds</li>
</ul>
// prev
cy.get('#veggies').prev()         // yields <li>bananas</li>
cy.get('li').prev('#veggies')     // yields <li id="veggies" class="header">Vegetables</li>    

// prevAll
cy.get('#veggies').prevAll()    // yields [<li>apples</li>, <li>oranges</li>, <li>bananas</li>]
cy.get('li').prevAll('#veggies')    // yields <li id="veggies" class="header">Vegetables</li>

// prevUntil
cy.get('#nuts').prevUntil('#veggies')      // yields [<li>cucumbers</li>
                                           // yields       <li>carrots</li>, <li>corn</li>]

 // next
cy.get('#veggies').next()         // yields <li>cucumbers</li>
cy.get('li').next('#veggies')     //        <li id="veggies" class="header">Vegetables</li>    

// nextAll
cy.get('#nuts').nextAll()    // yields [<li>walnuts</li>, <li>cashews</li>, <li>almonds</li>]
cy.get('li').nextAll('#nuts')    // yields <li id="nuts" class="header">Nuts</li>

// prevUntil
cy.get('#veggies').prevUntil('#nuts')      // yields [<li>cucumbers</li>,                                           
                                           //         <li>carrots</li>, <li>corn</li>]

Access element by position

within- Scopes all subsequent cy commands to within this element. Useful when working within a particular group of elements such as a <form>.

root- Get the root DOM element.

Examples:

<form>
    <input name="email" type="email">
    <input name="password" type="password">
    <button type="submit">Login</button>
</form>
cy.get('form').within(($form) => {
    // cy.get() will only search for elements within form,
    // not within the entire document
    cy.get('input[name="email"]').type('john.doe@email.com')
    cy.get('input[name="password"]').type('password')
    cy.root().submit()   // submits the form yielded from 'within'
})

Back to table of contents


3. Actions on elements

click

Purpose: Click a DOM element.

Syntax:

  • .click()
  • .click(options)
  • .click(position)
  • .click(position, options)
  • .click(x, y)
  • .click(x, y, options)

Examples:

// Click on button
cy.get('button').click() 

// Click on first el containing 'Welcome'
cy.contains('Welcome').click() 

// Click the top right corner of the button
cy.get('button').click('topRight')

// Specify explicit coordinates relative to the top left corner
cy.get('button').click(15, 40)

// Force a click regardless of its actionable state
// https://docs.cypress.io/guides/core-concepts/interacting-with-elements.html#Forcing
cy.get('button').click({ force: true })

// Click all buttons found on the page
cy.get('button').click({ multiple: true })

dblclick

Purpose: Double-click a DOM element.

Syntax:

  • .dblclick()
  • .dblclick(options)

Examples:

// Double click on button
cy.get('button').dblclick() 

// Double click on first el containing 'Welcome'
cy.contains('Welcome').dblclick()

type

Purpose: Type into a DOM element.

Syntax:

  • .type(text)
  • .type(text, options)

Examples:

// Type 'Hello, World' into the 'input'
cy.get('input').type('Hello, World')

// Type a key combination
cy.get('input').type('{shift}{alt}Q')     
// this is the same as a user holding down SHIFT and ALT, then pressing Q

// Special characters sequences
cy.get('#code-input').type('function (num) {return num * num;}', 
                           { parseSpecialCharSequences: false })   
// will not escape { } characters

// Implicit form submission behaviour
cy.get('#username').type('bob@burgers.com')
cy.get('#password').type('password123{enter}')

clear

Purpose: Clear the value of an input or textarea.
It is an alias for .type({selectall}{backspace})

Syntax:

  • .clear()
  • .clear(options)

Examples:

// Clear text input
cy.get('[type="text"]').clear()

// Clear the input and type a new value
cy.get('textarea').clear().type('Hello, World')

check

Purpose: Check checkbox(es) or radio(s). The element must be an <input> with type checkbox or radio.

Syntax:

  • .check()
  • .check(value)
  • .check(values)
  • .check(options)
  • .check(value, options)
  • .check(values, options)

Examples:

// Check all checkboxes
cy.get('[type="checkbox"]').check()

// Check the first checkbox
cy.get('[type="checkbox"]').first().check()

// Select all radios
cy.get('[type="radio"]').check()

// Select the radio with the value of ‘US’
cy.get('[type="radio"]').check('US')

// Check the checkboxes with the values ‘ga’ and ‘ca’
cy.get('[type="checkbox"]').check(['ga', 'ca'])

uncheck

Purpose: Uncheck checkbox(es) or radio(s). The element must be an <input> with type checkbox or radio.

Syntax:

  • .uncheck()
  • .uncheck(value)
  • .uncheck(values)
  • .uncheck(options)
  • .uncheck(value, options)
  • .uncheck(values, options)

Examples:

// Uncheck all checkboxes
cy.get('[type="checkbox"]').uncheck()

// Uncheck the first checkbox
cy.get('[type="checkbox"]').first().uncheck()

// Uncheck the checkboxes with the values ‘ga’ and ‘ca’
cy.get('[type="checkbox"]').uncheck(['ga', 'ca'])

select

Purpose: Select an <option> within a <select>.

Syntax:

  • .select(value)
  • .select(values)
  • .select(value, options)
  • .select(values, options)

Examples:

<select multiple>
    <option value="456">apples</option>
    <option value="457">oranges</option>
    <option value="458">bananas</option>
</select>
// Select the '456' option
cy.get('select').select('456')

// Select the options with the texts “apples” and “bananas”
cy.get('select').select(['apples', 'bananas'])

Back to table of contents


4. Assertions

Before we dive in and see what different cammads are there for assertions, there is a good news - many commands have a default, built-in assertion, or rather have requirements that may cause it to fail without needing an explicit assertion you’ve added.

Here are some examples:

  • cy.visit() expects the page to send text/html content with a 200 status code.
  • cy.get() expects the element to eventually exist in the DOM.
  • cy.contains() expects the element with content to eventually exist in the DOM.
  • .click() expects the element to eventually be in an actionable state.

There are two ways to write assertions in Cypress:

  1. Implicit Subjects: Using .should() and .and()
  2. Explicit Subjects: Using expect

Points to note:

  • Cypress bundles Chai, Chai-jQuery, and Sinon-Chai to provide built-in assertions. You can see a comprehensive list of them here.
  • Using .should() and .and() is the preferred way of making assertions in Cypress.
  • Assertions are automatically retried until they pass or time out.
  • In most cases, .should() and .and() yields the same subject it was given from the previous command. However, some chainers change the subject. The chainers that come from Chai or Chai-jQuery will always document what they return and that will help you know what assertions change the subject and which keep it the same.

should

Purpose: Create an assertion.

Syntax:

  • .should(chainers)
  • .should(chainers, value)
  • .should(chainers, method, value)
  • .should(callbackFn)

Examples:

cy.get('nav').should('be.visible')

cy.get('nav').should('be.disabled')

cy.get('nav').should('have.class', 'active')

cy.get('nav').should('not.have.id', 'Dashbaord')

cy.get('nav').should('have.attr', 'href', '/users')

cy.get('nav').children().should('have.length', 8)

Callback Function:
Say, we have to confirm the text inside each of the three items that appear. We can have 3 commands for 3 assertions:

cy.get('#app div:nth(0)').should('contain', 'first child')  
cy.get('#app div:nth(1)').should('contain', 'second child')  
cy.get('#app div:nth(2)').should('contain', 'third child')

This could be done in a single assertion:

cy.get('#app div')
   .should(($div) => {
      expect($div.eq(0)).to.contain('first child')
      expect($div.eq(1)).to.contain('second child')
      expect($div.eq(2)).to.contain('third child')
   })

and

Purpose: Create an assertion. An alias of .should()

Syntax:

  • .and(chainers)
  • .and(chainers, value)
  • .and(chainers, method, value)
  • .and(callbackFn)

Examples:

cy.get('nav')
    .should('be.visible')
    .and('be.disabled')
    .and('have.class', 'active')

cy.get('nav')
    .should('not.have.id', 'Dashbaord')
    .and('have.attr', 'href', '/users')

Back to table of contents


Executing Test

To run your tests you have got following options:

  • Test can be executed from GUI and from Command Line
  • Test can be executed in browser and in headless mode Also, the test runs automatically when you make some changes and save it. This comes handy when you are writing the test and want to execute it often to check. This is called 'Real time reloads'.

1. Running test from GUI

Running tests from GUI is easy.
First, let's launch the Cypress GUI using the following command:

npx cypress open

This is how it looks:

Cypress GUI

All the .js files are the test files.

To run any test simply click on it. Cypress Test Runner will open and the test will run in it.

Cypress Test Runner

This test runner is very intuitive and very powerful. The Command Log lists all the commands that ran and when you tower over them, the App preview section would should you the application state when the command was executed. This is the much loved 'Time Travel' feature that cypress provides out of the box.

2. Running test from Command Line

Using command line, test can be execute in browser as well as in headless mode.

2.1. Headless Mode

Using command line, by default, tests are run in headless mode. Cypress wil record videos when running headlessly.

Run a single spec file

npx cypress run --spec "cypress/integration/examples/actions.spec.js"

Run multiple spec files

npx cypress run --spec "cypress/integration/examples/actions.spec.js,
cypress/integration/examples/files.spec.js"

Run all spec files in a folder

npx cypress run --spec "cypress/integration/examples/**/*"

Run all the spec files in the project

npx cypress run

2.2. In Browser

To execute tests in browser using command line, you simply need to add '--headed' (for electron browser) or '--browser browserName' for some other browser. The “browser” argument can be set to “chrome”, “canary”, “chromium”, or “electron” to launch a browser detected on your system. Cypress will attempt to automatically find the installed browser for you.

# for electron browser
npx cypress run --headed

# for specifying your prefered browser: chrome, canary, chromium, or electron
cypress run --browser chrome

Back to table of contents


If you prefer to watch and learn, hop on to our Cypress series on Youtube
Subscribe to my Youtube Channel - QA Camp!


You can find a sample project here: https://github.com/bushralam/Cypress


Top comments (17)

Collapse
 
benjaminsmiths profile image
Ben Smith

I use Cypress now for all my projects and combined with Typescript it’s the only testing I do. No unit or component testing as I find this combination catch’s the majority of issues and it’s it’s fast to develop and maintain.

Good, through run though thanks for sharing

Collapse
 
jaredmeakin profile image
Jared Meakin

For those using cypress for E2E testing do you simply mock all your API calls?

Has anyone found a testing strategy similar to how RoR does it? Specifically, a test database that gets reset after every run?

I just hate mocking every single endpoint in JS projects that heavily rely on multiple microservices. It becomes an exercise in tedium.

Collapse
 
dmitry profile image
Dmitry Polushkin • Edited

In rails you normally can use FactoryBot through middleware on server side and commands to clean/setup specs in cypress. Doing calls to your real app makes everything updated, no need to write mocks, especially it can be a problem for websocket apps (not enough tools for now to mock websocket). So in other words you are doing integration testing, which is higher level, with easiness of unit testing - which is absolutely great and fast enough.

One of the solutions: github.com/shakacode/cypress-on-rails

Collapse
 
yucer profile image
yucer

I do also like to test the middle-ware directly.

It forces you to keep all the logic away from client code.

If someone implements part of the business logic with a javascript in the browser ... that is not secure and might not end well in some cases.

My opinion is that the browser code should be used only for usability requirements. And those use cases need also test, so +1 for cypress for helping on that.

Nevertheless the middleware / APIs should be tested directly, also for preventing those invalid input cases that the interface might never send, but that might break everything.

Collapse
 
yucer profile image
yucer

Do you define all your interfaces by hand ?

In the case of REST endpoints, the same code generator (Ex: openapi-generator) that generate the endpoints from the API specs (Ex: OpenAPI) might generate the client library with basic unit tests.

Collapse
 
jaredanson profile image
Jared

I recommend using Cypress Testing Library to extend the commands cypress can use. I very rarely use cy.get and instead use the commands that Cypress Testing Library gives you.

You can do things like cy.findByPlaceholderText, cy.findByText, or cy.findByTestId for hard to grab elements. And retries are built in if I remember correctly.

If you have more than one element that matches you can just add All after the find, for example cy.findAllByText and if you want to grab the first you'd do cy.findAllByText("hello button").eq(0).click()

testing-library.com/docs/cypress-t...

Collapse
 
nutankm profile image
Nutan Kumar

Hi Bushra,
Thank you for the very good article. I am new to cypress and javascript. please help me with below code. trying to get latest string value and compare it in while loop. but in while loop it keeps picking up old string value.
Thanking you in advance. Really appreciate your help.

allOpenCases.getCurrentSystemStep().then(($currentStep)=>
{
return $currentStep.text()

}).as('CSS')

cy.get('@CSS').then((cStep)=>{
let count = 1
while(cStep!==targetSystemStep && count < 50)
{
allOpenCases.getSearchButton().click()
cy.wait(3000)

allOpenCases.getCurrentSystemStep().then(function($currentStep)
{
let cStep = $currentStep.text() //latest text

})

count ++
}

})

Collapse
 
asherirving profile image
Asher Irving

Uh, using Cypress is such a bad experience.
I tried it because a colleague heard about the hype.
All your posts seem to be related to Cypress.
Why is that?

Collapse
 
chautelly profile image
John

I just finished figuring out how use Cypress combined with XState for auto-generated model based tests. I also wanted to mock API responses a little differently than Cypress suppports out-of-the-box currently but I was able to make it work. I really like that I can review the test at each stage with Cypress. Anyways I posted about my experience here: dev.to/rjdestigter/model-based-ui-...

Collapse
 
sunnysachdeva99 profile image
sunnysachdeva99

Agree Cypress is evolving however it can be fit everywhere. Not sure but it does not provide a free hand in using all the JS capabilities like in wdio. working with nested iframes is a pain. I don't want to change/manipulate the dom just to handle the new window opened by a link.

Also, on the architecture, I am not able to launch the url hubspot.com/ in Cypress but in wdio or playwright that's not a issue

Collapse
 
albertomontalesi profile image
AlbertoM

We've been using Cypress for now one and a half year. It's very good and easy to use

Collapse
 
chema profile image
José María CL

Awesome! Thank you so much for sharing. I'm trying to implement Cypress in our React App.

There are just few things I don't get... should we use our real graphql service (in my case) to test the data or it's better to mock the graphql requests??

If we should mock the service data.. it's not complicated to maintain the tests every time the requirements changes? Thanks in advance!

Collapse
 
jonsy13 profile image
Vedant Shrotria

It was an awesome description as well as a great read through tutorial.
Thanks Bushra Alam for such a simple got through post.
Need an advise for E2E Testing, if we have a frontend and backend both.
For using Cypress on frontend ,i will be needing my backend server running as well.So, how to manage frontend,backend and Cypress in CI?
Any advise is appreciated.
Thanks Again!!

Collapse
 
jaxon profile image
Ryan J

My project is trying to get automated UI testing setup. I might try this out. Thanks for the introduction.

Collapse
 
lauriy profile image
Lauri Elias

I heard TestCafe is in vogue now.

Collapse
 
monu180 profile image
Monu Kumar

thank you for sharing nice blog

Collapse
 
codeur47 profile image
YORO Ange Carmel

Very useful article. Thank Bushra