The Agile Way

The Agile Way

Share this post

The Agile Way
The Agile Way
Page Object Model is universally applicable in web test automation
Copy link
Facebook
Email
Notes
More

Page Object Model is universally applicable in web test automation

Using Page Object Model wisely to enhance automated test maintenance

Zhimin Zhan's avatar
Zhimin Zhan
Apr 19, 2023
∙ Paid
1

Share this post

The Agile Way
The Agile Way
Page Object Model is universally applicable in web test automation
Copy link
Facebook
Email
Notes
More
1
Share

Last year, I was requested to do a Test Automation POC (proof of concept) in a large financial company. The project manager, under the influence of a principal software engineer, wanted to implement a set of acceptance tests in different frameworks/languages (Java, JavaScript and Ruby). The plan for POC was three months. I accomplished the core tasks within three days.

How? I started with RSpec (Ruby) first, then re-implemented the tests in Java and JavaScript quickly. One key practice is that I used Page Object Model (POM) the same way in all languages (including C# and Python, which were not included in this POC). In the article, I will share my approach.

Table of Contents:
· What is Page Object Model?
  ∘ Demo
· Test Scripts: similar among all versions
· Page Classes: similar too
· How did I create with multiple versions of test scripts quickly? 
· If one works well, why bother another language? 
· Q & A

What is Page Object Model?

Page Object Model, also known as Page Object Pattern, is a well-known test design pattern in test automation, in fact, it is listed as one of the best practices on the Selenium website. The POM concept existed at least 30 years ago. One test manager (I still remember from Canberra) told me that he was happy to see my tool TestWise with built-in support for POM, which he had used this pattern in the 1980s.

The purpose of POM is “enhancing test maintenance and reducing code duplication” (quoted from the Selenium website). I believe many experienced test automation engineers have heard of it. I covered POM in my book: Practical Web Test Automation, and also in this article: Maintainable Automated Test Design. Read them or other resources on the Internet if you are not clear with POM.

I will continue to show how I used it in that POC.

Demo:

Firstly, I will show executions of a set of Selenium tests, which perform the same testing, but in different languages: Ruby, Python, and JavaScript.

Execute 2 tests in 3 different languages (Ruby, JS, Python) in the same testing tool TestWise

Test Scripts: similar among all versions

For all three versions, the test scripts (the top tier), by using POM, look quite similar.

RSpec (Ruby) version:

before(:each) do
  driver.get(site_url)
endit "[2] Return trip" do
  flight_page = FlightPage.new(driver)
  flight_page.select_trip_type("return")
  flight_page.select_depart_from("Sydney")
  flight_page.select_arrive_at("New York")  flight_page.select_depart_day("02")
  flight_page.select_depart_month("May 2021")
  flight_page.select_return_day("04")
  flight_page.select_return_month("June 2021")
  flight_page.click_continue  expect(page_text).to include("2021-05-02 Sydney to New York")
  expect(page_text).to include("2021-06-04 New York to Sydney")
endit "[3] One-way trip" do
  flight_page = FlightPage.new(driver)
  flight_page.select_trip_type("oneway")
  flight_page.select_depart_from("Sydney")
  flight_page.select_arrive_at("New York")  flight_page.select_depart_day("02")
  flight_page.select_depart_month("November 2021")
  flight_page.click_continue  expect(page_text).to include("2021-11-02 Sydney to New York")
end

Pytest (Python) version:

def setUp(self):
    self.driver.get(self.site_url())def test_select_return_flight(self):
    flight_page = FlightPage(self.driver)
    flight_page.select_trip_type("return")
    flight_page.select_depart_from("Sydney")
    flight_page.select_arrive_at("New York")
    flight_page.select_depart_day("02")
    flight_page.select_depart_month("May 2021")
    flight_page.select_return_day("04")
    flight_page.select_return_month("June 2021")
    flight_page.click_continue()
    self.assertIn("2021-05-02 Sydney to New York", 
         self.driver.find_element(By.TAG_NAME, "body").text)
    self.assertIn("2021-06-04 New York to Sydney", 
         self.driver.find_element(By.TAG_NAME, "body").text)def test_select_oneway_flight(self):
    flight_page = FlightPage(self.driver)
    flight_page.select_trip_type("oneway")
    flight_page.select_depart_from("Sydney")
    flight_page.select_arrive_at("New York")
    flight_page.select_depart_day("02")
    flight_page.select_depart_month("November 2021")
    flight_page.click_continue()
    self.assertIn("2021-11-02 Sydney to New York",  
         self.driver.find_element(By.TAG_NAME, "body").text)

Mocha (JavaScript) version:

beforeEach(async function() {
    this.timeout(timeOut);
    await driver.get(helper.site_url());
});it ('[2] Return trip', async function() {
    this.timeout(timeOut);
    
    let flight_page = new FlightPage(driver);
    await flight_page.selectTripType("return")
    await flight_page.selectDepartFrom("Sydney")
    await flight_page.selectArriveAt("New York")
    await flight_page.selectDepartDay("02")
    await flight_page.selectDepartMonth("May 2021")
    await flight_page.selectReturnDay("04")
    await flight_page.selectReturnMonth("June 2021")
    await flight_page.clickContinue()    await driver.findElement(By.tagName("body")).getText().then(function(the_page_text) {
      assert(the_page_text.includes("2021-05-02 Sydney to New York"))
      assert(the_page_text.includes("2021-06-04 New York to Sydney"))
    });
  });it ('[3] One-way trip', async function() {
    this.timeout(timeOut);
    let flight_page = new FlightPage(driver);
    await flight_page.selectTripType("oneway")
    await flight_page.selectDepartFrom("New York")
    await flight_page.selectArriveAt("Sydney")
    await flight_page.selectDepartDay("02")
    await flight_page.selectDepartMonth("May 2021")
    await flight_page.clickContinue()   await driver.findElement(By.tagName("body")).getText().then(function(the_page_text) {
      assert(the_page_text.includes("2021-05-02 New York to Sydney"))
   });
});

Page Classes: similar too

The page class definitions are quite similar too.

Ruby version:

require File.join(File.dirname(__FILE__), "abstract_page.rb")class FlightPage < AbstractPage
  def initialize(driver)
    super(driver, "")
  end def select_depart_from(from_port)     
   Selenium::WebDriver::Support::Select.new(driver.find_element(:name, "fromPort")).select_by(:text, from_port)
  end  def click_continue
    driver.find_element(:xpath, "//input[@value='Continue']").click
  end  # other functions ...end

Python version:

from pages.abstract_page import AbstractPagefrom selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.by import Byclass 

FlightPage(AbstractPage):  

  def select_depart_from(self, from_port):
    Select(self.driver.find_element(By.NAME, "fromPort")).select_by_visible_text(from_port)

  def click_continue(self):
    self.driver.find_element(By.XPATH, "//input[@value='Continue']").click()  # other functions

JavaScript version:

const AbstractPage = require('./abstract_page');var webdriver = require('selenium-webdriver'),
    By = webdriver.By,
    until = webdriver.until;class FlightPage extends AbstractPage {  constructor(driver) {
    super(driver);
  }  async selectDepartFrom(city) {
    await this.driver.findElement(By.name("fromPort")).sendKeys(city);
  }
  
  async clickContinue() {
    await this.driver.findElement(By.xpath("//input[@value='Continue']")).click();
  }  // other functions
}

Please note that I don’t use some kind of ‘page-object’ libraries, such as SitePrism, which often complicate things unnecessarily (or worse, wrongly), just a simple page class. You can find the full test scripts on Github, or the samples/demo folder in TestWise.

Some readers might think, “The examples you showed are all Selenium WebDriver, how about other frameworks?”. The answer is that this approach works for all. I included examples in Playwright in the following article,

  • Playwright vs Selenium WebDriver Syntax Comparison by Example

  • Playwright vs Selenium Speed Comparison

How did I create multiple versions of test scripts so quickly?

Keep reading with a 7-day free trial

Subscribe to The Agile Way to keep reading this post and get 7 days of free access to the full post archives.

Already a paid subscriber? Sign in
© 2025 Zhimin Zhan
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share

Copy link
Facebook
Email
Notes
More