DEV Community

Cover image for Best practices in test automation with Cypress
Walmyr
Walmyr

Posted on

Best practices in test automation with Cypress

On July 31, 2021, I had the pleasure of participating in the Minas Testing Conference, where I spoke about How to create fast and robust automated tests with Cypress.

Note: The lecture was in Portuguese.

The talk was really cool, and I brought you a summary with each of the best practices, so you can apply it to your projects and make your tests faster and more robust.

Note 2: You can apply most of the best practices mentioned below regardless of the test framework being used, but some are specific to Cypress.

1. Independent tests

Automated tests must run in isolation, without the need for another test to run to create some state in the application under test.

The failure of one test shouldn't impact the results of other tests.

In addition, independent tests are easier to parallelize.

Tip: use the beforeEach hook when you need to perform repeated steps for all tests of a given describe or context.

2. Programmatic authentication

Logging in via the graphical user interface as a pre-condition for all tests is costly (in terms of execution time), and it makes the tests dependent on each other, which is a bad practice.

A disabled login button due to an HTML error, for example, shouldn't break an entire test suite.

By adding mechanisms to log in programmatically, you make tests faster and more independent.

3. State creation mechanisms

By creating such mechanisms, we ensure that tests are completely decoupled from each other, we don't need abstractions that add complexity to tests, such as Page Objects, and we guarantee fast and straight-to-the-point tests.

Examples of these mechanisms are API calls for creating resources, communication with the database via tasks, or running scripts at the operating system level.

4. data-* attributes

Adding attributes to front-end elements, such as data-test, data-testid, or data-cy, adds to the application what I call "testability," as these attributes are specifically created for testing purposes, decreasing the chances of front-end changes break the tests.

5. Don't do this: var el = cy.get('selector')

Remember. Cypress is not Selenium! 😅

Cypress has its own architecture, where although in many cases it allows writing code that seems synchronous, even if it is asynchronous (since it puts each command in a queue for later execution), it is not possible to do something like:

const myBtn = cy.contains('button', 'My button')
myBtn.click()
Enter fullscreen mode Exit fullscreen mode

However, you can do:

cy.contains('button', 'My button').as('myBtn')
cy.get('@myBtn').click()
Enter fullscreen mode Exit fullscreen mode

Most of Cypress's commands are chainable!

6. Do not test external applications

Relying on Google's GUI login for your tests or some third-party API can make your tests unstable, as changes to these services (which you don't control) will break your tests, even if it's all right "on your side."

To ensure that such services are in line with your application, you can have a smoke-test suite, for example, or contract tests.

7. Tests too small or too large

End-to-end tests are not unit tests. Due to the cost of running them, it's worth adding more than one assertion per test to save time.

However, beware of extensive tests. Maybe these are testing a lot of unrelated things and could be broken down into smaller ones.

8. Do not use the after and afterEach hooks

If something goes wrong during the execution of the tests, such hooks run the risk of not being executed, leaving "junk" in the application.

As a good practice, do any cleanup before running the tests, using the beforeEach hook, for example.

9. Do not use cy.wait(Number)

Cypress already has automatic waiting with different default timeouts to wait for elements to be visible, for animations to end, for requests to be sent and responded and for pages to be loaded.

What you can do to make your tests even more robust is wait for elements to be visible:

E.g.: cy.get('[data-cy="avatar"]').should('be.visible')

Or, you can wait for a particular request that you gave an alias to finish.

E.g.:

cy.intercept(...).as('myReq')

...

cy.wait('@myReq')
Enter fullscreen mode Exit fullscreen mode

I wrote about it on cy.request vs. cy.intercept.

10. start-server-and-test

Use mechanisms such as the start-server-and-test library to initialize the application server before running the tests, ensuring that it will run with the correct code and that the tests will only run when the application is responding.

Besides, such an approach will help you run your tests on a Continuous Integration server.

11. Set the baseUrl

By setting the baseUrl in the configuration file (cypress.json), your tests can visit pages via relative URLs, making them cleaner.

In addition, the baseUrl can be overwritten via a cypress.env.json file, environment variables (CYPRESS_*), command line, plugins, etc., making it possible to run the same tests against different environments.

Conclusion

By following these best practices, you will have fast and robust tests that your team trusts and helps to maintain and evolve.

Top comments (2)

Collapse
 
michael_c_h_o profile image
Michael Cho

@walmyrlimaesilv is there a service which allows us to run cypress code in the cloud without installing any code? ie a product manager can just write the test in some UI and the service runs concurrent test builds

Collapse
 
walmyrlimaesilv profile image
Walmyr

Not that I know of.
The cloud service that Cypress offers is its Dashboard, but it doesn’t seem to be what you’re looking for.