Complete guide how to use design patterns in automation
With series of posts, I’ve described 5 design patterns that each automation test engineer should know and use. I’ve started with a brief description of the patterns. Then I’ve explained in details with code snippets following patterns: Page objects, Facade, Factory, Singleton, Null object. Code examples are located in GitHub for C# and Java.
Overview
This post is intended to bond all together in a complete guide how to do write better automation code. Generally, automation project consists of two parts. Automation framework project and tests project. Current guide is intended to describe how to build your automation testing framework. How to structure your tests is a different topic. Remember once having correctly designed framework then tests will be much more clean, maintainable and easy to write. To keep post shorter some of the code that is not essential for representing the idea is removed. The whole code is on GitHub.Page objects
Everything starts by defining proper page objects. There is no fixed recipe for this. It all depends on the structure of application under test. The general rule is that repeating elements (header, footer, menu, widget, etc) are extracted as separate objects. The whole idea is to have one element defined in only one place (stay DRY)! Below is our HomePage object. What you can do generally is make search and clear search terms. Note that clearing is done with jQuery code. This is because of a bug I've described with a workaround in Selenium WebDriver cannot click UTF-8 icons post.using OpenQA.Selenium;
namespace AutomationRhapsody.DesignPatterns
{
class HomePageObject
{
private WebDriverFacade webDriver;
public HomePageObject(WebDriverFacade webDriver)
{
this.webDriver = webDriver;
}
private IWebElement SearchField
{
get { return webDriver.FindElement(By.Id("search")); }
}
public void SearchFor(string text)
{
SearchField.SendKeys(text);
}
public void ClearSearch()
{
webDriver.ExecuteJavaScript("$('span.cancel').click()");
}
}
}
WebDriver factory
WebDriver factory will be responsible for instantiating the WebDriver based on a condition which browser we want to run our tests with.using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
namespace AutomationRhapsody.DesignPatterns
{
public class WebDriverFactory
{
public IWebDriver CreateInstance(Browsers browser)
{
if (Browsers.Chrome == browser)
{
return new ChromeDriver();
}
else if (Browsers.IE == browser)
{
return new InternetExplorerDriver();
}
else
{
return new FirefoxDriver();
}
}
}
}
The constructor takes an argument browser type. Browser type is defined as an enumeration. This is very important. Avoid passing back and forth strings. Always stick to enums or special purpose classes. This will save you time investigating bugs in your automation.
public enum Browsers
{
Chrome, IE, Firefox
}
NullWebElement
This is null object pattern and implements IWebElement. There is a NULL property that is used to compare is given element is not found or no.using OpenQA.Selenium;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
namespace AutomationRhapsody.DesignPatterns
{
public class NullWebElement : IWebElement
{
private const string nullWebElement = "NullWebElement";
public bool Displayed { get { return false; } }
public bool Enabled { get { return false; } }
public Point Location { get { return new Point(0, 0); } }
public bool Selected { get { return false; } }
public Size Size { get { return new Size(0, 0); } }
public string TagName { get { return nullWebElement; } }
public string Text { get { return nullWebElement; } }
public void Clear() { }
public void Click() { }
public string GetAttribute(string attributeName) { return nullWebElement; }
public string GetCssValue(string propertyName) { return nullWebElement; }
public void SendKeys(string text) { }
public void Submit() { }
public IWebElement FindElement(By by) { return this; }
public ReadOnlyCollection<IWebElement> FindElements(By by)
{
return new ReadOnlyCollection<IWebElement>(new List<IWebElement>());
}
private NullWebElement() { }
private static NullWebElement instance;
public static NullWebElement NULL
{
get
{
if (instance == null)
{
instance = new NullWebElement();
}
return instance;
}
}
}
}
WebDriver facade
WebDriver facade main responsibility is to define custom behavior on elements location. This gives you centralized control over elements location. The constructor takes browser type and uses the factory to create WebDriver instance which is used internally in the facade. FindElement method defines explicit wait. If the element is not found then NullWebElement which is actual implementation of Null object pattern. The idea is to safely locate elements with try/catch and then just use them skipping checks for null.using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.ObjectModel;
namespace AutomationRhapsody.DesignPatterns
{
public class WebDriverFacade
{
private IWebDriver webDriver = null;
private TimeSpan waitForElement = TimeSpan.FromSeconds(5);
public WebDriverFacade(Browsers browser)
{
WebDriverFactory factory = new WebDriverFactory();
webDriver = factory.CreateInstance(browser);
}
public void Start(string url)
{
webDriver.Url = url;
webDriver.Navigate();
}
public void Stop()
{
webDriver.Quit();
}
public object ExecuteJavaScript(string script)
{
return ((IJavaScriptExecutor)webDriver).
ExecuteScript("return " + script);
}
public IWebElement FindElement(By by)
{
try
{
WebDriverWait wait = new WebDriverWait(webDriver, waitForElement);
return wait.Until(ExpectedConditions.ElementIsVisible(by));
}
catch
{
return NullWebElement.NULL;
}
}
}
}
Tests
As I mentioned initially this post is about using efficiently design patterns in your framework automation project. Tests design are not being discussed here. Once you have designed the framework one simple test (without asserts) that makes search will look like the code below.Browsers browser = Browsers.Chrome;
WebDriverFacade webDriver = new WebDriverFacade(browser);
webDriver.Start("/examples/utf8icons.html");
HomePageObject homePage = new HomePageObject(webDriver);
homePage.ClearSearch();
homePage.SearchFor("automation");
webDriver.Stop();