Table of Contents

Factory Design Principle in Framework - Selenium Automation

Factory design pattern is one of the most useful patterns. You can see the usage of this pattern everywhere. One such usage of Factory Design Principle in Frameworks is in the Log4j API. As we all know Log4j is a brilliant logging API. A typical usage of Log4j is something like this

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

public class run {

	public static void main(String[] args) {

		Logger logger = LogManager.getLogger(run.class);

		logger.debug("This is a random debug message");
	}

}

Here you will notice that we are not directly creating a  Logger object by using the new keyword. We are calling another class called LogManager and on that class we have a static method called getLogger(). This getLogger method is responsible for returning us a Logger object instance. LogManager is based on the Factory pattern design principle, it is basically a factory that stores and provides different Logger objects for usage.

So what is a Factory pattern design principle?

A Factory pattern is used to encapsulate the complexity of creating an object. In the example above, LogManager has encapsulated the creation of Logger. As a result all we have to do is call the getLogger method on LogManager and get the Logger object. Below are the two important ideas behind Factory pattern:

  • Hide the logic of initializing an object inside the factory.
  • Refer to the object using a common interface instead of the concrete class.
  • Maintain/Cache the newly created object's life time. This is not main responsibility of a factory pattern.

Lets understand how a Factory Pattern is beneficial while designing a Test framework.

Browser Factory

From our experience with frameworks we know that maintaining and passing around a WebDriver object across different tests is a delicate process. Also, the complexity increases when we have to maintain only one instance of a WebDriver through out the test run. To overcome the problem on instantiation of WebDriver and maintaining the instance of browser we can use create a small class called browser factory.

A browser factory class with a basic WebDriver initialization logic will look something like this.

public class BrowserFactory {

	private static Map<String, WebDriver> drivers = new HashMap<String, WebDriver>();

	/*
	 * Factory method for getting browsers
	 */
	public static WebDriver getBrowser(String browserName) {
		WebDriver driver = null;

		switch (browserName) {
		case "Firefox":
			driver = drivers.get("Firefox");
			if (driver == null) {
				driver = new FirefoxDriver();
				drivers.put("Firefox", driver);
			}
			break;
		case "IE":
			driver = drivers.get("IE");
			if (driver == null) {
				System.setProperty("webdriver.ie.driver",
						"C:\\Users\\abc\\Desktop\\Server\\IEDriverServer.exe");
				driver = new InternetExplorerDriver();
				drivers.put("IE", driver);
			}
			break;
		case "Chrome":
			driver = drivers.get("Chrome");
			if (driver == null) {
				System.setProperty("webdriver.chrome.driver",
						"C:\\Users\\abc\\Desktop\\Server\\ChromeDriver.exe");
				driver = new ChromeDriver();
				drivers.put("Chrome", driver);
			}
			break;
		}
		return driver;
	}
}

Key points in the above implementation:

  1. A Map of Browser name to Browser is stored as a private member. So nobody can directly access it.
  2. In the getBrowser() method a browser is created only once, that will be when the very first call to get the browser of a particular type will come. After that only the existing browser is returned. This means that the browser will be cached through the session.
  3. Complexity of browser initialization and storage is abstracted out from tests. This means that tests will no longer have to worry about where the browser came from or where it is stored. All they have to do is call the BrowserFactory.GetBrowser(<BrowserName>) method.
  4. This will enable tests to get any type of drivers without worrying of creating unnecessary driver instances.

Closing the WebDrivers

To Close WebDrivers that BrowserFactory has, we can simply put a close drivers method on the class and make it static. This method will be called when your test run ends. A simple closeDrivers() method would look something like this

	public static void closeAllDriver() {
		for (String key : drivers.keySet()) {
			drivers.get(key).close();
			drivers.get(key).quit();
		}

Here its just iterating over each driver in the Map of drivers and closing it.

TestNg test using Browser Factory Pattern

Lets now quickly see how this can help while writing our tests. Here is a sample test class with two tests and they both use BrowserFactory to get the  a driver instance. These tests are testNg tests

public class Test001 {
	@Test
	public void sampleTest001()
	{
		WebDriver driver = BrowserFactory.getBrowser("Firefox");
		driver.get("https://toolsqa.com");

		//Test that both the browsers are actually only one instance of firefox driver
		WebDriver driver1 = BrowserFactory.getBrowser("Firefox");

		if(driver.equals(driver1))
		{
			System.out.println("The two firefox drivers are same");
		}
	}

	@Test
	public void sampleTest002()
	{
		WebDriver driver = BrowserFactory.getBrowser("Chrome");
		driver.get("https://google.com");

		//Test that both the browsers are actually only one instance of chrome driver
		WebDriver driver1 = BrowserFactory.getBrowser("Chrome");
		if(driver.equals(driver1))
		{
			System.out.println("The two chrome drivers are same");
		}
	}

	@AfterSuite
	public void tearDown()
	{
		BrowserFactory.closeAllDriver();
	}

}

Key points to take away from the tests are:

  1. Notice that there now no need to instantiating  or passing any WebDriver to the tests. This makes tests quite isolated from WebDriver managing responsibility.
  2. In the test we are checking if the two drivers that the test try to get from browser factory are same. The result comes to be same. This is important conclusion because it means that we are not creating unnecessary instances of browsers and we are just reusing them.
  3. All the browsers that were opened during the test run are closed in the AfterSuite method. So no test class or runner class need to have the responsibility to close the drivers.

Cons of this approach

  • As you can see that this is not thread safe, it means that it will not be able to work on parallel test environments. A few modifications in this class and it will become capable of handling parallel requests. We will learn about it in  coming articles on design principles in test frameworks.

Here is the complete BrowserFactory code:

public class BrowserFactory {

	private static Map<String, WebDriver> drivers = new HashMap<String, WebDriver>();

	/*
	 * Factory method for getting browsers
	 */
	public static WebDriver getBrowser(String browserName) {
		WebDriver driver = null;

		switch (browserName) {
		case "Firefox":
			driver = drivers.get("Firefox");
			if (driver == null) {
				driver = new FirefoxDriver();
				drivers.put("Firefox", driver);
			}
			break;
		case "IE":
			driver = drivers.get("IE");
			if (driver == null) {
				System.setProperty("webdriver.ie.driver",
						"C:\\Users\\abc\\Desktop\\Server\\IEDriverServer.exe");
				driver = new InternetExplorerDriver();
				drivers.put("IE", driver);
			}
			break;
		case "Chrome":
			driver = drivers.get("Chrome");
			if (driver == null) {
				System.setProperty("webdriver.chrome.driver",
						"C:\\Users\\abc\\Desktop\\Server\\ChromeDriver.exe");
				driver = new ChromeDriver();
				drivers.put("Chrome", driver);
			}
			break;
		}
		return driver;
	}

	public static void closeAllDriver() {
		for (String key : drivers.keySet()) {
			drivers.get(key).close();
			drivers.get(key).quit();
		}
	}
}
Project Code Base
Project Code Base
Previous Article
Object Repository in Selenium
Object Repository in Selenium
Next Article
Virender Singh
I am Virender Singh, I have around 14 years of experience in the Technology domain.
Reviewers
Lakshay Sharma's Photo
Lakshay Sharma

Similar Articles

Feedback