App & Browser Testing Made Easy

Give your users a seamless experience by testing on 3000+ real devices and browsers. Don't compromise with emulators and simulators

Get Started free
Home Guide Design Patterns in Automation Framework

Design Patterns in Automation Framework

By Sonal Dwivedi, Community Contributor -

Web/Mobile automation testing is inevitable in today’s Software Testing world. Most of the organisations have been implementing test automation for more than a decade in order to keep pace with testing of different types of devices, platform, browsers, screen resolutions, etc.

Every individual automation tester has their own way of writing automation scripts to accomplish a task. However, when you collaborate in a team or you work with an organisation, you are bound to use a structured automation framework implementing a design pattern. This will help in code quality, maintainability, reusability, scalability, extensibility, and readability. 

Choosing the right design pattern depends upon many factors such as complexity of the application to be tested, knowledge of the automation team members, estimated time of delivery of the application, etc. 

A poor design eventually fails the automation framework, which, in turn yields flaky test results, code duplication, and poor code maintenance. To avoid this, we should identify problems in the automation framework and try to figure out the best suitable design pattern to be implemented.

Design patterns are broadly classified as Structural, Creational and Behavioural patterns.

This article explains the following design patterns:

  1.       Page Object Model Pattern
  2.       Factory Design Pattern
  3.       Facade Pattern
  4.       Singleton Pattern
  5.       Fluent Page Object Model

Before understanding various design patterns and their implementation let us have the prerequisites set beforehand.

Pre-requisites

Step 1 Add the required dependencies in pom.xml file for a Maven project or add the required jars into project classpath

This article has code snippets and to get the complete code for all the Design Pattern examples explained in this article you can refer to this github repository

For a Maven project add the “Selenium Java” and “WebDriverManager” dependency:

<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>

<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.2.1</version>
</dependency>
</dependencies>

Step 2 Add/ update Java JRE version to 8 for the working project directory.

Step 3 Add the TestNG library to the working project directory.

TestNG library to the working project directory.

Page Object Model Design Pattern

Page Object Model also known as POM is the most popular structural design pattern in web/mobile automation.

When working in an Agile environment we should always be ready to implement changes in the software and ultimately in the automation code base. When any requirement is changed/deleted/added we need to update automation code as well to keep it up and running. If any element is changed in one webpage which is used by 20 test class files in the automation framework, we need to update at 20 different places if POM design is not followed.

POM simplifies this and helps to update at one single location which will be used by those 20 different test class files.

In this design pattern web pages are represented as classes. Web page elements are defined as variables and user interactions are defined as methods. For example, the LoginPage class would have locators such as username, password, loginbtn and methods such as login(String username, String password).

Test classes use methods of these page classes whenever it needs to interact with the UI of the page with the help of the object of the respective page class. If any element is updated in any page, we just need to update the locator/ method of that page class only.

Page Object is a class that represents a web page and holds the web page elements and action methods. Page Factory is a way to initialise the web elements we want to interact with within the page object when you create an instance of it.

LoginPage lp=new LoginPage(driver); // Creating object of page class at test class level

Pagefactory.initElements(driver, this); // Initialising the web elements in page class.

Below diagram shows the implementation of POM Design pattern for testing valid login and search scenarios in an ecommerce website.

POM

Below implementation helps to understand Page Object Model design pattern with the help of a demo ecommerce website (http://automationpractice.com/index.php)

Step 1 Create a base class and add driver initialisation, and launch URL code inside init() method.

public class BaseClass {
static WebDriver driver;
static String browserName = "chrome";
static String url = "http://automationpractice.com/index.php";

public static WebDriver init() {
if (browserName.equalsIgnoreCase("chrome")) {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
} else if (browserName.equalsIgnoreCase("firefox")) {
WebDriverManager.firefoxdriver().setup();
driver = new FirefoxDriver();
}
driver.manage().deleteAllCookies();
driver.manage().window().maximize();
driver.get(url);
return driver;
} 
}

Step 2 Create HomePage, LoginPage, and SearchPage. Add constructor, locators and methods which test classes can use to interact with the AUT. 

Below is a code snippet for HomePage. Likewise create LoginPage and SearchPage.

public class HomePage{
WebDriver driver;

@FindBy(css = "a.login")
private WebElement signIn;

@FindBy(css = "input#search_query_top")
private WebElement search;

@FindBy(xpath = "//button[@name='submit_search']")c
private WebElement seacrhIcon;

@FindBy(css = "a.logout")
private WebElement signOut;

public LoginPage clickSignIn() {
signIn.click();
return new LoginPage(driver);
} 
public SearchPage search(String text) {
search.sendKeys(text);
seacrhIcon.click();
return new SearchPage(driver);
} 
public boolean logoutisDisplayed() {
return signOut.isDisplayed();
} 
public HomePage(WebDriver driver) {
PageFactory.initElements(driver, this);
}

}

Step 3 Create LoginTest and create objects of required POM classes and use it to call methods of respective POM classes to perform valid login scenario.

public class LoginTest{
WebDriver driver;
LoginPage lp;
HomePage hp;
SearchPage sp;

@BeforeTest
public void setUp() {
driver=BaseClass.init();
hp=new HomePage(driver);
lp=new LoginPage(driver);
sp=new SearchPage(driver);
}

@Test(priority = 1)
public void validLogin() {
hp.clickSignIn();
lp.login("<username>", "<password>");
driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
Assert.assertTrue(hp.logoutisDisplayed());
}
}

Here, to validate login, we have created objects for HomePage, LoginPage and then used it to call their respective methods hp.clickSignIn() and lp.login().

 Page Object Model design pattern enhances test maintenance, reduces code duplication, and increases code readability and reusability. It is an object-oriented class that serves as an interface to a page of the Application Under Test (AUT). Page objects encapsulate web elements, or other class members and provide access to wrapper methods that hide all the logic.

Factory Design Pattern

Factory Design Pattern is one of the most used creational patterns as it provides one of the best ways to create  an object. The term factory here means that there should be a class with a factory method which deals with all the creational stuff. 

In this pattern, there is a superclass with multiple subclasses and based on the user input at test class level, it returns one of the subclasses. In other words, it is used to create an object from one of the possible classes that extends a common parent class/ implements an interface. The instantiation logic would be the responsibility of the class that is extending the parent class thereby it hides the complex code at test level. As a user, we just need to create an object of this class and use it in the test class to call the appropriate method holding the business logic.

Below diagram shows the implementation of Factory Design pattern in creation of Webdriver object in Selenium framework.

 

Page Factory Design Pattern 1Below implementation shows the concept of Factory Design Pattern in order to get desired driver instance

Step 1 Create an abstract class as DriverManager. Create a method getDriver which should return a driver object.

public abstract class DriverManager {
protected WebDriver driver;
public WebDriver getDriver() {
return driver;
}
}

Step 2 Create a ChromeDriverManager class which will extend the DriverManager class.

public class ChromeDriverManager extends DriverManager {

public ChromeDriverManager() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
}
}

Step 3 Create a DriverManagerFactory class and add a method getManager(<driver type>). This class would take the browser value whatever the user has requested for in test class, create the respective driver and return it.

public class DriverManagerFactory {

public static DriverManager getManager(DriverType type) {
DriverManager driverManager = null;

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

Step 4 Create an enum as DriverType and add all the required browser types. 

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

Step 5 Lastly create a test class FactoryDesignTest which will use driverManager.getDriver() method to fetch the requested driver instance.

public class FactoryDesignTest {

DriverManager driverManager;
WebDriver driver;

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


@Test
public void verifyBStackDemoAddToCart() {
driver.get("https://bstackdemo.com/");
List<WebElement> addToCartBtns = driver.findElements(By.cssSelector("div.shelf-item__buy-btn"));
addToCartBtns.get(0).click();
WebElement chkoutbtn = driver.findElement(By.cssSelector("div.buy-btn"));
driver.manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS);
Assert.assertTrue(chkoutbtn.isDisplayed());
}

@Test
public void verifyBStackDemoTitle() {
driver.get("https://bstackdemo.com/");
Assert.assertEquals(driver.getTitle(), "StackDemo");
}

@Test
public void verifyBStackDemoLogo() {
driver.get("https://bstackdemo.com/");
WebElement logo = driver.findElement(By.cssSelector(" a.Navbar_logo__26S5Y"));
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
Assert.assertTrue(logo.isDisplayed());
}

@AfterTest
public void afterMethod() {
driver.quit();
}

}

Instead of creating an object of DriverManager class by using the new keyword, here we are calling getManager static method of DriverManagerfactory which is returning an object of DriverManager class (appropriate browser driver instance)

In future, if you need to add any new browser, say Firefox, you can create a FirefoxDriverManager class and add all the driver instantiation details in it like we did in ChromeDriverManager

You can use Factory Design Pattern when you need to encapsulate the complexity of creating an object. This pattern is easily extendable without altering existing code. This pattern serves one of the best ways to create an object where object creation logic is hidden from the client/user.

Facade Design Pattern

Facade design pattern comes under structural design patterns. It provides a simple interface to deal with complex code. In this pattern we create a facade class which has methods that combine actions executed on different pages. This is an extension to the Page Object Model pattern.

Following example can help you under Facade in a layman sense.

When we visit any restaurant to order our favourite food, we are not aware of what cuisine the restaurant serves unless we see the menu card or ask the waiter about it. We are just interested in ordering the food by using a waiter/menu card as the interface(facade) and do not worry about how it is actually being prepared in the kitchen. 

Let us consider an example of online shopping in an ecommerce website (http://automationpractice.com/index.php), where a customer has to add some products to cart and do a checkout for payment. Customer has to first login to the application, then add product to cart, checkout, add address and then checkout for payment. 

To hide this complexity of visiting various pages and interacting with it, we can create a facade class and all the required business logic in a method.

Below diagram showcase the Facade design pattern

Facade

To automate the above scenario with Facade Design Pattern, we need to create the required page classes with locators and class methods as we do in POM Design Pattern. Next, we should create a facade class which has all the complex code implementation that can be used by test classes.

Step 1 Create HomePage, LoginPage and SummaryPage, ShippingPage, PaymentPage, and AddressPage with locators and action methods.

Below is a code snippet for LoginPage. Likewise the other pages can be created.

public class LoginPage {
WebDriver driver; 
public LoginPage(WebDriver driver) {
PageFactory.initElements(driver, this);
} 
@FindBy(css = "a.login")
private WebElement signIn;

@FindBy(css = "input#email")
private WebElement email;

public void login(String user, String pass) { 
signIn.click();
email.sendKeys(user);
password.sendKeys(pass);
signInBtn.click();
}
}

Step 2 Create a PlaceOrderFacade class which has method calls to the above mentioned page classes combined all together in the placeOrder method.

public class PlaceOrderFacade { 
WebDriver driver;
LoginPage lp;
HomePage hp;
SummaryPage sp;
ShippingPage shp;
PaymentPage pp;
AddressPage ap;

public String placeOrder(WebDriver driver) throws InterruptedException {
lp=new LoginPage(driver);
System.out.println("lp: "+lp);
lp.login("<username>", "<password>");
hp=lp.clickHomeBtn(driver);
sp=hp.addToCartAndProceedToChkOut(0, driver);
ap=sp.proceedToCheckOut(driver);
shp=ap.proceedToCheckOut(driver);
pp=shp.proceedToCheckOut(driver);
String cartCount=pp.clickPayByChequeAndConfirm(driver);
return cartCount;
}
}

Step 3 Create FacadeDesignTest which will use the facade class object to call the placeOrder method of facade class.

public class FacadeDesignTest {
WebDriver driver;
PlaceOrderFacade facade;

@BeforeTest
public void setUp() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.get("http://automationpractice.com/index.php");
driver.manage().window().maximize();
facade=new PlaceOrderFacade();
}

@Test
public void placeOrder() throws InterruptedException {
Assert.assertEquals(facade.placeOrder(driver), "");
}

@AfterTest
public void tearDown() {
driver.quit();
}
}

PlaceOrderFacade holds the business logic to call methods of Page classes to achieve place order functionality. By implementing Facade Design Pattern, you just need to call the placeOrder method of PlaceOrderFacade in the test class.

This way you do not have to create page classes objects individually in the test class and call the associated methods. Instead, you can create only facade class objects and call the facade method, thereby reducing complexity in the test script. Also, in future if there is any new business logic in between place order flow, you just need to update the facade class.

Singleton Design Pattern

Singleton Design Pattern is one of the easiest and straightforward patterns to be implemented in an automation framework. This design is used when we need to use the same object of a class across the automation framework. It restricts the instantiation of a class to a single instance. 

Steps to follow to create singleton class:

  1.   Declare the constructor of the class as ‘private’ so that no one can instantiate the class outside of it
  2.   Declare a static reference variable of class
  3.   Declare a static method with return type as an object of this singleton class which should check if the class is already instantiated once.

Below example helps to understand Singleton Design Pattern with Selenium Webdriver.

Step 1 Create a SingletonBaseClass with WebDriver object initialised as null.

public class SingletonBaseClass {

private static WebDriver driver = null;
private static String browserName= "chrome";

public static void init() {
if (driver == null) {
if (browserName.equalsIgnoreCase("chrome")) {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
} else if (browserName.equalsIgnoreCase("firefox")) {
WebDriverManager.firefoxdriver().setup();
driver = new FirefoxDriver();
}
}
driver.manage().deleteAllCookies();
driver.manage().window().maximize();
} 
public static WebDriver getDriver() {
return driver;
} 
public static void quit() {
driver.quit();
driver=null;
}
}

In SingletonBaseClass,

Webdriver object has been initialised as static and null.

  • init() static method is used to initialise the Webdriver object only when the Webdriver object is null. If driver has a value (not been destroyed/ not null), init() method will not be executed and the same driver would be used for the further execution. This serves the actual purpose of Singleton design pattern
  • getDriver() static method returns driver object to be used in test class.
  • quit() static method quits the driver and makes the driver as null to destroy the driver object completely.

Step 2 Create SingletonDesignTest class

public class SingletonDesignTest {

WebDriver driver1;

WebDriver driver2;

@BeforeClass

public void setUp() {

SingletonBaseClass.init();

}

@Test(priority = 1)

public void verifyBStackDemoTitle() {

driver1 = SingletonBaseClass.getDriver();

System.out.println("driver1: " + driver1);

driver1.get("https://bstackdemo.com/");

Assert.assertEquals(driver1.getTitle(), "StackDemo");

}

@Test(priority = 2)

public void verifyBStackDemoLogo() {

driver2 = SingletonBaseClass.getDriver();

System.out.println("driver2: " + driver2);

driver2.get("https://bstackdemo.com/");

WebElement logo = driver2.findElement(By.cssSelector(" a.Navbar_logo__26S5Y"));

driver2.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);

Assert.assertTrue(logo.isDisplayed());

}

@AfterClass

public void tearDown() {

SingletonBaseClass.quit();

}

}

In the SingletonDesignTest class, verifyBStackDemoTitle and verifyBStackDemoLogo methods have different driver instances as driver1 and driver2 respectively.

Run the above program and observe that Chrome browser will be launched and verifyBStackDemoTitle will be executed. After this though verifyBStackDemoLogo has a different driver initialization, the driver would hold the same value, as init() method would check that the driver is not null. Hence it will not create another driver instance and the same browser would be used to execute verifyBStackDemoLogo.

Singleton Pattern can be used for creating classes which need to restrict duplicate class object instances. This pattern improves performance and memory utilisation. This pattern is mostly used for Logger, Database connections, or External Resources.  

Fluent Page Object Model

Implementing OOPs concept in any automation framework involves dealing with lots of page classes and objects. Most of the time, the test code required to implement these objects and methods becomes very complex. This complexity can be minimised by using Fluent Interface and Method Chaining. 

Page Object Model is the most popular and widely used design pattern as it helps in code readability and maintainability. And it can be more simplified and readable by using Fluent Page Object Model as it uses method chaining.

In this design pattern, every action method in the page class returns this to implement chaining methods for the business logic. This does not mean you cannot return other page class objects. You can either return this (same class) or another page class too.

Method chaining is a technique where one method is called directly on another method forming a chain-like structure.

Let us understand Fluent Page Object Design Pattern with the help of registration example in http://automationpractice.com/index.php website

Step 1 Create HomePage, LoginPage, and RegistrationPage page classes with locators and action methods.

Code snippet for RegistrationPage. Likewise you can create other pages.

public class RegistrationPage {
WebDriver driver;

@FindBy(css = "div#uniform-id_gender2")
private WebElement mrsRadio;

@FindBy(css = "input#customer_firstname")
private WebElement firstName;

@FindBy(css = "input#customer_lastname")
private WebElement lastName;

@FindBy(css = "input#passwd")
private WebElement password;

@FindBy(css = "input#firstname")
private WebElement addFirstName;

@FindBy(css = "input#lastname")
private WebElement addLastName;

@FindBy(css = "input#city")
private WebElement city; 

@FindBy(css = "input#postcode")
private WebElement postcode;

@FindBy(css = "button#submitAccount")
private WebElement registerBtn;

RegistrationPage(WebDriver driver) {
this.driver=driver;
PageFactory.initElements(driver, this);
}


public RegistrationPage selectFemaleTitle() {
System.out.println("driver; " +driver);
mrsRadio.click();
return this;
}

public RegistrationPage enterFirstName(String first) {
firstName.sendKeys(first);
return this;
}

public RegistrationPage enterLastName(String last) {
lastName.sendKeys(last);
return this;
}

public RegistrationPage enterPassword(String pass) {
password.sendKeys(pass);
return this;
}

public RegistrationPage enterAddFirstName(String first) {
addFirstName.sendKeys(first);
return this;
}

public RegistrationPage enterAddLastName(String last) {
addLastName.sendKeys(last);
return this;
}

public RegistrationPage enterCity(String cityName) {
city.sendKeys(cityName);
return this;
}

public RegistrationPage enterCode(String post) {
postcode.sendKeys(post);
return this;
}

public HomePage clickRegister() {
registerBtn.click();
return new HomePage(driver);
}


public static RegistrationPage using(WebDriver driver) {
return new RegistrationPage(driver);
}
}

In the RegistrationPage page class, methods such as enterFirstName, enterLastName and enterPassword return this and clickRegister returns a HomePage object. This shows that it is not mandatory for the methods to always return this.

Step 2 Create FluentDesignTest test class

public class FluentDesignTest {
public void register() { 
RegistrationPage.using(driver)
.selectFemaleTitle()
.enterFirstName("Peter")
.enterLastName("John")
.enterPassword("Test@123")
.enterAddFirstName("Peter")
.enterAddLastName("John")
.enterCity("Los Angeles")
.enterCode("88205")
.clickRegister(); 
}
}

In the above test class, we are not creating objects of page classes to access the associated methods. Instead, a single chain is created to call all the required methods of a page class in one call itself. 

The fluent page objects significantly improve the readability of tests. Also, it is quite easy to write tests with Fluent style. 

Conclusion

As an automation tester, you might come across several problems in framework on a daily basis and sometimes you might do a quick fix at test level to keep the test execution up and running. However, this solution may not last for long. It is recommended that QAs should identify problems and try to fix it at base level by creating common wrapper methods that can be used by multiple test classes.

You should also figure out if this problem can be solved by implementing proper design patterns and try to implement it in the early stages of automation framework development. Constantly evaluating the problems and updating the automation code/structure is the key for a successful automation framework.

Hence, choosing and implementing the right Design Pattern which suits your test automation requirements is the key to efficient testing.

Using BrowserStack Automate for web testing and App Automate for mobile app testing, you can seamlessly integrate your Selenium, Cypress, Playwright, Puppeteer, Cucumber, and Appium tests to make automation testing easier through your desired Design Pattern. Not just that, you can speed up your tests by 30x with parallel testing to expand your test and browser coverage without compromising on build times.

Try BrowserStack Real Device Cloud

Tags
Automation Frameworks Automation Testing

Featured Articles

Page Object Model and Page Factory in Selenium

Page Object Model with Playwright: Tutorial

App & Browser Testing Made Easy

Seamlessly test across 20,000+ real devices with BrowserStack