Author : Edwin Hernandez Maynez
In this article we will do an in-depth examination of Code Design Patterns for UI test automation solutions and will refer mainly to Selenium and C#. If you want to know more about Functional Test Automation tools in general please have a look at our previous article: What are the best UI Test Automation Tools?
We will review what are the upsides of using a Design Pattern to develop an Automated UI test solution and why you should probably take it in consideration for large projects. This information applies to most test frameworks but it's more usually implemented on projects that use Selenium and Selenium-like frameworks such as Appium and WinAppDriver.
By applying a design pattern that allows for code reutilization, single point of maintenance for UI Elements and by also implementing Data-Driven testing, you can really minimize the effort associated with updating your test scripts on each test cycle.
There are a few design patterns that can be used to help you overcome the mentioned challenges. For the purposes of this article we will look into the Page Object Model, but just for clarification, here is a quick list of possible designs and components:
POM
[TestMethod]
public void POM_test()
{
NavigationBar navigationBar = new NavigationBar(driver);
navigationBar.SubmitSearch("123");
ResultsPane resultsPane = new ResultsPane(driver);
resultsPane.Select_Row(1);
int invoiceNumber = resultsPane.Get_InvoiceNumber();
Assert.AreEqual("83812302", invoiceNumber);
}
Fluent Design
[TestMethod]
public void Fluent_test()
{
NavigationBar navigationBar = new NavigationBar(driver);
Assert.AreEqual(“83812302”,
navigationBar.SubmitSearch("123")
.Select_Row(1)
.ClickOnInitialPopUps()
.Get_InvoiceNumber()
)
}
It's important to point out that these designs are not necessarily mutually exclusive and some of these components can be used on the same solution. Also, there are sources that cite Page Factory, Driver Factory and Object Repository as design patterns on their own but since they mostly require a POM to function, I will describe them in the next section.
There are many conceptual maps of how this looks and how it works, I personally like this one by Mohsin, which is similar to this other one by Vrishali T. For this article I will be describing a simple approach based on the projects I have worked on in the past and I will create my own conceptual map with a few pieces that I think should also be mentioned as part of the design. This should be just enough to get a newcomer started and I will include code examples that should help you setup the minimal structure.
First of all, a POM has the following elements/layers:
The conceptual map below takes in consideration all the elements above, except for the Page Factory, instead I'm using Page Objects straight from the test method, the page objects have methods themselves that use the selenium findElement(s) functions:
Below you can find a demo of how the minimal structure would look like for a POM project, it was based on the conceptual/class diagram above. I am showcasing the main features of the code on this article but you can also find the solution zip file for download at the bottom of the page. This is a Visual Studio Unit test project, written in C# and using the Selenium libraries.
Some notes about this project:
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", @"|DataDirectory|\TestData\Test1_Data.csv", "Test1_Data#csv", DataAccessMethod.Sequential)]
[TestMethod]
public void Test1()
{
IWebDriver driver = DriverFactory.InitBrowser("Chrome");
DriverFactory.LoadApplication(driver,"https://demoPOM.org");
NavigationBar.SubmitSearch(driver, TestContext.DataRow["SubmitStr"].ToString());
ResultsPane.Select_Row(driver, Int32.Parse(TestContext.DataRow["Row"].ToString()));
int invoiceNumber = ResultsPane.Get_InvoiceNumber(driver);
Assert.AreEqual("83812302", invoiceNumber);
}
Page Object Class Example:
public class NavigationBar
{
public static void SubmitSearch(IWebDriver driver, string searchStr)
{
By searchTxBxLocator = ObjRepoQuery.GetLocator("\\searchtxbx");
Wrappers.SmartFindElement(driver, searchTxBxLocator).SendKeys(searchStr);
By searchBtnLocator = ObjRepoQuery.GetLocator("\\searchbtn");
Wrappers.Click(driver,searchBtnLocator);
}
}
Driver Factory Example:
public static IWebDriver InitBrowser(string browserName)
{
IWebDriver driver = null;
switch (browserName)
{
case "Firefox":
FirefoxOptions fOptions = new FirefoxOptions();
fOptions.AddAdditionalOption("platform", "LINUX");
fOptions.AddAdditionalOption("version", "66");
driver = new FirefoxDriver(fOptions);
break;
case "Chrome":
ChromeOptions cOptions = new ChromeOptions();
cOptions.PlatformName = "windows";
cOptions.AddAdditionalOption("platform", "WIN10");
cOptions.AddAdditionalOption("version", "latest");
driver = new ChromeDriver(cOptions);
break;
}
return driver;
}
Wrappers/Helpers Example (partial):
public class Wrappers
{
public const int TIMEOUT_CONST = 10;
public static IWebElement SmartFindElement(IWebDriver driver, By byLocator, int timeout = TIMEOUT_CONST)
{
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeout));
var element = wait.Until(
SeleniumExtras.WaitHelpers.ExpectedConditions.ElementIsVisible(
byLocator));
return element;
}
...
Object Repository Query Class Example:
public class ObjRepoQuery
{
public static By GetLocator(string Xpath)
{
XmlDocument repo = new XmlDocument();
repo.Load(@"\ObjectRepository\XMLRepo.xml");
XmlNode node;
XmlElement root = repo.DocumentElement;
node = root.SelectSingleNode(Xpath);
By b = null;
string valueString = "";
if (node != null)
{
valueString = node.ChildNodes.OfType<XmlElement>().Where(e => e.Name == "value").FirstOrDefault().InnerText;
switch (node.ChildNodes.OfType<XmlElement>().Where(e => e.Name == "byType").FirstOrDefault().InnerText)
{
case "xpath":
b = By.XPath(valueString);
break;
case "name":
b = By.Name(valueString);
break;
case "class name":
b = By.ClassName(valueString);
break;
case "UAI":
b = By.Name(valueString);
break;
default:
break;
}
}
if (b == null) throw new NotFoundException("Locator not found on Object Repository");
return b;
}
}
<?xml version="1.0" encoding="utf-8" ?>
<DemoApp>
<LeftPanel>
<NotImplemented></NotImplemented>
</LeftPanel>
<Navigationbar>
<searchtxbx>
<byType>xpath</byType>
<value>//Frame[@ClassName="Top"][@Name="Main"]/Edit[@ClassName="Input"][@Name="Search Criteria"]</value>
</searchtxbx>
...
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.