How to implement Page Object Model (POM) using C# with Selenium

How to implement Page Object Model (POM) using C# with Selenium

Introduction:

Selenium is an open-source Web UI automation testing suite/tool. It supports automation across different browsers, platforms, and programming languages which includes Java, Python, C#, .net, Ruby, PHP, and Perl, etc. for developing automated tests. Selenium can be easily deployed on Windows, Linux, Solaris, and Macintosh Operating Systems. It also provides support for mobile applications like iOS, windows mobile, and Android for different Operating Systems.

Selenium consists of drivers specific to each language. Additionally, the Selenium Web driver is mostly used with Java and C#. 

Test scripts can be coded in selenium in any of the supported programming languages and can be run directly in most modern web browsers which include Internet Explorer, Microsoft Edge, Mozilla Firefox, Google Chrome, Safari, etc.

Furthermore, C# is an object-oriented programming language derived from C++ and Java.
It supports the development of console, windows, and web-based applications using Visual Studio IDE on the .Net platform.

With Selenium C#, there is a wide variety of automation frameworks that can be used for automated browser testing. Each framework has its own advantages and disadvantages, they are chosen on the basis of their requirement, compatibility, and the kind of solution they’d prefer. These are the most popular Selenium C# frameworks used for test automation.

NUnit:

It is a unit testing tool ported initially from JUnit for .Net Framework and is an Open Source Project. NUnit was released in the early 2000s, though the initial Nunit was ported from Junit, the recent .Net version 3 is completely rewritten from scratch.

To run the Nunit test we need to add attributes to our methods. An example, attribute [Test], Indicates the Test method. Below are the NuGet Packages required by NUnit

NUnit
NUnit3TestAdapter
Microsoft.NET.Test.Sdk 

XUnit:

XUnit is a unit testing tool for .Net Framework which was released in 2007 as an alternative to Nunit. xUnit has attributes for the execution of tests but is not similar to NUnit. [Fact] and [Theory] attributes are similar to [Test] 

Below are the NuGet Packages required by xUnit

Xunit
Xunit. runner.VisualStudio
Microsoft.NET.Test.Sdk

MSTest:

MSTest is a unit testing framework developed by Microsoft and ships with Visual Studio. However, Microsoft made version 2 open-source which can easily be downloaded. Additionally, MSTest has an attributes range similar to NUnit and provides a wide range of attributes along with parallel run support at the Class and Method level.

Prerequisite:

To get started with Selenium C# and the Page Object Model framework, first, we need to have the following things installed.

1) IDE: Download and install any IDE of your choice.

  •  Here we are using Microsoft Visual Studio 2022
  •  After downloading the Visual Studio Installer, select the .NET desktop development option and then click on Install.
  •  Now let the Visual Studio Installer download the packages and perform the installation.
  •  Install the latest version of the .NET Framework on your machine.

2) Create New Project: After the installation is over, begin using Visual Studio.

  •  select the Create a new project option, then select the xUnit Test Project option for C#.

3) Selenium Webdriver for Chrome Browser: You must also install Selenium’s web driver for Chrome browser.

  •  In Visual Studio navigate to Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution.
  • In the Search Bar, enter the name of the packages you want to install (e.g. Selenium .WebDriver).
  • Check the Project checkbox, and click on Install.
  • In the dialogue box asking to accept the licences click on Accept button.
  • This will start the installation process and install the Selenium WebDriver.

Selenium.WebDriver

This package contains the .NET bindings for concise and object-based

Selenium WebDriver API, which uses native OS-level events to manipulate the browser,
Selenium.Chrome.WebDriver (chrome driver exe)
This NuGet package installs Chrome Driver (Win32) for Selenium WebDriver in your xUnit Test Project.


Once Visual Studio is finished with the successful installation of the Selenium WebDriver, it will show a message in the output window.
Once the Visual Studio is set up with all dependencies, it’s ready for work.

Note: We will be using the demo testing website (https://www.calculator.net/) and trying to achieve the addition and subtraction operations for our automation test.

Writing the First Selenium C# Test:

Download the WebDriverManager from Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution.

WebDriverManager is an open-source Java Library that automates the management of driver executables required by Selenium WebDriverby performing the four steps (find, download, setup, and maintenance) for the drivers required for Selenium tests. Here are some benefits of WebDriverManager in Selenium:

  • WebDriverManager automates the management of WebDriver binaries, thereby avoiding installing any device binaries manually.
  • WebDriverManager checks the version of the browser installed on your machine and downloads the proper driver binaries into the local cache (~/.cache/selenium by default) if not already present.
  • WebDriverManager matches the version of the drivers. If unknown, it uses the latest version of the driver.
  • WebDriverManager offers cross-browser testing without the hassle of installing and maintaining different browser driver binaries.

In the UnitTest1 file, the final code looks like this:

public class UnitTest1
    {
        IWebDriver driver;
        CalculatorPage calc_page;
        public void Initialize_driver()
        {
         new WebDriverManager.DriverManager().SetUpDriver(new ChromeConfig());
            driver = new ChromeDriver();
          calc_page = new CalculatorPage();
        }
        public void Close_driver()
        {
        driver.Close();
        }
       [Fact]
        public void Add()
        {
            initialize_driver();
            calc_page.Initialize(driver);
            string actualresult = calc_page.calculate("14", "+", "5");
            Assert.Equal("19", actualresult);
            Close_driver();
        }
        [Fact]
        public void Subtract()
        {
            initialize_driver();
            calc_page.Initialize(driver);
            string actualresult = calc_page.calculate("24", "-", "5");
            Assert.Equal("19", actualresult);
           Close_driver()
        }
    }

Now just build your code by right-clicking the project xUnitTestProject1 or by pressing Ctrl + Shift + B and you will be able to see your test in “Test Explorer”.

After following the above procedure, run the test case. But this code will not execute unless the Chrome driver for the Selenium is not downloaded and unzipped on the system.

When developing a scalable and robust automation framework, it is important to consider the following challenges:

  1. Keeping up with UI changes: The primary goal of automated UI web tests is to validate the functionality of web page elements. However, the UI is subject to constant evolution, leading to changes in web locators. These frequent changes in web locators pose a challenge to code maintenance.
  2. Code maintenance: With the ever-changing UI, it is crucial to maintain the automation codebase effectively. Failing to update Selenium test automation scripts to reflect changes in web locators can result in test failures. Proper maintenance is essential to ensure the longevity and reliability of the test scripts.
  3. Test failure due to lack of maintenance: Inadequate maintenance of automation scripts can lead to scenarios where tests fail. One common cause is a change in web locators. If the Selenium test automation scripts are not updated accordingly, it can cause a significant number of tests to fail, impacting the overall test suite’s reliability.

So to address this, restructure the Selenium test automation scripts for increased modularity and reduced code duplication.

Utilizing the Page Object Model (POM) design pattern achieves code restructuring and minimizes the effort required for test code maintenance.

Now, let’s delve into a comprehensive overview of the Page Object Model, including the implementation and effective maintenance of your Selenium test automation scripts.

Why do we need 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 lines of code, its 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 to develop and maintain code.    

What is Page Object Model In Selenium C#?

Page Object Model is the most widely used design pattern by the Selenium community for automation tests in which each web page (or significant ones) is considered as a separate class and a central object repository is created for controls on a web page.

  • Each Page Object (or page classes) contain the elements of the corresponding web page along with the necessary methods to access the elements on the page.
  • Thus it is a layer between the test scripts and UI and encapsulates the features of the page.
  • 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.  
  • 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.
  • In complex test automation scenarios, 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. For example, if automation for a login page & dashboard page is to be performed, our implementation will have a class each for login & dashboard. The controls for the login page are in the ‘login page’ class and the controls for the dashboard page are in the ‘dashboard page’ class.


How to Use Page Object Model:

We will now implement the Page Object Model for the use case we considered above i.e. trying to achieve the addition and subtraction operations for our automation test on the Calculator page.
Create a class file – CalculatorPage.cs for Calculator page operation. This page class contains the locator information of the elements on that page. Also, we need to define the methods for that page in the CalculatorPage.cs class and call the methods from UnitTest1.cs.


We are initializing the Chromedriver object and launching the web page from the initialiseDriver() method from UnitTest1.cs. Also, we are creating the instance of CalculatePage from the same method.
The CalculatePage.cs contain an instance of IWebDriver and the following methods –

Initialize(): this method takes one IWebDriver object as an input parameter and it is assigned to a locally defined IWebDriver object. Also, the required web page is launched using this driver.

Calculate(): this method is actually used to do the calculation operation of two numbers..either addition or subtraction using 3 input parameters as user input number value1, number value 2, and operator like -’+’ or ‘-’. The required elements from the page are located and as per the operator, the required operation is performed on those.
The final code of CalculatePage.cs would look like the below:

public class CalculatePage
    {
        IWebDriver driver;

        public void Initialize(IWebDriver driver)
        {
            this.driver = driver;          
            driver.Navigate().GoToUrl("https://www.calculator.net/");
        }
       
    public string Calculate(string no1, string op, string no2)
        {
            IWebElement number1;
            char[] ch = no1.ToCharArray();

            for (int i = 0; i < no1.Length; i++)
            {
                number1 = driver.FindElement(By.XPath("//span[@onclick='r(" + ch[i] + ")']"));
                number1.Click();
             }

            IWebElement op_element = driver.FindElement(By.XPath("//span[@onclick=\"r('" +op + "')\"]"));
            op_element.Click();
            ch = no2.ToCharArray();

            for (int i = 0; i < no2.Length; i++)
            {
                number1 = driver.FindElement(By.XPath("//span[@onclick='r(" + ch[i] + ")']"));
                number1.Click();
            }
            IWebElement result = driver.FindElement(By.Id("sciOutPut"));
            string actual_result = result.Text.Trim();
            return actual_result;
      }
   }

Advantages of Page Object Model in Selenium C#:

Page Object Model is a widely used design pattern nowadays. It reduces code duplication, enhances code readability, and improves maintainability by emphasizing reusability and extensibility.
Furthermore, 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 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 increased usage of reusability with the Page Object Model in Selenium C#.

Simplification –One more important point of using this design pattern 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. 

Execution:

Navigate to Test -> Run All Tests.
This will launch the test explorer in Visual Studio and will run our test. 

You can run the test from the command prompt or visual studio’s terminal (Developer Command Prompt) with the following command-

dotnet test

This dotnet test command is used to run the tests in the project in the current directory. The dotnet test command builds the solution and runs a test host application for each test project in the solution. While running the tests from the project, you can put different filters while running the test, like running the tests with particular tags, from specific projects, with particular names, etc.

You can find this framework in the following Git Repository.

spurqlabs/CSharp-Selenium-Page-Object-Model (github.com)

Conclusion:

Implementing the Page Object Model in Selenium with C# provides a structured approach to automation testing, making the code more maintainable and reusable. It simplifies the handling of UI changes and enhances the overall efficiency of the testing process for large-scale applications.

Read more blogs here

How to create a BDD automation framework using Cucumber in JavaScript and Playwright?

How to create a BDD automation framework using Cucumber in JavaScript and Playwright?

Introduction:

In this Blog will learn How to create a BDD automation framework using Cucumber in JavaScript and Playwright.
The playwright is an open-source automation tool. Playwright offers several features that make it stand out are Multi-browser support, Automatic waiting Headless and headful mode, etc.
Cucumber is a popular open-source BDD(Behavior Driven Development) testing framework, Which helps build a framework that can be easily understood by both technical and non-technical stakeholders.

Prerequisite:

To get started with Cucumber BDD with playwright-js we need to have the following things installed.

1)VS Code: We will use VS Code as IDE to write our test cases, to get this you can visit ‘https://code.visualstudio.com/download’.

2)Node.js: It is a cross-platform JavaScript runtime environment that allows developers to run JavaScript code outside of a web browser.
To get Node.js you can visit: https://nodejs.org/en/download
To check Node.js is installed on your system you can use the command ‘node –version’ on CMD or VS Code Terminal.

3)Cucumber (Gherkin) Full Support Extention: This extension provides support for the Cucumber (Gherkin) language to VS Code.
To get the extension click on the extension icon on the left side panel of VS Code, search for the extension and install it.

Project setup:

Before starting with framework development we need to create a folder structure.

Create a folder structure with the following steps:
1) Create a folder as ‘Playwright-JS-Demo’,
2) In VS Code open the Playwright-JS-Demo folder
3) Install Playwright and Cucumber by executing the following commands in terminal
a. npm init playwright@latest

b.npm i @cucumber/cucumber
After execution of these commands, you can see package.json, node_modules, and playwright.config.js is created in the folder structure.

4) once you open the project, create a folder structure as below.

As our project structure is ready, we can start with the framework.

1. TestHooks.js:

  • Test Hooks mainly contain methods to execute Before and After every execution.
  • The Before hook gets executed before each Scenario in a cucumber test suite.
  • In this hook, a new Chrome browser instance is launched for every Test Case.
  • A new page is created in context with ‘await context.newPage()‘ and assigned to the global variable ‘page‘, which is accessible for any Test Case. This ensures that the browser page is available for each scenario in the test suite. 
  • Once execution is done After the method gets executed It closes the current browser instance.
const { Before, AfterAll } = require('@cucumber/cucumber')
const page = require('@playwright/test')
Before(async () => {
  let browser = await page.chromium.launch({ headless: false })
  global.browser = browser
  const context = await browser.newContext()
  global.page = await context.newPage()
})
AfterAll(async () => {
  await global. browser.close()
})

2. Login.feature:

  • With Cucumber-BDD we can write scenarios in understandable language.
    Here I have created a Scenario for OrangeHRM Login.
  • We can create a separate scenario for each functionality.
  • Every step in a Feature File describes the action we are going to perform on UI.
  • In the feature file, we can add specific tags for scenarios or complete feature file ex. @login, @smoke.
  • If you add a tag for a specific scenario then it will only execute the particular scenario. But if you add tags for the Feature It will execute all the Scenarios from the Feature File.
  • It will make test execution easy if you want to execute test cases for specific functionality.
Feature: Login Feature
@login
Scenario: Login to OrangeHRM
 When I Visit the OrangeHRM login page
 And I enter username
 And I enter Password
 And I click on Login button
Then I verify dashboard URL

3. LoginSteps.js:

Essentially, the purpose of the step file is to attach steps from the feature file to the page file, where actual implementation is available.

We use the “Given-When-Then” (BDD) format to write step definitions.
The required statement imports the necessary modules like:

  • Cucumber library that contains definitions for When Then etc.
  • A custom LoginPage module that likely contains functions for interaction with the login page.

The steps with ‘When’ are related to the user actions like navigation, clicking on the button, and filling in the information in input boxes, etc.
The steps with ‘Then’ are related to the verifications or Assertions, just like in this case I have verified if the Login is successful.

const { When, Then } = require('@cucumber/cucumber')
const { LoginPage } = require('../page/LoginPage')
let loginPage = new LoginPage()
When('I Visit the OrangeHRM login page', async () => {
    await loginPage.navigate()
})
When('I enter username', async () => {
    await loginPage.enterUsername()
})
When('I enter Password', async () => {
    await loginPage.enterPassword()
})

When('I click on Login button', async () => {
   await loginPage.clickOnLoginButton()
})
Then('I verify dashboard URL', async () => {
    await loginPage.verifyDashboardURL()
})

4. LoginPage.js:

The page file contains the actual implementation of the scenario, We also defined all the functions needed for test execution.
Additionally, the LoginPage class contains functions that interact with the login page elements, like navigation to the Login page, entering the username and password, clicking the login button, and verifying the dashboard URL. Moreover, the playwright provides different functions to handle the UI elements and pages.
For this test case I have used goto(), click(), fill() waitFor().

process.env.WEB_URL, process.env.WEB_USERNAME and process.env.WEB_PASSWORD are the variables we are accessing from the .env file. (The use of the .env file is explained below in point no 6).

To access the .env file I have imported const path = require(‘path’);

const { expect } = require('@playwright/test')
const path = require('path');
require('dotenv').config({
    path: path.join(__dirname, '../.env'),
});
class LoginPage {
    async navigate() {
        await global.page.goto(process.env.WEB_URL)
    }
    async enterUsername() {
        await global.page.locator('//input[@placeholder="Username"]').waitFor({ status: 'visible' })
        await global.page.locator('//input[@placeholder="Username"]').fill(process.env.WEB_USERNAME)
    }
    async enterPassword() {
        await global.page.locator('//input[@placeholder="Password"]').fill(process.env.WEB_PASSWORD)
    }
    async clickOnLoginButton() {
        await global.page.locator('//button[@type="submit"]').click()
    }
    async verifyDashboardURL() {
       expect(await global.page.url()).toEqual('https://opensource-demo.orangehrmlive.com/web/index.php/dashboard/index')
    }
}
module.exports = { LoginPage }

5. Cucumber.json:

In this file, we specify the paths of all the required files. It helps to identify files in the framework.
Whenever you create any new file in the framework, you have to add a path for that new file in this Cucumber.json file so that it can be accessible during the test case execution.

{
    "default":{
        "require": [
            "steps/*.js",
            "page/*.js",
          "Utility/*.js"
        }
    }
}

6. .env:

.env file is a Configuration file that contains environment variables. Using a .env file is a best practice for keeping sensitive information separate from the code. Moreover, for this framework, we have stored information like URL to navigate and username and password to Login into the OrangeHRM Web application.

headless= false
WEB_URL= 'https://opensource-demo.orangehrmlive.com/web/index.php/auth/login'
WEB_USERNAME = 'Admin'
WEB_PASSWORD = 'admin123'

To execute the test case and get the report you will need to add the following command in a script tag of the package.json file

"scripts": 
{
    "test": "npx cucumber-js --require ./steps/*.js --tags @login --publish
}

We are executing the ‘npx’ command to run the cucumber-js package which is a test framework for BDD.
1) –require ./steps/*.js specifies The step files from the specified path that should be loaded.
2)–tags @login specifies scenarios with @login tags are going to be executed.
You can add more than one tag if needed.

3)–publish flag specifies that test results should be published to the Cucumber Cloud which is a service for storing and analyzing test results.

To start execution you can execute the command ‘npm run test’ in the terminal.
Once execution is completed you can see the link for cucumber reports is available in the terminal. By clicking on this link you can see the execution report.

This is how the report looks when you click on the above URL

Now as we have the framework ready, I have added this framework to the following Git Repository.
https://github.com/spurqlabs/Playwright_JS_BDD_Framework

Conclusion:

In conclusion, the BDD automation framework using Cucumber in JavaScript and Playwright helps improve the quality and efficiency of their testing process.
This framework will help you to write test cases for any web application very efficiently, It also provides a great reusability of code.

Read more blogs here.