Vani Suruvu

Mar 12, 20235 min

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

    3300
    0