Cypress For Test Automation: A Step-by-Step Guide
Tags: cypress, testautomation, testgrid
Published: 2024-04-22 13:59:32

With the complexity of software applications growing every day, one of the most important things for every testing team to focus on is optimizing test coverage. Testers all over the world use testing tools and frameworks to conduct various kinds of testing. However, testing software systems from end-to-end along with all of their subsystems is an urgent necessity.

Cypress is a modern JavaScript-based E2E (End-to-End) testing framework designed for modern web applications. It provides a powerful and easy-to-use way to automate tests. It is primarily used for testing web applications and can be integrated with various tools and frameworks like React, Angular, Vue, and more. Its unique features, like time travel and automatic waiting, can significantly improve the efficiency and reliability of your testing process.
In this blog, we will deep dive into how to set up Cypress to perform End to End testing with Cypress locally and in the cloud using TestGrid
	

## About Cypress Testing Framework

Cypress is a popular open-source testing framework that is primarily used for web application testing. It is built using JavaScript and operates directly within the browser.

One of the key advantages of Cypress over other testing frameworks like Selenium is that it doesn’t require the use of driver binaries. The screenshot below shows how tests are executed inside of the browser, which reduces test execution time and removes network latency.

Cypress automation testing runs on a NodeJS server. To create a connection between the browser and the Node.js server, Cypress uses WebSocket protocol. WebSockets provide full-duplex communication which allows Cypress to send commands, such as navigating to URLs, interacting with elements, and making assertions, from the test code to the browser in real-time. It also enables Cypress to receive feedback, such as DOM snapshots, console logs, and other test-related information, from the browser back to the server.
Let’s break down the components and how they interact:

**User Interaction:** The process starts with a user interacting with the web application. This could involve actions like clicking buttons, selecting values from drop down, filling forms, or navigating through pages.

**Cypress Test Scripts:** Developers create test scripts using JavaScript or Typescript. These scripts simulate user interactions and verify that the application behaves as expected.

**Cypress Runner:** The Cypress Runner executes the test scripts. It interacts with the web application, capturing screenshots and videos during the test.

**Proxy Server:** A proxy server sits between the Cypress Runner and the web application. It intercepts requests and responses, allowing developers to manipulate them.

**Node.js:** Cypress runs on Node.js, providing a runtime environment for executing JavaScript/or Typescript code.
Web Socket: Web sockets protocol enables real-time communication between the Cypress Runner and the web application.

HTTP Requests/Responses: In the architecture diagram you can see HTTP requests (e.g., GET, POST) and responses exchanged between the Cypress Runner and the application server.

## How Cypress Test Execution Works:

- The user interacts with the web application.
- Cypress test scripts send commands to the Cypress Runner.
- The Cypress Runner communicates with the proxy server.
- The proxy server forwards requests to the application server.
- The application server processes requests and sends back responses.
- The Cypress Runner captures screenshots and videos during the test.
- Developers analyze the test results to ensure the application functions correctly.

So far we have seen how test cases are executed internally in Cypress. In the next section, you will see why Cypress is the best choice for automation.

## Why Cypress For Automation Testing

Cypress has become very popular in the automation testing space due to several reasons:
**Easy Setup:** Cypress has a simple installation process and does not require any additional dependencies or setup. It is simple to include into both new and current projects.

**Automatic Waiting:** Cypress automatically waits for the elements to appear on the page before performing any actions or assertions. It intelligently handles asynchronous actions, eliminating the need for explicit waits or sleep statements.

**Real-Time Reloads:** As you make changes to your test files or application code, Cypress automatically reloads the test runner, providing real-time feedback. Debugging and development are sped up by this functionality.

**Time Travel:** Cypress allows you to go back and forth in time during test execution. You can take snapshots at different points in your test and even manipulate the application’s state to debug failures more effectively.

**Interactive Test Runner:** Cypress provides a user-friendly test runner interface that displays the application being tested in real-time. You can interact with the application, inspect elements, and see assertions and command logs.

**Debuggability:** Cypress offers built-in debugging tools that allow you to pause test execution at any point and inspect the application’s state. You can also use the browser’s developer tools alongside Cypress for advanced debugging.

**Network and XHR Control:** Cypress provides full control over the network and XHR requests. You can stub or mock network requests, modify responses and test various scenarios without making actual HTTP calls.

**Cross-Browser Testing:** Cypress supports cross-browser testing and allows you to run tests on different browsers simultaneously. It also provides plugins for integration with popular Continuous Integration (CI) platforms and cloud-based testing services.

## Automate Your First Cypress Test Case

Let’s create a new folder under the e2e folder named  “cypress_testgrid_testcases” to perform Cypress end-to-end testing.
Let’s create a new folder under the e2e folder named  “cypress_testgrid_testcases” to perform Cypress end-to-end testing.
Let’s take cypress testing examples to test end to end test scenarios from login to the site https://testgrid.io/ with valid credentials. Verify tab functionality and logout from the application.

### Test Scenario

1. Visit the Site https://testgrid.io/
2. Login into the site valid credential 
3. Verify the user is logged in by varying the text  “Dashboard”
4. Click on the ‘Codeless’  link under the Automation section.
5.Verify the text “Let’s get you started with codeless automation”
6.Open The Link ‘Real Device Cloud’ in the New Tab and then Back To the Parent Page
7.Verify text “Selenium” to make sure user back to parent page

Write a simple Cypress test in file ‘loginAndTabFunctionality.cy.js’ which covered below functionality
Visiting the website https://testgrid.io
Login into the site with valid data
Verify tabbing functionality.
/// <reference types="cypress" />
describe("Verify Login/Logout And Tab functionality", () => {
 beforeEach(() => {
   cy.visit("https://testgrid.io/")
   cy.get('[title="Sign in"]').click()
   cy.get("#email").clear("xxxxxx@gmail.com").type("xxxxxx@gmail.com");
   cy.get("#password").clear().type("xxxxx");
   cy.get(".signin-button").click();
 });
 afterEach(() => {
   cy.get("[data-toggle='dropdown']").click();
   cy.contains("Logout").click();
   cy.contains("Forgot Password?");
 });
 it("Login and Click on 'Codeless' link Under Automation Section", function () {
   cy.contains('Dashboard')
   cy.get("#tgtestcase").click();
   cy.contains("Lets get you started with codeless automation");
 });
 it("Open The Link 'Real Device Cloud' in New Tab and Back To Parent Page", function () {
   cy.get('[id="realdevice_li" ] > a').invoke("removeAttr", "target").click();
   cy.url().should("include", "/devicecloud");
   cy.contains('Device Groups')
   cy.go("back");
   cy.contains("Selenium");
 });
});

### Code walkthrough

To understand the cypress testing example script in detail. Let’s undertake the above code walkthrough.

### Before Each Test (beforeEach):

- Visit the TestGrid.io homepage (cy.visit(“https://testgrid.io/”)).
- Clicks on the “Sign in” button (cy.get(‘[title=”Sign in”]’).click()).
- Enters the email address “xxxxx@gmail.com” in the email field (cy.get(“#email”).clear().type(“jxxxxx@gmail.com”)).
- Enters the password “xxxxx” in the password field (cy.get(“#password”).clear().type(“xxxxx”)).
- Clickson the “Sign in” button (cy.get(“.signin-button”).click()).

### After Each Test (afterEach):

- Clicks on the user dropdown menu (cy.get(“[data-toggle=’dropdown’]”).click()).
- Clickson the “Logout” option (cy.contains(“Logout”).click()).
Verifies the presence of the text “Forgot Password?” after logout  for confirmation user is logged-out
**Test 1:** Login and Click on ‘Codeless’ Link
Verifies the presence of the text “Dashboard” after login (likely to confirm successful login).
Clicks on the element with ID “tgtestcase”
Verifies the presence of the text “Lets get you started with codeless automation” to confirm navigating to the codeless automation section
**Test 2:** Open Link in New Tab and Go Back
Clicks on the link text within the element with ID “realdevice_li” and removes the “target” attribute using invoke (likely to open the link in a new tab – cy.get(‘[id=”realdevice_li” ] > a’).invoke(“removeAttr”, “target”).click()).
Asserts that the URL includes “/devicecloud” to confirm navigation to the “Real Device Cloud” page
Verifies the presence of the text “Device Groups” on the new page.
Uses cy.go(“back”) to navigate back to the previous page (TestGrid homepage).

Verifies the presence of the text “Selenium” after returning to the homepage of TestGrid.

We have written our FIRST script now time to execute the script locally. In the next section you will see how we can execute the test script locally.

## Execute The Test Cases Locally

To execute the test cases, you can use Cypress commands in your terminal. Cypress provides options to run tests both in headed mode (where you can see the browser window) and headless mode (where tests run in the background without a visible browser window). 
Here’s how you can execute the test cases in both modes:

### Run Test Cases In Headed Mode:
In headed mode, you can see the test execution in a visible browser window.

**Open your terminal.**
Navigate to the directory where your Cypress tests are located.
Run the following command to execute the tests in headed mode.
Run the command  yarn cypress open   Or  npx cypress open .  will open below screen

Click on E2E testing and select the browser and finally execute the test case from the list, in our case we are executing the test case ‘loginAndTabFunctionality.cy.js’
When we click on loginAndTabFunctionality.cy.js test case starts executing and finally BOTH the test case pass successfully.

### Run Test case In Headless Mode

By default, [Cypress runs in “headless mode](https://testgrid.io/blog/how-to-run-cypress-tests-in-headless-mode/),” where the browser runs in the background without opening a visible browser window.
Command to run the test case in headless mode  yarn cypress run   Or  npx cypress run

**Run the command: **
‘yarn cypress run –spec cypress/e2e/cypress_testgrid_testcase/loginAndTabFunctionality.cy.js’. As you execute this command, the test case starts executing in headless mode. 

## Cypress Best Practices
 
Every tool has some best practices that can help maximize its effectiveness and efficiency. Below are a few best practices of Cypress.

**Avoid Unnecessary Wait :**

Suppose you have a scenario where you want to test the visibility of an element after clicking on a button. Instead of using a static cy.wait() command, which introduces unnecessary waiting time, you can use Cypress commands like cy.get() with assertions to wait for the element to become visible.
describe('Avoid Unnecessary Waits Example', () => {
   it('should check visibility after button click', () => {
     // Visit the webpage containing the button and element
     cy.visit('https://example.com')
      // Click on the button
     cy.get('#myButton').click()
      // Instead of using cy.wait(), wait for the element to become visible
     cy.get('#myElement')
       .should('be.visible')
       .and('have.text', 'Expected Text')
   })
 })

**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 . With the baseUrl set in the configuration file, Cypress will automatically prepend this base URL to any relative URLs used in your tests. 
Here’s how you can use cy.visit() with a relative URL in your test files:
For example, in your cypress.config.js file, you can set the baseUrl to the login page URL
const { defineConfig } = require("cypress");

module.exports = defineConfig({
 e2e: {
   "baseUrl": "https://example.com",
   experimentalStudio:true,
   setupNodeEvents(on, config) {
     // implement node event listeners here
   },
 },
});
describe('Login Page Test', () => {
   it('should load the login page', () => {
     // Cypress will automatically prepend the baseUrl to this relative URL
     cy.visit('/login')
      // Add your test assertions here
   })
 })
Multiple assertions per test Cypress
Writing a single assertion in a test can make it run more slowly and lead to problems with performance. The recommended practice is to add multiple assertions with a single command, which speeds up testing and improves test clarity and organization. In the below example you can see we have multiple validation in a single command.
cy.get('.element')
 .should('have.class', 'active')
 .and('be.visible')
 .and('have.text', 'Hello World');
Isolate it() blocks
Isolating it() blocks is indeed a best practice in Cypress (as well as in other testing frameworks). Each it() block should be independent and self-contained, with its own setup and teardown steps. This ensures that tests can run independently of each other, making them more robust and reliable.
Here’s how you can isolate it() blocks in Cypress:
describe('Test Suite', () => {
   beforeEach(() => {
     // Common setup steps before each test
     cy.visit('/'); // Navigate to the page
     // Other setup steps as needed
   });
    afterEach(() => {
     // Common teardown steps after each test
     // Cleanup tasks, if any
     // We can add logout test case here 
 
   });
    it('Test Case 1', () => {
     // Test steps for the first scenario
     cy.get('.element').should('have.class', 'active');
     cy.get('.element').should('be.visible');
   });
    it('Test Case 2', () => {
     // Test steps for the second scenario
     cy.get('.other-element').should('have.text', 'Hello World');
     cy.get('.other-element').click();
     // Additional assertions and interactions
   });
 });

Keeping test data separate
Keeping test data separate is indeed a best practice in Cypress. Separate data files are easier to update and manage, especially when dealing with a lot of test data. You can reuse the same test data across multiple tests if applicable
Cypress allows you to define test data in external JSON files called fixtures. This keeps your test code clean and separates concerns between test logic and test data.
// Example fixture file: test-data.json
{
   "user": {
     "username": "testuser",
     "password": "password123"
   }
 }
// Example usage in a test
beforeEach(() => {
  cy.fixture('test-data.json').then((data) => {
    this.userData = data.user;
  });
});

## Conclusion 

This blog post provided a comprehensive introduction to Cypress, a modern and powerful end-to-end testing framework. It covered the fundamentals of Cypress, its benefits, and best practices for writing and executing test cases efficiently. With its ease of use and robust features, Cypress is an excellent choice for automating web applications and ensuring a seamless user experience.
This blog is originally published at [TestGrid](https://testgrid.io/blog/cypress-testing/)