DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
View Events Video Library
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service

Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.

Monitoring and Observability for LLMs: Datadog and Google Cloud discuss how to achieve optimal AI model performance.

Automated Testing: The latest on architecture, TDD, and the benefits of AI and low-code tools.

Related

  • Using A/B Testing To Make Data-Driven Product Decisions
  • Deep Learning Neural Networks: Revolutionising Software Test Case Generation and Optimization
  • Automated Testing: When to Start?
  • Chaos Engineering: Path To Build Resilient and Fault-Tolerant Software Applications

Trending

  • What Technical Skills Can You Expect To Gain From a DevOps Course Syllabus?
  • Snowflake vs. Data Bricks: Compete To Create the Best Cloud Data Platform
  • Architecting a Completely Private VPC Network and Automating the Deployment
  • How To Simplify Multi-Cluster Istio Service Mesh Using Admiral
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Best Practices for Using Cypress for Front-End Automation Testing

Best Practices for Using Cypress for Front-End Automation Testing

This blog discusses the best practices a QA Engineer should know when utilizing Cypress for front-end automation testing.

Kailash Pathak user avatar by
Kailash Pathak
·
Jul. 19, 23 · Tutorial
Like (1)
Save
Tweet
Share
4.79K Views

Join the DZone community and get the full member experience.

Join For Free

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

In this blog, we’ll talk about the best practices a QA Engineer should know when utilizing 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 website and the city. wait(5000) command is used to pause the test execution for five seconds before continuing to the following command. 

Even if the site is loaded in a 2-second system, wait till five seconds. The better way to handle this problem is to use dynamic wait using cy.intercept().

JavaScript
 
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 example 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 four hooks, i.e., before(),beforeEach(), after(), and afterEach().

Writing repetitive code is not a smart idea. Suppose we have five 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.

JavaScript
 
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(“applitoolsautomation@yopmail.com”,{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.

Best practices for using Cypress for Front-end Automation Testing 2

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.

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

 beforeEach(() => {

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

   cy.get(‘[name=”email”]’).type(“applitoolsautomation@yopmail.com”, {

     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.

JavaScript
 
{

“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:

JavaScript
 
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.

HTML
 
<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:

HTML
 
<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’, () => {

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

     cy.visit(‘/login’)

     cy.get(‘[data-cy=”email-input”]’).type(‘testuser@example.com’)

     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:

  • 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.
  • 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.
  • 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:

JavaScript
 
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(“applitoolsautomation11@yopmail.com”, {

     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(“applitoolsautomation@yopmail.com”, {

     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.

test runs

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.

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

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

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

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

   .and(“not.have.value”, “test@qa.com”)

   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 lines of code for different assertions.

verified assertions

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:

  • 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.
  • In this file, define the test data as key-value pairs. For example:
JavaScript
 
{

 “username”: “testuser”,

 “password”: “testpassword”

}


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

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

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

JavaScript
 
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.

JavaScript
 
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(“applitoolsautomation@yopmail.com”,{ force: true })

     .should(“have.value”, “applitoolsautomation@yopmail.com”)

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

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

     .and(“not.have.value”, “test@qa.com”)

   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(“applitoolsautomation@yopmail.com”, {

     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(“applitoolsautomation@yopmail.com”, {

     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.

The three aliases in the log.

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 best practices. 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.

Best practice JavaScript Test case Test data Data (computing) Testing

Published at DZone with permission of Kailash Pathak. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Using A/B Testing To Make Data-Driven Product Decisions
  • Deep Learning Neural Networks: Revolutionising Software Test Case Generation and Optimization
  • Automated Testing: When to Start?
  • Chaos Engineering: Path To Build Resilient and Fault-Tolerant Software Applications

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: