Page Objects in Cypress – Easy Steps

In this article we are going to learn how to use Page Objects in Cypress.

Let’s get started, right away!

Step 1:

Create a node project and install Cypress. If you are new to Cypress, I suggest you to go through this article first and come here.

npm install cypress --save-dev

To generate default sample scripts –

npx cypress open

Step 2:

We will use our favourite Parabank demo application. Our scenario will be Bill Pay. A simple one with few components in the page.

Login with the default credentials – john/demo and navigate to Bill Pay link on the left pane.

Login Page
Bill Payments Page

Step 3:

Great! Let’s create a new PO class for Login Page and Bill Payment pages. Before that, create a new folder under Cypress and call it page_objects.

Step 4:

The first PO class we need to create is the BasePage where we can place all the common reusable functions like launching the application, pause, etc.,

Navigate inside page_objects folder and create a class file and name it as BasePage.js

Tips: Add the first line with <reference types=”cypress” code to give Cypress intellisense to your class file

/// <reference types="cypress" />

class BasePage {

    launchApplication(){
     cy.visit("https://parabank.parasoft.com/parabank/index.htm")
    }

    logOutFromApplication(){
     cy.contains('Log Out').click()
    }

}

export default BasePage 

Note: There are 2 ways in which we can write Page Objects class. We will see one way in LoginPage.js and another in BillPayments.js.

Step 5:

Let’s create another class file called – LoginPage.js and paste this code –

/// <reference types="cypress" />

import BasePage from "./BasePage"

class LoginPage extends BasePage {

    login(){
       
        const basePage = new BasePage()
        basePage.launchApplication()
        
        cy.get(':nth-child(2) > .input').type("john")
        cy.get(':nth-child(4) > .input').type("demo {enter}")

    }

}

export default LoginPage 

So, what have we done here?

  1. We have imported the BasePage class
  2. Created a method for login
  3. Instantiated the BasePage class with a constant
  4. Called the method from BasePage to launch our application
  5. Finally, created 2 steps to input credentials and hit enter to login into application
  6. Exporting this class so that we can use it from another class

Step 6:

Create another PO file called BillPaymentsPage.js

This PO class is a bit different from the LoginPage.js. We will use a bit advanced features from Cypress like Aliases to allocate the locators for reusability.

See the below PO code –

/// <reference types="cypress" />

class BillPaymentsPage {

    navigateToBillPaymentsPage(){

        cy.get('#leftPanel > ul > :nth-child(4) > a').click()

    }

    enterPayeeDetailsAndPay(){

        this.navigateToBillPaymentsPage()

        // Locators with Aliases
        cy.get(':nth-child(1) > [width="20%"] > .input').as("firstName")
        cy.get(':nth-child(2) > [width="20%"] > .input').as("address")
        cy.get(':nth-child(3) > [width="20%"] > .input').as("city")
        cy.get(':nth-child(4) > [width="20%"] > .input').as("state")
        cy.get(':nth-child(5) > [width="20%"] > .input').as("zipcode")
        cy.get(':nth-child(6) > [width="20%"] > .input').as("phoneNumber")
        cy.get(':nth-child(8) > :nth-child(2) > .input').as("accountNumber")
        cy.get(':nth-child(9) > [width="20%"] > .input').as("verifyAccountNumber")
        cy.get(':nth-child(11) > [width="20%"] > .input').as("amount")
        cy.get(':nth-child(13) > :nth-child(2) > .input').as('fromAccountNumber')
        cy.get('[ng-show="showResult"] > .title').as('result')

        // Actions
        cy.get('@firstName').type("Automation")
        cy.get('@address').type("London")
        cy.get('@city').type("Greater London")
        cy.get('@state').type("London")
        cy.get('@zipcode').type("EY1 20I")
        cy.get('@phoneNumber').type("0987654321")
        cy.get('@accountNumber').type("12345")
        cy.get('@verifyAccountNumber').type("12345")
        cy.get('@fromAccountNumber').select('13011')
        cy.get('@amount').type("100 {enter}")

        this.validateResults()

    }

    validateResults(){
        
        cy.get('@result').should("have.text","Bill Payment Complete")

    }

}

export default BillPaymentsPage

So, what’s the use of Aliases?

This plays an important role to hold the locators and use their value in actions where we actually interact with the test. Let’s say for example if the locator of “address” is changed, we don’t have to update in multiple places, rather modify in only one place where we have defined the locator and it will be automatically used across the class.

Which of the above 2 PO methods I prefer? I generally use the second way (BillPaymentsPage.js) way of constructing my PO as it addresses the reusability factor.

Step 7:

Time to call the POs in our spec file. Let’s create a file under integration/parabank/ and call it as bill.payment.spec.js

/// <reference types="cypress" />

import LoginPage from "../../page_objects/LoginPage"
import BillPaymentsPage from "../../page_objects/BillPaymentsPage"
import BasePage from "../../page_objects/BasePage"

const loginPage = new LoginPage
const billPaymentsPage = new BillPaymentsPage
const basePage = new BasePage

describe("Bill Payment Test",()=>{

    it("Pay a Bill",()=>{
        loginPage.login()
        billPaymentsPage.enterPayeeDetailsAndPay()
        basePage.logOutFromApplication()
    })

})

So, what have we done again here?

  1. Imported the BasePage, LoginPage and BillPaymentsPage POs
  2. Instantiated the PO classes with constants
  3. In the Mocha test, we are directly calling the methods in a logical way to construct our tests

P.S: If you are new to Mocha, follow this course to familiarise yourself.

Step 8:

Now, it’s time to run our tests.

Execute this command to open Cypress Test Runner –

npx cypress open

GitHub Repo:

Click here

Conclusion:

As a Selenium user, I love to use this design pattern in my framework for ages. My honest opinion about Page objects is “it’s great!”, but when it comes to a test automation framework for an enterprise level application, I found it very tedious to maintain as it has numerous class files and of course loads of code! I agree that it addresses the reusability factor, but I slightly believe that it fails from maintainability factor. Not sure whether you feel the same way!

So, the next article is going to be about App Actions. Stay tuned!

Happy Testing!!!

5 thoughts on “Page Objects in Cypress – Easy Steps

Add yours

  1. Great work and design pattern. For the login page, you extended the base class and also initialized it, I suppose just extending it alone would have given you access to the methods in the class without having to initialize it. Also it is possible to use pages as against classes for the PO so in every test they would be required the methods in the PO are accessible by merely importing the PO without having to initialize it.

    Great Job!

    Like

  2. Nice Article Giridhar!

    I have a question in this page objects. In BillPaymentsPage.js, if i am using firstName element in multiple methods. Should i define locator for this firstname element multiple times or is there a way that we can declare locator for firstname element outside methods and have it as a global variable and use it in all the methods?

    Thanks,
    Manoj

    Like

    1. Hi Manoj, Just once should be ok as the scope is defined across the class. If you see in the BillPaymentsPage.js, I have defined @result alias in one method and used in another. Hope this helps 🙂

      Like

Leave a comment

Website Powered by WordPress.com.

Up ↑