Page objects returning page objects

It seems to be common for page object automated testing frameworks to encourage the chaining of page objects through page objects returning other page objects. The selenium team recommends this on their page objects page (emphasis added):

Summary

  • The public methods represent the services that the page offers
  • Try not to expose the internals of the page
  • Generally don’t make assertions
  • Methods return other PageObjects
  • Need not represent an entire page
  • Different results for the same action are modelled as different methods

I don’t agree with this however. I find returning other page objects creates more tightly coupled pages with less flexibility, and duplication of code, for example, as the selenium page shows, there are two methods required: ‘loginAs‘ and ‘loginAsExpectingError‘, each doing roughly the same thing but each returning a different page.

I prefer to not return pages, but rather instantiate each page from the calling code, usually my Cucumber of SpecFlow step definitions. This means you can call any page in any order, which is often the case in a web app – for example, a search page may go straight to the puppies page when searching for ‘puppies’ as shown below, and this could be simply represented by a single method: search_for.

Better ways to instantiate pages in calling code

The most straightforward way to instantiate pages in calling code (for example, Cucumber steps) is to create new objects each time you want to interact with a page.

In ruby this would be like:

@search_page = SearchPage.new @browser
@search_page.search_for 'kittens'

or in C#:

SearchPage = new SearchPageModel(Driver);
SearchPage.SearchFor("kittens");

A nicer way in ruby is to use blocks and automatically instantiate pages as needed (this is supported in the page-object ruby gem):

on SearchPage do |page|
  page.search_for 'kittens'
end

Whilst I don’t think there’s anything this nice in C#, I found using the ‘using’ statement makes it look a little similar:

using (var page = new SearchPageModel(Driver))
{
  page.SearchFor("kittens");
}

Summary

I prefer to keep my page objects loosely coupled for maximum flexibility and reuse, and I find using blocks allows me to easily instantiate pages as needed.

11 thoughts on “Page objects returning page objects

    • I don’t really know C#, but if I understand your code correctly, you just pass page class name to method, create new instance of page class in method and return.

      It can be done in Ruby with a method argument of class name, but it’s not like using in C#.

      • Hi, my ideas have nothing to do with “using” in C#. I’m just saying that you can tell the Login method what page object to return rather than having lots of duplicate methods.

    • Robbie, due to Ruby’s dynamic typing, it can do what you suggest without having to worry about what class your page is – just return whatever you want. Whether you’d want to return page objects or not is a different matter… and the answer is ‘not’ :)

      • Mmm, not convinced yet that returning page objects is bad. We”ll have to meet up next time I’m in London and discuss over a some fine wine and food. I still remember the person who thought Cucumber and BDD was the work of the devil, the “record and playback” of the 21st Century ;-)

        • If we’re going ‘ad hominem’, let’s get accurate :) To clarify your libellous and scandalous comments, I thought cucumber used as a test tool was the work of the devil – I still do. And I never had a problem with BDD. In fact BDD is how I do all my stuff (using cuke too); see the commit logs in my github account :P Old age has clearly muddled your memory :)

          Cucumber, when used for its intended purpose is fantastic. It’s just a shame that it’s used as a test tool rather than a BDD tool by most. Just like “record-and-playback” tools that promised so much but delivered so little, cucumber used as a test tool promises a lot, but delivers pain. Everyone wants it on their CV (just like QTP back in the day), so it gets implemented a lot. Badly.

          But yeah, wine and food would be good! Maybe some tapas? ;) Let me know when you’re next in town.

          • That’s a great argument for BDD I had thought of before; it is indeed not a test tool but a development process. My concerns about BDD are when it is used a test automation tool.

            Thanks

  1. I agree that returning page objects bring flexibility issues. But what if we have several pages with similar services methods. When we just call @page.service_method, we shouldn’t care about page reference. That’s +1 for them, while I agree with you.

  2. Hmm, I am in dubio. Duplication of code for “loginAs” & “loginAsWithException” seems far-fetched, since you can have a (in C#) private method that does the actual typing in of username & password, and clicking the “login” button for example.

    But I do get your main point. I find it a bit silly that you should have two methods both doing the same basic thing (input data & clicking a button). In C# I use an interface for each PageObject (simply called IPage), and I return IPage objects. That way, I only have one method ‘loginAs’ and it is decided in that method whether I should return a LoginPage or a HomePage object.

  3. I think I have a counter-argument or two for you. Let me know what you think…

    I am testing a complicated web site with literally over 100 pages. Having the target page object returned in the navigating method helps the writer of the test case to not have to remember (or look up) the name of the relevant page class.

    The URLs of these pages are dynamically generated, too, so you can’t use the “visit” method in 19 out of 20 cases. You have to navigate around using the buttons and links in the site. Sometimes you have to do a long (>6) string of actions that each instantiates a new page object. Using your “on” method means explicitly declaring every new page class along with the single action to be performed on that page.

    It seems to me that these things will ultimately lead to more lines of code than you’ll get from what seems to me to be the relatively rare case of needing to write methods that explicitly expect errors.

Comments are closed.