top of page
Writer's pictureVani Suruvu

Cypress - Part 4 - Data Driven Testing, Custom Commands, Browser Navigation, Screenshots & More



Welcome to the fourth and last part of Cypress series of blogs. Third part of the blog can be found here.


In this blog, we will see Fixtures & Data Driven Testing (Using JSON Data), Creating Custom Commands, Browser Navigation( go() & reload() ), Capturing Screenshots & Videos on Test Failures, Generate HTML Reports, Headed & Headless mode execution. We will also see Page Object Model Pattern in Cypress.


Fixtures & Data Driven Testing (Using JSON Data):

Fixtures are files which have some data and used in tests. The different formats can include JSON, JavaScript, Text, CSV, etc.


We will see two approaches - when data from file is used in single test case ("it" block), when data from the file can be used across multiple test cases.

Data in orangehrm.json:

{

"username": "Admin",

"password": "admin123",

"expected": "Dashboard"

}

describe('FixturesTest', ()=>{
    // Approach1: Direct Access
    it('FixturesDemoTest', ()=>{
        cy.visit("https://opensource-demo.orangehrmlive.com/")

        // get data from json file
        cy.fixture('orangehrm').then( (data)=>{
            cy.get("input[placeholder='Username']").type(data.username);
            cy.get("input[placeholder='Password']").type(data.password);
            cy.get("button[type='submit']").click();

            cy.get(".oxd-text.oxd-text--h6.oxd-topbar-header-breadcrumb-module")
                .should('have.text', data.expected);
        })
    })

    // Approach2: Access through Hook - for multiple it blocks
    // to use in multiple tests, we can use this approach
    let userdata;
    before( ()=>{
        cy.fixture('orangehrm').then((data)=>{
            userdata = data;
        })
    })

    it('FixturesDemoTest', ()=>{
        cy.visit("https://opensource-demo.orangehrmlive.com/")
        cy.get("input[placeholder='Username']").type(userdata.username);
        cy.get("input[placeholder='Password']").type(userdata.password);
        cy.get("button[type='submit']").click();

        cy.get(".oxd-text.oxd-text--h6.oxd-topbar-header-breadcrumb-module")
            .should('have.text', userdata.expected);
    })
})

When there are multiple data to be run for each test, we use forEach and execute each of data input inside the loop. This is parameterizing the tests. JSON is most popular format used.

describe('DataDrivenTestSuite', ()=>{
    // Data Driven Test
    it('DataDrivenTest', ()=>{
        cy.visit("https://opensource-demo.orangehrmlive.com/web/index.php/auth/login")

        // get data from json file
        cy.fixture("orangehrm2").then( (data)=>{
            data.forEach( (userdata)=>{
                cy.get("input[placeholder='Username']").type(userdata.username);
                cy.get("input[placeholder='Password']").type(userdata.password);
                cy.get("button[type='submit']").click();

                if(userdata.username == 'Admin' && userdata.password=="admin123"){
                    cy.get(".oxd-text.oxd-text--h6.oxd-topbar-header-breadcrumb-module")
                        .should('have.text', userdata.expected);
                    //logout
                    cy.get(".oxd-userdropdown-tab > .oxd-icon").click();
                    cy.get(':nth-child(4) > .oxd-userdropdown-link').click(); // logout in dropdown
                }else{
                    cy.get(".oxd-text.oxd-text--p.oxd-alert-content-text")
                        .should('have.text', userdata.expected);
                }
            })
        })
    })
})

Data in orangehrm2.json:

[

{

"username": "Admin",

"password": "admin123",

"expected": "Dashboard"

},

{

"username": "xyz",

"password": "admin123",

"expected": "Invalid credentials"

},

{

"username": "Admin",

"password": "xyz",

"expected": "Invalid credentials"

}

]


Custom Commands in Cypress:

a. click on link using label b. over writing existing contains() command c. re-usable custom command


b. Overwriting existing command: contains method is case-sensitive. contains command provided by Cypress takes 5 arguments, which needs to be passed, when overwriting the command. (needs error check)


commands.js:

// Custom command for clicking on link using label
Cypress.Commands.add('clickLinkLabel', (label)=>{
	cy.get('a').contains(label).click();
})

// Custom command overwriting: contains() command
/* Cypress.Commands.overwrite('contains', (originalFn, subject, filter, text, options = {})=>{
	// determine if a filter argument was passed
	if(typeof text === 'object'){ // when text is passed => true
		options = text
		// text = filter
		filter = undefined
	}
	
	options.matchCase = false  // default is true
	return originalFn(subject, filter, text, options)
}) */

// Custom command for login:
Cypress.Commands.add("loginapp", (email, password)=>{
	cy.get("#Email").type(email);
	cy.get("#Password").type(password);
	cy.get("button[class='button-1 login-button']").click();
})

in CustomCommands.cy.js:


// commands.js:
// Cypress.Commands.add --> add new custom command
// Cypress.Commands.overwrite --> overwrite existing command
describe('Custom Commands', ()=>{
    it("handling links", ()=>{
        // click using label or linktext
        cy.visit("https://demo.nopcommerce.com/")
        // Apple MacBook Pro 13-inch without custom commands
        // cy.get("div[class='item-grid'] div:nth-child(2) div:nth-child(1) div:nth-child(2) h2:nth-child(1) a:nth-child(1)").click();
        // cy.get("div[class='product-name'] h1").should('have.text', 'Apple MacBook Pro 13-inch');
        
        // using custom command
        cy.clickLinkLabel("Apple MacBook Pro 13-inch");
        cy.get("div[class='product-name'] h1").should('have.text', 'Apple MacBook Pro 13-inch'); 
    })

    it("overwriting existing command", ()=>{
        cy.visit("https://demo.nopcommerce.com/");
        cy.clickLinkLabel("APPLE MacBook Pro 13-inch");  // with contains overwritten and working
        cy.get("div[class='product-name'] h1").should('have.text', 'Apple MacBook Pro 13-inch'); 
    })

    it.only("Login command", ()=>{
        // login, then search
        // commonly used feature can be invoked this way using custom command 
        cy.visit("https://demo.nopcommerce.com/");
        cy.clickLinkLabel("Log in");  // Custom command call
        cy.loginapp("testing@gmail.com", "test123"); // custom command call
        cy.get('.ico-account').should('have.text', 'My account');
    })
})

Browser Navigation: go() & reload():

go() --> to go to particular url

reload() --> reloads the same url on page.

describe('mysuite', ()=>{
    it('NavigationTest', ()=>{
        cy.visit("https://demo.opencart.com/");
        cy.title().should('eq',"Your Store"); // Home page

        cy.get("li:nth-child(7) a:nth-child(1)").click();
        cy.get("div[id='content'] h2").should('have.text',"Cameras"); // cameras

        cy.go('back'); // go back to home
        cy.title().should('eq',"Your Store");

        cy.go('forward'); // cameras
        cy.get("div[id='content'] h2").should('have.text',"Cameras");

        cy.go(-1); // home
        cy.title().should('eq',"Your Store");

        cy.go(1); // cameras
        cy.get("div[id='content'] h2").should('have.text',"Cameras");

        cy.reload();
    })
})

Capture Screenshots & Videos in Cypress:

Screenshots can be captured intentionally, when required, using cy.screenshot(). On failure, screenshots are automatically captured in Cypress.

On failure, when tests are run in command prompt or CLI tool mode, then Cypress captures screenshot.

describe('mysuite', ()=>{
    it('Capture Screenshots & Videos', ()=>{
        cy.visit("https://demo.opencart.com/");
        // cy.screenshot("homepage");
        // cy.wait(5000);
        // cy.get("img[title='Your Store']").screenshot("logo"); // specific element 

        // on failure... 
        cy.get("li:nth-child(7) a:nth-child(1)").click(); // Cameras
        //failing intentionally here not given Cameras, but Tablets to check
        cy.get("div[id='content'] h2").should('have.text', "Tablets");  
        // in command prompt: npx cypress run --spec cypress/e2e/ScreenshotVideos.cy.js
        // screenshots folder and videos folder
    })
})

Vidoes are captured in videos folder, Screenshots are captured in screenshots folder under cypress folder.


Generate HTML Reports - Headed & Headlesss mode execution:

Using cypress-mochawesome-reporter plugin, we can create HTML Reports in Cypress.

Prerequisites: It is built on npm v3.2.3 and requires node >=14 version. We can see Compatibility matrix also in the link above.

Steps to generate HTML Reports:

1. Install plugin: Use command to setup: npm i --save-dev cypress-mochawesome-reporter

2. Change Cypress Reporter & Setup Hooks:

Edit Config file(cypress.config.js by default):

const { defineConfig } = require("cypress");

module.exports = defineConfig({
  reporter: 'cypress-mochawesome-reporter', // for reports
  e2e: {
    setupNodeEvents(on, config) {
      // implement node event listeners here
      require('cypress-mochawesome-reporter/plugin')(on); // for reports
    },
  },
});

3. Add to cypress/support/e2e.js

import 'cypress-mochawesome-reporter/register';

4. Run Cypress: npx cypress run --spec cypress\e2e\MyFirstTest.cy.js

Tests are executed by default in headless mode, in default Electron browser.


To execute on Chrome: npx cypress run --spec cypress\e2e\MyFirstTest.cy.js --browser chrome


MyTest.cy.js:

describe('My First Test', ()=>{
    it('Verify Title - Positive Test', ()=>{
        cy.visit("https://opensource-demo.orangehrmlive.com/")
        cy.title().should('eq','OrangeHRM');
    })

    it('Verify Title - Negative Test', ()=>{
        cy.visit("https://opensource-demo.orangehrmlive.com/")
        cy.title().should('eq', 'OrangeHRM123');
    })
})

Screenshot is taken for a negative Test Result or failed test cases.


To execute in headed mode: npx cypress run --headed --spec cypress\e2e\MyFirstTest.cy.js --browser chrome

Parameters can be in any order in the above command, after "run".


Page Object Model Pattern in Cypress:

Why Page Object Model:

Using PageObjectModel, we write page elements in separate file. Actions are written in other files. Test methods are combination of both these files: page elements file and actions file along with tests / validations. Each Test Case will have multiple Test Methods.


Without PageObjectModel, there is duplication of elements / locators. Also updating of these elements is challenging, when an element changes. With PageObjectModel, these issues are addressed, as all elements are declared in page files and used by multiple test files.

Page Object Model in Cypress - Implementation:

  1. Create PageObject class for Login - LoginPage.js:

class Login{
    setUserName(username){
        cy.get("input[placeholder='Username']").type("username");
    }

    setPassword(password){
        cy.get("input[placeholder='Password']").type("password");
    }

    clickSubmit(){
        cy.get("button[type='submit']").click();
    }

    verifyLogin(){
        cy.get(".oxd-topbar-header-breadcrumb > .oxd-text").should('have.text', 'Dashboard');
    }
}
export default Login;

2. Export LoginPage.js class specifiying "export default Login;" at the end of PageObject class - LoginPage.js.

3. Create LoginTestPOM.cy.js file with tests. To use Login Page Object, import the class.

import Login from "../PageObjects/LoginPage.js"; // .js is optional
describe('LoginTest', ()=>{
    // General Approach
    it('LoginGeneral', ()=>{
        cy.visit("https://opensource-demo.orangehrmlive.com/")
        cy.get("input[placeholder='Username']").type("Admin");
        cy.get("input[placeholder='Password']").type("admin123");
        cy.get("button[type='submit']").click();
        cy.get('.oxd-topbar-header-breadcrumb > .oxd-text').should('have.text', 'Dashboard');
    })

    // using Page Object Model
    it.only("LoginTestPOM", ()=>{
        cy.visit("https://opensource-demo.orangehrmlive.com/")
        // import page object class: Static method => with class name
        const ln = new Login();
        ln.setUserName("Admin");
        ln.setPassword("admin123");
        ln.clickSubmit();
        ln.verifyLogin();
    })
})

Another approach, keeping element locators at one place:

LoginPage2.js:


class Login{
    txtUserName = "input[placeholder='Username']";
    txtPassword = "input[placeholder='Password']";
    btnSubmit = "button[type='submit']";
    lblMsg = ".oxd-topbar-header-breadcrumb > .oxd-text";

    setUserName(username){
        cy.get(this.txtUserName).type(username);
    }
    setPassword(password){
        cy.get(this.txtPassword).type(password);
    }
    clickSubmit(){
        cy.get(this.btnSubmit).click();
    }
    verifyLogin(){
        cy.get(this.lblMsg).should('have.text', 'Dashboard');
    }
}
export default Login;

LoginTestPOM.cy.js:

Use LoginPage2.js import, as below, in the same file used in previous approach. Here, we separate elements from Action methods.

import Login from "../PageObjects/LoginPage2.js"; // .js is optional


Using JSON file to use Data Driven Testing:

To get data from orangehrm.json file for login credentials:

// using Page Object Model with fixture
     it.only("LoginTestPOMFixture", ()=>{
        cy.visit("https://opensource-demo.orangehrmlive.com/")
        // import page object class: Static method => with class name
        cy.fixture('orangehrm.json').then( (data)=>{
            const ln = new Login();
            ln.setUserName(data.username);
            ln.setPassword(data.password);
            ln.clickSubmit();
            ln.verifyLogin();
        })
    })

Page Object Model is not a framework, but a pattern, where we maintain Page Objects in one file and Test scripts in another file. We can reuse Page Objects in multiple test scripts, and update elements only at one place using Page Object Model.


Conclusion:

Thus far, we have seen how to deal with Fixtures & Data Driven Testing (Using JSON Data), Creating Custom Commands, Browser Navigation( go() & reload() ), Capturing Screenshots & Videos on Test Failures, Generate HTML Reports, Headed & Headless mode execution. We also learnt Page Object Model Pattern in Cypress.


Hope you enjoyed learning Cypress through 4 part series of blogs. Thank you!!!

References: https://www.youtube.com/playlist?list=PLUDwpEzHYYLvA7QFkC1C0y0pDPqYS56iU

378 views

Recent Posts

See All
bottom of page