Selenium Page Objects + Site Objects, Data Objects & High Level Navigation

Page Objects are gaining popularity when writing Selenium tests, and I’m glad to see people advocating for, and teaching them.  But I think that’s just the start.

With a page object you can have tests that look like this:

testChangePassword() {
  browser.open("http://one-shore.com/login");
  verifyTextPresent("Enter your username and password");
  LoginPage.login("bob", "secret");
  verifyTextPresent("Welcome, Robert");
  HomePage.goTotPreferences();
  PrefsPage.changePassword("secret", "moresecret");
  verifyTextPresent("Password changed");
}

You get a high level object that knows how to:

  1. find elements on the page
  2. perform aggregate actions

If your page object also returns the copy of the current expected page, you can do things like this:

homePage = loginPage.login(username, password);
prefsPage = homePage.goToPreferences();
prefPage.changePassword(oldpassword, newpassword);

or

page = new LoginPage();
page.login(username, password);
page.goToPreferences();
page.changePassword(oldPassword, newPassword);

or even:

landingPage.goToLoginPage().login(username, password).goToPreferences().changePassword(oldPassword, newPassword);

though I wouldn’t  recommend it.

While that’s a useful pattern, I prefer to keep my page objects  “dumb” (and even static) and build navigation into a higher level “Site Object”.  Pages can still perform in-page actions, but the Site Object is responsible for navigating, and can handle multi-page aggregate actions.

Because alot can happen, I also combine data into smarter objects.  So you can do things like this:

user bob = getRegisteredUser();
Site.changePassword(bob, newPassword);
verifyTextPresent("Password changed for" + bob.firstName);

Behind the scenes, my LoginPage class might look like this:

class LoginPage extends OneShorePage
{
  // standard fields (e.g. header, footer, navigation) are inherited for all pages on the site

  public static String url = baseUrl + "/login";
  public static String usernameField = "//div[@class='loginform']/table/tr/td[2]/input[@type='text']";
  public static String passwordField = "//div[@class='loginform']/table/tr[1]/td[2]/input[@type='password']";
  public static String loginButton = "//input[@type='submit' and @value='login']";

  public static void login(username, password)
  {
    browser.type(usernameField, username);
    browser.type(passwordField, password);
    browser.click(loginButton);
  }
}

and my Site would contain instances of all the pages, and know how to navigate between them:

class MySite {
  public static LoginPage;
  public static HomePage;
  public static PrefsPage;

  public changePassword(user, newPassword);
  {
    browser.open(LoginPage.url);
    verifyTextPresent("Enter your username and password");
    browser.type(LoginPage.usernameField, user.username);
    browser.type(LoginPage.passwordField, user.password);
    browser.click(LoginPage.loginButton);
    verifyTextPresent("Welcome, " + user.firstname);
    browser.click(HomePage.PreferencesLink);
    // this is actually a really nasty javascript popup, but I don't care, someone else wrote the Prefs page logic
    browser.click(PrefsPage.changePasswordLink);
    browser.type(PrefsPage.oldPasswordField, user.password);
    browser.type(PrefsPage.newPasswordField, newPassword);
    browser.click(PrefsPage.save);
  }
}

But of course, we can do better than that,:

  public changePasswordRefactored(user, newPassword)
  {
     Site.login(user.username, user.password);
     Site.navigateToPreferences();
     Site.changePassword(user.password, newPassword);
  }

  //...

  public login(username, password)
  {
    browser.open(Site.HomePage.url);
    browser.click(Site.HomePage.loginLink);
    verifyTextPresent("Enter your username and password");
    browser.type(Site.LoginPage.usernameInput, username);
    browser.type(Site.LoginPage.passwordInput, password);
    browser.click(Site.LoginPage.submitButton);

    try {
      verifyTextPresent("Welcome, " + username);
    }
    catch (Exception e) {
      return LoginPage;
    }

    return HomePage;
  }
}

of course, it could also use smarter page objects:

public changePreferences()
{
   LoginPage.open();
   LoginPage.enterUserName(username);
   LoginPage.enterPassWord(password);
   LoginPage.clickLogin();

   // or even just LoginPage.login(username, password);
}

Now my test looks like this (regardless of the implemenation details):

testChangePassword() {

 // these three lines would of course be refactored into setup
 User bob = getRegisteredUser(...);
 Selenium browser = new Selenium(...);
 Site.attachBrowser(browser);

 Site.changePassword(user, newPassword);
 verifyTextPresent("Password changed");
}
Advertisements

12 thoughts on “Selenium Page Objects + Site Objects, Data Objects & High Level Navigation

  1. Pingback: Transitioning from Selenium IDE to Remote Control « Fiji Ecuador Seattle Greece Montana

  2. I am just starting work on automating journeys and behaviour on a CMS and 50 supported websites. I am going to be using WebDriver and FitNesse, implementing a PageObject pattern using Java. Your post is very helpful – nice to see some real page objects in action. Please post any more tips you have or problems encountered with the page object pattern.

    • Nathan, thanks for the comment. I haven’t played with webdriver yet, but since it’ll be the new selenium 2, I should be looking at it soon.

      I’d be interested in learning more about how other people abstract and organize their tests. It might make an interesting read, 10 essays from different individuals on how they approached their test automation. Some might be similar, some wildly different.

  3. I recommend having a look into Webdriver if you have time – I will write a “getting started with webdriver” blog post sometime soon and let you know the URL. I am also making good progress with the Webdriver/FitNesse project, will let you know about that soon too.

  4. Have you seen this blog post by Patrick Wilson-Welsh?

    http://patrickwilsonwelsh.com/?p=47

    He talks in there about creating a page object, and later than again about OOD in test code as well.

    Similarly, he and I are giving a talk at Agile2010 about design of test code.

    This is a topic I have been *very* interested in, as I believe testers are becoming more technical, but with less programming training. I want to teach them programming practices and patterns that will make their test code better.

    Keep it up, and thanks for writing this up.

  5. Pingback: Smart or dumb page objects « Fiji Ecuador Seattle Greece Montana

  6. Hi Aaron,

    A lil confusin over the page objects you have described above. I am under assumption that page objects should only perform tests with out making any assertions or verifications. But in method – “changePassword” I see verification being done in test itself. What is your take on this?

    Thanks
    Tarun K

    • Tarun-

      In theory, I agree with you that page objects shouldn’t contain assertions. But in practice, I’ve often added them to make it easier to do sanity checking.

      There’s a trade off. On the one hand, clean page objects are easier to maintain and are “pure” domain objects. On the other hand, having your page objects “test aware” (i.e. able to assert) makes for an easier framework and allows tests to be more concise and readable at the expense of the portability and conciseness of your page objects.

      I’ve spent a lot of time looking for a good way to achieve both, but haven’t see one that I like yet. Either your page objects become just a map of locators, or you do a lot of sanity checking in your tests.

  7. Hey Aaron,

    Great post (if maybe a little old, I wish I had stumbled here earlier), but your strategy for handling multipage tasks has me a little worried. Won’t the Site class quickly become a God object, if the site is of a decent size or greater? I’ve got one of those right now and the redesign I’m looking at doing is to break that object down, but maybe it’s not worth it if I’m just trading one megaclass for another.

    Seeing the age of this article, perhaps time has given you a different view of this strategy? How has this worked out for you, and what would you do differently if you could?

    • Charlie-

      The site object can become a “God” object (and looking back over my description it looks like I described it that way) but how I use it now is primarily as a page “factory” with a bit of intelligence. So it’s a bit of a controller/helper still. On my latest project I’ve reduced the site to merely a helper role and work with “smart” page objects that pass around a selenium instance through an explicit page factory. Each page then instantiates a page factory that can return the next page.

      So my test looks like this:


      public class MyTest extends TestBase {

      @Test
      public void testLogin() {
      page = PageFactory.create(LoginPage.class); // my selenium object is injected into the PageFactory from TestBase in @BeforeClass
      assertThat(page.getTitle() is(LoginPage.Expected.title));
      page = loginPage.login(username, password);
      assertThat(page.getTitle() is(HomePage.Expected.title));
      }

      and LoginPage looks like this


      class LoginPage extends PageBase {
      public static String username = "username-locator";
      public static String password = "password-locator";
      public static String loginButton = "login-button-locator";

      public static class Expected {
      public static String title = "Please login to MySite";
      }

      public HomePage login(username, password) {
      selenium.type(this.username, username);
      selenium.type(this.password, password);
      selenium.click(loginButton);

      return PageFactory.create(HomePage.class);
      }
      }

      This is a break from my former opinion that page objects should be “dumb” that is — they don’t know about selenium and how to click. It’s an experiment for me but follows the more traditional page object pattern.

      I haven’t really created a large project with this method but I could see building “navigation” methods that are more complex than login (for instance a “register” flow) that would be better suited to a “Site” object than a series of page objects.

      Separating the PageFactory from the “God” site class is, I think a good idea, but I’m leaning towards using composition and putting checks into a site object again — which becomes more explicitly like a controller.

      One other thing I’ve started doing (but am starting to feel against) is pushing exceptions onto an error stack the way selenese does with “verify” steps that is then checked in the test — to avoid assertions in page objects.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s