Aaron
January 20, 2020
Cypress Testing and the practices we have learned
Introduction
When nerd.vision was built, the company needed a way to be able to determine the reliability and stability of the front end being built in Angular. After some research we adopted cypress.io with open arms. It offered everything we needed to be able to ensure that the UI (User interface) was letting us know when things were breaking.
At first the power of cypress was not necessarily clear to the team. We understood the purpose of what it is that this software was trying to achieve but we didn't see the benefits until we started to learn more about its intricacies and details.
Cypress.io is a front end testing framework designed to provide End to End testing with a visual interface. This way you can see what is happening when something in the UI is going wrong. It records snapshots of when tests break, can be integrated into pipelines and even provides the ability to run the test suite simulating different screen resolutions or devices.
Practise makes perfect
So what did cypress offer for us? It provides powerful tools in the front end that really took the hard work out of UI testing for the team. I won't talk about the very basics of general testing principles or cypress as that is covered extensively in the documentation over at docs.cypress.io, however I want to discuss some of the parts that provided much needed functionality and support in testing.
cy.server
Calling cy.server at the start of your test suite or test will start up an instance of cypress server allowing you to route responses through it. This allows you to intercept, stub and assert responses and requests are correct when testing. What is great that we began to learn over time is that you can configure this with arguments that bring a lot of interesting results
cy.server({
delay : 0,
whitelist : (xhr) => {
return xhr.method === 'GET'
&& /^((?!github)(?!gitlab)(?!bitbucket).)*\.(jsx?|html|css)(\?.*)?$/.test(xhr.url);
}
});
Providing these arguments as part of the cy.server allows us to whitelist requests coming from github/gitlab and bitbucket. This proved essential when the response being returned contained a JavaScript format and was causing errors within the test suite.
Custom commands
Cypress also provides this incredible ability to create custom commands that can be used anywhere within any test as shown in the example below
Cypress.Commands.add('openFile', (file) => {
cy.get('#source-management-tree')
.within(() => {
cy.contains(file)
.should('be.visible')
.click({force: true});
});
});
This simple command would open a file given to the command within nerd.vision during a test. This meant that anywhere where a file was needed to be opened as part of the set up or test, we could call this code stored in a centralized commands.js file using the code below.
cy.openFile('exampleFile.js');
This is great when the test suite starts to get more complex and bigger so that repeated functionality can be stored away in these custom commands to be used anywhere. One aspect we managed to include in this was to be able to provide assertions within the command so that when files were being opened, it would wait for that file to be visible before returning.
Another useful use case using custom commands, can be for authorising a login during each test. Setting up the correct request/response whether stubbed or not can be stored inside a custom command so that each test can authorise and login before starting.
Stubbing
Although this is a hot topic, and cypress actually provides guidance on avoiding it in order to provide a clear end to end test suite, for our situation the positives of stubbing data outweighed relying on API's and delayed responses.
We have other forms of testing and frameworks for elements of the product outside of the UI. So for reliability in a strict UI test suite we explored the options within cypress.
Cypress provides a powerful tool of being able to perform a UI action such as clicking a button, to which a request to an API would be sent. It can then intercept that request and provide a stubbed response to which the tests can be asserted on the UI following the data being returned.
Let's look at our example and break it down
example.json
[
{
"dataId": "your_data_id",
"tags": {
"data": "data"
},
"someMoreData ": [
"data"
],
}
]
cy.fixture(fixtures/example.json')
.as(example);
So the first part refers to a location where we store our fixture containing the JSON response/request and a reference name is given to that data. In this case, example.json
cy.route('GET', '**/someService/v1/*/exampleRoute, '@example)
.as(exampleRoute);
The second stage is to define which route or routes you want cypress to identify as the route to intercept and then provide a reference to the stubbed response which in our case here is @example.
When this route is called during the test, cypress will intercept and return the stubbed data instead of firing off the request. This not only provides a much faster test suite without having to wait for API's to return data, but also is far more stable in that you can simulate the exact data you want returned and can be 100% sure that it will be the data you get as a response.
Conclusion
We have run into many more interesting and useful features that we could talk about in detail. But our general feel is that cypress.io really is a powerful tool to have at your disposal. Integrating it into our build pipeline has enabled us to act on any UI related breaking changes at a much faster rate and has proven time and time again that using it has made us create a more reliable and stable code base and product.
UI/UX developer