In This Article

Just a few years ago Selenium was the go-to tool for creating automated functional tests for your web application. Now a new automation framework called Cypress has made its way onto the scene and has surpassed Selenium in downloads for JavaScript usage. We will walk you through the advantages of Cypress and then let you know whether we believe Selenium is indeed dead.

Introducing Cypress

Cypress is an automation-as-code tool using JavaScript. One of its major advantages is that one download gives you all the browser engines, frameworks and assertion libraries needed to get started with automation. With Selenium, you must separately download the Selenium engine, chromedriver, multiple test frameworks and assertion libraries. Cypress features easier debugging and extensive documentation on how to best use their API. Test runs are more reliable, and its code is easier to read. We were also impressed with the size of the active community for Cypress and how many useful NPM packages and add-ons exist. Cypress is an open-source tool, meaning it can be downloaded for free. They do offer a licensed version that adds email support and a test summary dashboard, which might be useful to some but is certainly not a must-have. 

The Cypress Test Runner, included in the free version, has a unique, user-friendly, and interactive interface that displays helpful information about your tests. The Test Runner gives you a visual step-by-step breakdown of each test as it executes which remains on-screen for diagnosis after your test run is complete. The Test Runner automatically re-runs your tests every time they are modified and saved. You can also see before and after statuses of your application when doing actions like cy.click(). Cypress automatically produces screenshots and videos for each of your test runs. With Selenium, screenshots and videos require extra work to set up or possibly a third-party paid tool.

Finding elements easily 

One of the advantages of the Cypress library is that you do not have to identify the element to use the element in the test. Using the cy.contains() command, you can select plain or button text on the webpage and Cypress will find that text in the test without you having to inspect the DOM and retrieve the element from the HTML. Elements often get changed with code updates to a web page causing tests to break, whereas with retrieving the text on the page the tests will not break unless the page text itself is changed. An example of how this looks with Cypress is below:

cy.contains('World Wide Technology').should('be.visible').click() 

A slight limitation to this is that if the text is repeated multiple times on the page, the command will fail, or it will take the first element. Cypress has a solution for this by allowing the element type to be added to the text such as with the H3 below. 

cy.contains('h3', 'World Wide Technology').should('be.visible').click() 

If you still have multiple text of the same element type, Cypress will take all of the elements found in the cy.contains() text and turn those elements into an array based on the order found in the HTML. In the example below, you can choose which element to select by using the .eq(#), the 1 is the second element with text of H3 World Wide Technology on the page. 

cy.contains('h3', 'World Wide Technology').eq(1).should('be.visible').click() 

This is not an option with Selenium. You still must retrieve the element by an attribute like id and are not able to simply utilize the text from the webpage.

driver.findElement(By.id('page-title')).should('be.visible').click() 

Even if you want to select by text in Selenium, you still have to inspect the DOM and find what type of element the text is affiliated with.

driver.findElement(By.Xpath("//h3[text()='World Wide Technology'")).should('be.visible').click()

If your code does not have id attributes this can be even more challenging. You would have to find the element by XPath which could result in having to go through multiple layers of the HTML. This also makes the automation code very fragile as one simple update to a web page's structure can break all the tests that interact with that page. 

driver.findElement(By.Xpath("(//*[contains (@class, 'titlebar-container')]//*[contains (@class, 'page-title')])").should('be.visible').click() 

You can see how using cy.contains() can be very advantageous.

The downfalls of Selenium

Selenium test runs can be slower and more brittle than Cypress. There is a higher likelihood of failures even when there is nothing wrong with your application. It can take a highly experienced automation engineer to get around the brittle nature of Selenium. Promises are handled less gracefully thus the code is harder to read because of the reliance on async/await and .then. The order of execution within a test file is alphabetical meaning the last test could run first if it starts with 'A'. Creating independent tests is the proper way to avoid that but we realize some may not follow that discipline. With Cypress, the test execution is from top to bottom of the test file in the order tests are written. We mentioned how Selenium is more challenging and time-consuming to set up. Another pitfall is that the community support and documentation around Selenium is becoming stale. We recently used Selenium for a client project here at WWT and were astonished by how many of the online support articles were outdated.

Does that mean Selenium is dead?

No, we do not believe Selenium is dead. In fact, while we love the advantages of Cypress, there are many times where Selenium may be more appropriate because of things Cypress cannot do. Selenium can handle iFrames whereas with Cypress it is very challenging. We recently completed a test automation project for a client where we had to shift from Cypress to Selenium because the client's third-party authentication took over the Cypress Test Runner and could not stay within the iFrame. Selenium can switch between browser tabs and handle external links, something Cypress cannot do. Selenium can also be written in multiple languages like Java, C# and Python in addition to JavaScript whereas Cypress is only JavaScript or Typescript. You can use Selenium to move the mouse to specific coordinates within the browser. It also simulates keypresses and clicks whereas Cypress does not provide true user simulations for these actions.  

One more advantage is that Selenium has add-ons like Winium where you can access the desktop in your browser tests. On the Insitu project, WWT created a two-hundred test automation suite for the client using Selenium WebDriver. One of the tests focused on a drag-and-drop feature. We were able to use Winium to control the desktop, grab a file, drag it into the web browser and verify the file was uploaded to the webpage, all in one test. Cypress does not have this capability with the desktop.

Authentication -- a common issue

Authentication can be challenging with automation tools in general. The larger the automation suite, the more frustrating it can become. With Selenium and Cypress, you must authenticate for each class or test file, adding considerable time to your test run assuming you authenticate several or dozens of times for each test user. For WWT's corporate website automation suite, where we have almost two-hundred Cypress tests, we wanted to create a way to only authenticate once per test user for each test run. WWT Senior Developer Tom Conway created a custom solution that includes running the authentication process one time for each of our test users at the start of the test run, storing the cookies, then setting the cookies in the before clause of each test file. This keeps the test users authenticated throughout the entire test run.   

How we solved the cookie problem

Below is the code we created for setting cookies. We start with a function to generate the cookies from the login script. 

describe('Session Login', () => {
  if (Cypress.env('generatingCookies')) {
    Cypress.env('testUserCredentials').map(credential => {
      it('Generates Session Cookies', () => {
        cy.task('readFileMaybe', `cookies/${Cypress.env('testEnvironment')}_${credential.username}Cookies.json`).then((jsonOrNull) => {
          if (jsonOrNull === null) {
            cy.visit('/')
            cy.get(cookiesModal.acceptButton).click()
            cy.get('a[id=wwt-user-login]').click()
            cy.get('input[name=username]').type(credential.username)
            cy.get('button[aria-label="next"]').click().wait(2000)
            cy.get('body').then(($body) => {
              if ($body.find('button[aria-label="Log In With Password"]').length) {
                cy.get('button[aria-label="Log In With Password"]').click().wait(2000)
                }
              })
            cy.get('input[name=password]').type(credential.password)
            cy.get('input[type=submit]').click()
            cy.wait(10000)
            cy.getCookies().then((cookies) => {
              cy.writeFile(`cookies/${Cypress.env('testEnvironment')}_${credential.username}Cookies.json`, cookies, {
                encoding: 'utf8'
              })
            })
          }
        })
      })
    })
  }
})

Next, we have a custom Cypress command to set the cookies that were generated in the first code snippet. 

Cypress.Commands.add('setAuthCookies', (username) => {
  clearCookies()
  cy.readFile(`cookies/${Cypress.env('testEnvironment')}_${username}Cookies.json`, 'utf-8', { timeout: 60000 }).then(cookies => {
    Cypress.Cookies.defaults({
      preserve: Object.values(cookies).map(cookie => cookie.name)
    })
    cookies.forEach((cookie) => {
      const { name, value, path, httpOnly, secure, domain } = cookie
      cy.setCookie(name, value, { path, httpOnly, secure, domain })
    })
  })
})

Lastly, we use the set cookies command in the before section of the test file to authenticate without having to log in again. 

describe('A logged in user viewing an Article Page', function() {
	before(function() {
    	cy.setAuthCookies(Cypress.env('cypressExternalUserName'))
  		cy.visit('/article/leverage-iaas-paas-saas-with-netapp-cloud-data-services')
  		cy.wait(1000)
	})

	it('Can comment on an Article', function(){	 	
		cy.get(articleHeader.commentButton).eq(1).click().wait(500)
		commentCounter = Math.floor(Math.random() * 10000)
		cy.get('textarea').eq(0).clear().type(`Its a beautiful day in the neighborhood ${commentCounter}`)
		cy.get(commentArea.newCommentSection).eq(0).get('button[type=button]').eq(0).click()	
		cy.wait(1000)	 	
		cy.contains(`Its a beautiful day in the neighborhood ${commentCounter}`).should('be.visible')	
	})

Conclusion

Selenium still has its place because there could be times when Cypress may not be suitable for your automation needs. We believe that Cypress is a great tool and is worth exploring if you have not experimented with it yet. You will see how easy it is to get started writing tests and the benefit of their Test Runner. Hopefully, you can utilize our cookie and authentication code to cut some time from your test run the same way it did for us.