1. PageFactory & PageObjects

As part of the first advanced tutorial I thought it would be best to cover the Page factory and Page Objects. Now there is a large amount of terminology around regarding the Page Object Model, but I am a fan of KISS ( Keep It Simple Stupid) so I will break it down to common terms. Page Objects are by definition the pages within your website. If you imagine a site like a structure of a book you have a table of contents and chapters, as well as references to each page (page number). Translating this to a web project the table of contents could be considered the base of the site, and the chapters each type of page. The page numbers are therefore the content versions of each page type. So if we push this to an example site such as Google.com then we can say it has the following page types :

  • Homepage
  • Search Results Page

So we have two Page Objects, a homepage and a search results page. In this article I will only cover the homepage, since it holds enough to make a start with the Selenium PageFactory. So onto the PageFactory. In order to support the Page Object design, WebDriver includes inside its support section a factory called PageFactory that is designed to support Page Object models. Its support enables to better utilise the Page Object model to ensure that the tests are structured efficiently and all the advantages that come with it

Namespaces….
In the previous project I left the namespaces all as Selenium_Automation. For those who do not know, the namespace can be used as a reference for methods. A Namespace is known to declare a scope for code use. In our case the root namespace was Selenium_Automation, however we are going to declare sub namespaces that allow us to continue to interact with the methods and objects in subfolders of the project. Whilst this is not essential I thought now would be a good time to introduce this to show how it works… As you review the code below notice that the namespace changes for items to include Selenium_Automation.FolderName (FolderName referencing the folder in which the class file is located.) There is one exception to this rule. This is the TestFixture.cs file. This is not named spaced correctly for a specific reason. Placing this in the namespace Selenium_Automation.Setup will create another test row inside NUnit and will also close the browser before the tests are completed, making the system fail. As such I left the namespace as is.

A Diagram of Class…
So we have the description, the Namespaces and we are still a little unsure. Well they say a picture paints a thousand words so here goes :

Selenium PageFactory

This is the class diagram for the Selenium PageFactory

On to the code…
The first step is to create a PageObjectBase class. This is important because from the PageObjectBase class all other Page Objects can derive. A Base class allows us to perform basic tests and requirements that exist on all pages, or tasks that are common to all pages to allow code reuse, something that’s very important when coding tests. In this article I am assuming that you have completed the introduction section or downloaded the base project here

The Project Structure
Since we are now creating Page Objects, its suitable to create a new folder. In my example I have named this PageObjects. Inside here I will create all of the files that use my new Page Object design with the PageFactory provided by Selenium. To create the folder, as always :

Right click on the Project and select Add -> New Folder
Name the folder “PageObjects“

The PageObjectBase.cs File
Our first new file will be named PageObjectBase.cs. This is where we will begin the implementation. To create the required class file :

Select the PageObjects Folder in the Solution Explorer
On the Project menu, click Add New Item.
The Add New Item dialog box appears.
Select the Class File template
Enter the file name PageObjectBase.cs in the file name area
click Add.

Once this is completed the following code is needed. The code as normal will be commented so that its easier to understand. Put the code into the file, then ensure the file is saved.

PageObjectBase.cs

using System;
using OpenQA.Selenium;

namespace Selenium_Automation.PageObjects
{
    /* The PageObjectBase class.  This class is to represent anything that applies to all pages
     * of the site to be tested.  The example here shows that you can pass in by default a
     * Title from the driver to ensure that the correct page is loaded, but the intention 
     * of this is to provide a base class so that codereplication is kept to a minimum
     */
    class PageObjectBase
    {
        private IWebDriver Driver { get; set; }

        public PageObjectBase(IWebDriver driver,String titleOfPage)
        {
            Driver = driver;
            if (driver.Title != titleOfPage)
                throw new NoSuchWindowException("PageObjectBase: The Page Title doesnt match.");

        }
    }
}

As you can see from above its very basic and lightweight. Over time this class will extend to include more functionality, but for the purpose of this post, I thought this was enough. In Order to call the PageObjectBase you have to pass in the enabled driver object and an expected title.

The Homepage.cs File

Our next new file will be named Homepage.cs. To create the required class file :

Select the PageObjects Folder in the Solution Explorer
On the Project menu, click Add New Item.
The Add New Item dialog box appears.
Select the Class File template
Enter the file name Homepage.cs in the file name area
click Add.

Once completed, add the code below and save the file:

Homepage.cs

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

namespace Selenium_Automation.PageObjects
{
    class HomePage : PageObjectBase
    {

        private IWebDriver Driver{ get; set; }

        /*These are the elements that you wish to reference on the page.  The first part
         * is to identify how to actually find the element, including the search
         * reference so I have given Name and XPath examples and the second part is 
         * the object reference that you wish to assign.  I have given public and private 
         * references so that you can see how each are used
         */

        [FindsBy(How = How.XPath, Using = ".//TITLE")]
        public IWebElement Title{ get; set; }

        [FindsBy(How= How.Name,Using="q")]
        private IWebElement SearchDialog { get; set; }

        [FindsBy(How = How.Name, Using = "btnG")]
        private IWebElement SearchButton { get; set; }

        public HomePage(IWebDriver driver)
            /*This is to reference the PageObjectBase Class.  Passing in the Title of the 
             * page that is expected for the HomePage to ensure the correct Page is loaded
             * before starting any tests.
             */
            : base(driver,"Google")
        {
            Driver = driver;
            PageFactory.InitElements(driver, this);
        }

        /* Here are two example methods of using the newly generated objects with friendlier
         *  names so that it clearly identifies the task being completed
         * Note that there is no validation on these, so if they are to fail there will 
         * be no friendly output provided other than the failure.
         */
        public void EnterSearchText(String text)
        {
            SearchDialog.SendKeys(text);
        }
        public void Search()
        {
            SearchButton.Click();
        }
    }
}

Once completed this is the page object files competed. As you can see from the homepage file it includes all of the element definitions, declaring elements by Id and Name and turning them into sensible object names. Google is a good example of this, as none of the names or Id’s are usually generated and as such don’t make much sense when used in code. Replacing this with a name makes the code readable, and also ensures that if the element name or Id changes then its only a single place to alter it and the test can continue to function.

The existing files
Since there are also changes required to the existing files, I have included the source for each of them before. Each files should have all of the code replaced with the code below and then saved.

WebDriver.cs

using System;
using System.Configuration;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Firefox;

namespace Selenium_Automation.Selenium
{
    public class SeleniumDriver
    {
        private static IWebDriver _driver;
        protected static IWebDriver WebDriver
        {
            get
            {
                if (_driver == null)
                {
                    string driverConfig = ConfigurationManager.AppSettings["browser"];
                    if (!String.IsNullOrEmpty(driverConfig))
                    {
                        switch (ConfigurationManager.AppSettings["browser"])
                        {
                            case "Chrome":
                                _driver = new ChromeDriver();
                                ConfigureDriver();
                                break;
                            case "Firefox":
                                _driver = new FirefoxDriver();
                                ConfigureDriver();
                                break;
                            case "IE":
                                _driver = new InternetExplorerDriver();
                                ConfigureDriver();
                                break;
                            default:
                                Console.WriteLine("App.config key error.");
                                Console.WriteLine("Defaulting to Firefox");
                                _driver = new FirefoxDriver();
                                ConfigureDriver();
                                break;
                        }
                    }
                    else
                    {
                        Console.WriteLine("* * * * DEFAULTMODE * * * *");
                        Console.WriteLine("App.config key not present.");
                        _driver = new FirefoxDriver();
                        ConfigureDriver();
                    }
                }
                return _driver;
            }
        }
        internal static void ConfigureDriver()
        {
            _driver.Manage().Timeouts().ImplicitlyWait(new TimeSpan(0, 0, 30));
            _driver.Manage().Cookies.DeleteAllCookies();
            _driver.Manage().Window.Maximize();
        }
        public static void Visit(String url)
        {
            var rootUrl = new Uri(url);
            WebDriver.Navigate().GoToUrl(rootUrl);
        }
    }
}

testFixture.cs

using NUnit.Framework;
using Selenium_Automation.Selenium;

namespace Selenium_Automation
{
    [SetUpFixture]
    public class TestConfiguration : SeleniumDriver
    {
        [SetUp]
        public void FixtureSetup()
        {
        }

        [TearDown]
        public void FixtureTearDown()
        {
            if (WebDriver != null) WebDriver.Quit();
        }
    }
}

exampleTest.cs

using NUnit.Framework;
using System.Threading;
using Selenium_Automation.PageObjects;
using Selenium_Automation.Selenium;

namespace Selenium_Automation.Tests
{
    [TestFixture]
    public class GoogleTest : SeleniumDriver
    {
        [SetUp]
        public void TestSetUp()
        {
            Visit("http://www.google.com");
        }

        [Test]
        public void GooglePageTitle()
        {
            /*  The first task is to create the HomePage Object.  This
             * allows us to reference the Homepage and all of the defined elements
             * as shown in the examples below
             */
            HomePage homepage = new HomePage(WebDriver);

            /* Here is an example of using the new Elements to validate information
             * within the webpage using the pagefactory
             */
            Assert.AreEqual("Google", homepage.Title.Text);
            homepage.EnterSearchText("hello world");
            homepage.Search();
            /* The following sleep statement has only been placed in this code to demostrate
             * the button push as there is no validation of the search results in place
             * so its not possible to actually see the result of the button click.
             */ 
            Thread.Sleep(5000);
        }
    }
}

Once completed you should have all of the code required to build the solution. The final piece of code shows how the PageObject is loaded, but also how to use your PageObjects and methods to ensure suitable testing. As part of the tutorial I have uploaded the final code to the repository available here

After a lot of hunting around and sorting out the various tutorials and suggestions I feel that this is a significant step towards a default selenium project for C#. Whether you use the project, or just the code for reference in your own project, I hope that it is useful.

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