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 Decorator Pattern which is one of the Structural design patterns.
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.
Problem Statement:
I have a manage console / support application in which depends on the role of the logged in user, we show different components on the screen.
For ex: The ‘user’ role can only manage order specific functionalities – like create, edit, cancel orders.
‘super user’ role can manage ‘user’, ‘orders’ & also can generate ‘reports’.
‘admin’ role user will have ‘business unit’ management in addition to the ‘super user’ features.
So, We have a base user page. Then we add few components in the UI depends on the role. This is what exactly the decorator pattern is.
To understand this better, consider the below example in which we have a base weapon. Then you add few accessories (decorate base weapon with required accessories) to create a new weapon. [More info is here]
Decorator Pattern:
Decorator pattern allows us to add additional responsibilities to an object dynamically. Our ManageConsole page follows the same pattern. Basically I have a base user page, then we add few components.
Lets see how we could use this decorator pattern to solve our test design.
Admin Site Page Object:
Lets create a page object for Admin site considering all the possible features. [I have followed Advanced Page Object design to create the page. That is, all the components of the page are considered as separate fragments. By combining these fragments, we create the ManageConsole page. If you have not read about that page object design before, I would suggest you to read that first].
public class ManageConsolePage {
@FindBy(id="bumgmt")
private BUManagement buManagement;
@FindBy(id="ordermgmt")
private OrderManagement orderManagement;
@FindBy(id="usermgmt")
private UsersManagement usersManagement;
@FindBy(id="reports")
private Reports reports;
public BUManagement getBuManagement() {
return buManagement;
}
public OrderManagement getOrderManagement() {
return orderManagement;
}
...
}
Each fragment in the class has a isPresent method to check if the component is displayed in UI or not.
Decorator Pattern Design:
As part of my test, I need to ensure that based on the logged in user role if the corresponding components are displayed in UI. So, I have a base ‘Order Validation’. Then I have few additional rules like user management screen presence check and an absence check etc.
In my other Design Patterns articles, I used different classes to explain the pattern . But this time, instead of creating multiple classes, I am going to make use of Java8 Consumer Functional Interface. Consumer interface allows us to chain another Consumer interface – This is like adding additional feature to an existing object.
For ex: Consider this example
Consumer<String> toLower = (s) -> {
System.out.println(s.toLowerCase());
};
Consumer<String> toUpper = (s) -> {
System.out.println(s.toUpperCase());
};
toLower.andThen(toUpper).accept("Test");
The above code prints the following. We chained different behaviors to execute one after another.
test
TEST
I am creating a Decorator class which consists of many Consumers – they are like accessories to decorate our page object.
public class ManageConsoleTestDecorators {
//base validation
public static Consumer<ManageConsolePage> orderValidation = (page) -> {
Assert.assertTrue(page.getOrderManagement().isDisplayed());
page.getOrderManagement().createNewOrder();
Assert.assertTrue(page.getOrderManagement().isOrderSucccessful());
};
//user component presence check
public static Consumer<ManageConsolePage> userValidation = (page) -> {
Assert.assertTrue(page.getUserManagement().isDisplayed());
};
//user component absence check
public static Consumer<ManageConsolePage> userAbsenceValidation = (page) -> {
Assert.assertFalse(page.getUserManagement().isDisplayed());
};
//report component presence check
public static Consumer<ManageConsolePage> reportValidation = (page) -> {
Assert.assertTrue(page.getReport().isDisplayed());
};
//report component absence check
public static Consumer<ManageConsolePage> reportAbsenceValidation = (page) -> {
Assert.assertFalse(page.getReport().isDisplayed());
};
//bu component presence check
public static Consumer<ManageConsolePage> buValidation = (page) -> {
Assert.assertTrue(page.getBuManagement().isDisplayed());
};
//bu component absence check
public static Consumer<ManageConsolePage> buAbsenceValidation = (page) -> {
Assert.assertFalse(page.getBuManagement().isDisplayed());
};
}
Test Design:
Now in my test, I can create 3 different page objects – 1 for each role with specific component validations by using above decorators as shown below.
public class ManageConsoleTest {
//user role page should have report, order, bu absence validations
private Consumer<ManageConsolePage> userRolePage = orderValidation.andThen(reportAbsenceValidation)
.andThen(userAbsenceValidation)
.andThen(buAbsenceValidation);
//super user role page should have report, order presence validation & bu absence validations
private Consumer<ManageConsolePage> superUserRolePage = orderValidation.andThen(userValidation)
.andThen(reportValidation)
.andThen(buAbsenceValidation);
//admin user should all components presence validation
private Consumer<ManageConsolePage> adminRolePage = orderValidation.andThen(userValidation)
.andThen(reportValidation)
.andThen(buValidation);
@Test(dataProvider = "getData")
public void manageConsoleSiteTest(String username, String password, Consumer<ManageConsolePage> decoratedPageValidation){
ManageConsolePage page = new ManageConsolePage(DriverManager.getDriver());
page.signin(username, password);
decoratedPageValidation.accept(page);
page.signout();
}
@DataProvider
public Object[][] getData(){
return new Object[][]{
{"user1", "password", userRolePage} ,
{"superuser1", "password", superUserRolePage} ,
{"admin1", "password", adminRolePage} ,
};
}
}
Summary:
By using decorator pattern, we create page objects with dynamic components. In future, if the application adds another component or another role type, it is extremely easy to update the test. By using a single test class, we test all the possible roles and components of the page. Without decorator pattern, we would end up having the traditional if-else approach with additional branching for the component & role type validation. It would have increased the complexity of the maintenance of the test design. Without a single conditional check, Decorator pattern improves the readability & maintainability of the tests.
Please check other design patterns articles here.
Happy Testing & Subscribe 🙂