How to use the Decorator pattern in the project.
In my recent article, I reviewed the main automation patterns that can be applied in your work if necessary. Today I continue a series of articles devoted to the consideration of design patterns. In this article, we’ll talk about Technical Patterns and the decorator pattern.
The main goal of Technical Patterns is to encapsulate technical details from test logic, providing extra low-level control over them.
Decorator.
A decorator also knows as Wrapper is a technical design pattern that allows you to dynamically add new functionality to objects by wrapping them in useful wrappers.
Decorator is a very well-known pattern since discussed in many other programming books and articles. The example of this one is simple. Let’s imagine you’re working with any driver implementation (e.g. WebDriver) and are willing to add extra functionality to it, like logging or caching. But at the same time, you don’t wanna reveal that add functionality to your actual tests, leaving test logic the same as it was before. That’s where you want to use a Decorator.
Decorator helps to implement the so-called “Cabbage principle”, when you are able to wrap one driver implementation into another, in the way cabbage leaves are formed. Your tests won’t be aware of that extra layer since they work with the same interface as before.
For instance, you want to log every click on some elements in your tests. All you need to do is to decorate your initial WebDriver object, by wrapping it into the EventWebDriver and registering a new listener, while your tests don’t have to be changed at all:
new EventWebDriver(driver).register(new AbstractDriverListener(){
@Override
public void afterClickOn(WebElement element,
WebDriver driver){
LOG.log(Level.INFO,"Click on element "
+element.getTagName());
}
});
To get rid of this kind of wrapping in the test logic completely, one could leverage something like Factory Pattern, when the user can get the browser/driver simply requesting it from the Factory. Also using the Browser Pool might be not a bad idea too.
Advantages:
- More flexibility than extends.
- Allows you to add responsibilities on the fly.
- You can add several new responsibilities at once.
- Allows you to have several small objects instead of one object for all occasions.
Disadvantages:
- It is difficult to configure multiple wrapped objects.
- An abundance of tiny classes.
By using the decorator pattern, we create page objects with dynamic components. In the 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 a 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, the Decorator pattern improves the readability & maintainability of the tests.