QA Automation Labs

Best practices for using Cypress for front-end automation testing

c1

Best practices for using Cypress for front-end automation testing

Blog originally published under the https://talent500.co/blog/best-practices-for-using-cypress-for-front-end-automation-testing/

Best automation practices refer to a set of guidelines or recommendations for creating effective and efficient automated tests. These practices cover various aspects of the testing process, including planning, designing, implementing, executing, and maintaining automated tests.

Cypress is a popular testing tool that has gained significant popularity in recent years due to its user-friendly testing framework and built-in features that simplify the testing process. 

But if you don’t use it correctly and don’t follow Cypress’s best practices, your tests’ performance will suffer significantly, you’ll make unnecessary mistakes, and your test code will be unreliable and flaky.

As a test automation engineer, you should adhere to certain best practices in a Cypress project so that the code is more effective, reusable, readable, and maintainable. 

Cypress Best practices for using Cypress for front-end automation testing

In this blog, we’ll talk about the best practices a QA Engineer should be aware of when utilising Cypress for front-end automation testing.

Here are some best practices for using Cypress:

1. Avoid Unnecessary Wait

It is generally considered a bad practice to use static wait commands like cy. wait(timeout) in your Cypress tests because they introduce unnecessary delays and can make your tests less reliable.

cy.visit(‘https://talent500.co/’)

cy.wait(5000)

In this code snippet, the cy. visit() command is used to navigate to the https://talent500.co/

website, and the city. wait(5000) command is used to pause the test execution for 5 seconds before continuing to the next command. 

Even if the site https://talent500.co/ is loaded in a 2-second system wait till 5 seconds. The better way to handle this problem is to use dynamic wait using cy.intercept().

cy.intercept(‘/api/data’).as(‘getData’)

cy.visit(‘https://example.com/’)

cy.wait(‘@getData’)

 .its(‘response.statusCode’)

 .should(‘eq’, 200)

In this example, we are intercepting a network request to /api/data and giving it an alias of getData. Then, we navigate to the https://example.com page and wait for the getData request to complete using cy. wait(‘@getData’). Finally, we check that the response has a status code of 200.

2. Use Hooks Wisely

In Cypress, we have 4 hooks i.e. before(),beforeEach(), after() and afterEach().

Writing repetitive code is not a smart idea. Suppose we have 5 test cases and in each test case we have some set of lines that are common in every test case. So a better way to handle the duplicate code is to use a hook in this case you can use beforeEach().

Use before() and after() hooks sparingly: Since these hooks are executed once for the entire suite, they can be slow and make it harder to isolate test failures. Instead, try to use beforeEach() and afterEach() hooks to set up and clean up the test scenario.

Example

In the below example, you can see we have used hooks before() and after(). In this scenario, these hooks best match but we don’t need to always use these hooks, totally depending on the requirement.

describe(“Login into talent500.”, () => {

 before(() => {

   cy.visit(“https://talent500.co/auth/signin”);

   cy.viewport(1280,800)

 });

 it(“Login into talent500”, () => {

   cy.get(‘[name=”email”]’).type(“[email protected]”,{force: true} );

   cy.get(‘[name=”password”]’).type(“Test@123”,{force: true});

   cy.get(‘[data-id=”submit-login-btn”]’).click({force: true});

   cy.contains(“John”);

 });

 it(“Verify user is logged in by verifying text ‘Contact us’ “, () => {

   cy.contains(“Contact us”);

 });

 after(() => {

   cy.get(‘[data-id=”nav-dropdown-logout”]’).click({force: true});

 });

});

Below screenshot of the test case execution of the above script

Use beforeEach() and afterEach() hooks to set up and clean up the test scenario: By using these hooks to reset the state between tests, you can make your tests more independent and reduce the risk of side effects and flaky tests.

Example

In this example, the beforeEach() hook is used to visit the login page and fill out the email and password fields, and click the login button. The it() test checks that the “Contact us” link is visible on the dashboard. The afterEach() hook is used to click the logout button.

describe(“Login Test Suite”, () => {

 beforeEach(() => {

   cy.visit(“https://talent500.co/auth/signin”);

   cy.get(‘[name=”email”]’).type(“[email protected]”, {

     force: true,

   });

   cy.get(‘[name=”password”]’).type(“Test@123”, { force: true });

   cy.get(‘[data-id=”submit-login-btn”]’).click({ force: true });

 });

 it(“should display the dashboard”, () => {

   cy.contains(“Contact us”).should(“be.visible”);

 });

 afterEach(() => {

   cy.get(‘[data-id=”nav-dropdown-logout”]’).click({ force: true });

 });

});

3.Set baseUrl In Cypress configuration File

Hard-coding the baseUrl using cy.visit() in the before() block of each spec file is not the best approach as it can lead to redundant code and make your tests harder to maintain.

A better approach would be to set the baseUrl in the cypress.config.js configuration file and use the cy.visit() command with relative URLs in your test files.

For example, in your cypress.json file, you can set the baseUrl to the login page URL

{

“baseUrl”: “https://www.example.com/login”

}

Then, in your test files, you can use relative URLs to navigate to other pages of your application. For example

describe(‘My Test Suite’, () => {

 beforeEach(() => {

   cy.visit(‘/’)

 })

 it(‘should perform some test’, () => {

   // Perform some test

 })

})

4. Use the data-cy” attribute for identifying locators

Using data-cy attributes for identifying locators is a best practice in Cypress as it provides a reliable and consistent way to target elements in your application. The data-cy attribute is a custom attribute that you can add to any element in your application to make it easier to target that element in your Cypress tests.

Here’s an example of how you can use data-cy attributes to target a login form in your application:

Suppose we have the following HTML code for a login form:

<form>

 <label for=”email”>Email:</label>

 <input type=”email” id=”email” name=”email”>

 <label for=”password”>Password:</label>

 <input type=”password” id=”password” name=”password”>

 <button type=”submit”>Login</button>

</form>

To use ‘data-cy-*’ attributes to identify locators, you can add the ‘data-cy’ attribute to each element we want to select in our tests. For example:

<form>

 <label for=”email”>Email:</label>

 <input type=”email” id=”email” name=”email” data-cy=”email-input”>

 <label for=”password”>Password:</label>

 <input type=”password” id=”password” name=”password” data-cy=”password-input”>

 <button type=”submit” data-cy=”login-button”>Login</button>

</form>

You can now use these data-cy attributes to select the elements in our Cypress tests, like this:

describe(‘Login form’, () => {

   it(‘should allow a user to log in’, () => {

     cy.visit(‘/login’)

     cy.get(‘[data-cy=”email-input”]’).type(‘[email protected]’)

     cy.get(‘[data-cy=”password-input”]’).type(‘password123’)

     cy.get(‘[data-cy=”login-button”]’).click()

   })

 })

5. Isolate it() blocks

The best practice is to use an independent it() block that does not depend on the outcome of any other it() block to run successfully. Each it() block should have its own setup and teardown steps and should not rely on the state of the application from any previous test.

Here are some benefits of using independent it() blocks in Cypress

  1. Tests run in isolation: By using independent it() blocks, you can ensure that each test runs in isolation, without depending on the state of the application from any previous test. This makes our tests more reliable and easier to maintain.
  2. Tests are easier to understand: Independent it() blocks are easier to understand since each block represents a specific test case. This makes it easier to troubleshoot and fix issues.
  3. Tests run faster: Since each it() block runs in isolation, the tests run faster, as there is no need to set up the state of the application for each test.

Here’s an example of how to use independent it() blocks in Cypress

describe(“Login into talent500 With invalid crediential”, () => {

 beforeEach(() => {

   cy.visit(‘/signin’)

   cy.viewport(1280, 800);

 });

 it(“Login into talent500 when Email is incorrect”, () => {

   cy.get(‘[name=”email”]’).type(“[email protected]”, {

     force: true,

   });

   cy.get(‘[name=”password”]’).type(“Test@1234”, { force: true });

   cy.get(‘[data-id=”submit-login-btn”]’).click({ force: true });

   cy.contains(“Unable to login with the provided credentials”);

 });

 it(“Login into talent500 when Password is incorrect”, () => {

   cy.get(‘[name=”email”]’).type(“[email protected]”, {

     force: true,

   });

   cy.get(‘[name=”password”]’).type(“Test@123444”, { force: true });

   cy.get(‘[data-id=”submit-login-btn”]’).click({ force: true });

   cy.contains(“Unable to login with the provided credentials”);

 });

});

Output

In the below screenshot, you can see both test cases run independently and pass.

6. Multiple assertions per test

Writing single assertions in one test can slow down your tests and cause performance issues. The best practice is adding multiple assertions with a single command makes tests faster and better for test organization and clarity

it(“Login into talent500 when Email is incorrect”, () => {

   cy.get(‘[name=”email”]’).type(“[email protected]”,{ force: true }).should(“have.value”, “[email protected]”)

  .and(“include.value”, “.”)

  .and(“include.value”, “@”)

   .and(“not.have.value”, “[email protected]”)

   cy.get(‘[name=”password”]’).type(“Test@1234”, { force: true });

   cy.get(‘[data-id=”submit-login-btn”]’).click({ force: true });

   cy.contains(“Unable to login with the provided credentials”);

 });

Output
In the below screenshot you can see we have verified all the assertions in a single line instead of writing different line of code for different assertion.

7. Keeping test data separate

Keeping test data separate from the test code is a best practice in Cypress automation.Data separate from the test code can help make your tests more maintainable and easier to update

Here is an example of how to keep test data separate in Cypress:

  1. Create a separate file to store the test data. For example, you could create a JSON file called “test-data.json” in your project’s root directory.
  2. In this file, define the test data as key-value pairs. For example:

{

  “username”: “testuser”,

  “password”: “testpassword”

}

  1. In your test code, import the test data from the JSON file using the Cypress fixture.

import testData from ‘../fixtures/test-data.json’

  1. Use the test data in your tests by referencing the keys defined in the JSON file. For example:

describe(‘Login’, () => {

 it(‘should login successfully’, () => {

   cy.visit(‘/login’)

   cy.get(‘#username’).type(testData.username)

   cy.get(‘#password’).type(testData.password)

   cy.get(‘#login-btn’).click()

   cy.url().should(‘include’, ‘/dashboard’)

 })

})

By storing the test data in a separate file, you can easily update the test data without modifying the test code itself. 

8. Use aliases

Use aliases to chain commands together, instead of repeating the same selectors in each command. This makes the test code more readable and maintainable.

For example, you can alias a login button and then use it in subsequent tests without having to locate it again.

In this example, we’re using the ’as’ command to assign aliases to the email input, password input, and submit button. We’re then using those aliases in the test itself. This makes the test more readable and easier to maintain, especially if you have multiple tests that use the same selectors.

describe(“Login into talent500.”, () => {

 beforeEach(() => {

   cy.visit(‘/signin’)

   cy.get(‘[name=”email”]’).as(’emailInput’)

   cy.get(‘[name=”password”]’).as(‘passwordInput’)

   cy.get(‘[data-id=”submit-login-btn”]’).as(‘loginButton’)

 });

 it(“Login into talent500 when Email is incorrect”, () => {

   cy.get(‘@emailInput’).type(“[email protected]”,{ force: true })

     .should(“have.value”, “[email protected]”)

     .and(“include. value”, “.”)

     .and(“include.value”, “@”)

     .and(“not.have.value”, “[email protected]”)

   cy.get(‘@passwordInput’).type(“Test@1234”, { force: true });

   cy.get(‘@loginButton’).click({ force: true });

   cy.contains(“Unable to login with the provided credentials”);

 });

 it(“Login into talent500 when Password is incorrect”, () => {

   cy.get(‘@emailInput’).type(“[email protected]”, {

     force: true,

   });

   cy.get(‘@passwordInput’).type(“Test@123444”, { force: true });

   cy.get(‘@loginButton’).click({ force: true });

   cy.contains(“Unable to login with the provided credentials”);

 });

 it(“Login into talent500 with valid credentials “, () => {

   cy.get(‘@emailInput’).type(“[email protected]”, {

     force: true,

   });

   cy.get(‘@passwordInput’).type(“Test@123”, { force: true });

   cy.get(‘@loginButton’).click({ force: true });

   cy.get(‘[data-id=”nav-dropdown-logout”]’).click({ force: true });

 });

});

Output

In the below screenshot, you can see three aliases in the log and the test case is executed successfully.

Wrapping up

Cypress is a fantastic testing framework, it’s important to note that its effectiveness largely depends on how well it’s used and the adherence to for using Cypress for front-end automation testing. By investing time and effort in setting up your tests correctly and following best practices, you can save yourself a lot of time and effort down the line and ensure that your tests are reliable, efficient, and easy to maintain.

Leave a Comment

Your email address will not be published. Required fields are marked *

Recent Posts

Let’s Play with Cypress Feature “Test Replay”

Problem Statement Before Cypress v13, test failures in CI have historically been captured through screenshots, videos, and stack trace outputs, b
Read More

Handling Shadow DOM Using Cypress

The idea of componentization has completely changed how we design and maintain user interfaces in the field of web development. The advent of Shadow D
Read More

Gear up for upcoming Conferences of 2024! ✨✨

Gear up for our upcoming Conferences of 2024! ✨ ✨ #TheTestTribe #SoftwareTesting #Testers #Community #Worqference #Worqference2024 #QonfX 
Read More