.NET Core Test Automation in Selenium Using Page Object & Page Factory

Selenium is a very powerful tool if you want to automate your web testing, and a few months ago even support for .NET Core tests was added to Selenium. However, .NET Core is missing the best feature that Selenium gives you: support for Page Object Model (or POM for short).

If you are unfamiliar with the concept, don’t worry! By the end of this tutorial you will be ready to write great tests using Page Object Model, and this is where TestProject‘s test automation platform comes in to help. TestProject‘s .NET Core SDK provides the ability to write tests using the PageFactory class and Page Object Model, all in one place.

So, what are the advantages of using .NET Core to write your tests?

  • C# is a powerful language with syntax such as nullables, properties, linq and more.
  • .NET Core runs on every platform, including Linux and macOS. Thus, you can easily write cross-platform tests!
  • .NET Core has some of the best development tools on the market, such as Visual Studio and Resharper. 

Tutorial Overview

  1. Build your First Selenium Test with .NET Core.
  2. Use Page Object Model to Improve your Selenium Test.
  3. Use TestProject SDK to Make Page Factory Work with .NET Core.
  4. Upload .NET Core Test Automation in Selenium to TestProject Using Page Object.
  5. Example of TestProject Framework Execution Reports.

1. Build your First Selenium Test with .NET Core

Let’s start this tutorial by learning how to create a “plain” Selenium automated test:
This test example below automates the TestProject Demo website. You can view the website here.

In this test we will do the following:

  1. Create a driver
  2. Navigate to example page
  3. Enter username & password
  4. Click login
  5. Input new profile pages
  6. Update the profile

Here’s the full code sample:

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System;

namespace SeleniumTests
{
  class MyFirstSeleniumTest
  {
    string name = "John Smith";
    string password = "12345";
    string country = "United States";
    string address = "Street number and name";
    string email = "[email protected]";
    string phone = "+1 555 555 55";

    public bool Execute()
    {
      // Create driver
      var driver = new ChromeDriver("PathToDriverExecutableLibrary");

      // Navigate to example page
      driver.Navigate().GoToUrl("https://example.testproject.io/web/");

      // Enter username & password
      driver.FindElementById("name").SendKeys(name);
      driver.FindElementById("password").SendKeys(password);

      // Click login
      driver.FindElementById("login").Click();

      // Input new profile pages
      new SelectElement(driver.FindElementById("country")).SelectByText(country);
      driver.FindElementById("address").SendKeys(address);
      driver.FindElementById("email").SendKeys(email);
      driver.FindElementById("phone").SendKeys(phone);

      // Update the profile
      driver.FindElementById("save").Click();
      return new WebDriverWait(driver, TimeSpan.FromSeconds(2)).Until(d => d.FindElement(By.Id("saved")).Displayed);
    }
  }
}

Let’s break it down, step by step:

1. Create a driver
var driver = new ChromeDriver("PathToYourDriverExeLibrary");

Here we use Selenium’s ChromeDriver. It receives a path to chromedriver executable folder. Every time we want to automate a different browser, we will have to create a different driver.

2. Navigate to the example page
driver.Navigate().GoToUrl("https://example.testproject.io/web/");

3. Enter username & password

driver.FindElementById("name").SendKeys(name);
driver.FindElementById("password").SendKeys(password);

Here we find the input fields by their ID’s and fill them out by using SendKeys. If we would have to do this more than once, we will have to locate the elements again.

4. Click login
driver.FindElementById("login").Click();

5. Input new profile pages

new SelectElement(driver.FindElementById("country")).SelectByText(country);
driver.FindElementById("address").SendKeys(address);
driver.FindElementById("email").SendKeys(email);
driver.FindElementById("phone").SendKeys(phone);

This is the same as step 3 above, except that now we have an additional combo box to select from, using the SelectElement wrapper.

6. Update the profile

driver.FindElementById("save").Click();
return new WebDriverWait(driver, TimeSpan.FromSeconds(2)).Until(d => d.FindElement(By.Id("saved")).Displayed);

In addition to saving the profile, we waited for the “saved” element that becomes visible after save.

This code is fine for a first test. However, it has several problems:

  • We have to manually search for the elements we want to use. If we reload the page, we have to do this several times.
  • If we want to do things more than once, we’ll have to duplicate our code.
  • From this example, it is hard to know on which page we are.

Let’s see how we can get around these problems using Page Object Model.

 

2. Use Page Object Model to Improve your Selenium Test

Page Object Model is an object design pattern in Selenium. In this design pattern web pages are represented as classes, and the various elements on the page are defined as variables on the class. All possible user interactions can then be implemented as methods on the class.

Let’s use Page Objects to represent our example. In our case we have 2 pages: the Login Page and the Profile Page.
Let’s look at the Login Page first:

using OpenQA.Selenium;
using SeleniumExtras.PageObjects;

namespace SeleniumTests
{
  class LoginPage
  {
    [FindsBy(How = How.Id, Using = "name")]
    private IWebElement nameElement;
    [FindsBy(How = How.Id, Using = "password")]
    private IWebElement passwordElement;
    [FindsBy(How = How.Id, Using = "login")]
    private IWebElement loginElement;
    public bool Displayed => nameElement.Displayed;
    public void TypeName(string name)
    {
      nameElement.SendKeys(name);
    }
    public void TypePassword(string password)
    {
      passwordElement.SendKeys(password);
    }
    public void ClickLogin()
    {
      loginElement.Click();
    }
    public void Login(string name, string password)
    {
      TypeName(name);
      TypePassword(password);
      ClickLogin();
    }
  }
}

Each page object consists of a set of elements, marked with the [FindsBy]attribute. This attribute tells us how to locate the element on the page. As of writing this article the attribute exists in 2 namespaces: OpenQA.Selenium.Support.PageObjects.FindsByAttributeand SeleniumExtras.PageObjects.FindsByAttribute. The former is the “old” version and therefore we use the latter.
Every action you can do to the page has a method, and information such as the page’s saved state is retrieved by property.

Let’s look at our other page, the Profile Page:

using OpenQA.Selenium;
using SeleniumExtras.PageObjects;
using OpenQA.Selenium.Support.UI;

namespace TestProjectTests
{
  class ProfilePage
  {
    [FindsBy(How = How.Id, Using = "logout")]
    private IWebElement logoutElement;
    [FindsBy(How = How.Id, Using = "country")]
    private IWebElement countryElement;
    [FindsBy(How = How.Id, Using = "address")]
    private IWebElement addressElement;
    [FindsBy(How = How.Id, Using = "email")]
    private IWebElement emailElement;
    [FindsBy(How = How.Id, Using = "phone")]
    private IWebElement phoneElement;
    [FindsBy(How = How.Id, Using = "save")]
    private IWebElement saveElement;
    [FindsBy(How = How.Id, Using = "saved")]
    private IWebElement savedElement;
    public bool Displayed => logoutElement.Displayed;
    public bool Saved => savedElement.Displayed;
    public void SelectCountry(string country)
    {
      var countrySelect = new SelectElement(countryElement);
      countrySelect.SelectByText(country);
    }
    public void TypeAddress(string address)
    {
      addressElement.SendKeys(address);
    }
    public void TypeEmail(string email)
    {
      emailElement.SendKeys(email);
    }
    public void TypePhone(string phone)
    {
      phoneElement.SendKeys(phone);
    }
    public void UpdateProfile(string country, string address, string email, string phone)
    {
      SelectCountry(country);
      TypeAddress(address);
      TypeEmail(email);
      TypePhone(phone);
      Save();
    }
    public void Save()
    {
      saveElement.Click();
    }
    public By GetPhoneElement()
    {
      return By.Id("phone");
    }
  }
}

Looking good so far! Now it is time to update our test to use our new page objects:

public bool Execute()
{
    var driver = new ChromeDriver("PathToDriverExecutableLibrary");
    driver.Navigate().GoToUrl("https://example.testproject.io/web/");

    var loginPage = PageFactory.InitElements<LoginPage>(driver);
  loginPage.Login(name, password);

    var profilePage = PageFactory.InitElements<ProfilePage>(driver);
    profilePage.UpdateProfile(country, address, email, phone);

    return profilePage.Saved;
}

Now our code looks much cleaner and easier to understand. However, there is a problem: our test no longer works! If we run it, we’ll get this error:

System.NullReferenceException : Object reference not set to an instance of an object.
	at SeleniumTests.LoginPage.TypeName(String name)
	at SeleniumTests.LoginPage.Login(String name, String password)
	at SeleniumTests.MyFirstSeleniumTest.Execute()

 

3. Use TestProject SDK to Make Page Factory Work with .NET Core

Our problem was that nothing actually filled the page object – All the elements are null.
In .NET Framework Selenium implemented the PageFactory class and it was used to create and populate page objects. However, it was found to be problematic and the code was removed from the .NET Core version. Therefore, you cannot use Page Object Model in .NET Core with Selenium in its current state.

Lucky for us, we already have the solution: TestProject!  😉
It contains a PageFactory similar to selenium’s old version. 
To use PageFactory follow these steps:

  1. Get TestProject Agent: No worries, TestProject is FREE to use forever. All you need to do is sign up (here), install and register the TestProject agent. Here‘s a quick video showing just how you can do it. 
  2. Add the TestProject.SDK NuGet package to your project (it can be found here: nuget.org). NOTE: TestProject.SDK requires .Net Core 2.1 or higher
  3. Change our test to use TestProject:
    using TestProject.SDK.Tests;
    using TestProject.SDK.Tests.Helpers;
    using TestProject.SDK.PageObjects;
    
    namespace TestProjectTests
    {
        public class MyFirstTestProjectTest : IWebTest
        {
    
            public string name = "John Smith";
            public string password = "12345";
            public string country = "United States";
            public string address = "Street number and name";
            public string email = "[email protected]";
            public string phone = "+1 555 555 55";
    
    
            public ExecutionResult Execute(WebTestHelper helper)
            {
                // Get driver initialized by TestProject Agent
                // No need to specify browser type, it can be done later via UI
                var driver = helper.Driver;
    
                // Navigate to TestProject Demo website
                driver.Navigate().GoToUrl("https://example.testproject.io/web/");
    
                // Initialize the properties of the LoginPage with the driver
                var loginPage = new LoginPage();
                PageFactory.InitElements(driver, loginPage);
    
                // Login using provided credentials
                loginPage.Login(name, password);
    
                // Initialize the properties of the profilePage with the driver
                var profilePage = new ProfilePage();
                PageFactory.InitElements(driver, profilePage);
    
                // Complete profile forms and save it
                profilePage.UpdateProfile(country, address, email, phone);
    
                return profilePage.Saved ? ExecutionResult.Passed : ExecutionResult.Failed;
            }
        }
    }
  4. Create a runner class that runs the test:
    using TestProject.SDK.Common.Enums;
    
    namespace TestProjectTests
    {
      class Program
      {
        private static string DevToken = "YOUR_DEV_TOKEN";
        private static AutomatedBrowserType BrowserType = AutomatedBrowserType.Chrome; // Choose different browser as needed
    
        static void Main(string[] args)
        {
          using (Runner runner = RunnerFactory.Instance.CreateWeb(DevToken, BrowserType))
            runner.Run(new MyFirstTestProjectTest ());
        }
      }
    }

If we execute the runner class we’ll see that the test is now successful! All that is left is to upload the test to TestProject Platform.

 

4. Upload .NET Core Test Automation in Selenium to TestProject Using Page Factory

In order to upload your test to TestProject, navigate to app.testproject.io, then click on “New Test” and choose the “Code” option:

TestProject_NewTest

 

 

 

 

 

 

 

 

 

 

Click “next”, upload your DLL file and create a test package (in our example – we created a web coded test, so we chose “web”):

TestProject_UploadTest

 

 

 

 

 

 

 

 

 

 

TestProject_CreatePackage

 

 

 

 

 

 

 

 

 

 

Now we can execute our test, just click on the play button:

TestProject_ExecuteTest

 

 

 

 

 

 

Or you can create a new job to execute your test. A job can aggregate as many tests as you want; run them on multiple browsers or devices; it has a built-in scheduler; email notification and webhooks; and you can even choose on which agent to run the job – it can run on any agent that is connected to your account:

TestProject_NewJob

 

 

 

 

 

 

 

 

 

5. Example of TestProject Framework Execution Reports

Once the execution is completed, go ahead to the TestProject reports section to explore detailed reports that provide extensive insights on your test and execution targets (browsers or mobile devices – in our case, we ran the job against multiple browsers – as seen in the screenshot below). Get a deep dive into each and every test step to view screenshots, execution durations and quickly identify failed steps:

TestProject_Reports

 

Summary

That’s it! We have completed our first TestProject .NET Core automated test in Selenium using Page Object Model and Page Factory. You can now go on to creating your own unique tests using TestProject’s .NET Core SDK that provides you with the ability to write web and mobile (Android and iOS) tests using the PageFactory class and Page Object Model, all in one place.

Let us know how it goes for you! We would love to hear your feedback in the comments section below  😀