4 tips to improve your UI test automation with Cypress
Tags: Testing, Development, Continuous Integration, Automation, UI

4 tips to improve your UI test automation with Cypress

UI Test best practices

Image from Cypress website: https://www.cypress.io

Having User Interface testing automation can make a development team highly productive as it may sooner catch problems/bugs which would only be found during integration, manual tests or, worse case, in production. However, if few practices are ignored whilst creating the tests and executing them, the automation can become an extra burden to the team as opposed to support them. Following, there are few actions, such as standard selectors, parallelisation, resilience and shared responsibility, which will help increase the performance and quality of UI tests.

1. Create specific selectors for your tests

Although there are several ways to get an element through UI automation, the most used are xpath, ID or Class and they share the same problem: be changed. Imagine a test is using the ID named #login to make an action to the Login button, but then someone just change its name to #loginID. The test will start failing because the #login is no longer part of the DOM. Same for xpath if changes are made in the structure of the DOM and for Class if a Front End Dev just decide to refactor some classes.

How to fix this problem? Defining one type of selector, agreed across the team, which will be used only for test purposes, hence, not changed for a different reason.

Which selector? Cypress has a best practice documentation where they suggest to create an attribute — Something like data-cy=’login’. On the other hand, csswizardry.com has an article informing that getting an element by attribute is one of the most inefficient ways, not to mention that the syntax is not very friendly to write ‘[data-cy=login]’ and the name should not be coupled to your framework as you never know when you may change it. Furthermore, getting an element by ID is only limited to one element — yes, if there are two elements with same ID, only the first will be returned. As a result, using classes could be the best choice. They will represent something like class=’login-style test-login’ and could be referenced just by ‘.test-login’ or ‘.test-login-form .test-login’. If there is a possibility of the selector to be used by another client or tool, the team could opt for a more generic name avoiding the intuitive word ‘test’.

2. Create independent and resilient tests

There is nothing more frustrating, and time consuming, than having to reset data in order to have your tests running after some failure, nevertheless, in order to minimise these issues your tests should be able to self-heal.

Make sure the entities (users for login, data, integration access) used during the tests are used only by the automation. Ex: The developers should not execute development tests using the same login used by the automation.

Keep some data in which the tests are dependent on and describe them semantically. Ex: If to create an order it is necessary to select a country, it is good to have something already defined, like “country test”. This will make your order test completely independent of the country test and it will make easier to manage parallelisation (covered further).

Use a “beforeAll” step in order to arrange the test environment. However, if possible, use it as well as a test and use conditions to avoid issues. Instead of calling an API to delete all countries, create a test (it) which will be called “cleanAll” and use the UI to do it. Ex: Consider the country test case which is very likely to be a CRUD and you need to prepare some data to execute it. Create a cleanAll test to delete all countries different of “country test” and have the “delete” feature tested without adding an extra test for that. Moreover, if there is a failure during the CRUD process and no country is created, just use a condition and skip any action for the cleaning step. The goal here is to avoid doing the delete using the API when it can be done via UI and, since it has been done via the UI, benefit from it avoiding another test for the delete action. Also note that Cypress runs the tests in sequence for each spec.

Even though it is suggested for every test (it) to be independent, due to the fact that a simple extra login step could drastically increase the execution time of each test (it), it is preferable to have the spec (describe) as an independent source, which in this case means executing the login in a beforeAll step and using the authentication for the other tests under the same spec.

3. Use parallelisation to execute your tests

Running your tests in parallel will be the best approach to save your test execution time, however it increases the complexity of your tests as they will be executed at the same time and could be manipulating the same content.

When it comes to test parallelisation, it can mean executing it in different browsers, operational systems or clients (mobile, web) at the same time, but in this case, it means having your specs simultaneously. Let’s imagine a UI test project which contains 10 specs which are being executed in 4 machines. As the specs are sorted by the execution time, each machine will pull a spec from the queue until they are done.

Image of Cypress dashboard with machines running specs in parallel

Despite the fact that most of the issues can be avoided with the suggestions of the topic above, let’s see another case: Consider two test specs UserRegistration and UserUpdate, where the latter requires info of the user to exist and the former does not. It could be a rare possibility of the specs to be executed at the same time, but if it happens, and it will, the tests are compromised. In this case one different userLogin for each spec could be the best solution.

Regarding resources, the number of machines used to execute the tests in parallel are not directly proportional to the total execution time, in other words, having 10 specs running on 10 machines in parallel will not result in the quickest test execution. Why not? It may vary from different CI/CD tools, but a machine to be ready to run the tests needs a setup and it could take longer than your quickest spec. Cypress shows the predicted execution time based on machine quantity, but it is suggested to consider the longests tests as a base to decide the amount of machines will give the best performance. Ex: If out of these 10 specs, 4 take around 1–2 mins to be executed and the others less than 1, it is very likely 5 machines will do the job.

4. Share the responsibility

It is common for the UI automation tests to be managed by a dedicated team, however it is important for the other teams (which are part of the solution) to take responsibility in their changes, making sure they will not break the tests. And if they do, they should have a reactive info from the tests (or access to see it), so they can fix the code before passing it ahead.

When it comes to a test syntax errors after a code update, the first topic will help to minimise it, but it is important to decide what are the levels of responsibility. For example: If a new compulsory field is created in a form, the test could be updated as well, but if an extra step was created in a flow and it requires more complex update, the failing tests could be commented out and the other team be informed to handle it. Because the tests are part of CI validation and it may stop the development flow in case of failure, the goal here is to reduce the bureaucracy of having the tests fixed and having the productivity decreased to do so.

Cypress offers a very friendly dashboard where it is possible to see where the tests are failing and there are logs describing the step/action causing the error. Having access to it will help team members to understand what is happening and eventually take the right action. It is an attitude of “I know what I have done wrong and I am willing to fix” than “It works on my computer. The problem is in the tests.”

Image from Cypress website: https://www.cypress.io

Conclusion

There might be other approaches which may work better for other teams, but a good communication, predefined standards, parallelisation and collaboration can help not only the team, but the company to achieve high quality products through UI Test automation. As a consequence, when possible, try to:

Have a standard selector type.

Write resilient tests and avoid repetition.

Run the test specs in parallel.

Share the responsibility across team members.

Glossary

Describe → Is how tests are grouped in the code. Usually I have one per specs.

It → Is how a test is created in the code and it means a unit of test.

Spec → Is like a test suite.

UI → User Interface.

Resources

https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements

https://csswizardry.com/2011/09/writing-efficient-css-selectors/