18 Refactoring: Extract to Page Object Model
Greatly enhance your automated test scripts' maintainability and readability.
Learning Objectives
Refactoring - Extract to Page Object Model
Autocomplete page functions
Test Data
Site: https://travel.agileway.net
Test Project: download here (or your existing one), and open in TestWise. We will be focus on the select flight tests (oneway and return trip) which we have done before.
The test script (before refactoring): `flight_spec.rb
`
load File.dirname(__FILE__) + "/../test_helper.rb"
describe "Select Flights" do
include TestHelper
before(:all) do
@driver = Selenium::WebDriver.for(browser_type, browser_options)
driver.manage().window().resize_to(1280, 800)
driver.get("https://travel.agileway.net")
sign_in("agileway", "testwise")
end
before(:each) do
driver.get("https://travel.agileway.net/flights/start")
end
after(:all) do
driver.quit unless debugging?
end
it "User can select one way trip" do
driver.find_element(:xpath, "//input[@value='oneway']").click
Selenium::WebDriver::Support::Select.new(driver.find_element(:name, "fromPort")).select_by(:text, "Sydney")
Selenium::WebDriver::Support::Select.new(driver.find_element(:name, "toPort")).select_by(:text, "New York")
driver.find_element(:xpath, "//input[@value='Continue']").click
expect(page_text).to include("Sydney to New York")
end
it "User can select return trip" do
driver.find_element(:xpath, "//input[@value='return']").click
Selenium::WebDriver::Support::Select.new(driver.find_element(:name, "fromPort")).select_by(:text, "Sydney")
Selenium::WebDriver::Support::Select.new(driver.find_element(:name, "toPort")).select_by(:text, "New York")
Selenium::WebDriver::Support::Select.new(driver.find_element(:id, "departDay")).select_by(:text, "02")
Selenium::WebDriver::Support::Select.new(driver.find_element(:id, "departMonth")).select_by(:text, "March 2021")
Selenium::WebDriver::Support::Select.new(driver.find_element(:id, "returnDay")).select_by(:text, "03")
Selenium::WebDriver::Support::Select.new(driver.find_element(:id, "returnMonth")).select_by(:text, "April 2021")
driver.find_element(:xpath, "//input[@value='Continue']").click
expect(page_text).to include("Sydney to New York")
expect(page_text).to include("New York to Sydney")
end
end
Tasks
In this exercise, I will introduce a few new concepts. Don’t worry, it is not hard. If you do not fully understand it yet, that’s OK, just know how to use it, like in the video below.
Knowledge Point: Page Object Model makes Automated Test Scripts easier to read and maintain
“Page Object is a Design Pattern that has become popular in test automation for enhancing test maintenance and reducing code duplication”. [Selenium Doc]
Let me illustrate it with an example. What do you think of the test script below:
It is not too bad, some may think. But it is not, from a maintenance perspective. Suppose, you have 100 test scripts like the above, and the user name textbox’s name is changed from `fromPort
` to `fromCity
` (on the flight page), what will happen? Will you do a global search and replace in 100 test script files, it’s not very good, isn’t it?
Now hold that thought. Have a quick look at the design below.
I break the test steps into four sections, each section corresponding to the user operations on a web page:
Home page
enter user name
enter password
click the sign in button
Flight page
Select from city
Select destination city
Click the next button
Passenger page
Enter the passenger's first name
Enter the passenger’s last name
Click the next button
Confirmation page
Assertion
Now accept there are Page Objects you can use, the “one-way trip” test case is changed to below:
it "User can select one way trip" do
flight_page = FlightPage.new(driver)
flight_page.select_oneway_trip
flight_page.select_from_city("Sydney")
flight_page.enter_to_city("New York")
flight_page.click_continue
expect(page_text).to include("Sydney to New York")
end
You might wonder where did the selenium steps go? In the `pages/flight_page.rb
`.
require File.join(File.dirname(__FILE__), "abstract_page.rb")
class FlightPage < AbstractPage
def initialize(driver)
super(driver, "") # <= TEXT UNIQUE TO THIS PAGE
end
def select_oneway_trip
driver.find_element(:xpath, "//input[@value='oneway']").click
end
def select_from_city(from_port)
Selenium::WebDriver::Support::Select.new(driver.find_element(:name, "fromPort")).select_by(:text, from_port)
end
def select_to_city(to_port)
Selenium::WebDriver::Support::Select.new(driver.find_element(:name, "toPort")).select_by(:text, to_port)
end
def click_continue
driver.find_element(:xpath, "//input[@value='Continue']").click
end
end
What are the benefits? Many, here I will just highlight one: much easier to maintain. This is related to the thought I asked you to hold earlier. If the `fromPort
` is changed to `fromCity
`, you just need to change one place (bold in the above), in `FlightPage
`. That’s it, regardless of how many test scripts you have.
For more about Page Objects and Test Refactoring, check out my book “Practical Web Test Automation with Selenium WebDriver”.
In this article, I will show you the steps to make this change in TestWise.
Task 1: Extract to Page Function Refactoring in “Oneway trip” test case
Open the test project, and click the file `flight_spec.rb` to open it in the editor.
Select the first user operation step, “click the oneway radio button”.
Select the menu “Refactor” → “Extract to Page Function …”
In the new “Extract to Page Function” dialog,
type “
FlightPage
” in “Page Name”type “click_oneway_trip” in “Function Name”
Click the “Refactor” button.
Done, the first operation. The test fragment is changed (by TestWise) to this:
It is pretty easy and quick in TestWise, isn’t it?
Review
Keep reading with a 7-day free trial
Subscribe to AgileWay’s Test Automation & Continuous Testing Blog to keep reading this post and get 7 days of free access to the full post archives.