In the last chapter of Browser Factory, we learned the concept of Factory Design Pattern and we also learned the importance of hiding complexities from the test and Encapsulating logics with in the Test Automation Framework.

The Thumb Rule of Factory Design Pattern is the avoiding object initialization in the test. Take a look at the current state of the LogInTest.cs :

    class LogInTest
    {
        [Test]
        public void Test() {

            BrowserFactory.InitBrowser("Firefox");
            BrowserFactory.LoadApplication(ConfigurationManager.AppSettings["URL"]);   

            var homePage = new HomePage(driver);
            homePage.ClickOnMyAccount();

            var loginPage = new LoginPage(driver);
            loginPage.LoginToApplication("LogInTest");

            BrowserFactory.CloseAllDrivers();
        }    
    }

Notice that the initializing the object of HomePage and LoginPage are still dependent on the Test. This is against the rule of the Factory Design Pattern.

In this chapter, we will learn How to hide the Page Object Initialization within the Test Framework and How to Design Page Generator. But before that we need to understand the concept of Generics and Constraints over Generics in C#.

Generics and Constraints over Generics

What are Generics?

Generics provides us a way to create Classes/Methods/Types and many more without specifying the specific type of parameters. Parameters must be provided at the time of creating the instances. So we can say Generics are only a blueprint/template version and actual type is defined at Runtime.

So let's first create a simple method say GetPage:

private static void GetPage<T>()
        {

        }

Here as you can see, I have created a generic method Page, which will take any PageObject class as a parameter. It can be HomePage class or LoginPage  or any other PageObject class. T represent Type, means any Type.

But the question is, that what type of PageObject would it return. If we are not sure of the input PageObject Class, we are not sure of the output as well. So, in this situation, it is feasible to use Generic type T as an output as well.

private static T GetPage<T>()
        {
             return T;
        }

What are Constrains?

Constraints are used in Generics to restrict the types that can be substituted for type parameters. When we create a new instance of a generic type we can restrict the type parameters using constraints. This means if any code tries to instantiate your class by using a type that is not allowed by a constraint, the result is a compile-time error. These restrictions are called constraints. Constraints are specified by using the where contextual keyword.

Where Clause

In a generic type definition, the where clause is used to specify constraints on the types that can be used as arguments for a type parameter defined in a generic declaration. The where clause may also include a constructor constraint. It is possible to create an instance of a type parameter using the new operator. The new() Constraint lets the compiler know that any type argument supplied must have an accessible parameterless or default constructor. For example:

private static T GetPage<T>() where T : new()
        {
            var page = new T();    
            return page;
        }

If we want this method to initialize PageObjects, the code will look like this:

      private static T GetPage<T>() where T : new()
        {
            var page = new T();
            PageFactory.InitElements(driver, page);       
            return page;
        }

Let's see how the above code will get fit in to our Test Automation Framework.

Page Generator Implementation

Create a new C# class in the PageObjects folder, name it as Page. Write the implementation of the Page class.

Page Class

using OnlineStore.WrapperFactory;
using OpenQA.Selenium.Support.PageObjects;

namespace OnlineStore.PageObjects
{
    public static class Page
    {
        private static T GetPage<T>() where T : new()
        {
            var page = new T();
            PageFactory.InitElements(BrowserFactory.Driver, page);       
            return page;
        }

        public static HomePage Home
        {
            get { return GetPage<HomePage>(); }
        }

        public static LoginPage Login
        {
            get { return GetPage<LoginPage>(); }
        }
    }
}

HomePage Page Object Class

using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;

namespace OnlineStore.PageObjects
{
    public class HomePage
    {
        private IWebDriver driver;

        [FindsBy(How = How.Id, Using = "account")]
        [CacheLookup]
        private IWebElement MyAccount { get; set; }

        public void ClickOnMyAccount()
        {
            MyAccount.Click();
        }
    }
}

I hope you noticed that the below code is been removed from the HomePage class. If you go to the previous chapter of Data Driven Technique, you will notice that HomePage class had the below constructor. Now the same duty is assigned to Page Generator class and it is no longer needed.

        public HomePage(IWebDriver driver)
        {
            this.driver = driver;
            PageFactory.InitElements(driver, this);
        }

LoginPage Page Object Class

using OnlineStore.TestDataAccess;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;

namespace OnlineStore.PageObjects
{
    public class LoginPage
    {
        private IWebDriver driver;

        [FindsBy(How = How.Id, Using = "log")]
        [CacheLookup]
        private IWebElement UserName { get; set; }

        [FindsBy(How = How.Id, Using = "pwd")]
        [CacheLookup]
        private IWebElement Password { get; set; }

        [FindsBy(How = How.Id, Using = "login")]
        [CacheLookup]
        private IWebElement Submit { get; set; }

        public void LoginToApplication(string testName)
        {
            var userData = ExcelDataAccess.GetTestData(testName);
            UserName.SendKeys(userData.Username);
            Password.SendKeys(userData.Password);
            Submit.Submit();
        }
    }
}

LogInTest.cs Test Case

using NUnit.Framework;
using OnlineStore.PageObjects;
using OnlineStore.WrapperFactory;
using OpenQA.Selenium;
using System.Configuration;

namespace OnlineStore.TestCases
{
    class LogInTest
    {
        [Test]
        public void Test() {

            BrowserFactory.InitBrowser("Firefox");
            BrowserFactory.LoadApplication(ConfigurationManager.AppSettings["URL"]);

            Page.Home.ClickOnMyAccount();
            Page.Login.LoginToApplication("LogInTest");

            BrowserFactory.CloseAllDrivers();
        }    
    }
}

With the help of Page Generator, calling PageObjects in the Test are really easy. The intellisence will pick up all the PageObject classes and will reflect that in the Page class. Take a look at the below screenshot.

Base Page Object Class

Project Solution Explorer

Base Page Objects project

Browser Factory or WebDriver Factory
Browser Factory or WebDriver Factory
Previous Article
WebElement Extensions Method
WebElement Extensions Method
Next Article
Lakshay Sharma
I’M LAKSHAY SHARMA AND I’M A FULL-STACK TEST AUTOMATION ENGINEER. Have passed 16 years playing with automation in mammoth projects like O2 (UK), Sprint (US), TD Bank (CA), Canadian Tire (CA), NHS (UK) & ASOS(UK). Currently, I am working with RABO Bank as a Chapter Lead QA. I am passionate about designing Automation Frameworks that follow OOPS concepts and Design patterns.

Similar Articles

Feedback