Design Pattern – Page Object Model

Design Pattern - Factory

In the first article of the this Design Pattern series, we looked at describing three of the more popular design patterns when it came to writing a framework. One of the three we learnt about was the Page Object Model design pattern. This is probably the most commonly used as it lends itself to creating simple and easy maintainable frameworks.

To give a quick reminder, the Page Object Model pattern was about breaking down a web application or website in to its each individual pages and having a class for each of these pages. And inside the class, we will wrap all of the elements and actions, as well as dealing with any validations. Although the validations can be done in the test, it also can make sense to put them in our page objects class too.

For this code example, we’re going to imagine we have a website that requires a login when navigating to that page. A user will enter their username and password and press the login button. Or if they forget their password, they can click a button to reset that as well. A pretty standard login page, I think you’ll agree.

So what would a page like this look like as a page object class. Let’s have a look:

public class LoginPage()
{
    private readonly IWebDriver driver;

    public LoginPage(IWebDriver webDriver)
    {
        this.driver = webDriver;
    }

    public IWebElement LoginButton = driver.FindElement(By.Id("idHere");
    public IWebElement ForgotPasswordButton = driver.FindElement(By.Id("idHere");
    public IWebElement UsernameTextBox = driver.FindElement(By.Id("idHere");
    public IWebElement PasswordTextBox = driver.FindElement(By.Id("idHere");
    public void EnterLoginDetails(string username, string password)
    {
        this.UsernameTextBox.Clear();
        this.UsernameTextBox.SendKeys(username);
        this.PasswordTextBox.Clear();
        this.PasswordTextBox.SendKeys(password);
        this.LoginButton.Click();
    }

    public void ClickForgotPassword()
    {
        this.PasswordTextBox.Click();
    }

}

Let’s start from the top. Depending on your implementation of the framework, every page objects class will have its own IWebDriver instance, that it will use inside its actions to interact with elements. This is then initialised in the constructor, which again, would be standard in every page objects class.

Then we get to the main code of the class, where we declare our elements and initialise them. We declare each element as Seleniums own IWebElement type, and we initialise it by using a FindElement call. In this example you can see I’ve used a generic ID, but obviously in your code you’d replace that with the actual ID, or other appropriate locator.

By doing this this way, we are able to use this elements inside our actions straight away, without having to initialise them in each method. This makes for far more maintainable code, and also reduces overhead.

There is another way of doing this without using the driver.FindElement call for each IWebElement.

FindsBy(How = How.Id, Using = "idHere")]
public IWebElement LoginButton { get; set; }

 

This uses the Selenium.Support classes, which are a separate Nuget package. I purposefully avoided using that way of initialising because we haven’t covered Selenium.Support yet. But we definitely will later on as it can be a cleaner way of doing it.

After we have created our elements, we have two methods for logging in and for if we have forgotten our password. Both are extremely simple methods due to the us already dealing with our elements in the class. We simply reference which element we want to use and perform the appropriate action. It really is that simple.

This clean and tidy class can now be used in a test. I’ll now take you through how we’d use a class like this in a simple test, to demonstrate just how well this design pattern works.

[TestFixture]
public class SearchEngineTests
{
    public IWebDriver Driver { get; set; }

    [Setup]
    public void SetupTest()
    {
        this.Driver = new ChromeDriver();
        this.Wait = new WebDriverWait(this.Driver, TimeSpan.FromSeconds(30));
        driver.Navigate().GoToUrl("http://www.ourwebsite.co.uk")
    }

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

    [Test]
    public void UserCanLogin()
    {
        LoginPage loginPage = new LoginPage(this.Driver);
        loginPage.EnterLoginDetails("admin", "goodpassword");
        Assert.IsTrue(driver.Url.Equals("http://www.ourwebsite.co.uk/homepage"), "Login was not successful")
    }

    [Test]
    public void UserCanResetPassword()
    {
        LoginPage loginPage = new LoginPage(this.Driver);
        loginPage.GoToResetPasswordPage();
        Assert.IsTrue(driver.Url.Equals("http://www.ourwebsite.co.uk/ForgottenPassword"), "Password reset not successful")
    }
}

Ignoring everything but the UserCanLogin() and the UserCanResetPassword() methods, as that would be generic to any test, you can see just how simple the tests that a Page Object Model framework allows are.

We simply create our LoginPage object, call the action method that we are testing, and then assert for the results. If, as mentioned earlier, you put your validators in your page objects class, then you would simply call that validate method instead of using an NUnit assert. But the number of lines would be the same.

Using this design pattern allows complete removal of all WebDriver code from our tests, instead keeping it wrapped up within our page objects class. This means that if at any point the behaviour of that particular page changes throughout the life cycle of that page, we simply update the page object class and our tests will still pass. This removes points of failure from the tests themselves and means code fixing in one place can cover hundreds of tests that use that class.

Tests are massively reduced in size by following this design pattern as well, due to only needing to call the page objects actions as opposed to writing code within the tests themselves.

Give the above code a try and modify the page objects class to add new elements and action methods. Then write your own test to call that action. And in the next article, we’ll look at how to implement a test using Facade.