Skip to main content

_ruby

Example

In this example we are going to take the code from our previous tip (Using a Page Object) and modify it to use a Base Page Object.

Previous tip's code to be modified:

require 'selenium-webdriver'
require 'rspec/expectations'
include RSpec::Matchers

def setup
@driver = Selenium::WebDriver.for :firefox
ENV['base_url'] = 'http://the-internet.herokuapp.com/dynamic_loading/2'
end

def teardown
@driver.quit
end

def run
setup
yield
teardown
end

class DynamicLoading

START_BUTTON = { css: '#start button' }
FINISH_BUTTON = { css: '#finish' }

attr_reader :driver

def initialize(driver)
@driver = driver
visit
verify_page
end

def visit
driver.get ENV['base_url']
end

def start
driver.find_element(START_BUTTON).click
end

def finish_present?
wait_for { @driver.find_element(FINISH_BUTTON).displayed? }
end

def finish_text
driver.find_element(FINISH_BUTTON).text
end

private

def verify_page
expect(driver.title).to eql('The Internet')
end

def wait_for(seconds=10)
Selenium::WebDriver::Wait.new(:timeout => seconds).until { yield }
end

end

end

run do
dynamic_loading = DynamicLoading.new(@driver)
dynamic_loading.start
dynamic_loading.finish_present?
expect(dynamic_loading.finish_text).to eql('Hello World!')
end

Now let's modify this code to perform a Google search. First, we include our libraries and wire up our setup, teardown, and run actions as usual.

require 'selenium-webdriver'
require 'rspec/expectations'
include RSpec::Matchers

def setup
@driver = Selenium::WebDriver.for :firefox
ENV['base_url'] = 'http://www.google.com'
end

def teardown
@driver.quit
end

def run
setup
yield
teardown
end

Next we create our Base Page Object. It's a simple Ruby class that our other Page Objects will inherit from.

At the top of it we set an attr_reader to store the Selenium driver object that gets passed to it through the initialize method. And the rest of the class is filled with a series of common Selenium actions that we pull from the GoogleSearch Page Object.

class Base

attr_reader :driver

def initialize(driver)
@driver = driver
end

def visit(url='/')
driver.get(ENV['base_url'] + url)
end

def find(locator)
driver.find_element locator
end

def clear(locator)
find(locator).clear
end

def type(locator, input)
find(locator).send_keys input
end

def click_on(locator)
find(locator).click
end

def displayed?(locator)
driver.find_element(locator).displayed?
true
rescue Selenium::WebDriver::Error::NoSuchElementError
false
end

def text_of(locator)
find(locator).text
end

def title
driver.title
end

def wait_for(seconds=5)
Selenium::WebDriver::Wait.new(:timeout => seconds).until { yield }
end

end

With the Base class in hand we simply update the GoogleSearch Page Object to inherit from it (with < Base after the class name) and wire it up and clean things up.

Since we have some actions we want to execute when we load the GoogleSearch object, we keep them in the initialize method. But since we are inheriting from the Base class we need to call super before them. This is responsible for passing the driver object into the Base class and making all of its methods run smoothly.

class GoogleSearch < Base

SEARCH_BOX = { name: 'q' }
TOP_SEARCH_RESULT = { css: '#rso .g' }

def initialize(driver)
super(driver)
visit('/')
verify_page
end

def search_for(search_term)
type SEARCH_BOX, search_term
find(SEARCH_BOX).send_keys(:enter)
end

def search_result_present?(search_result)
wait_for { displayed?(TOP_SEARCH_RESULT) }
text_of(TOP_SEARCH_RESULT).include? search_result
end

private

def verify_page
expect(driver.title).to include ('Google')
end

end

Once that's done, we'll add our actual test script.

run {
google = GoogleSearch.new(@driver)
google.search_for 'elemental selenium tips'
result = google.search_result_present? 'Elemental Selenium'
expect(result).to be == true
}

Expected Behavior

  • Load Google
  • Search for elemental selenium tips
  • Wait for the first search result to render
  • Grab the text from it
  • Assert that the text we want is within it

Summary

Now we are in a position to write more Page Objects that leverage common actions without duplication or any Selenium code directly in them.

Hopefully this tip has helped you find your way further down the rabbit hole of Page Objects (in a good way).

Happy Testing!