Webdriver Design Patterns

Himanshu Bahuguna
Dotdash Meredith Tech Blog
5 min readSep 16, 2017

--

A test automation framework allows a user to write tests without dealing directly with the underlying test automation tool being used. A user should be able to focus on the business requirements when writing tests. Most of the time a user of a test automation framework is a tester who understands the product well and does not want to go into the technical details of the testing tool being used. So its important to note that test automation framework should provide test writers with an interface thats easy to use and hides the working of underlying testing tool.

When designing an automation framework we often encounter problems that require us to consider specific Patterns. In this post I have mentioned some patterns that we have successfully used in our testing.

Component Object

Modern web design enables web pages to be comprised of smaller components that can be reused across the templates. A component itself can contain other components and can be contained by another component. We can also replicate this component based model in an automation framework which can then be inherited by the actual representation of components in automation. Our component object should consist of driver instance and the web element that identifies the component on the page. Then the components or the web elements inside the component will be searched with respect to the web element of the component, not from the top of page. So it will make sense for us to use findElement method of the web element inside the component rather than using findElement method of the driver.

Base Component class :

// Base class for all components
public class Component {
private final WebElement element;
private final WebDriver driver;
public Component(WebElement element, WebDriver driver) {
this.element = element;
this.driver = driver;
}
protected WebElement findElement(By by) {
return element.findElement(by);
}
protected T findComponent(By by, Class T componentClass) {
T component = null;
try {
Constructor T constructor = componentClass.getConstructor(new Class[] { WebElement.class, WebDriver.class });
component = constructor.newInstance(findElement(by), driver);
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
throw new IllegalArgumentException();
}
return component;
}
}

ContentComponent inheriting Component:

// Google search page content component inheriting base component
public class ContentComponent extends Component {
private final WebElement image;
private final FooterComponent footer;
public ContentComponent(WebElement element, WebDriver driver) {
super(element, driver);
this.image = findElement(By.tagName("img"));
this.footer = findComponent(By.id("footer"), FooterComponent.class);
}
// ContentComponent contains FooterComponent
public FooterComponent footer() {
return footer;
}
public WebElement image() {
return image;
}
}

FooterComponent inheriting Component:

// Google search page footer component 
public class FooterComponent extends Component {
private final WebElement advertising;
public FooterComponent(WebElement element, WebDriver driver) {
super(element, driver);
this.advertising = findElement(By.cssSelector(".fbar #fsl a"));
}
public WebElement advertising() {
return advertising;
}
}

Page Object

Since a web page template is comprised of certain components, we can reflect this behavior in automation by creating page objects for these templates. Now we can declare all the components that are needed on a certain template. This object which defines a particular template can then be used in tests to initiate a test on a particular instance of that template. We can first create a base Page class that can then be inherited by other Page Objects representing templates. This base Page class extends from Component class mentioned above because we would like to reuse findComponent method in our Page. We are assuming that the page has an HTML tag which contains head and body tags. We can treat body tag as a component which will contain other components on a page.

Base Page class Inheriting Component:

//Base class for a Page Object
public class Page extends Component {
private final WebElement head;
private final Component body;
public Page(WebDriver driver) {
super(driver.findElement(By.tagName("html")), driver);
this.head = findElement(By.tagName("head"));
this.body = findComponent(By.tagName("body"), Component.class);
}
public WebElement head() {
return head;
}
public Component body() {
return body;
}
}

Google Search Page Inheriting Page:

//GooglePage inherits Page
public class GooglePage extends Page {
private ContentComponent content;
public GooglePage(WebDriver driver) {
super(driver);
this.content = findComponent(By.className("content"), ContentComponent.class);
}
public ContentComponent content() {
return content;
}
}

WebDriver Factory

Often times we want to run our automation tests on more than one browser. We need to come with a way to select a particular instance of WebDriver based on the browser where we want to run a test. We can pass a browser as an argument to the test using browser parameter on command line or as a VM argument in eclipse e.g. -Dbrowser=chrome.So we first create a Browser enum that represents all the browsers we support in our automation.

public enum Browser {
chrome,
firefox;
}

Now, we need to create a WebDriverFactory interface with getDriver method which will be implemented by every browser type in Browser enum.

public interface WebDriverFactory {
WebDriver getDriver(Browser browser);
}

So we will first create a class called ChromeFactory that implements WebDriverFactory for chrome. If browser parameter passed to the test matches chrome then we will instantiate and return a ChromeDriver instance.

public class ChromeFactory implements WebDriverFactory {
private static final String CHROMEDRIVER_PATH = "selenium/chromedriver";
@Override
public WebDriver getDriver(Browser browser) {
if(browser.equals(Browser.chrome)) {
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName(BrowserType.CHROME);
ChromeDriverService service = new ChromeDriverService.Builder().usingAnyFreePort()
.usingDriverExecutable(new File(FileUtils.getUserDirectory(), CHROMEDRIVER_PATH)).build();
return new ChromeDriver(service);
}
return null;
}
}

Now we will create a class called FirefoxFactory that implements WebDriverFactory for firefox. If browser parameter passed to the test matches firefox then we will instantiate and return a FirefoxDriver instance.

public class FirefoxFactory implements WebDriverFactory {
@Override
public WebDriver getDriver(Browser browser) {
if(browser.equals(Browser.firefox)) {
DesiredCapabilities capabilities = DesiredCapabilities.firefox();
return new FirefoxDriver(capabilities);
}
return null;
}

}

Once we have our factories ready we can then use them to initiate the test. For this we will create a class called DriverTest that takes care of instantiating a desired WebDriver instance based on browser parameter passed to the test. We need to create a list of available WebDriverFactories, then we will iterate over this list to find an appropriate driver based on the browser parameter passed to the test. To find the value of browser parameter, we collect all system properties in a hashmap called propertiesMap, and then look for the value of browser parameter in the propertiesMap.

public class DriverTest {
private final static Map<String, String> propertiesMap = new HashMap<>();
private final WebDriver driver;
static{
Properties systemProperties = System.getProperties();
for (String key : systemProperties.stringPropertyNames()) {
propertiesMap.put(key, systemProperties.getProperty(key));
}
}
public DriverTest() {
this.driver = getDriverForBrowser(Browser.valueOf(propertiesMap.get("browser")));
}
public WebDriver driver() {
return driver;
}
private WebDriver getDriverForBrowser(Browser browser) {
ImmutableList WebDriverFactory webDriverFactories = ImmutableList.WebDriverFactory builder()
.add(new ChromeFactory())
.add(new FirefoxFactory())
.build();
for (WebDriverFactory driverFactory : webDriverFactories) {
WebDriver driver = driverFactory.getDriver(browser);
if (driver != null) return driver;
}
throw new IllegalStateException("WebDriver was not instantiated!");
}

}

We are now ready to use the GooglePage Object created above in our test. Below is an example of a test that uses the objects using Page and Component objects :

public class GooglePageTest {
DriverTest test = new DriverTest();
@Test
public void gTest() {
WebDriver driver = test.driver();
try {
driver.get("https://www.google.com/");
GooglePage gPage = new GooglePage(driver);
WebElement advertising = gPage.content().footer().advertising();
advertising.click();
} finally {
driver.quit();
}
}
}

--

--