logo logo

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

.NET Core

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?

  • 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
  3. Edit your csproj file; Find the <PackageReference> tag containing TestProject SDK and add to it the Publish="false" property. This is required to package your code so you can upload it later.
  4. 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;
            }
        }
    }
  5. 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

First thing we need to do is package our code. To do so, simply do the following:

  1. Open command line and navigate to the folder we created our project in (not the solution!)
  2. Run this command: dotnet publish -o /your-output-folder ./your-project.csproj
  3. Open the folder you specified above. if it contains more than one DLL file just zip them all together. Otherwise you can simply upload the DLL.

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_NET Core

 

 

 

 

 

 

 

 

 

 

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_NET Core

 

 

 

 

 

 

 

 

 

 

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  😀 

Comments

11 9 comments
  • orleansd December 19, 2018, 8:45 pm

    I believe you need to add the Click() method to your #4 above. It is the URL method.

  • Emmerikku April 17, 2019, 3:25 pm

    I can’t import the .dll generated file on the web site.
    Upload error : Unable to load one or more of the requested types. Could not load file or assembly ‘TestProject.SDk Version ………

    Any ideas ?

  • Emmerikku April 17, 2019, 3:30 pm

    Another thing :
    this code doen’t work. Because xUnit generate his own Main() so error CS0017 at generation

    static void Main(string[] args)
    {
    using (Runner runner = RunnerFactory.Instance.CreateWeb(DevToken, BrowserType))
    runner.Run(new MyFirstTestProjectTest ());
    }

    • Shai Glatshtein April 18, 2019, 8:24 am

      Hi Emmeriku.
      Thanks for the reply! The provided example is a console application, hence the Main method. If you want to run the code in an xUnit test simply add the 2 lines (using runner + runner.run) in an xUnit test method and run the test.

  • Andrew Sieffert October 17, 2019, 11:19 pm

    Thanks for the post Shai! I was hoping you might be able to tell me how I create my methods when we dont have an id for the element.

    public void TypeAddress(string address)
    {
    addressElement.SendKeys(address);
    }

    It looks like we can simply pass it the id = name as a string parameter but I am not sure how I get the method to accept a cssselector when and id is not available.

    Please help!

    Thanks,
    Andrew

    • Shai Glatshtein October 23, 2019, 10:30 am

      Hi Andrew. Glad you are finding this post useful! The answer to your question is in another part of the ProfilePage class: the declaration!
      [FindsBy(How = How.Id, Using = “address”)]
      private IWebElement addressElement;
      As you can see, the “address” element is automatically located by the “FindsBy” attribute. the “How” is how you find it, and the “Using” is the value you search. If you want to search by other ways than Id, try one of the other values the “How” enum supports, such as “Name’ or “XPath”. Simply adjust the “Using” value according to your use.

Leave a Reply

FacebookLinkedInTwitterEmail