29

I have started using the Selenium 2 /Webdriver web automation framework a couple of weeks ago and I'm generally pleased, but I find myself unsure about how to best design my Page Objects. The following issues have been bothering me :

  1. Is a page URL a property of the Page Object ? What if a number of different URLs lead to the same page ? Also, what if the URL is used to pass some parameters, do you if at all deal with that ?

  2. What fields do your page objects have except for WebElements and a WebDriver instance ?

  3. How do you initialize your page objects ? In the constructor? Using a factory method ? Do you use the PageFactory class provided by WebDriver ?

  4. Are your page objects ever responsible for browsing to the appropriate web page or do you depend on an "external force" to do the browsing ? In WebDriver terms, do your objects ever do driver.get("URL") ?

I of course have my own answers to these questions, but I will leave them out of the main post to avoid clutter.

5
  • To clarify, are you new to UI automation tools, or are you trying to apply what you learned with a different tool to Selenium 2/Webdriver?
    – user246
    Commented May 13, 2011 at 23:12
  • I'm new to test automation at general, heck I'm pretty new to testing at general. Not sure why that matters though, except maybe as an excuse for me asking silly questions ... :)
    – Sassy
    Commented May 14, 2011 at 10:50
  • I was just trying to figure out whether you meant "page object" as something in the Selenium API or "page object" as something you write as a way of organizing your code. Sounds like you meant the latter.
    – user246
    Commented May 14, 2011 at 23:47
  • Yes, I meant the Page Object design pattern, not something in any way particular to Selenium .
    – Sassy
    Commented May 16, 2011 at 19:19
  • Did you tried with ISFW? It provides powerful page concepts with selenium1 as well as selenium2/webdriver. The page defined in ISFW are not url based. For example, consider the page of user information. The page is the same but content will be different for different, ie. fname, lname, adderss. The url also be dynamic in such pages. In ISFW all this thinks are taken care Thanks
    – user861594
    Commented Jan 7, 2012 at 13:56

9 Answers 9

14

I find the Page Object pattern very useful, and use a modified PageFactory (parameterized for custom timeouts).

I also use WidgetObjects (basically, PageObjects with a parent reference via constructor) to represent common themes across pages.

  1. I don't think URL association is necessary on all pages, so I keep it out of my base class. URL params? I keep my PageObjects user-centric; if the user doesn't need to know, the PageObject doesn't either. Object state is a product of sequential executions and constructions, only.

    • format(:Date) : String
    • select(:WebElement, :String)
    • selectMany(:WebElement, :int)
    • selectRadio(:WebElement, ...)
    • ....
    • I also have many Predicates, Functions, and ExpectedConditions defined in a singleton utility class, which are used with FluentWait to implement:

      • waitToBeAt(path:String, timeout:int)
      • varEquals(var:String, value:String, timeout:int)
      • switchToWindow(path:String, value:String, timeout:int) : WebDriver
      • windowIsClosed(winTitle:String, timeout:int)
      • elementLocated(:By, timeout:int) : WebElement
      • ....
    • I also keep JavaScript snippets in discrete files and at startup, read them into a Map, indexed by the filename. This is infinitely more maintainable than writing escaped source within source.
  2. I usually use my custom PageFactory to initialize, or for complex objects I call the constructor and pass the instance to PageFactory for static element initialization.
  3. My PageObjects always know how to navigate to the next page, and have methods returning those PageObjects. I use chaining (return this;) wherever possible because it makes the test code extremely fluent, which is my favorite part about using this pattern:

/**
 * Test Utility.  Create an Assembly.
 * @return A reference to the viewport of the created Assembly.
 */
public ViewAssemblyPage createAssembly(String title, String type, 
        String description, Date dueDate, int numParts) {

    AssemblyPage assy = login("JoeCoder", "foopass")
        .createRecord()
        .assembly(type)
        .formInput(title, description, dueDate);

    return assy.getTable()
        .openPartsPicker()
        .findParts(type)
        .selectSomeParts(numParts)
        .submitSelection();
}
2
  • I don't think page objects returning other page object is always a good solution. Imagine a LoginPage object, which may take you to the HomePage if login method succeeds and back to the LoginPage if it fails.
    – dzieciou
    Commented May 29, 2014 at 20:25
  • 1
    As test author, you know when your given login info will be successful. It therefore makes sense to create distinct methods for each destination page, in this case: public HomePage loginFail(String username, String password) {...}. I usually recommend creating a parameterized method with a generic return type to keep the code DRY. If your code goes off the path, e.g. navigates to an unexpected page, you should throw an Exception to be caught by the test runner.
    – Joe Coder
    Commented Jun 1, 2014 at 3:49
4

My answers below are based on the framework that we've built, which uses a state machine to traverse the application we're testing.

  1. The URL (or, rather, a compiled regex representing the URL) is indeed a property of the individual page objects in my test suite. They all descend from a base page object very similar to terryp's (though we've converted to PyQuery instead of BeautifulSoup -- mostly).
  2. In __init__, the page is scraped for form elements, and they're wrapped up into a .form attribute.
  3. The WhereAmI method of the state machine pulls the regexes from all the page objects in a given module (e.g., lib.pages) to find the most appropriate one. In cases where one url is used for many different pages (ajax, etc), other identifying information based on content (eugh, I know) is used.
  4. In the state machine model we use, the state machine is an iterator. So, the browsing from one page to the next happens in the next() method on each page object (via __iter__ on the state machine)
2

I write my Selenium tests in Clojure, a Lisp that runs on the JVM. While it is possible to define objects in Clojure, it makes more sense to organize Lisp code using functions rather than objects, so my tests are all written as functions with the occasional closure. (I mention Clojure to put into context why I don't use page objects, not as an endorsement or recommendation to anyone else.) Despite what I just said, I think some of my answers my be applicable to your question.

I don't test the resulting URL of a page. For each page, I have a function that knows how to navigate to that page. The function knows the URI. Its parameters include the base URL and any parameters that need to be included in the final URL. If there are multiple paths to a page, I test each of them in the context of the originating pages.

My tests usually don't have state specific to a page. There may be global state (e.g. usernames and passwords for logging in to accounts with different kinds of privileges), but since that aren't specific to a page, I store them in a global "context" that gets passed around.

My "go to page X" function does a driver.get(URL), or rather it calls a function that calls driver.get(URL). It assumes that the browser session is already in a state where it makes sense to go to that page. For example, if you must login before going to page X, an external force (a test that wants to know what happens when you go to page X) will first login via the login page.

2

I use Python and Selenium 1 and find page objects very useful.

  1. Mostly don't care about the URL. I check the page contents to make sure I'm where I expect.

  2. Most of the fields in my page objects relate to form fields on the page. I also have object fields that can read bits of static text based on the DOM.

  3. I have a base class with a variety of utility functions (wait_for_element, wait_for_text, wait_for_page, diff(actual,expected), login, etc). The base class also holds the Selenium session object. Most subclasses are initialized from a base instance: p=HistoryPage(base), but I can also do p=base.clone_to(HistoryPage). This is mostly passing the Selenium session around, probably a better way to do that.

  4. The base class handles most navigation, with base.visit("link=Settings") most often. Some page objects have methods that do navigation and return a new page object for the new page: transaction_page = history_page.find_transaction('canceled')

4
  • I see, so basically your page objects are the one responsible for navigation and initialization. It is unclear to me how a page knows to what URL to browse. What does "link=Settings" mean ?
    – Sassy
    Commented May 20, 2011 at 6:29
  • In Selenium 1 it means the link with innerText()=='Settings'. Selenium 2 would use By.linkText('Settings'). Commented May 20, 2011 at 13:11
  • I see. But what of the starting point ? Basically, the first page object to be interacted with, how does it know from where to start ? There is no link/button to press. I can see two ways, either your tests put in the first URL, in which case, where does it take that URL from ? Or, all your tests begin at the same point, say the home page and browse from there by interaction.
    – Sassy
    Commented May 20, 2011 at 16:58
  • They all start from "/". Commented May 20, 2011 at 18:16
1

Is a page URL a property of the Page Object ? What if a number of different URLs lead to the same page ? Also, what if the URL is used to pass some parameters, do you if at all deal with that ?

Most of my pages don't know (or care) about the URLs that lead to them. When I have pages that I want to jump to directly (rather than navigating to by following links on other pages), I often bundle the knowledge of those URLs into a "site" object or "site navigation" object.

What fields do your page objects have except for WebElements and a WebDriver instance ?

Mostly private static fields (Java) for the locators for the interesting elements on the page.

I don't usually store WebElement objects for use across page operations. Instead, I create fresh ones for each operation. Usually.

My page objects often use a polling DSL to poll for various conditions. So pages generally also hold a poller or default timer that I can use in my poll expressions.

How do you initialize your page objects ? In the constructor? Using a factory method ? Do you use the PageFactory class provided by WebDriver ?

My page object constructors merely stash the WebDriver instance and whatever other parameters they'll need later. I work very hard to make sure my page objects do no real work in their constructors.

I almost always create factory methods that hide the incidental details of calling the constructor. So instead of writing this in my test:

@Test public void disasterRecovery() {
    ...
    new HalPage(webDriver, site, defaultTimer).openThePodBayDoor();
    ...
}

... I'll write this:

@Ignore("Random failures. Cause unknown. Adaptive AI probably makes recovery unnecessary."
@Test public void disasterRecovery() {
    ...
    hal().openThePodBayDoor();
    ...
}

private HalPage hal() {
    return new HalPage(webDriver, site, defaultTimer);
}

If I find that I'm creating the hal() factory method all over the place, I'll often move it into a "fixture" base class which provides numerous common factory methods. I'm becoming nervous about this practice, because over time the fixture base class becomes bloated with factory methods.

Another practice that I currently use extensively is to bundle those common page parameters into some kind of "context" object, and hand that around instead of the individual parameters. Lately I'm feeling queasy about this practice, because the context objects tend to collect many disparate objects, which increase coupling and reduces cohesion.

Are your page objects ever responsible for browsing to the appropriate web page or do you depend on an "external force" to do the browsing ? In WebDriver terms, do your objects ever do driver.get("URL") ?

I never have pages that do that. I assign all site navigation responsibility to a "site" or "site navigation" object.

A common pattern with page objects is: When a page operation navigates to another page, the operation will return a new page object that represents the destination page. Lately I prefer not to do this. The practice couples each page to each of its destinations. Instead, when some method invokes a page operation that leads to a new page, I make the invoking method create the page object for destination page.

0

This is from some older code that I had written a couple of years ago.

    def __init__(self):
        self.b = t.get_browser()
        self.url = self._get_url()
        self.resource = self._get_resource()
        self.title = self._get_title()
        self.html = self._get_html()
        self.soup = BeautifulSoup(self.html)
        self.meta_keywords = self._get_meta_keywords()
        self.meta_description = self._get_meta_description()
        self.links = self._get_links()
        self.images = self._get_images()

This was the base implementation of our page class. So for landing pages, etc you could subclass and extend this class with whatever else you needed.

For this base class the page itself could validate that it loaded correctly. And again as you get into more custom implementations of pages you could extend validations and actions as needed. The idea - as I recall Simon Stewart talking about it @ GTAC 2007 - is that you need to make a page as atomic as it needs to be without overcustomization.

0

I would answer it on the bases of how I use page object

Disclaimer: I have rarely used Selenium 2 but page object concept is irrespective of any tool.

Here go my answers -

  1. My page objects don't know about page URL, in fact page URL is some thing is known to only one base class in my case. This base class (I usually term as - SelTestCase) is extended by other classes. I never had to deal with passing parameters for URL though.

  2. My page object also knows about fields of page - i.e. Textbox etc.

  3. I don't user webdriver, I use constructor to initialize my page object.

  4. I don't do driver.get or selenium.open in my page object, thpugh my page object uses application elements to reach to a page. In fact by the time control reaches to page object, page is already in known state and I proceed from there.

And do post your answers, once you have received satisfactory number of responses...

0

Did you tried with ISFW? It provides powerful page concepts with selenium1 as well as selenium2/webdriver. The page defined in ISFW are not url based. For example, consider the page of user information. The page is the same but content will be different for different, ie. fname, lname, adderss. The url also be dynamic in such pages. In ISFW all this thinks are taken care Thanks

0

Take a look at Test Automation Framework which provide a enhanced Page Object Model.

It have certain enhanced features like

  • A "to" method which will navigate your browser to specified page and verify that the page is loaded.
  • A "at" verification method which will verify that the broser is on a said Page.
  • A explicit wait utility method which will wait until a specified page is loaded when you take an action on a page.

You can mention a base url and then mention extended page url in your Page class which will automatically appended to your base url at runtime.

http://menonvarun.github.io/taf/index.html

http://menonvarun.github.io/taf/pages/page_object_model_in_taf.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.