Chapter[20]: Essential Design Patterns for Automating a Whole Website
Why Do We Need Design Patterns in Automation?
Let’s say you’re building a car. You wouldn’t randomly weld metal pieces together, right? You’d follow a structured plan so that everything fits perfectly and functions smoothly.
Automation is the same! Without structure, your test scripts become a chaotic mess — hard to maintain, impossible to scale.
This is where design patterns come in. They help organize code so that it is reusable, maintainable, and scalable.
Let’s explore some must-know design patterns for automation.
1. Singleton Pattern — Managing a Single WebDriver Instance
Problem:
If every test case creates a new WebDriver instance, multiple browsers will open, which slows down execution and consumes unnecessary resources.
Solution:
The Singleton Pattern ensures that only one WebDriver instance is used throughout the test execution.
How It Works:
- Prevents multiple browsers from opening unnecessarily.
- Ensures the same WebDriver instance is used across tests.
Code Example:
public class WebDriverManager {
private static WebDriver driver; // Single WebDriver instance
// Private constructor to prevent external object creation
private WebDriverManager() {}
// Method to return the single WebDriver instance
public static WebDriver getInstance() {
if (driver == null) { // If no driver instance exists, create one
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
}
return driver; // Return the same instance every time
}
}
How to Use It in Your Tests?
WebDriver driver = WebDriverManager.getInstance();
With this approach, only one browser window is opened and shared across test cases.
2. Page Object Model (POM) — Organizing Your Code
Problem:
Locators (By.xpath()
, By.id()
) are scattered throughout test scripts, making updates a nightmare when something changes on the website.
Solution:
The Page Object Model (POM) separates UI elements into dedicated classes, keeping the test scripts clean and readable.
Code Example:
public class LoginPage {
private WebDriver driver; // WebDriver instance for this page
// Define locators for elements
By usernameField = By.id("username");
By passwordField = By.id("password");
By loginButton = By.id("login");
// Constructor to initialize WebDriver
public LoginPage(WebDriver driver) {
this.driver = driver;
}
// Method to enter username
public void enterUsername(String username) {
driver.findElement(usernameField).sendKeys(username);
}
// Method to enter password
public void enterPassword(String password) {
driver.findElement(passwordField).sendKeys(password);
}
// Method to click the login button
public void clickLogin() {
driver.findElement(loginButton).click();
}
}
How to Use It in Your Test?
LoginPage login = new LoginPage(driver);
login.enterUsername("user123");
login.enterPassword("password123");
login.clickLogin();
If the login button’s ID changes in the future, you only update it in the LoginPage
class, instead of searching through multiple test cases.
3. Factory Pattern — Creating WebDriver Instances Dynamically
Problem:
You want to run tests on multiple browsers like Chrome, Firefox, and Edge, but hardcoding each one isn’t practical.
Solution:
The Factory Pattern lets you create WebDriver instances dynamically based on input(Note: This is just a basic example, we will learn more about this in the upcoming articles).
Code Example:
public class chooseBrowserType{
// Method to create a WebDriver instance based on browser type
public static WebDriver getDriver(String browserType) {
switch (browserType.toLowerCase()) {
case "chrome":
WebDriverManager.chromedriver().setup();
return new ChromeDriver(); // Returns ChromeDriver instance
case "firefox":
WebDriverManager.firefoxdriver().setup();
return new FirefoxDriver(); // Returns FirefoxDriver instance
default:
throw new IllegalArgumentException("Browser not supported: " + browserType);
}
}
}
How to Use It in Your Test?
WebDriver driver = DriverFactory.getDriver("chrome");
Now, changing the browser is super easy!
4. Fluent Interface Pattern — Cleaner Test Steps
Problem:
Test scripts can get cluttered and hard to read.
Solution:
The Fluent Interface Pattern allows method chaining, making test steps more readable.
Code Example:
public class LoginPage {
private WebDriver driver;
public LoginPage(WebDriver driver) {
this.driver = driver;
}
// Method to enter username with method chaining
public LoginPage enterUsername(String username) {
driver.findElement(By.id("username")).sendKeys(username);
return this; // Returns the same object for chaining
}
// Method to enter password with method chaining
public LoginPage enterPassword(String password) {
driver.findElement(By.id("password")).sendKeys(password);
return this;
}
// Method to click login button with method chaining
public LoginPage clickLogin() {
driver.findElement(By.id("login")).click();
return this;
}
}
How to Use It in Your Test?
new LoginPage(driver)
.enterUsername("user123")
.enterPassword("password123")
.clickLogin();
This makes test scripts easier to read and maintain.
.
.
.
Happy Coding!