Writing Automated Tests Using Page Object Design Pattern


A few years ago, I was working on a project where we decided to use Selenium as our automation tool. Writing automated tests was easy and very soon we ended up copying & pasting code and at times hardcoding values in tests and using xpath to get the job done. Although such tests were quick to write and were giving us results that we wanted at the time, as our test suite grew, we could see ourselves getting into a test code maintenance nightmare. We fixed this problem by using the Page Object Design Pattern.

Lets explain with an example. Look at this code:

Site.Click("crt1h-yth");
Site.Click("chk-formb1");
Site.Click("pay_02");

and compare it with this

Shop.AddToBasket();
Shop.ReviewOrder();
Shop.MakePayment();

The functionality is the same, but the way to present this is so much different.

So what are page objects ?

Page Object is a Design Pattern which has become popular in test automation for enhancing test maintenance and reducing code duplication. Page object is a language neutral pattern for representing a complete page or a part of a page in an objected oriented manner. We use them to model the application’s user interface. Page objects expose methods that reflect things that a user can see and do on a page. It also hides the details of telling the browser how to do those things. In short, page object encapsulates behaviour of a page.

Your tests then use the methods of this page object class whenever they need to interact with that page of the UI. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently all changes to support that new UI are located in one place.

So why use page objects? The main reasons are:

  • Maintenance
  • Readability of scripts
  • Reduced or eliminated duplication
  • Reusability

Consider the following example in webdriver and C#, which does not use page object design.

namespace SeleniumTests
{
[TestFixture]
public class LinkedInTest
{
private IWebDriver driver;
private string baseURL;

[SetUp]
public void SetupTest()
{
driver = new FirefoxDriver();
baseURL = "http://www.linkedin.com/";

}

[TearDown]
public void TeardownTest()
{
driver.Quit();
}

[Test]
public void LoginAndLogoutOfLinkedIn()
{
driver.Navigate().GoToUrl(baseURL + "/");
driver.FindElement(By.Id("session_key-login")).Clear();
driver.FindElement(By.Id("session_key-login")).SendKeys("test@123.com");
driver.FindElement(By.Id("session_password-login")).Clear();
driver.FindElement(By.Id("session_password-login")).SendKeys("awesomePswd");
driver.FindElement(By.Id("btn-login")).Click();
Assert.assertTrue(driver.isElementPresent("inbox button"),
                                "Login was unsuccessful");
driver.FindElement(By.LinkText("Sign Out")).Click();
}

}
}

here are a few problems with this approach:

  1. There is no clear separation between test methods and the locators of the application (Ids or LinkText locators that I’ve used above). They are all in a single method. If the application changes its identifiers or layout, then the tests must change.
  2. Imagine a scenario of multiple tests which require the use of this login functionality (There can be various scenarios to do with testing login functionality). The same login code will be repeated again and again in each test. Any change in UI will mean that all tests will have to be modified.
  3. The above code is not very readable, or maintainable, has duplication and is not reusable. (Exactly the issues addressed by Page Object Design).

By applying the Page Object design technique, I can rewrite the above test in the following way.

Firstly here is the flow that I will be automating using C#, Visual Studio 2010, NUnit and Webdriver:

  1. Navigate to the LinkedIn Homepage
  2. Enter Username
  3. Enter Password
  4. Click Login
  5. Assert Some Text is appearing on the HomePage after login
  6. Logout
  7. Assert that I have signed out

Below is the Page Object for the Login-In Page:

using System;
using OpenQA.Selenium;

namespace SeleniumTests
{
    public class LoginPage
    {
        protected readonly IWebDriver WebDriver;

        public LoginPage(IWebDriver webdriver)
        {
            this.WebDriver = webdriver;

            string title = WebDriver.Title;

            if (!title.Equals("World's Largest Professional Network | LinkedIn"))
            {
                throw new InvalidOperationException("This is not the Login Page. Current page is: "
                                                    + title);
            }
        }

        /*
        A property to enter username on login page
     */
        public string EnterUsername
        {
            set
            {
                IWebElement usernameField = WebDriver.FindElement(By.Id("session_key-login"));
                usernameField.Clear();
                usernameField.SendKeys(value);
            }
        }

        /*
        A property to enter password on login page
     */
        public string EnterPassword
        {
            set
            {
                IWebElement passwordField = WebDriver.FindElement(By.Id("session_password-login"));
                passwordField.Clear();
                passwordField.SendKeys(value);
            }
        }

        /*
        A method to click the login button on the page
     */
        public void ClickLogin()
        {
            IWebElement loginButton = WebDriver.FindElement(By.Id("btn-login")); // find the login button
            loginButton.Click(); // click on the login button
        }
    }
}

Here is how the Page Object for the HomePage looks like

using System;
using OpenQA.Selenium;

namespace SeleniumTests
{
    public class HomePage
    {
        protected readonly IWebDriver WebDriver;

        public HomePage(IWebDriver webdriver)
        {
            this.WebDriver = webdriver;

            string title = WebDriver.Title;

            if (!title.Equals("Welcome, Hasan! | LinkedIn"))
            {
                throw new InvalidOperationException("This is not the HomePage. Current page is: "
                                                    + title);
            }
        }

        /*
A method to logout of LinkedIn
*/
 public void Logout()
        {
            driver.FindElement(By.LinkText("Sign Out")).Click();
        }
    }

Finally, here is my LinkedIn login Test that consumes the two page objects:

using NUnit.Framework;
using OpenQA.Selenium;

namespace SeleniumTests
{
    [TestFixture]
    public class LinkedInTest
    {
        private IWebDriver _driver;

        [SetUp]
        public void Setup()
        {
            _driver = SeleniumHelper.GetSelenium();
        }

        [TearDown]
        public void TearDown()
        {
            SeleniumHelper.TearDownCurrentSelenium();
        }

        [Test]
        public void VerifyThatYouCanLoginAndLogoutOfLinkedIn()
        {
           // Create an instance of the Login Page
            LoginPage login = new LoginPage(_driver);

            //Enter Username
            login.EnterUsername = "HasanAziz@test.com";

            //Enter Password
            login.EnterPassword = "XYZ123";

            // Click Login
            login.ClickLogin();

            //Create an instance of the HomePage
            HomePage home = new HomePage(_driver);

            //Assert that some text is displayed on the Home Page
            Assert.IsTrue(_driver.PageSource.Contains("LinkedIn Today:"));

            //LogOut from LinkedIn
            home.Logout();

            // Assert
            Assert.IsTrue(_driver.Title.Equals("Signed Out | LinkedIn"));
        }
    }
}

As you can see from above, this is much more clearer now. There is a lot of flexibility when it comes to designing page objects. I could also use techniques like inheritance, function overloading or any other Object Oriented programming concept, and  instead of having the browser name and LinkedIn URL set as hardcoded values, I could have picked them up from a config file etc. Lets take another example of the thetrainline.com homepage. You can automate it using a single Page Object if you wish, but ideally you can have five or six Page objects for the home page only.

Although the flexibility is present, there are a few basic rules that you should adhere to for mainiting your code:

  1. Do not create all page objects at once, do only what you need at this given time. You can spend weeks in trying to create page objects for your whole application and this would be a waste of time. Your page objects will grow when new requirements come in which inturn will require new test scripts.
  2. Asserts do not belong in page objects, they belong in test scripts. Page objects only interact with the page, they do not make the decision of whether something is passing or failing.
  3. An exception to the above rule is that there should be one single verification within the page object and that is to verify that the page and any important elements within the page were loaded correctly. This verification should be done while instantiating the page object. In my example above, both the LogInPage and HomePage constructors check that the expected page is available and ready for requests from the test.

Further Reading:

  • There are other Design Patterns, some use Page Factory for instantiating objects. More on this here
  • Some Information on Page objects can be found on code.google.com
  • Very Useful test design considerations can be found on the Selenium HQ site

Books on this topic:

3 thoughts on “Writing Automated Tests Using Page Object Design Pattern

  1. Pingback: 5 Common Pitfalls of UI Test Automation – Software Drivel

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