Factory Method Design Pattern in Java with Webdriver.
Factory Design Pattern
Type: Creational design pattern.
Purpose: Focuses on the efficient creation of objects without exposing the instantiation logic to the client.
Key Concepts:
Defines an interface (Java interface or abstract class) for creating objects.
Delegates the responsibility of deciding which class to instantiate to subclasses (concrete factories).
Hides the object creation logic from the user, providing a simple method to get the required object.
Promotes loose coupling between client code and the classes it depends on.
Benefits:
Encapsulation of object creation logic: The client only interacts with the factory, not the specific classes.
Improved code maintainability: Adding new classes requires minimal changes to existing code.
Enhanced flexibility: Makes the codebase scalable as new product types can be introduced without affecting client code.
Promotes the Open/Closed Principle: Classes are open for extension but closed for modification.
Real-World Examples:
Java’s Calendar.getInstance() method.
JDBC DriverManager: The getConnection() method hides the details of database driver instantiation.
UI Component Libraries: Generating different UI elements (buttons, text fields) using a common factory.
When to Use:
When you need to create similar types of objects with slight variations.
When the exact type of object to be created is determined at runtime.
When the creation process is complex and needs to be centralized.
When you want to decouple client code from concrete classes.
When implementing the Open/Closed Principle, allowing new types to be added without modifying existing code.
Before moving to Webdriver , Here is a basic example of Factory design , for crreating Vanila or Chocolote ice cream.
// Interface defining the contract for all IceCream types
interface IceCream {
String getDescription(); // Method to get the description of the ice cream
}
// Concrete class representing Chocolate Ice Cream
class ChocolateIceCream implements IceCream {
@Override
public String getDescription() {
return "Delicious Chocolate Ice Cream";
}
}
// Concrete class representing Vanilla Ice Cream
class VanillaIceCream implements IceCream {
@Override
public String getDescription() {
return "Classic Vanilla Ice Cream";
}
}
// Factory class responsible for creating IceCream instances
class IceCreamFactory {
// Factory method to create IceCream instances based on type
public static IceCream createIceCream(String type) {
switch (type.toLowerCase()) { // Switch on the ice cream type (case-insensitive)
case "chocolate":
return new ChocolateIceCream(); // Create and return a ChocolateIceCream instance
case "vanilla":
return new VanillaIceCream(); // Create and return a VanillaIceCream instance
default:
throw new IllegalArgumentException("Invalid ice cream type: " + type); // Handle invalid types
}
}
}
// Client class using the IceCreamFactory
public class IceCreamLover {
public static void main(String args) {
// Get Chocolate Ice Cream using the factory
IceCream chocolateIceCream = IceCreamFactory.createIceCream("chocolate");
System.out.println(chocolateIceCream.getDescription()); // Output: Delicious Chocolate Ice Cream
// Get Vanilla Ice Cream using the factory
IceCream vanillaIceCream = IceCreamFactory.createIceCream("vanilla");
System.out.println(vanillaIceCream.getDescription()); // Output: Classic Vanilla Ice Cream
// Try to get an invalid ice cream type
try {
IceCream invalidIceCream = IceCreamFactory.createIceCream("strawberry"); // Will throw an exception
System.out.println(invalidIceCream.getDescription());
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage()); // Output: Error: Invalid ice cream type: strawberry
}
}
}
Explanation with Inline Comments:
IceCream Interface: This interface defines the contract for all ice cream types. All concrete ice cream classes must implement this interface and provide a getDescription() method.
ChocolateIceCream and VanillaIceCream Classes: These are the concrete implementations of the IceCream interface. Each class represents a specific type of ice cream and provides its own implementation of the getDescription() method.
IceCreamFactory Class: This is the factory class. Its createIceCream() method is responsible for creating the correct IceCream instance based on the type parameter. The switch statement acts as the “factory method” and determines which concrete ice cream class to instantiate.
IceCreamLover Class (Client): This class is the client code. It uses the IceCreamFactory to get the desired IceCream instance. It doesn’t need to know the specific concrete classes of the ice cream; it just asks the factory for an ice cream of a certain type.
How it Works:
The IceCreamLover class wants to have some ice cream.
It uses the IceCreamFactory.createIceCream() method, passing the desired type (“chocolate” or “vanilla”).
The IceCreamFactory creates the correct IceCream object (either ChocolateIceCream or VanillaIceCream) and returns it.
The IceCreamLover class can then use the IceCream object without needing to know its specific type.
This example illustrates the basic principles of the Factory Design Pattern: defining an interface, creating concrete classes, and using a factory to create instances of those classes based on a given input. This pattern promotes loose coupling, making your code more flexible and easier to maintain.
1️⃣ DriverManager Interface
public interface DriverManager {
WebDriver createDriver();
}
// Interface defining the contract for creating WebDriver instances
2️⃣ Concrete Driver Managers
ChromeDriverManager -Concrete class for creating Chrome drivers
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
public class ChromeDriverManager implements DriverManager {
@Override
public WebDriver createDriver() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--start-maximized"); // Example option
return new ChromeDriver(options);
}
}
FirefoxDriverManager — Concrete class for creating Firefox drivers
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
public class FirefoxDriverManager implements DriverManager {
@Override
public WebDriver createDriver() {
FirefoxOptions options = new FirefoxOptions();
options.addArguments("--width=1280");
options.addArguments("--height=800");
return new FirefoxDriver(options);
}
}
EdgeDriverManager -Concrete class for creating Edge drivers
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions;
public class EdgeDriverManager implements DriverManager {
@Override
public WebDriver createDriver() {
EdgeOptions options = new EdgeOptions();
options.addArguments("start-maximized");
return new EdgeDriver(options);
}
}
// Factory class responsible for managing WebDriver instances and ensuring thread safety
class DriverManagerFactory {
// ThreadLocal to store WebDriver instances, ensuring each thread has its own instance
private static final ThreadLocal<WebDriver> driverThreadLocal = new ThreadLocal<>();
// Method to get the WebDriver instance for the specified browser type
public static WebDriver getDriver(String browserType) {
// Get the WebDriver instance from the ThreadLocal
WebDriver driver = driverThreadLocal.get();
// If no driver exists for the current thread, create one
if (driver == null) {
DriverManager driverManager = createDriverManager(browserType); // Create the appropriate DriverManager
driver = driverManager.createDriver(); // Create the WebDriver instance
driverThreadLocal.set(driver); // Store the driver in the ThreadLocal
}
return driver;
}
// Method to create the appropriate DriverManager based on the browser type
private static DriverManager createDriverManager(String browserType) {
switch (browserType.toLowerCase()) {
case "chrome":
return new ChromeDriverManager();
case "firefox":
return new FirefoxDriverManager();
case "edge":
return new EdgeDriverManager();
default:
throw new IllegalArgumentException("Unknown browser: " + browserType);
}
}
// Method to quit the WebDriver instance and remove it from the ThreadLocal
public static void quitDriver() {
WebDriver driver = driverThreadLocal.get(); // Get the driver from ThreadLocal
if (driver != null) {
driver.quit(); // Quit the driver
driverThreadLocal.remove(); // Very important: Clean up the ThreadLocal to prevent memory leaks
}
}
}
Test class using TestNG for parallel execution
public class WebTestCase {
// Method to set up the driver before each test method
@Parameters("browser") // Get the browser parameter from the TestNG XML
@BeforeMethod
public void setUp(String browser) {
WebDriver driver = DriverManagerFactory.getDriver(browser); // Get the driver based on the browser parameter
// Any other setup you need before each test (e.g., navigation)
}
// Test method 1
@Test
public void testMethod1() {
WebDriver driver = DriverManagerFactory.getDriver("chrome"); // You can also get it here if needed.
driver.get("https://www.google.com");
// Your test logic for Chrome
System.out.println("Test method 1 on " + driver.getTitle());
}
// Test method 2
@Test
public void testMethod2() {
WebDriver driver = DriverManagerFactory.getDriver("firefox");
driver.get("https://www.bing.com");
// Your test logic for Firefox
System.out.println("Test method 2 on " + driver.getTitle());
}
// Test method 3
@Test
public void testMethod3() {
WebDriver driver = DriverManagerFactory.getDriver("edge");
driver.get("https://www.microsoft.com");
// Your test logic for Edge
System.out.println("Test method 3 on " + driver.getTitle());
}
// Method to tear down the driver after each test method
@AfterMethod
public void tearDown() {
DriverManagerFactory.quitDriver(); // Quit the driver and clean up the ThreadLocal
}
}
Sample XML
public class WebTestCase {
// Method to set up the driver before each test method
@Parameters("browser") // Get the browser parameter from the TestNG XML
@BeforeMethod
public void setUp(String browser) {
WebDriver driver = DriverManagerFactory.getDriver(browser); // Get the driver based on the browser parameter
// Any other setup you need before each test (e.g., navigation)
}
// Test method 1
@Test
public void testMethod1() {
WebDriver driver = DriverManagerFactory.getDriver("chrome"); // You can also get it here if needed.
driver.get("https://www.google.com");
// Your test logic for Chrome
System.out.println("Test method 1 on " + driver.getTitle());
}
// Test method 2
@Test
public void testMethod2() {
WebDriver driver = DriverManagerFactory.getDriver("firefox");
driver.get("https://www.bing.com");
// Your test logic for Firefox
System.out.println("Test method 2 on " + driver.getTitle());
}
// Test method 3
@Test
public void testMethod3() {
WebDriver driver = DriverManagerFactory.getDriver("edge");
driver.get("https://www.microsoft.com");
// Your test logic for Edge
System.out.println("Test method 3 on " + driver.getTitle());
}
// Method to tear down the driver after each test method
@AfterMethod
public void tearDown() {
DriverManagerFactory.quitDriver(); // Quit the driver and clean up the ThreadLocal
}
}