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.
Udemy – Selenium WebDriver and Design Patterns Course:
VinsGuru has released a brand new course in Udemy on Selenium WebDriver and Design Patterns. ~8 hours course with all the important design patterns to design better, reusable page object models, test classes etc. Please access the above link which gives you the special discount. You can also get your money back if you do not like the course within 30 days.
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.
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.
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());
}
}
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!
- Decorator Design Pattern
- Strategy Design Pattern
- Template Method Design Pattern
- Proxy Method Design Pattern
- Chain Of Responsibility Design Pattern
- Execute Around Method Pattern
All above patterns are code design patterns. There are other patterns at system / process level etc. For more information, you can take a look at this site.
Happy Testing & Subscribe 🙂
I am your fan…Thank you so much
🙂 Thanks.
Very nice explanation . Surely I will try this 🙂
How would this work using RemoteWebDriver?
Factory Pattern is just an idea to solve some complex software design. You could come up with similar design with appropriate methods for RemoteWebDriver. RemoteWebDriver can also extend DriverManager class. so that You could invoke
DriverManagerFactory.getManager(DriverType.REMOTE)
Great Article. Really love the way you explain the concepts. Just 1 question:
I am using a properties file to check the browser that needs to be invoked to run the tests. In that case, do I need to define the ENUM class ? Can you please that as well?
if your property is like this – browser=CHROME – then you could convert the string to ENUM via
DriverType.valueOf(prop.get("browser')) //this returns DriverType.CHROME
here prop is the instance of Properties.I have become your fan.Its a wonderful description.
I have a basic question- in the previous selenium verisons say 2.41, we didn’t need drivers for firefox and I, but in the latest versions , geckodriver,IEdriver etc are required. So, in the code above we just cannot add FIREFOX,IE to the driver type. We will have to create firefoxDriverManager and IEDriverManager class that extends DriverManager class. Correct me of I’m wrong.
Your understanding is right Raveena. The aim here is to give the readers the idea how it can be done.
Thanks for the great articles on Selenium design patterns. I do have a few questions, I will start with this one. Each of the Drivers has a different set of Capabilities or now Options. I would like to configure these via an external Json file. I had used properties files in the past but I don’t like the organization or lack of. Also, Grid and Node(s) are configured via json files. I’m struggling to find a good approach to read these files in and pass them thru to the factories. I created a jackson JsonFileMapper and pass the Map in to the Factory, but there are so many options: Chrome has Options & experimental preferences, FF has options and preferences … The docs on the options aren’t very good, every time I require a new one I have to change the code. Its a bit of a mess. Any thoughts / patterns on how you or other folks are handling this? – THanks!
Hi, just wanted to check what is the of Driver Types, how does it interact, can you explain on this.
Thanks
Sorry for the late reply. DriverType is an enum type in Java. Please google on that. It is basically optional. Based on the type like chrome, firefox etc we return the corresponding DriverManager. Having enum makes the code little bit more elegant and helps with type checking. But you can ignore that part. Instead you can use String. For ex:
if(driver.equals("chrome")) return new ChromeDriverManager();
Thank you man. Great information. Best regards.
I noticed you are using DriverService, I’m not familiar with it. Could you say a few words on its use? I have been using the Driver(s) directly and always quit after each test.
DriverService is the one which manages the lifecycle of the WebDriver. All the WebDriver implementations would have corresponding DriverService implementations. Actually every time you launch a WebDriver, it launches its driver service. Instead of that, You can first create a driver service, then launch the WebDriver and pass the driver service, this way you could see performance improvement.
The code does not work – line
return driverManager; shows error in DriverManagerFactory class.
Please show how to create FirefoxDriverManager because I assume this will require geckoDriverService and different Firefox Options settings.
This article is few years old. The concept remains same. My idea was to demonstrate the pattern.