page-object is a simple ruby gem that assists in creating flexible page objects for testing browser based applications. To understand the inspiration for this gem please read this blog post.
This post will walk you through some of the core features of the gem. Most of the materials in this post are also on the project wiki. Please refer to the wiki for updated documentation.
Get me started right now!
For the impatient among you we will get directly to showing you how to use it.
Create your page
The first thing you must do is create your page objects. These are simple ruby classes that include the PageObject Module.
class RegistrationPage include PageObject end
By including the PageObject Module you have added a lot of capabilities to your page. Let’s take a look at how we might use some of that right now.
Describe your page
After you create your page object class you need to describe the web page this class represents. The RegistrationPage example might look like this:
class RegistrationPage include PageObject text_field(:name, :id => 'name') text_field(:email, :id => 'email') button(:register, :value => 'Register') end
By calling these methods, the PageObject Module will add several additional methods for you. To learn about what methods are available and what methods they generate please see this page.
Use your page
Now that we have a basic page object defined it is time to put it to use. You can use either watir-webdriver or selenium-webdriver as the driver gem. Just pass them into the constructor.
browser = Watir::Browser.new :firefox registration_page = RegistrationPage.new(browser)
or
browser = Selenium::WebDriver.for :firefox registration_page = RegistrationPage.new(browser)
Once created, you can interact with the page using the generated methods.
registration_page.name = 'Test User' registration_page.email = 'test@example.com' registration_page.register
That’s all there is to getting started with the gem. The good news is that there is a lot of additional functionality that was not covered in this brief introduction. Continue reading to learn more.
Page-Object Features
Here are a few of the features you will find in this gem:
A simple DSL for defining and interacting with the content of your page
The PageObject Module adds several methods to your page object. The methods that are added follow a similar pattern. Let’s take a look at a few of the methods and their generated output.
The call to text_field like this one
text_field(:first_name, :id => 'first')
will produce the following three methods
first_name # return the value in the text field first_name= # set the value in the text field first_name_element # return the text field element
There is one special purpose generator method that does not follow this pattern. When you call the page_url method
page_url 'http://google.com'
it will produce a method named goto. To see all of the methods available and what methods the generated read the documentation for the Accessors module here.
Support for both watir-webdriver and selenium-webdriver
The page-object gem can use either watir-webdriver or selenium-webdriver as the underlying gem to drive the browser. The way you choose which driver to use is by passing it into the constructor of your PageObject
browser = Watir::Browser.new :firefox registration_page = RegistrationPage.new(browser)
or
browser = Selenium::WebDriver.for :firefox registration_page = RegistrationPage.new(browser)
In order to make this work seamlessly, the page-object gem had to add capabilities found in one driver and not in the other. Let’s look at one example.
Locating elements
There were differences in the way Watir and Selenium allowed you to locate elements on a page. We had to add functionality to page-object to eliminate those differences. Here are a few notable ones:
- Watir allows you to provide multiple locators when identifying an element on a page. Selenium only allows you to provide one locator. With
page-objectyou can now use multiple parameters when using Selenium. - Watir provides the ability to use
:indexwhen locating an element. Selenium did not have this capability. Withpage-objectyou can now use:indexwhen using Selenium. - In Selenium you could not find a link by
:hrefwhile this ability existed in Watir. Withpage-objecton Selenium you can now use:hrefwhen identifying a link. - Watir supports finding a hidden field by
:text. This ability does not exist in Selenium. Withpage-objectyou can find a hidden field by:textwhen using Selenium. - What did not support finding div, span, table, table data, ordered lists, unordered lists, and list items by
:name. Selenium had this ability. Withpage-objectyou can now find these elements using:name.
Creating and using page objects
Alister Scott blogged about an idea for creating a factory to create instances of page objects. In the past I have blogged about page objects returning page objects. After a lot of contemplation, I am completely in the Alister camp. page-object now supports this approach as well.
PageFactory
A module named PageFactory provides this ability. This module has two methods.
def visit_page(page_class, &block) def on_page(page_class, visit=false, &block)
Let’s take a look at how you would use these methods in your cucumber scripts.
Given /^I am on the registration page$/ do visit_page RegistrationPage end
This call will cause the browser to open the page specified by the call to page_url in the class.
class RegistrationPage include PageObject page_url "http://mysite.com/registration" ... end
If you wish to perform some activity on that page when you navigate to it you can pass a block to the method.
When /^I register on the registration page$/ do
visit_page RegistrationPage do |page|
page.register_user
end
end
If you are already on a page and wish to interact with it you can use the on_page method.
Then /^I should be able to cancel my order$/ do
on_page CheckoutPage do |page|
page.cancel_order
end
end
Handling Ajax calls
The secret to working with Ajax is having the ability to wait until different page events occur. The page-object gem supports waiting at two different levels – the page level and the element level. Let’s look at both.
Page level waiting
On the PageObject module (and therefore on your page objects) there is a method that assist with waiting.
def wait_until(timeout=30, message=nil, &block)
This method will wait until the passed in block returns true. If the block does not return true within the specified timeout seconds then an error is thrown with a default message or the provided message. Let’s take a look at how we might use this.
@page.wait_until do @page.text.include? "Success" end
In this example we are using the default timeout and message. This code will wait until the page includes the text “Success”. Let’s look at another example.
@page.wait_until(5, "Call not returned within 5 seconds") do @page.text.include? "Value returned from Ajax call" end
In this example we are setting the timeout to 5 seconds and providing a custom message.
Element level waiting
On the Element class we have several methods to assist with waiting.
def when_present(timeout=5) def when_visible(timeout=5) def when_not_visible(timeout=5) def wait_until(timeout=5, message=nil, &block)
The usage of the first three methods are fairly self explanatory. They will simply wait until the element is present, visible, or not visible and then continue. If the wait exceeds the timeout value then an error occurs. Let’s look at a simple example.
@page.continue_element.when_visible do @page.continue end
As of release 0.3.2 you can combine these into a single call like this:
@page.continue_element.when_visible.continue
In this code we are waiting until a link defined by a call to link(:continue, :id => 'cont') is visible and then we are clicking it.
There are times when you want to wait for something other than the element being present or visible. The last wait call wait_until handles this well. It will wait until the block returns true.
Handling JavaScript popups
Handling JavaScript popups in Watir and Selenium has always been a hassle. The approach the page-object gem takes is to intercept the call to the popup and cause it to not happen. At the same time, the gem provides the information to the user that they would wish to receive if they had access to the popup. Let’s look at a few examples.
Alerts
The PageObject module has a method named alert. Her’s how you can use it.
message = @page.alert do @page.button_that_cases_the_alert end message.should == "My Alert!"
This call demonstrates that we are passing a block to the alert method. That block is the code that causes the alert to occur. The alert will not popup but the message that was included in the popup is returned by the block.
Confirms
PageObject also has a confirm method that handles confirm popups. It works the same way as the alert method except it requires a boolean parameter which is the value returned to the browser when the confirm popup is called. Here’s an example.
message = @page.confirm(true) do @page.button_causing_the_confirm end
Prompts
Finally, PageObject has a prompt method that follows the same pattern. There are two differences. The first is that it accepts a parameter that is the value returned from the prompt. The second is that it returns a Hash with two keys – :message contains the message from the confirm popup and :default_value contains the default value if one was provided.
confirm_result = @page.prompt("Cheese") do
@page.what_do_you_like_button
end
confirm_result[:message].should == "What do you like?"
Additional documentation
You can read the RDoc documentation here.
The ChangeLog with the gem’s history can be found here.
Requesting features and reporting issues
The project has an Issues tracker where you can request new functionality and report defects.
Pingback: A Smattering of Selenium #57 « Official Selenium Blog
This is really amazing!! I have been playing around with this gem for a week now, and I should say that this addresses the common issues with UI automated testing.
I am going to implement this model in my forthcoming project.
Keep up the good work…kudos to you and Alister as well.
Cheers,
Karthik
Hey Cheezy, this gem has undoubtedly saved me literally weeks of work in setting up useful mappings in my scripts!
One question, though: Is file_field not supported, or am I just missing it while I’m looking through the method and class lists in your docs?
Abe,
I haven’t had a request for file_field support yet. Please add an issue to the project page and I’ll be sure to add it in the next release.
-Cheezy
This is a really nice framework and I’m learning a lot by studying it.
I’m curious about one thing: how would you incorporate RSpec::Matchers in this? For example, let’s say I wanted something like this:
RSpec::Matchers.define :should_have_title do |text|
match do |the_page|
the_page.should_have_title?(text)
end
end
It seems I couldn’t just do something like this:
@page.should_have_title(/^Test App$/)
The reason I ask is because while I like using RSpec as an underlying check mechanism, it’s messages are often not as helpful when you want business users to be able to get better messages about why something failed. For example, right now RSpec will report the following if a page title is not correct:
expected “Test” to match /^Test App$/ (RSpec::Expectations::ExpectationNotMetError)
Without context, that message is a bit confusing. What I’d like is:
Expected “Test App” for the title of the page. Found “Test” instead.
So I’m just curious how you might go about incorporating such things into a framework as you have constructed it?
- Jeff
But you can specify the failure message as well:
RSpec::Matchers.define :be_a_multiple_of do |expected|
match do |actual|
actual % expected == 0
end
failure_message_for_should do |actual|
“expected that #{actual} would be a multiple of #{expected}”
end
end
There’s also #failure_message_for_should_not
Wouldn’t you know it? After posting, I figured it out. You could just make a matchers.rb file (with a Matchers module). Have that included in the page-object module. It actually works pretty seamlessly.
Sorry for the comment bloat to the article.
I’ve written a post about Page Objects in my library WatirSplash (http://github.com/jarmo/watirsplash).
Read about the cool way of constructing and using page objects http://itreallymatters.net/post/12242886944/awesome-page-objects-in-testing
I don’t know how people are getting this to work because it consistently fails for me. What am I doing wrong?
I installed the gem.
I have a basic Cucumber setup:
(1) I have a features directory. In that directory is a login.feature file.
(2) I have a login_page.rb file in the features directory. It looks like this:
class LoginPage
include PageObject
page_url “http://mysite”
end
I also have some declarations in place that I snipped for brevity’s sake.
(3) I have a step_definitions folder with a login_steps.rb file.
It’s not clear from the directions on this blog post that I’m supposed to include page-object but, of course, I know I need to. I put the following line:
require ‘page-object’
in my env.rb file. I assume that’s okay. It’s not at all clear where I’m supposed to put this line:
browser = Watir::Browser.new :firefox
So, again, I just put it in env.rb.
One of my steps looks like this:
Given /^I am on the login page$/ do
@page = LoginPage.new(browser)
end
When I run Cucumber, I get this:
Using the default profile…
uninitialized constant Object::Watir (NameError)
So I took out the browser = line from env.rb.
That actually gets me farther in that the step tries to run, but then I get this:
Given I am on the login page
undefined local variable or method `browser’ for # (NameError)
I think the main problem I have is I don’t know where to get the browser instance. Even if I put this line:
browser = Watir::Browser.new :firefox
In the step definition file itself, it still doesn’t work. I get this:
Using the default profile…
uninitialized constant Object::Watir (NameError)
c:/Terminus/_refs/testerproject/features/step_definitions/login_steps.rb:1:in `’
Am I missing something obvious?
Tatiana,
The problem you are experiencing is that your browser variable is a local variable and therefore goes out of scope. I would suggest trying the following:
add the following to your env.rb file:
require 'page-object'
require 'watir-webdriver'
Before do
@browser = Watir::Browser.new :firefox
end
After do
@browser.close
end
When you create your page objects you can pass the
@browserinstance variable to the constructor. If you wish to use the PageFactory in your step definitions then you should also add this to your env.rb file:require 'page-object/page_factory'
World(PageObject::PageFactory)
Then your step above could be rewritten as:
Given /^I am on the login page$/ do
visit_page LoginPage
end
Additional steps could use code like this:
on_page(SomeOtherPage) do |page|
page.perform_first_action
page.perform_second_action
end
I hope this helps you solve your issue.
Hi.
The page factory thing never seems to work for me. I’m wondering if I’m putting the call in the wrong place. I have this line:
World(PageObject::PageFactory)
I always get this error when I do:
uninitialized constant PageObject::PageFactory (NameError)
I had my World line in my env.rb file at first. Then I moved it to a helpers.rb file that is in the support directory along with env.rb. I always get the above error. My page objects do have the line include PageObject in them. I am requiring page-object from my initial files.
Is there something else that has to be done to get this to work correctly?
Never mind! Figured it out.
Hey Tatiana,
Do you remember how you figured this problem out because I am running into the same error that you were: uninitialized constant PageObject::PageFactory (NameError). In my env.rb file I have
require ‘page-object/page_factory’
World PageObject::PageFactory
and in my pages.rb file I have:
class ExamplePage
include PageObject
I also have everything set up with cucumber – feature files, step definitions
I can’t figure out why I keep getting this error no matter what I do. Any insight you have would be greatly appreciated.
Jenna
Jenna,
Can you post some of the code that demonstrates how you are trying to use the PageFactory module? That would help me help you.
Thanks
-Cheezy
Hey Cheezy,
Thank you for responding to me. I guess I made a new post instead of replying to your comment. I provided some of my code below. Any insight you could provide would be greatly appreciated! I am switching from Allister’s watir-page helper to your page-object framework. Thanks in advance!
Jenna
Pingback: Jason’s Technology Radar | Element 84
Yes. This is my page:
class AboutCorporatePage include PageObject direct_url "www.master.staging" expected_title "OneRecovery" #Login iframe: frame :login_iframe, :id => "login_iframe" text_field(:email) { |page| page.frame(:id => "login_iframe").text_field(:class => /email_input/) } #email text field text_field(:password) { |page| page.frame(:id => "login_iframe").text_field(:class => /password_input/) } #password text field button(:login) { |page| page.frame(:id => "login_iframe").button(:class => /login_submit/) } #login button link(:forgot_log) { |page| page.frame(:id => "login_iframe").link(:class => /forgot_login/) } checkbox(:remember) { |page| page.frame(:id => "login_iframe").checkbox(:class => "remember_me") } #remember me checkbox link(:request_invite) { |page| page.frame(:id => "login_iframe").link(:class => /request_invite/) } #request invite link # Methods #Login using login iframe def login_creds(email, pass) self.email = email self.password = pass self.login end endThis is my step definition:
Given /^I am on the corporate website$/ do site "About" visit :corporate end When /^I enter my login credentials$/ do on :corporate do |page| page.login_creds("qa+10@onerecovery.com", "Rehabh#123") end end Then /^I enter OneRecovery$/ do site "Social" on :Newsfeed do |page| sleep 3 page.if_emoticon_popup(/emoticon_5/) page.first_post_li.wait_until_present #wait until main column loads - this is last thing to load on page and is often a point of failure. end endAnd I always get this error: “uninitialized constant AboutCorporatePage::PageObject (NameError)”
If you want to access the page you can go to http://www.onerecovery.com (the one in my code is our testing site and I don’t think it is publicly available)
My env.rb file contains:
Any insight would be helpful! Thank you!
Jenna
The first thing I see is that you are not using the visit and on methods properly. Try change to this form:
Given /^I am on the corporate website$/ do site "About" visit AboutCorporatePage end When /^I enter my login credentials$/ do on AboutCorporatePage do |page| page.login_creds("qa+10@onerecovery.com", "Rehabh#123") end endOne other thing, I am not familiar with the call to site. What is this used for?
Hey Cheezy,
Thank you so much for the response. The call to site I have defined in my env.rb file as:
def site name
@site = name
end
end
It is something that I used with Alister’s Watir-page-helper gem . I use it because I am testing multiple sites that interact with each other and utilize exact same code in multiple places. So for example there is a place that users can login to the site which is considered the “social” site and then there is another place where admins login which is the “admin” site. The social site and admin sites are two different sites but the code to login is exactly the same so I use the call to site so that I don’t have to duplicate my automation code. Does that make sense?? I am a newbie coder so forgive me if I don’t sound like I have all of the right terminology. The two sites communicate using rest.api
I changed my visit and on calls as you showed me above but I am still getting the same error: uninitialized constant AboutCorporatePage::PageObject (NameError)
I tried taking out the site “About” line and I also double checked to make sure that my env.rb file has all the necessary elements (I am comparing to the env.rb file in Alister’s etsy example where he showed how to transition from his watir-page-helper gem to your page-0bject gem) and I still get the same error.
Is there anything else that you can see that I am doing wrong. Is there other info I can give you that will help you help me? I really appreciate your insight and I will continue to try different things in the mean time. Thank you in advance.
Jenna
Jenna,
Can you email me the stack trace that is produced when you get this error?
Thanks
-Cheezy
Hey Cheezy,
I figured out what was going on… I had gems that weren’t the correct versions (either ahead or behind) and now my automation is running nicely now that I have installed the correct versions. The only thing I noticed is that I use the li method to index in some of my automation code and the page-object gem does not support that method. Any way that will be included in the gem in the near future?
I really appreciate you helping me and taking the time to look into my code for me. Thanks.
Jenna
Hi!
I have an issue with PageFactory and don’t know if its on my side (probably…) or its a know problem.
Using:
Given /^i am on the home page$/ do @BROWSER.goto('http://www.redsauce.net') end…the three steps are passed. But using:
…with an env.rb:
…and a class homepage:
…i get the following error:
Given i am on the home page # step_definitions/redsauce_link_step.rb:6
Unable to pick a platform for the provided browser (RuntimeError)
./step_definitions/redsauce_link_step.rb:8:in `/^i am on the home page$/’
./redsauce_link.feature:6:in `Given i am on the home page’
Any tip, idea or suggestion about what could be wrong would be appreciated.
Thank you so much!
Pablo,
Thanks for using the gem. The solution to your problem is very simple. The PageFactory methods assume there is an instance variable named
@browser. Simply rename@BROWSERto@browserand everything should work.One other note -> there is no need to create the HomePage instance variable in your
Beforeblock.-Cheezy
Thank you so much, it works fine now!
But there is a strange behavior: creating the object on env.rb inside the “Before do” as:
Before do
@browser = Watir::Browser.new 'firefox'
end
…works fine, opening a new browser for each feature and closing it on the “After do”. That slows the execution because a new browser starts and closes every feature. But if i create the @browser out of the “Before do” in order to have the same browser session for all the features, i have a:
” Unable to pick a platform for the provided browser (RuntimeError)
“, launching no test at all.
Can you tell me please what am i doing wrong?
Thank you so much!
The reason for this is that if you create it out of the Before block you are not creating an instance variable. Instead you are creating a class variable. The page-object gem is looking for an instance variable named
@browser.Hi!
Regarding the last question, i made the same on Stackoverflow…
http://stackoverflow.com/questions/11017708/error-when-using-the-same-browser-instance-for-several-features-on-cucumber
Dont know if you prefer to answer here or in Stack…
Thank you so much, really
Hi,
I want to use the method initialize_page for one of my page objects.
Could anyone please tell me what are the parameters required for this method or the method signature?
The method takes no parameters. Simply create the method like this:
Please note that this method will be call when your page object is created. If it is created in multiple steps it will be called multiple times.
-Cheezy
Hi Cheezy,
Great thanks for your website, and I learn a lot from it.
But I am two questions about page factory. one is when will the function “initialize_page” be called, like a constructor function of a page class; the other is sometimes when I use page factory like:
on_page(MyPage).xxx
even I am in MyPage, the “MyPage” will reload again. This trouble me a lot, and I find my implementation for MyPage is exactly like the way I implement other pages.
I put my codes here for your reference:
***page class**
class MyPage
include PageObject
link(:my_database, text: ‘My Database’)
def initialize_page
sleep 1
end
end
***use page factory to call the link my_database***
on_page(MyPage).my_database #I have already in my page, but this function call will reload my page.
Could you please help me out in your convenient time? Thanks a lot.
I’ll try to answer both questions here. The call to initialize_page only happens when a new PageObject class is initialized. Here is the initialize method from the PageObject module:
def initialize(browser, visit=false) initialize_browser(browser) goto if visit && respond_to?(:goto) initialize_page if respond_to?(:initialize_page) endAs you see, during class creation that method will be called if it exists.
As far as on_page, it will not reload the page ever unless you override the default parameters. It will call the constructor of the PageObject class which will in turn call the initialize_page method. Here is the on_page method from PageFactory:
def on_page(page_class, visit=false, &block) @current_page = page_class.new(@browser, visit) block.call @current_page if block @current_page endTo summarize, the initialize_page method will be called every time a new instance of your PageObject class is created. The on_page method will create a new object each time it is called but will not reload your page unless you override the visit default value.
Hi Cheezy,
Thanks for your detail explanation. It really helps me. Thanks again.
If the PageObject class is expecting a @browser variable how can I possibly run tests for different browsers? I was thinking of having @firefox @chrome @ie etc. and eventually run scenarios in parallel.
Luca,
You are correct that if you use the PageFactory methods to instantiate your objects (which I highly recommend) then page-object expects there to be an instance variable named @browser. This in no way prohibits you from running your tests with different browsers. Many people do this all of the time. You will simply assign the proper object to the @browser depending on the browser you wish to run.
Running your tests in parallel is also fairly easy these days. I would suggest looking at a gem named parallel_tests. If you plan to run many tests in parallel you will also need to setup a Selenium Grid 2. Both projects have good documentation so it you should have no problems setting it up. I have helped several companies setup a grid like this and it takes less than 30 minutes assuming all of the hardware is in place.
-Cheezy
Thanks a lot Cheezy. This helped.
This is interesting stuff. Is it possible (with PageObject) to dig into the contents of a page, find a specific DIV and then return its contents?
I’d love to be able to ask my PageObject to return the “innerHTML” of the div with the id abc.
Cheezy (or anyone else knowledgeable),
In implementing page-object into my UI test infrastructure, I’m trying to provide some nested structure for my page definitions. Something like this:
features
-support
-pages
-city_login_page.rb
-admin
-site_settings
-features_page.rb
Given this structure, my features_page.rb is defined this way:
class Admin::SiteSettings::FeaturesPage
include PageObject
#page-specific stuff here
end
All well and good, but when I actually try to run my cukes that leverage this page object:
uninitialized constant Admin (NameError)
Any pointers for how to make nested page_object definitions work?
Can you show me the code that is getting that error? Is it in a step definition?
Hello, awesome gem and blog posts!
I was wondering if you could write a blog post or elaborate on how your gem can be used to represent a specific area of a page rather than an entire page. I’ve read in several places that Page Objects don’t have to represent a single page and could instead represent a part of a page. For example, say we were writing tests against Gmail’s interface, it might be useful to split up the left hand column into its own “page object”, and then call methods on it.
I’m new to Page Objects, so I might be way off with my assumptions, but it almost seems like there should be another ComponentObject class which represents part of a page. You would then be able to return instances of this object from PageObjects or other ComponentObjects. Perhaps a helper method ‘component’ would be useful as well. For example, instead of calling the ‘div’ method in the PageObject, we might call component(:side_bar, SideBar) which would then create a new instance of SideBar whenever we call the automatically declared side_bar method. Instead of calling page_url, SideBar could call “component_selector css: ‘div.side_bar’”. Perhaps a ‘components’ method would be useful as well.
To mimic the PageFactory feature, there might be a ComponentFactory that would make on_component SideBar do { |component| … } work.
Finally, my last thought was that when you have a component that is tied to a specific section of a page (e.g. a div tag that houses many other elements), it would be useful if each of the selectors used for registering elements (like buttons and div tags) only looked inside this parent tag. For example, Capybara lets me do something like this: find(‘div.side_bar’).find(‘button.submit’). Thus, when I am inside the SideBar class, and I register a button using button(:submit, css: ‘button.submit’), it automatically knows to only search for the ‘.submit’ inside this side bar and doesn’t get confused with elements that match the same selector but lie outside it.
I was thinking of contributing to your project and incorporating some of these ideas into it, so I would really like to hear your thoughts on this. If your gem can already handle this fairly well, then I would love to see a blog post that would help new people know how to make this work! Thank you very much!
Could you please help on how to select an item from a drop down using page – objects.?
Thanks
You simply declare it in your page class like this:
And you can select a value like this:
-Cheezy
Hi, I’m trying to access the selenium web driver elements (PageObject::Platforms::SeleniumWebDriver::Element). But I’m getting Undefined method error when I use the elements like (visible? , text, exists?). If I need to include any class to use this elements already I have included Page Object.
Could you please provide the solution?
I would need to see some code to understand what you are doing wrong. Any chance you could post a little here?