fac001

Selenium WebDriver – Design Patterns in Test Automation – Factory Pattern

Overview:

As a software engineer, We all face some errors/exceptions while writing code! So what do we do when we face such a problem? If we are not sure, We google for solutions immediately. Don’t we? We google because we know that we would not be alone and someone would have already found the solution, for the problem we are facing now, which we could use to solve our problem.

Well, What to do if we face an issue in the high level software design – when connecting different classes / modules – when you have only a vague idea!! How can we google such things when we ourselves are not sure of the problem we have in the first place!!

No worries, You still have a solution!

Design patterns are the solutions for the problems which you might face in the software design!!

Design Patterns are well optimized and reusable solutions to a given problem in the software design. It helps us to show the relationship among the classes and the way in which they interact. Design pattern is a template which you have to carefully analyze and use it in appropriate places.

More information on design pattern can be found here.

Design Patterns in Test Automation:

As an automated test engineer, should we really care about design principles and patterns? Why do we need to learn/use Design Patterns in functional test automation?

After all, Test automation framework/automated test case is also a software which is used to test another software. So, we could apply the same design principles/patterns in our test automation design as well to come up with more elegant solution for any given design related problem in the test automation framework.

Remember that Design Patterns are NOT really mandatory. So you do not have to use them! But when you learn them, you would know exactly when to use! Basically these are all well tested solutions. So when you come across a problem while designing your framework/automated test cases, these design patterns could immediately help you by proving you a formula / template & saves us a lot of time and effort.

Note: Your aim should not be to implement a certain pattern in your framework. Instead, identify a problem in the framework and then recognize the pattern which could be used to solve the problem. Do not use any design pattern where it is not really required!!

In this article, We are going to see where we could use the Factory Pattern.

 

Factory Pattern:

Factory Pattern is one of the creation Patterns. It is mostly used when we need to create an object from one of several possible classes that share a common super class / implements an interface. It creates objects without exposing the instantiation logic to the user. We, as the user, refer to the newly created object through a common interface.

Best example is – WebDriver.

fac001

As all these Chrome/Firefox/Safari/IE driver concrete classes implement this WebDriver interface, We are able to refer to the, ChromeDriver/FirefoxDriver etc, instance through the WebDriver interface without much change in the code.

Problem in Creating WebDriver Instance:

Even though the above concrete classes implements a common interface, We need to follow different approaches in creating an instance from one of these concrete classes which depends on the browser instance we might want to use.

For ex: ChromeDriver requires a Driver Server setup but Firefox (till version 45) does not need anything.

WebDriver driver = new FirefoxDriver();

The above code simply works – but the below code might not unless we set driver server executable.

WebDriver driver = new ChromeDriver();

But we know how to solve!!

The easiest solution is – to put all the logic in a if else block!! right?

if (Browser.equals("chrome")) {

    // logic to start the driver service
    // then to create driver etc

} else if (Browser.equals("firefox")) {

    // logic to start the driver service
    // then to create driver etc

} else if (Browser.equals("ie")) {


} else if (Browser.equals("safari")) {


} else if (Browser.equals("phantomjs")) {


}

It might look like an easy solution. But it is really NOT. When we have to start/stop the Driver Service ourselves, the code becomes significantly larger and very difficult to maintain.

Factory Pattern in Creating WebDriver Instance:

Lets see, How we can address this using a Factory Pattern. Test classes, as the users, should not really care how the drivers are actually created. What it needs is just a WebDriver instance to execute the given test case. So we come up with our own abstract class – DriverManager – which test classes could use to get a driver instance from it and use them in their tests.

fac002

Lets see the sample concrete implementation.

 

Driver Manager:

public abstract class DriverManager {

    protected WebDriver driver;
    protected abstract void startService();
    protected abstract void stopService();
    protected abstract void createDriver();

    public void quitDriver() {
        if (null != driver) {
            driver.quit();
            driver = null;
        }

    }

    public WebDriver getDriver() {
        if (null == driver) {
            startService();
            createDriver();
        }
        return driver;
    }
}

Chrome Driver Manager:

public class ChromeDriverManager extends DriverManager {

    private ChromeDriverService chService;

    @Override
    public void startService() {
        if (null == chService) {
            try {
                chService = new ChromeDriverService.Builder()
                    .usingDriverExecutable(new File("chromedriver.exe"))
                    .usingAnyFreePort()
                    .build();
                chService.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void stopService() {
        if (null != chService && chService.isRunning())
            chService.stop();
    }

    @Override
    public void createDriver() {
        DesiredCapabilities capabilities = DesiredCapabilities.chrome();
        ChromeOptions options = new ChromeOptions();
        options.addArguments("test-type");
        capabilities.setCapability(ChromeOptions.CAPABILITY, options);
        driver = new ChromeDriver(chService, capabilities);
    }

}

Driver Types:

public enum DriverType {
    CHROME,
    FIREFOX,
    IE,
    SAFARI;
}

Driver Manager Factory:

public class DriverManagerFactory {

    public static DriverManager getManager(DriverType type) {

        DriverManager driverManager;

        switch (type) {
            case CHROME:
                driverManager = new ChromeDriverManager();
                break;
            case FIREFOX:
                driverManager = new FirefoxDriverManager();
                break;
            default:
                driverManager = new SafariDriverManager();
                break;
        }
        return driverManager;

    }
}

Factory Pattern Test:

public class FactoryPatternTest {

    DriverManager driverManager;
    WebDriver driver;

    @BeforeTest
    public void beforeTest() {
        driverManager = DriverManagerFactory.getManager(DriverType.CHROME);
    }

    @BeforeMethod
    public void beforeMethod() {
        driver = driverManager.getDriver();
    }

    @AfterMethod
    public void afterMethod() {
        driverManager.quitDriver();
    }

    @Test
    public void launchTestAutomationGuruTest() {
        driver.get("http://testautomationguru.com");
        Assert.assertEquals("TestAutomationGuru – A technical blog on test automation", driver.getTitle());
    }

    @Test
    public void launchGoogleTest() {
        driver.get("https://www.google.com");
        Assert.assertEquals("Google", driver.getTitle());
    }

    @Test
    public void launchYahooTest() {
        driver.get("https://www.yahoo.com");
        Assert.assertEquals("Yahoo", driver.getTitle());
    }

}

 

fac003

If the test class has to use Firefox, just use as given below.

driverManager = DriverManagerFactory.getManager(DriverType.FIREFOX);

 

Summary:

By using Factory Pattern, we completely hide the creational logic of the browser / service instances to the test classes. If we get a new requirement to add a new browser, say PhantomJS, should not be a big deal. We just need to create a PhantomJSDriverManager which extends DriverManager. [Ofcourse you have to add PhantomJS in DriverType]

Design Pattern is a very good tool for effective communication. How? As soon as we name a pattern, Others can immediately picture the high-level-design in their heads & get the solution for the given problem. Thus, Design Pattern enables us to explain the design in a much better way.

You might be interested in other design patterns as well. Check below articles to learn more!

 

Happy Testing & Subscribe 🙂

 

 

Share This:

Categories: Articles, Best Practices, Design Pattern, Factory Pattern, Framework, Selenium

Leave a Reply

Your email address will not be published. Required fields are marked *