This is not a Selenium bash but a specific case of using it for a time and finding Cypress could be better in some situations. Selenium has some great qualities:
We moved from Ruby to Node.js for our UI tests. We spent a couple of months evaluating JS frameworks related to UI testing and Selenium specifically. Some of our evaluations:
We opted to write extensions for what we needed and build out our own framework since we wanted to do mobile testing and use WD.js which was used by Appium at the time.
The overarching goal is to have the entire team write UI Tests.
Typically you’ll have an Sdet (engineer specializing in quality) along with developers building features. Our goal was to have all developers write UI tests not just the sdet.
We focused on getting more developers to maintain and write tests. They would do it if forced but it wasn’t something they found value in. We spent most of our time figuring out sleeps/waits and even when something was easy it took some effort to find why the test broke.
We struggled to get wide adoption in teams for writing UI tests. Therefore scaling with more developers was not going to work.
Even with all of the extensions, improvements and wrappers we built into our custom framework developer interest remains low and maintenance remains high.
Some of the problems we encountered:
Our team piloted a project using Cypress to see if we could overcome some of the challenges mentioned above. The goal of this post isn’t to convince you not to use Selenium but to describe some of the things we found useful with Cypress that may help overcome some objections you might receive in trying to scale and strengthen your UI testing.
Cypress provides detailed guides to get started but we’ll highlight a few steps below to help summarize.
Cypress can be easily installed with npm. Create a directory for your Cypress solution and install Cypress.
$ npm install cypress --save-dev
Everything you need to start writing tests with Cypress will be installed for you within seconds. Once the installation has finished, open Cypress (note: you will use npx since the Cypress node module has been installed within the current directory).
$ npx cypress open
The Cypress Test Runner will load with a pre-loaded set of tests which run against a Cypress example application.
In addition to the detailed guides provided on the Cypress website, Cypress provides a search capability on their documentation site helping to find answers quicker.
Similar to Selenium, Cypress is also open source which has allowed us to look at their code to find how it works and provided insight into issues others have run into providing potential workarounds until the issue can be resolved properly. What sets it apart from Selenium is that all of the source code you need is in one place. There are no other drivers or tools in other repos you may need to go hunt for.
A dedicated Cypress room on Gitter has proved valuable to find information as well. Cypress team members actively respond to questions there and the search functionality provides history of past questions and answers. There are several Selenium resources on Gitter as well but the abundance of rooms can make it noisier to find the right answers.
Consider the following code (taken from cypress.io):
describe('Post Resource', function() {
it('Creating a New Post', function() {
cy.visit('/posts/new')
cy.get('input.post-title')
.type('My First Post')
cy.get('input.post-body')
.type('Hello, world!')
cy.contains('Submit')
.click()
cy.url()
.should('include', '/posts/my-first-post')
cy.get('h1')
.should('contain', 'My First Post')
})
})
Notice how easy and simple this code is to understand?!?! The time it takes for someone to become familiar with how to write Cypress tests is minimal. The learning curve is drastically reduced by:
.visit()
, .get()
and .click()
id
or class
since Cypress uses jQuery to get elementsOne of the more impressive features of Cypress is the Test Runner. Inside the Test Runner, Cypress offers a Selector Playground that can be used to generate selectors for your tests.
Gone are the days of inspecting elements or hunting through page source to generate a selector. Cypress defines a
strategy of finding the best unique selector and provides the command needed within your test code. In the above
example, Cypress has determined the best selector for the ‘Add to this page’ button is .pivot-list > .btn
. The
strategy for selecting elements is customizable. The Selector Playground will also let you free-form type selectors
and show you how many elements match that selector so you can have confidence knowing you’ve created a unique
selector for your element.
Another feature of the Test Runner is the Command Log which details every step of the test.
On the left side a list of commands will show exactly what request was made making it easy to debug when problems arise. On the GoDaddy GoCentral team, we use a testing environment to verify new features before deploying to our production environment where customers interact with our site. The testing environment has many dependencies on services maintained by teams throughout the company and sometimes one of those services becomes unavailable. In the example below you can see a call to one of our APIs that is returning a 404 response. This allows us to debug our test and inspect the request and response made to determine if our test is working properly.
As mentioned in the previous section, flaky or slow APIs can drag down the efficiency of UI testing. When a service doesn’t return as expected, it’s hard to verify UI functionality. Cypress introduces mocking within your test code to account for this scenario allowing you to have more resilient UI tests.
One instance where we use this on the GoDaddy GoCentral team is when calls are made to our billing API. We have a potential race condition when making calls to our billing API due to the fast nature of Cypress tests.
To avoid this race condition, we can simulate the call to the billing API using the .route()
method Cypress provides
as shown below.
cy.server();
cy.route({
method: 'POST',
url: 'api/v2/accounts/*/enableautorenew',
status: 204,
response: {
enabled: true
}
});
In the above code, we capture any requests that match the url provided and return a 204 response with a response body. This helps avoid any issue that may occur with the service being called and potentially speeds up the test by avoiding making the actual call to the service. We can also guarantee that our test should never fail because of this race condition. We also simulated the JSON response received from the endpoint. This can be useful when wanting to test various responses without having to setup test data before each test.
Another useful example of mocking responses is to verify UI functionality when things go bad. With Cypress, it’s easy to simulate what an error might look like to a customer when a service outage occurs.
cy.server();
cy.route({
method: 'POST',
url: 'api/v2/accounts/*/enableautorenew',
status: 500,
response: {}
});
With the above code, we can simulate our endpoint returning a 500 response to verify the customer sees the appropriate error message on their screen
cy.visit()
it will look like your tests are
reloading. It also will rerun any commands issued (in our case, shopper setup) all over again. Use baseUrl to avoid this.