Building a Test Automation Framework using Cypress.io — Adding Page Object Model (POM) (Part 4)

Bonjour!

In Part-3 of this article series we added native API testing support to our Test Automation Framework. In this article, we will organize our Test framework to accommodate Page Object Model aka POM for UI web elements.
Allons-y!

Cypress Page Object Model

What is a Page Object Model?

Page Object Model (POM) is a design pattern, popularly used in test automation that creates Object Repository for web UI elements. The advantage of the model is that it reduces code duplication and improves test maintenance.

Under this model, for each web page in the application, there should be a corresponding Page Class. This Page class will identify the Web Elements of that web page and also contains Page methods which perform operations on those Web Elements. Name of these methods should be given as per the task they are performing, i.e., if a loader is waiting for the payment gateway to appear, POM method name can be 'waitForPaymentScreenDisplay()'.[1]

Advantages of POM

  1. Page Object Pattern says operations and flows in the UI should be separated from verification. This concept makes our code cleaner and easy to understand.
  2. The Second benefit is the object repository is independent of test cases, so we can use the same object repository for a different purpose with different tools. For example, we can integrate POM with TestNG/JUnit for functional Testing and at the same time with JBehave/Cucumber for acceptance testing.
  3. Code becomes less, optimized and easy to maintain because of the reusable page methods in the POM classes.
  4. Methods get more realistic names which can be easily mapped with the operation happening in UI. i.e. if after clicking on the button we land on the home page, the method name will be like 'gotoHomePage()'.

Integrating Page Object Model


In order to add Page objects to our existing framework, we don't need any additional Cypress features or external JS libraries. All that we have to do is restructure our project a bit to include page classes and add respective elements and actions in the  so called 'Pages'.

Let's begin extending our UI test case a bit, as for it only validates a title hence no web elements are use.

Original test without any web elements

Adding new test steps to enter a search query and then validate the first result of search on Google.com.

@UI
Feature: Navigating to Google.com and verifying title and match result keyword
    @smoke @test
    Scenario: Perform Search
        Given I open the Google web url
        Then I verify title of web page as 'Google'
        When I provide search query as "Pokemon"
        Then Verify first search result to match "Pokemon" keyword
Updated test case

Now add a new folder under /integration named pages

new folder 'pages' to store all page objects

Now as we will be entering the search keyword in a input box on Google home page and then validating the result on Search result page. So we can say we have 2 pages here and we have to store elements and actions for these 2 pages.

Now we will create 2 folder for pages HomePage and ResultPage respectively.
How I like to group elements and actions in a Page is; I create 2 separate JS file one each for elements and Cypress Actions. So for example, under HomePage folder elements.js stores Web Elements and HomePage.js stores Cypress actions

HomePage Elements is a simple module.exports file, which contains a HOMEPAGE objects to store its Web Elements:

module.exports = {
    HOMEPAGE:{
        SEARCH_TXTBOX: "input[name='q']"
    } 
}
elements.js for HomePage

Similarly for ResultPage Elements:

module.exports = {
    RESULTPAGE:{
        SEARCH_RESULT_FIRST: "div.rc"
    } 
}
elements.js for HomePage

Based on these elements we'll write our Actions for HomePage:

var elements = require('./elements')
class HomePage {
  clickSearchTxtBox() {
    return cy.get(elements.HOMEPAGE.SEARCH_TXTBOX).click();
  }

  typeInSearchTxtBox(value) {
    return cy.get(elements.HOMEPAGE.SEARCH_TXTBOX).type(value);
  }

  submitSearchQuery() {
  //press enter after query is provided, for submission
    return cy.get(elements.HOMEPAGE.SEARCH_TXTBOX).type('{enter}');
  }

  }
  export default HomePage;
HomePage.js

and ResultPage actions:

var elements = require('./elements')
class ResultPage {
  
    verifyFirstResult(search_keyword) {    
        //matches partial text of result string
        return cy.get(elements.RESULTPAGE.SEARCH_RESULT_FIRST).first().text().then(value => {
          cy.log("Text is :", value);
          expect(value).to.include(search_keyword);
          
        });
      }
  
  }
  export default ResultPage;
ResultPage.js
One important thing cypress text() function is not present by default, we have to write a custom command in /support/command.js file as follow:

Cypress.Commands.add("text", { prevSubject: true }, (subject, options) => {    return subject.text();  
});

Now that we have our Page Object Model in place we can add this to our BDD Steps.

BDD Steps with Page Object Model:

In order to use Page Classed in our project, we will have to first import it into or Step-Definition file commonStep.js using import statement.

import HomePage from '../../../pages/HomePage/HomePage';
import ResultPage from '../../../pages/ResultPage/ResultPage';

const homePage = new HomePage();
const resultPage = new ResultPage();
...
Page imports into commonStep.js file

and the BDD steps will look like:

Given('I open the Google web url', () => {
    cy.visit('https://www.google.com');
    
  });
  
Then(
    'I verify title of web page as {string}',
    (title) => {
        cy.title().should('include', title);
    }
  );

When(
    'I provide search query as {string}',
    (query) => {
      homePage.clickSearchTxtBox();
      homePage.typeInSearchTxtBox(query);
      homePage.submitSearchQuery();
    }
  );

Then(
    'Verify first search result to match {string} keyword',
    (search_keyword) => {
      let result = resultPage.verifyFirstResult(search_keyword);
    }
  );
BDD Steps in commonSteps.js

That was it. All we have to do is now run this test using following command:

npx cypress run --env TAGS="@UI"

or via Cypress UI:

npx cypress open
--env TAGS="@UI" makes sure that only UI test is run, while Skipping API test execution

Test Result:

UI test execution was pass while Skipping API tests

OR, using Cypress UI:

Cypress UI test run result

So that's it, we integrated POM to our framework

If you want to see the whole test run video; you can just navigate to following folder: cypress\videos\features\UI and there you will find Test1.js.mp4, which will have complete test run execution recorded.

See you next time, when we will see adding some other cool things to our Frameworks such as reports & Multiple Environment support etc. So keep tuned in.

Au Revoir!

GitHub repo: https://github.com/far11ven/Cypress-TestFramework/tree/develop/Part 04