Selenium‌ ‌C#‌:‌ Page‌ ‌Object‌ ‌Model‌ Tutorial With‌ ‌Examples

While writing Selenium test automation scripts, it is important to make sure that your test scripts are scalable and can keep up with any changes in the UI of your web page. But, sadly it’s easier said than done! The goal of automated UI web tests is to verify the functionality of the elements of the web page. With the ever-changing UI, the web locators also change at times, these frequent changes in the web locators can make the task of code maintenance quite challenging.

I have come across many scenarios, where tests failed due to lack of proper maintenance. At times, a change in web locator, when the Selenium test automation scripts are not updated, can cause ‘most’ of the tests to fail!

Page Object‌ ‌Model‌ Tutorial

The solution to this was simple, restructure the Selenium test automation scripts to make it more modular and minimize the duplication of code. So, how did I do it? I used a design pattern called the Page Object Model (POM), which helped in restructuring the code, thereby minimizing the overall effort spent in test code maintenance activity.

In this Selenium C# tutorial, I’ll give you a detailed look at Page Object Model along with how to implement it to make sure you maintain your Selenium test automation scripts in a better manner.

Page Object Model is language-agnostic, which means that the basic principles which we would discuss can be used for other programming languages like Python, Java, etc.

Why use Page Object Model in Selenium C#?

Selenium test automation scripts become more complex as the web applications add more features and web pages. With every new page added, new test scenarios are included in the Selenium test automation scripts. With this increase in LOC (Lines of Code), code maintenance can. become very tedious and time-consuming. Also, the Repetitive use of web locators and their respective test methods can make the test code difficult to read.

Instead of spending time updating the same set of locators in multiple Selenium test automation scripts, a design pattern such as the Page Object Model can be used for the development and maintenance of code.

In this Selenium C# tutorial, i’d implement Page Object Model is a widely used design pattern in Selenium that:

  • Minimizes the usage of duplicated code.
  • Makes the code more readable & maintainable as there is immense focus on aspects such as reusability, extensibility, etc.

What is Page Object Model (POM) In Selenium C#?

Page Object Model in Selenium C# is a design pattern that is extensively used by the Selenium community for automation tests. The basic design principle that the Page Object Model in Selenium C# follows is that a central object repository should be created for controls on a web page. Hence, each web page will be represented by a separate class.

The Page Objects (or page classes) contains the elements of the corresponding web page along with necessary methods to access the elements on the page. Hence, Selenium test automation implementation that uses the Page Object Model in Selenium C# will constitute different classes for each web page thereby making code maintenance easier.

For example, if automation for a login page & check-out page is to be performed, our implementation will have a class each for login & check-out. The controls for the login page are in the ‘login page’ class and controls for the check-out page are in the ‘check out page’ class.

The Selenium test automation scripts do not interact directly with web elements on the page, instead, a new layer (i.e. page class/page object) resides between the test code and UI on the web page.

In complex Selenium test automation scenarios, Selenium test automation scripts based on Page Object Model can have several page classes (or page objects). It is recommended that you follow a common nomenclature while coming up with file names (representing page objects) as well as the methods used in the corresponding classes.

Advantages of Page Object Model In Selenium C

Below are some of the major advantages of using the Page Object Model in Selenium C#

  • Better Maintenance – With separate page objects (or page classes) for different web pages, functionality or web locator changes will have a less impact on the change in test scripts. This makes the code cleaner and more maintainable as Selenium test automation implementation is spread across separate page classes.
  • Minimal Changes Due To UI Updates – The effect of changes in the web locators will only be limited to the page classes, created for automated browser testing of those web pages. This reduces the overall effort spent in changing test scripts due to frequent UI updates.
  • Reusability – The page object methods defined in different page classes can be reused across Selenium test automation scripts. This, in turn, results in a reduction of code size as there is the increased usage of reusability with Page Object Model in Selenium C#.

Apart from these advantages of Page Object Model in Selenium C#, another plus point of using this design pattern I’d like to point out in this Selenium C# tutorial is that it simplifies the visualization of the functionality and model of the web page as both these entities are located in separate page classes.

What is Page Factory?

Page Factory, an extension to Page Objects, is primarily used for initialization of the web elements defined in the page classes (or page objects). Web elements used with Page Objects have to be initialized before they can be used further and Page Factory simplifies the initialization with the initElements method.

Shown below are some of the ways in which initElements function can be used:

Method 1

HomePage test_page = new HomePage(web_driver);
PageFactory.initElements(web_driver, test_page);

Method 2

HomePage page = PageFactory.intElements(web_driver,HomePage.class)

Method 3

//Constructor of HomePage Class
public HomePage(WebDriver web_driver) {           
      this.driver = web_driver; 
      PageFactory.initElements(web_driver, this);
}

Using @FindsBy annotation, every WebElement variable is initialized by the Page Factory based on the locators configured to locate the element on the web page.

[FindsBy(How = How.XPath, Using = "//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input")]
[CacheLookup]
private IWebElement elem_search_text;

The @FindsBy annotation can accept XPath, Id, Name, CssSelector, ClassName, LinkText, PartialLinkText, etc. as attributes

page-object-csharp

Each time a method is called on the WebElement variable, e.g. elem_search_text in the code snippet above for this Selenium C# tutorial, the Selenium WebDriver will find the element on the current web page before simulating the UI interaction.

The @CacheLookup annotation is used to cache the looked up field that was earlier read using the @FindsBy annotation. This annotation is very useful in scenarios where we know that the elements on the webpage can always be found, and we won’t be spending much time on that particular webpage, i.e. we would eventually navigate to a new page.

The @FindsBy annotation supports many other strategies; however, most of these strategies are not relevant in the context of cross browser testing or automated browser testing. More details about Page Factory are available on the official page of Page Factory.

How To Implement Page Object Model In Selenium C

For Selenium test automation script development, we use the Visual Studio IDE (Community Edition) which can be downloaded from here. In case you want to know more about setting up selenium in visual studio, you can refer to our previous article on the topic.

To get started with this Selenium C# tutorial, you’ll have to create an account on LambdaTest and make a note of the user-name & access-key which is available on the Profile Page. We use the LambdaTest capabilities generator to generate Selenium capabilities for different browser + OS combinations.

Page Object Model – Directory Structure

Though the choice of the directory structure purely depends on the test requirements, it is important that a uniform structure is followed so that there are no ambiguities in development & enhancements.

We use the directory structure shown below for demonstrating the usage of the Page Object Model with C# and Selenium.

project-directory

As shown in the structure above, the directory Project-Directory\Src\PageObject\Pages contains the Page Classes/Page Objects for the different web pages. The directory Project-Directory\Test\Scripts contains the actual test code implementation. The file(s) present in this directory will use the C# test framework (NUnit/xUnit/MSTest) for automated browser testing.

Page Object Model – Demonstration Using C#, Selenium, and NUnit Framework

We make use of the NUnit test framework in this Selenium C# tutorial for test case development. In case you want to know more about the NUnit test framework, you can take a look at our detailed article on NUnit test automation using Selenium C#.

To get started, create a new NUnit test Project in C# navigating to New -> Project -> NUnit Test Project (.Net Core).

create-a-new-project

As the project is ready, you need to install the necessary packages for enabling support for Page Objects. Execute the following commands on the Page Manager (PM) console of the newly created project:

Install-Package DotNetSeleniumExtras
Install-Package DotNetSeleniumExtras.PageObjects
Install-Package DotNetSeleniumExtras.PageObjects.Core
Install-Package DotNetSeleniumExtras.WaitHelpers

The status of the installed packages can be obtained using the Get-Package Package Manager command:

PM> Get-Package

Id                                  Versions
--                                  --------
nunit                               {3.12.0}
NUnit3TestAdapter                   {3.13.0}
DotNetSeleniumExtras.PageObjects... {3.12.0}
Selenium.Support                    {3.141.0}
Microsoft.NET.Test.Sdk              {16.2.0}
DotNetSeleniumExtras.PageObjects    {3.11.0}
Selenium.WebDriver                  {3.141.0}

The PageFactory class is no longer a part of the Selenium.Support package. Instead, it is now a part of the DotNetSeleniumExtras Package, replacing the implementation originally supported by the Selenium project.

If you encounter the error “The name PageFactory does not exist in the current context”, make sure that DotNetSeleniumExtras package is installed & SeleniumExtras.PageObjects namespace is included in the code.

// For supporting Page Object Model
// Obsolete - using OpenQA.Selenium.Support.PageObjects;
using SeleniumExtras.PageObjects;

For demonstrating the usage of Page Object Model and Page Factory with Selenium & C#, let’s have a look at two test scenarios:

In this example, I’ll search for ‘LambdaTest’ in the Google search bar. Here, we are running the test in parallel by using the Parallelizable attribute.

[Parallelizable(ParallelScope.All)]

Below is the combination of browser + OS combinations on which cross browser testing is performed:

Capture1.PNG

Implementation

For performing search on Google with search string as LambdaTest, we create three Page Classes/Page Objects as mentioned below:

HomePage.cs – This page class contains the locator information of the elements (required for cross browser testing) on the Google home page. Search for LambdaTest is also performed in this Page Object. The @FindsBy attribute is used on the properties in the HomePage Page Object to describe how the WebElements on the page can be found.


// Home Page Object (Src\PageObject\Pages\HomePage.cs)

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
// For supporting Page Object Model
// Obsolete - using OpenQA.Selenium.Support.PageObjects;
using SeleniumExtras.PageObjects;

namespace POMExample.PageObjects
{
    class HomePage
    {
        String test_url = "https://www.google.com";

        private IWebDriver driver;
        private WebDriverWait wait;

        public HomePage(IWebDriver driver)
        {
            this.driver = driver;
            wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
            PageFactory.InitElements(driver, this);
        }

        //[FindsBy(How = How.XPath, Using = "//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input")]
        [FindsBy(How = How.Name, Using = "q")]
        [CacheLookup]
        private IWebElement elem_search_text;

        //[FindsBy(How = How.XPath, Using = "//*[@id='tsf']/div[2]/div/div[3]/center/input[1]")]
        [FindsBy(How = How.Name, Using = "btnI")]
        [CacheLookup]
        private IWebElement elem_submit_button;

        //[FindsBy(How = How.XPath, Using = "//*[@id='hplogo']")]
        [FindsBy(How = How.Id, Using = "hplogo")]
        [CacheLookup]
        private IWebElement elem_logo_img;

        // Go to the designated page
        public void goToPage()
        {
            driver.Navigate().GoToUrl(test_url);
        }

        // Returns the Page Title
        public String getPageTitle()
        {
            return driver.Title;
        }

        // Returns the search string
        public String getSearchText()
        {
            return elem_search_text.Text;
        }

        // Checks whether the Logo is displayed properly or not
        public bool getWebPageLogo()
        {
            return elem_logo_img.Displayed;
        }

        public SearchPage test_search(string input_search)
        {
            elem_search_text.SendKeys(input_search);
            //wait.Until(ExpectedConditions.ElementToBeClickable(elem_submit_button)).Submit();
            elem_search_text.Submit();
            return new SearchPage(driver);
        }
    }
}

In the HomePage constructor, the page objects are initialized using the InitElements() method from the PageFactory class. In our case, the constructor of the page class i.e. HomePage which takes an input argument as a WebDriver instance is used.

Page objects with its fields initialized are returned on successful execution whereas an exception is thrown if the class cannot be instantiated.

The @FindsBy annotation is used to locate elements on the web page (google.com in this case). Though you can use the ‘Inspect’ option in Chrome/Firefox to locate the elements, we made use of the Selenium Page Object Generator extension in Chrome to for performing the job.

[FindsBy(How = How.Name, Using = "q")]
[CacheLookup]
private IWebElement elem_search_text;

The test_search() method is created to perform a search for the desired input on Google. It returns the SearchPage object.

public SearchPage test_search(string input_search)
{
    elem_search_text.SendKeys(input_search);
      elem_search_text.Submit();
    return new SearchPage(driver);
}

SearchPage.cs – This is the PageObject for the Google Search page. As the search results are already ready, a method for performing a click on the first test result is created as a part of this Page object.

// Search Page Object (Src\PageObject\Pages\SearchPage.cs)

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
// For supporting Page Object Model
// Obsolete - using OpenQA.Selenium.Support.PageObjects;
using SeleniumExtras.PageObjects;

namespace POMExample.PageObjects
{
    class SearchPage
    {
        private IWebDriver driver;
        Int32 timeout = 10000; // in milliseconds

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

        [FindsBy(How = How.XPath, Using = "//*[@id='rso']/div[1]/div/div/div/div/div[1]/a/h3")]
        private IWebElement elem_first_result;

        async void async_delay()
        {
            await Task.Delay(50);
        }

        public FinalPage click_search_results()
        {
            var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeout));

            // Wait for the page to load
            wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));

            elem_first_result.Click();

            async_delay();

            return new FinalPage(driver);
        }
    }
}

The major part of the SearchPage page object implementation is self-explanatory as it is modelled on the design of HomePage page object. In the method click_search_results(), an async wait is implemented till the DOM status is Complete i.e. till the page load is complete. It returns the FinalPage page class.

public FinalPage click_search_results()
{
    var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeout));

    // Wait for the page to load
    wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));

    elem_first_result.Click();

    async_delay();

    return new FinalPage(driver);
}

FinalPage.cs – The Page Class that contains methods to check whether the intended page i.e. LambdaTest homepage has loaded or not.

// Final Page Object (Src\PageObject\Pages\FinalPage.cs)

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
// For supporting Page Object Model
// Obsolete - using OpenQA.Selenium.Support.PageObjects;
using SeleniumExtras.PageObjects;

namespace POMExample.PageObjects
{
    class FinalPage
    {
        private IWebDriver driver;
        Int32 timeout = 10000; // in milliseconds

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

        [FindsBy(How = How.XPath, Using = "/html/body/div[1]/header/div[3]/nav/a/img")]
        private IWebElement elem_lt_logo;

        public String getPageTitle()
        {
            return driver.Title;
        }

        // Checks whether the LambdaTest Logo is displayed properly or not
        public bool getLTPageLogo()
        {
            return elem_lt_logo.Displayed;
        }

        public void load_complete()
        {
            var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeout));

            // Wait for the page to load
            wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
        }
    }
}

As the destination web page i.e. LambdaTest home page is already loaded (via the click on the first search result on Google), an async wait is performed to ensure that the page load is complete.

public void load_complete()
{
        var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeout));

     // Wait for the page to load
    wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
}

Test Case Implementation (using NUnit framework)

Once the design of the Page Objects required for attaining the test scenario is complete, we implement the test code using the uses NUnit framework and Selenium test suite.

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using POMExample.PageObjects;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
// For supporting Page Object Model
// Obsolete - using OpenQA.Selenium.Support.PageObjects;
using SeleniumExtras.PageObjects;

namespace POMExample
{
    [TestFixture("chrome", "72.0", "Windows 10")]
    [TestFixture("internet explorer", "11.0", "Windows 10")]
    [TestFixture("Safari", "11.0", "macOS High Sierra")]
    [TestFixture("MicrosoftEdge", "18.0", "Windows 10")]
    [Parallelizable(ParallelScope.All)]
    public class ParallelLTTests
    {
        String search_string = "LambdaTest";
        String web_page_title = "Google";
        ThreadLocal<IWebDriver> driver = new ThreadLocal<IWebDriver>();
        private String browser;
        private String version;
        private String os;

        public ParallelLTTests(String browser, String version, String os)
        {
            this.browser = browser;
            this.version = version;
            this.os = os;
        }

        [SetUp]
        public void Init()
        {
            String username = "user-name";
            String accesskey = "access-key";
            String gridURL = "@hub.lambdatest.com/wd/hub";

            DesiredCapabilities capabilities = new DesiredCapabilities();

            capabilities.SetCapability("user", username);
            capabilities.SetCapability("accessKey", accesskey);
            capabilities.SetCapability("browserName", browser);
            capabilities.SetCapability("version", version);
            capabilities.SetCapability("platform", os);

            driver.Value = new RemoteWebDriver(new Uri("https://" + username + ":" + accesskey + gridURL), capabilities, TimeSpan.FromSeconds(600));

            System.Threading.Thread.Sleep(2000);
        }

        [Test]
        public void SearchLT_Google()
        {
            String expected_PageTitle = "Cross Browser Testing Tools | Free Automated Website Testing | LambdaTest";
            String result_PageTitle;

            HomePage home_page = new HomePage(driver.Value);
            home_page.goToPage();
            home_page.test_search(search_string);

            SearchPage search_page = new SearchPage(driver.Value); ;
            FinalPage final_page = search_page.click_search_results();

            //As the web page is loaded, we just check if the page title matches or not.
            result_PageTitle = final_page.getPageTitle();

            // Ensure that the page load is complete    
            final_page.load_complete();

            if (result_PageTitle == expected_PageTitle)
            {
                Console.WriteLine("SearchLT_Google Failed");
            }
            else
            {
                Console.WriteLine("SearchLT_Google Passed");
            }
        }

        [TearDown]
        public void Cleanup()
        {
            bool passed = TestContext.CurrentContext.Result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Passed;
            try
            {
                // Logs the result to Lambdatest
                ((IJavaScriptExecutor)driver.Value).ExecuteScript("lambda-status=" + (passed ? "passed" : "failed"));
            }
            finally
            {
                // Terminates the remote webdriver session
                driver.Value.Quit();
            }
        }
    }
}

Browser capabilities for the browser + OS combinations are generated using the LambdaTest capabilities generator. The Selenium test automation cases are executed in parallel to leverage the capabilities of the LambdaTest platform. The option ParallelScope.All are used with the Parallelizable annotation so that the tests and its descendants can execute in parallel with others at the same level.

[TestFixture("chrome", "72.0", "Windows 10")]
[TestFixture("internet explorer", "11.0", "Windows 10")]
[TestFixture("Safari", "11.0", "macOS High Sierra")]
[TestFixture("MicrosoftEdge", "18.0", "Windows 10")]
[Parallelizable(ParallelScope.All)]

We make use of the appropriate methods in the HomePage, SearchPage, and FinalPage page objects to perform the Google search for LambdaTest.

The implementation is pretty straightforward and self-explanatory. The advantage of using Page Object Model in Selenium C# is evident from the test code as any changes in the UI will only affect that particular Page Object whereas the implementation in other Page Objects remains unchanged.

HomePage home_page = new HomePage(driver.Value);
home_page.goToPage();
home_page.test_search(search_string);

SearchPage search_page = new SearchPage(driver.Value); ;
FinalPage final_page = search_page.click_search_results();

//As the web page is loaded, we just check if the page title matches or not.
result_PageTitle = final_page.getPageTitle();

// Ensure that the page load is complete
final_page.load_complete();

Shown below is the execution snapshot from LambdaTest dashboard and
selenium-automation

Execution snapshot from Visual Studio IDE.

visual-studio

Test Scenario 2 – Login on LambdaTest

For this Selenium C# tutorial for Page Object Model, let’s automate the Login process on LambdaTest. We have to automate the following steps

  1. Visit LambdaTest.com
  2. Click on ‘Sign In’ button
  3. Enter a valid combination of username and password
  4. Click on the ‘Login’ button
  5. Once on the automation dashboard, click on the Automation tab

The browser + OS combination remains the same as in the previous example.

Implementation

For automating the Login process on LambdaTest, we first breakdown the scenario in sub-scenarios and create a Page Object per sub-scenario. Below are the page objects/page classes created for automating the scenario:

HomePage.cs – This page class contains the locator information for the necessary web elements on the LambdaTest homepage. A method that returns LoginPage object is created. In the method, we locate the Login button and perform a click operation.

// Home Page Object (Src\PageObject\Pages\HomePage.cs)

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
// For supporting Page Object Model
// Obsolete - using OpenQA.Selenium.Support.PageObjects;
using SeleniumExtras.PageObjects;

namespace POMExample.PageObjects
{
    class HomePage
    {
        String test_url = "https://www.lambdatest.com/";

        private IWebDriver driver;
        private WebDriverWait wait;
        Int32 timeout = 10000; // in milliseconds

        public HomePage(IWebDriver driver)
        {
            this.driver = driver;
            wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
            PageFactory.InitElements(driver, this);
        }

        [FindsBy(How = How.XPath, Using = "/html/body/div[1]/header/div[3]/nav/a/img")]
        private IWebElement elem_lt_logo;

        [FindsBy(How = How.XPath, Using = "//*[@id='navbarSupportedContent']/ul/li[7]/a]")]
        private IWebElement elem_lt_signup;

        [FindsBy(How = How.LinkText, Using = "Log in")]
        private IWebElement elem_lt_login;

        [FindsBy(How = How.XPath, Using = "//a[contains(text(),'Automation')]")]
        private IWebElement elem_lt_automation;

        async void async_delay()
        {
            await Task.Delay(50);
        }

        public void wait_page_completion(int timeout)
        {
            var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeout));

            // Wait for the page to load
            wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
        }

        // Go to the designated page
        public void goToPage()
        {
            driver.Navigate().GoToUrl(test_url);
        }

        // Returns the Page Title
        public String getPageTitle()
        {
            return driver.Title;
        }

        // Checks whether the Logo is displayed properly or not
        public bool getHomePageLogo()
        {
            return elem_lt_logo.Displayed;
        }

        public String getHomePageAttribute(String input_attribute)
        {
            return elem_lt_logo.GetAttribute(input_attribute);
        }

        public LoginPage goToLoginPage()
        {
            elem_lt_login.Click();

            //Delay added to ensure that the page load is complete
            //We can also use non-blocking delay of the same time
            //but using the DOM state is more advantageous

            //async_delay();
            wait_page_completion(timeout);
            return new LoginPage(driver);
        }
    }
}

LoginPage.cs – This is the PageObject for the Login Page of Lambdatest i.e. https://accounts.lambdatest.com/login. In this Page Class, the username and password textboxes are located and a method that takes username & password as input arguments is created.

Once the ‘LOGIN’ button is clicked, we navigate to the Lambdatest dashboard. Hence, the method (submit_uidpwd) returns FinalPage page object.

// Login Page Object (Src\PageObject\Pages\LoginPage.cs)

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
// For supporting Page Object Model
// Obsolete - using OpenQA.Selenium.Support.PageObjects;
using SeleniumExtras.PageObjects;

namespace POMExample.PageObjects
{
    class LoginPage
    {
        private IWebDriver driver;
        Int32 timeout = 10000; // in milliseconds

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

        [FindsBy(How = How.XPath, Using = "//*[@id='app']/div/div/div/div/form/div[1]/input")]
        private IWebElement elem_lt_login_user_name;

        [FindsBy(How = How.XPath, Using = "//*[@id='userpassword']")]
        private IWebElement elem_lt_login_password;

        [FindsBy(How = How.XPath, Using = "//*[@id='app']/div/div/div/div/form/div[3]/button")]
        private IWebElement elem_lt_login_button;

        // Returns the Page Title
        public String getPageTitle()
        {
            return driver.Title;
        }

        async void async_delay(int delay)
        {
            await Task.Delay(delay);
        }

        public void wait_page_completion(int timeout)
        {
            var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeout));

            // Wait for the page to load
            wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
        }

        public void send_userpassword(String user_name, String password)
        {
            //As the page load is already complete, we can directly enter the user name and password
            elem_lt_login_user_name.SendKeys(user_name);
            async_delay(50);
            elem_lt_login_password.SendKeys(password);
            async_delay(50);
        }

        public FinalPage submit_uidpwd()
        {
            elem_lt_login_button.Click();

            // Wait for the new page to load. This is the LambdaTest dashboard
            wait_page_completion(timeout);

            return new FinalPage(driver);
        }
    }
}

FinalPage.cs – This is the PageObject for the LambdaTest Automation Dashboard. A method (automation_tab_click) is created to click on the Automation tab on the Dashboard. The rest of the implementation is self-explanatory.

// Final Page Object (Src\PageObject\Pages\FinalPage.cs)

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
// For supporting Page Object Model
// Obsolete - using OpenQA.Selenium.Support.PageObjects;
using SeleniumExtras.PageObjects;

namespace POMExample.PageObjects
{
    class FinalPage
    {
        private IWebDriver driver;
        Int32 timeout = 10000; // in milliseconds

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

        [FindsBy(How = How.XPath, Using = "//*[@id='app']/header/div/div/div[1]/a/svg/g/path[2]")]
        private IWebElement elem_lt_logo;

        [FindsBy(How = How.XPath, Using = "//*[@id='app']/header/aside/ul/li[4]/a")]
        private IWebElement elem_lt_automation;

        public String getPageTitle()
        {
            return driver.Title;
        }

        // Checks whether the LambdaTest Logo is displayed properly or not
        public bool getLTPageLogo()
        {
            return elem_lt_logo.Displayed;
        }

        public void load_complete(int timeout)
        {
            var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeout));

            // Wait for the page to load
            wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
        }

        public void automation_tab_click()
        {
            elem_lt_automation.Click();

            load_complete(timeout);
        }
    }
}

Test Case Implementation (using NUnit framework)

Like the previous example, we implement a test case that uses the Page Objects and NUnit framework. As the same set of browser & OS combinations are used for testing, that part of the implementation remains unchanged.
In the code implemented under the [Test] annotation, the methods implemented by the Page Classes are used to automate the login operation on LambdaTest. The implementation is self-explanatory.

// FileName - Test\Scripts\test_POM.cs

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
using System.Threading.Tasks;
using OpenQA.Selenium.Support.UI;
using OpenQA.Selenium.Chrome;
// For supporting Page Object Model
// Obsolete - using OpenQA.Selenium.Support.PageObjects;
using POMExample.PageObjects;
using SeleniumExtras.PageObjects;

namespace POMExample
{
    [TestFixture("chrome", "72.0", "Windows 10")]
    [TestFixture("internet explorer", "11.0", "Windows 10")]
    [TestFixture("Safari", "11.0", "macOS High Sierra")]
    [TestFixture("MicrosoftEdge", "18.0", "Windows 10")]
    [Parallelizable(ParallelScope.All)]
    public class ParallelLTTests
    {
        String search_string = "LambdaTest";
        String web_page_title = "Google";
        ThreadLocal<IWebDriver> driver = new ThreadLocal<IWebDriver>();
        private String browser;
        private String version;
        private String os;

        public ParallelLTTests(String browser, String version, String os)
        {
            this.browser = browser;
            this.version = version;
            this.os = os;
        }

        [SetUp]
        public void Init()
        {
            String username = "user-name";
            String accesskey = "access-key";
            String gridURL = "@hub.lambdatest.com/wd/hub";

            DesiredCapabilities capabilities = new DesiredCapabilities();

            capabilities.SetCapability("user", username);
            capabilities.SetCapability("accessKey", accesskey);
            capabilities.SetCapability("browserName", browser);
            capabilities.SetCapability("version", version);
            capabilities.SetCapability("platform", os);

            //driver = new ChromeDriver();
            driver.Value = new RemoteWebDriver(new Uri("https://" + username + ":" + accesskey + gridURL), capabilities, TimeSpan.FromSeconds(600));

            System.Threading.Thread.Sleep(2000);
        }

        [Test]
        public void LT_Login_Test()
        {
            // Add your LambdaTest credentials here
            String username = "user-name@gmail.com";
            String password = "password";
            String expected_dashboard_title = "Welcome - LambdaTest";
            String fetched_dashboard_title;

            HomePage home_page = new HomePage(driver.Value);
            home_page.goToPage();

            //Keeping the error catching mechanism very minimal
            if (!home_page.getHomePageLogo())
            {
                Console.WriteLine("LT_Login_Test Failed");
            }

            // Since the LT Home page is displayed, we perform the Login Click Operation
            home_page.goToLoginPage();

            LoginPage login_page = new LoginPage(driver.Value);

            login_page.send_userpassword(username, password);

            // Since the wait is already added in the code, we just click the Submit button
            login_page.submit_uidpwd();

            // Now we have logged in and on the Dashboard page, check the title of the page
            FinalPage final_page = new FinalPage(driver.Value);

            fetched_dashboard_title = final_page.getPageTitle();
            if (fetched_dashboard_title == expected_dashboard_title)
            {
                Console.WriteLine("LambdaTest Dashboard open successful");

                final_page.automation_tab_click();

                Console.WriteLine("LambdaTest Automation Tab open successful");
            }
            else
            {
                Console.WriteLine("LambdaTest Dashboard open not successful");
            }
        }

        [TearDown]
        public void Cleanup()
        {
            bool passed = TestContext.CurrentContext.Result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Passed;
            try
            {
                // Logs the result to Lambdatest
                ((IJavaScriptExecutor)driver.Value).ExecuteScript("lambda-status=" + (passed ? "passed" : "failed"));
            }
            finally
            {
                // Terminates the remote webdriver session
                driver.Value.Quit();
            }
        }
    }
}

Shown below is the execution snapshot from LambdaTest

selenium-automation-testing

Execution Snapshot from Visual Studio IDE.

visual-studio-ie

In order to scale your testing efforts, you’d need to use a Selenium Grid. You can do this in two ways by setting up a local Selenium grid or opting for a cloud based solution. The issue with using a local Selenium grid for Cross browser testing is that setting up a local infrastructure can be very tedious and time-consuming.

Instead, cloud-based cross browser testing on a remote Selenium grid can help in attaining better test coverage along with accelerated execution of the Selenium test automation scenarios. With LambdaTest Selenium Grid you can perform cross browser testing on 2000+ browsers & operating systems online. Parallel test execution on a remote Selenium grid can speed up the entire Selenium test automation process.

Wrapping It Up

In large consumer web applications, code maintenance can be a big challenge if corrective measures are not taken at the right time. With the Page Object Model in Selenium, you can improve the clarity and maintainability of the code. You no longer need to update each and every script just cause there is an update in the UI.

Page Object Model in Selenium C# is pivotal in automated browser testing as the testing can involve large numbers of browser & OS combinations. Irrespective of whether the product UI goes through frequent changes or not, it is always recommended to use a design pattern like Page Object Model that is programming language agnostic and extremely beneficial in the long run.

Update : We’ve now completed the Selenium C# tutorial series, so in order to help you easily navigate through the tutorials, we’ve compiled the complete list of tutorials, which you can find in the section below.

This brings us to the conclusion of the article and to our Selenium C# tutorial series, this was all I had to share on Page Object Model in Selenium C#. You now know how to keep up with those frequent UI changes, they won’t be a problem anymore. Help your peers, facing the same issue, retweet us and help us in reaching out to more people. Happy testing!!☺

Learn Something New Everyday,
Connect With The Best Developers!

Sign Up Now!

& 500k+ others use Hashnode actively.

No Comments Yet