As we have learned in the last chapter of Page Object model is an object design pattern in Selenium, where web pages are represented as classes. 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. Since well-named methods in classes are easy to read, this works as an elegant way to implement test routines that are both readable and easier to maintain or update in the future.
In order to support Page Object model, we used Page Factory. Page Factory is an extension to Page Object and can be used in various ways. As we know that web page classes or Page Objects containing web elements need to be initialized using Page Factory before the web element variables can be used. This can be done simply through the use of initElements function on PageFactory. Still, this is not enough in the PageFactory, it can be utilized much efficiently by Optimizing Page Object Model :
- Initialize Elements within the Constructor
- Binding methods within the PageObject class
Initialize Elements within the Constructor
In the first chapter of Selenium PageObjects and PageFactory , we did this to initialize elements of the page:
var loginPage = new LoginPage();
PageFactory.InitElements(driver, loginPage);
But the duty of the Framework is to minimize the code where ever it can. Let say if the application has ten pages to automate. To do the task, it is required to create the object of ten pages first and then initialize ten pages to use their objects. This will make the test very lengthy and filthy. It is a good idea to abstract this task from the test. By placing the Init statement in the Page Class Constructor, half of the code from the test can be reduced.
public LoginPage(IWebDriver driver){
this.driver = driver;
PageFactory.InitElements(driver, this);
}
Every time the object for the LogIN page is created, it will first go into the constructor and initialize all the object of the page. This is a nice way of Optimizing Page Object Model. To read more about Constructors, please refer the article in java.
Binding Methods within the Page Object Model Class
The second way to Optimizing Page Object Model or improving the test code is to wrap re-usable actions in the PageObject class itself. Now think of the reusable function in the example, that we have used in this Selenium Framework C# series. The answer is log into the application. As of now we just have one username and password, but what if there are more than thirty users for the application. Then the code for log into the application will be called again and again in every test script. Isn't a nice idea to wrap that action into a function and use it as many time as required.
Doing this is not a difficult task, as above we already initialized the elements of the page within the class constructor. Now the elements are ready to use in the PageObject class itself.
UserName.SendKeys("TestUser_1");
Password.SendKeys("Test@123");
Submit.Submit();
But the above code has to be bind in the Function or a Method. We will be using the same LogIn Scenario for this. Let's just see how the Page classes and the test class will look like after binding login method within the LoginPage class.
HomePage PageObject Class
using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
namespace OnlineStore.PageObjects
{
class HomePage
{
private IWebDriver driver;
[FindsBy(How = How.Id, Using = "account")][CacheLookup]
public IWebElement MyAccount { get; set; }
public HomePage(IWebDriver driver)
{
this.driver = driver;
PageFactory.InitElements(driver, this);
}
}
}
LoginPage PageObject Class
using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
namespace OnlineStore.PageObjects
{
public class LoginPage
{
private IWebDriver driver;
[FindsBy(How = How.Id, Using = "log")]
[CacheLookup]
public IWebElement UserName { get; set; }
[FindsBy(How = How.Id, Using = "pwd")]
[CacheLookup]
public IWebElement Password { get; set; }
[FindsBy(How = How.Id, Using = "login")]
[CacheLookup]
public IWebElement Submit { get; set; }
public LoginPage(IWebDriver driver)
{
this.driver = driver;
PageFactory.InitElements(driver, this);
}
public void LoginToApplication()
{
UserName.SendKeys("TestUser_1");
Password.SendKeys("Test@123");
Submit.Submit();
}
Note: Always give PageObject methods logical names. The other names I can think of for the above LogIn task can be LoginAction, PerformLogin or anything.
LogInTest.cs TestCase
using NUnit.Framework;
using OnlineStore.PageObjects;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
namespace OnlineStore.TestCases
{
class LogInTest
{
[Test]
public void Test()
{
IWebDriver driver = new FirefoxDriver();
driver.Url = "https://www.store.demoqa.com";
var homePage = new HomePage(driver);
homePage.MyAccount.Click();
var loginPage = new LoginPage(driver);
loginPage.LoginToApplication();
driver.Close();
}
}
}
I hope you have realized that how simple code looks now after subtracting user actions. I would say there is still more code to go from the test. That we will learn in the following chapters of Selenium Automation Framework in C#.
Conclusion
Page Object and Page Factory make it easy to model web pages in Selenium and test them automatically and make the life of both developers and QAs much more simpler. When done right, these Page Object classes can be reused across your entire test suite and to give yourself the opportunity to implement automated Selenium tests for your projects early on, without compromising agile development. By abstracting away user interactions in your page object models and keeping your test routines light and simple, you can adapt your test suite to changing requirements with little effort.
I hope I have managed to show you how to write a nice and clean test code that is easy to maintain.