Decorator Design Pattern in Automated Testing Java Code

Decorator Design Pattern in Automated Testing Java Code

In the article Strategy Design Pattern, I explained the benefits of the Strategy Design Pattern application in your automation tests. Some of the advantages are more maintainable code, encapsulated algorithm logic, easily interchangeable algorithms, and less complicated code. The Strategy Design Pattern follows the Open-Closed Principle that states that “Classes should be open for extension, but closed for modification“. Another way to create open for extension classes is through the usage of the Decorator Design Pattern. In this publication, I will refactor the code examples from the previously mentioned articles to be even more extendable. The used strategies are going to be “wrapped” through decorators. The Decorator Design Pattern allows us easily to attach additional responsibilities to an object dynamically. I believe that it can be heavily utilized in automation tests because of all its benefits.

Note

If you are not familiar with the above patterns, I suggest you read my articles about them first to understand the presented concepts thoroughly. (Especially the ones related to Strategy Design Pattern*).*

Definition

The Decorator Design Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Benefits

  • You can wrap a component with any number of decorators.

  • Change the component’s behavior by adding new functionality before and/or after method calls to the component.

  • Decorator classes mirror the type of components they decorate.

  • Provides an alternative to subclassing for extending behavior.

Abstract UML Class Diagram

classDiagram
    Component <|-- ConcreteComponent
    Component <|-- Decorator
    Decorator <|-- ConcreteDecoratorA
    Decorator <|-- ConcreteDecoratorB
    Decorator o-- Component
    class Component {
        +methodA()
        +methodB()
    }
    class ConcreteComponent {
        +methodA()
        +methodB()
    }
    class Decorator {
        +componentWrappedObj
        +methodA()
        +methodB()
    }
    class ConcreteDecoratorA {
        +newState
        +addNewBehavior()
        +methodA()
        +methodB()
    }
    class ConcreteDecoratorB {
        +newState
        +addNewBehavior()
        +methodA()
        +methodB()
    }

The classes and objects participating in this pattern are:

  • Component

    Defines the interface for objects that can have responsibilities added to them dynamically.

  • Decorator

    The decorators implement the same interface (abstract class) as the component they will decorate. The decorator has a HAS-A relationship with the extending object, which means that the former has an instance variable that references the latter.

  • ConcreteComponent

    This is the object that is going to be enhanced dynamically. It inherits the Component.

  • ConcreteDecorator

    Decorators can enhance the state of the component. They can add new methods. The new behavior is typically added before or after an existing method in the component.

Decorator Design Pattern Java Code

Test’s Test Case

The test case of the examples is going to be the same as the previous articles. The primary goal is going to be to purchase different items from Online Store. The prices on the last step of the buying process should be validated – taxes, discounts, etc.

1. Navigate to Item’s Page

Bellatrix Demos Item Page

Bellatrix Demos View Shopping Cart Button

4. Fill in the purchase info

Bellatrix Demos Purchase Info Fill

Bellatrix Demos Assert Total Price

The previous article explains in detail how to automate the whole purchase process. However, to introduce the Decorator Design Pattern’s benefits, only the last step is going to be necessary – Order Summary Validation. In the Strategy Design Pattern posts, the prices on the last stage of the purchase process are validated through different Purchase Strategies that implement the OrderPurchaseStrategy.

public class PurchaseContext {

  private final OrderPurchaseStrategy orderPurchaseStrategy;
  private final ItemPage itemPage;
  private final ShoppingCartPage shoppingCartPage;
  private final CheckoutPage checkoutPage;

  public PurchaseContext(OrderPurchaseStrategy orderPurchaseStrategy) {
    this.orderPurchaseStrategy = orderPurchaseStrategy;
    itemPage = new ItemPage();
    shoppingCartPage = new ShoppingCartPage();
    checkoutPage = new CheckoutPage();
  }

  public void purchaseItem(
    String itemUrl,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    itemPage.navigate(itemUrl);
    itemPage.clickBuyNowButton();
    itemPage.clickViewShoppingCartButton();
    shoppingCartPage.clickProceedToCheckoutButton();
    checkoutPage.fillBillingInfo(purchaseInfo);
    orderPurchaseStrategy.assertOrderSummary(itemPrice, purchaseInfo);
  }
}

A while ago, when we worked on the first version of the BELLATRIX test automation framework, I did this research. Afterward, we used a similar approach in many of the features of the solution.

Improved Version Advanced Strategy Design Pattern Applied

public class PurchaseContext {

  private final OrderPurchaseStrategy[] orderPurchaseStrategies;
  private final ItemPage itemPage;
  private final ShoppingCartPage shoppingCartPage;
  private final CheckoutPage checkoutPage;

  public PurchaseContext(OrderPurchaseStrategy... orderPurchaseStrategies) {
    this.orderPurchaseStrategies = orderPurchaseStrategies;
    itemPage = new ItemPage();
    shoppingCartPage = new ShoppingCartPage();
    checkoutPage = new CheckoutPage();
  }

  public void purchaseItem(
    String itemUrl,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    validatePurchaseInfo(purchaseInfo);
    itemPage.navigate(itemUrl);
    itemPage.clickBuyNowButton();
    itemPage.clickViewShoppingCartButton();
    shoppingCartPage.clickProceedToCheckoutButton();
    checkoutPage.fillBillingInfo(purchaseInfo);
    validateOrderSummary(itemPrice, purchaseInfo);
  }

  public void validatePurchaseInfo(PurchaseInfo purchaseInfo) {
    for (var currentStrategy : orderPurchaseStrategies) {
      currentStrategy.validatePurchaseInfo(purchaseInfo);
    }
  }

  public void validateOrderSummary(
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    for (var currentStrategy : orderPurchaseStrategies) {
      currentStrategy.assertOrderSummary(itemPrice, purchaseInfo);
    }
  }
}

The usage of the PurchaseContext is not so straightforward, as you can see from the code below.

new PurchaseContext(new VatTaxOrderPurchaseStrategy(), new CouponCodeOrderPurchaseStrategy())
.purchaseItem(itemUrl, itemPrice, purchaseInfo);

Different prices validations mix is achieved through the iteration of the initialized strategies. However, the disadvantage of the provided solution is that for every new method in the OrderPurchaseStrategy interface, you need to create a new one with a “foreach” statement in the PurchaseContext class. Also, I believe that the initialization of the PurchaseContext in the test method is a little bit unreadable.

If you don’t understand the above code examples thoroughly, you can find more detailed explanations in my articles about the Strategy Design Pattern.

One of the resolutions of the initialization problem of the PurchaseContext is to create more strategy classes that combine the different behaviors, e.g., VatTaxOrderPurchaseStrategy, GiftOrderPurchaseStrategy, CouponCodeOrderPurchaseStrategy, NoTaxesOrderPurchaseStrategy, etc. But as you can see this escalated quickly – a typical example of a class explosion.

classDiagram
    OrderPurchaseStrategy <|-- CouponCodeVatTaxOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- CouponCodeGiftOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- VatSalesTaxOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- SalesTaxOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- VatTaxOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- NoTaxOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- CouponCodeOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- GiftOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- CouponCodeVatTaxGiftOrderPurchaseStrategy
    class OrderPurchaseStrategy {
        +calculateTotalPrice()
        +assertOrderSummary()
    }
    class CouponCodeVatTaxOrderPurchaseStrategy {
        +vatTax
        +vatTaxCalculationService
        +couponCodeDiscount
        +couponCodeCalculationService
        +calculateTotalPrice()
        +assertOrderSummary()
    }
    class CouponCodeGiftOrderPurchaseStrategy {
        +giftWrappingPrice
        +giftWrappingPriceCalculationService
        +couponCodeDiscount
        +couponCodeCalculationService
        +calculateTotalPrice()
        +assertOrderSummary()
    }
    class VatSalesTaxOrderPurchaseStrategy {
        +vatTax
        +vatTaxCalculationService
        +salesTaxCalculationService
        +calculateTotalPrice()
        +assertOrderSummary()
    }
    class SalesTaxOrderPurchaseStrategy {
        +salesTax
        +salesTaxCalculationService
        +calculateTotalPrice()
        +assertOrderSummary()
    }
    class VatTaxOrderPurchaseStrategy {
        +vatTax
        +vatTaxCalculationService
        +calculateTotalPrice()
        +assertOrderSummary()
    }
    class NoTaxOrderPurchaseStrategy {
        +assertOrderSummary()
    }
    class CouponCodeOrderPurchaseStrategy {
        +couponCodeDiscount
        +couponCodeCalculationService
        +assertOrderSummary()
    }
    class GiftOrderPurchaseStrategy {
        +giftWrappingPrice
        +giftWrappingCalculationService
        +calculateTotalPrice()
        +assertOrderSummary()
    }
    class CouponCodeVatTaxGiftOrderPurchaseStrategy {
        +vatTax
        +giftWrappingPrice
        +couponCodeDiscount
        +couponCodeCalculationService
        +calculateTotalPrice()
        +assertOrderSummary()
    }

If you need to add additional assertions, you will have to add a couple of more classes to achieve the mixing behavior. Here is where the Decorator Design Pattern comes to play. The attached behavior through inheritance can be determined only statically at compile time. However, through the help of composition, the decorators can extend the component at runtime.

Specific UML Class Diagram

classDiagram
    OrderPurchaseStrategy <|-- TotalPriceOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- OrderPurchaseStrategyDecorator
    OrderPurchaseStrategyDecorator <|-- NoTaxOrderPurchaseStrategy
    OrderPurchaseStrategyDecorator <|-- GiftOrderPurchaseStrategy
    OrderPurchaseStrategyDecorator <|-- VatTaxOrderPurchaseStrategy
    OrderPurchaseStrategyDecorator <|-- SalesTaxOrderPurchaseStrategy
    PurchaseContext --> OrderPurchaseStrategy
    class PurchaseContext {
        +orderPurchaseStrategy
        +purchaseItem(String itemUrl, double itemPrice, PurchaseInfo purchaseInfo)
    }
    class OrderPurchaseStrategy {
        +calculateTotalPrice()
        +assertOrderSummary(double totalPrice)
    }
    class TotalPriceOrderPurchaseStrategy {
        +calculateTotalPrice()
        +assertOrderSummary(double totalPrice)
    }
    class OrderPurchaseStrategyDecorator {
        +purchaseInfo
        +itemPrice
        +orderPurchaseStrategy
        +calculateTotalPrice()
        +assertOrderSummary(double totalPrice)
    }
    class NoTaxOrderPurchaseStrategy {
        +assertOrderSummary(double totalPrice)
    }
    class GiftOrderPurchaseStrategy {
        +giftWrappingPrice
        +giftWrappingCalculationService
        +calculateTotalPrice()
        +assertOrderSummary(double totalPrice)
    }
    class VatTaxOrderPurchaseStrategy {
        +vatTax
        +vatTaxCalculationService
        +calculateTotalPrice()
        +assertOrderSummary(double totalPrice)
    }
    class SalesTaxOrderPurchaseStrategy {
        +salesTax
        +salesTaxCalculationService
        +calculateTotalPrice()
        +assertOrderSummary(double totalPrice)
    }

Participants

The classes and objects participating in this pattern are:

  • OrderPurchaseStrategy (Component)

    Defines the interface for all concrete strategies that will assert the different prices on the last step of the purchasing process.

  • OrderPurchaseStrategyDecorator (Component Decorator)

    The decorator has an instance variable that holds a reference to the OrderPurchaseStrategy. Also, it contains another useful info that is going to be used by the concrete decorators to calculate the different expected amounts.

  • TotalPriceOrderPurchaseStrategy (ConcreteComponent)

    It is a descendant of the OrderPurchaseStrategy, and it is used to verify the total cost of the order.

  • VatTaxOrderPurchaseStrategy (ConcreteDecorator)

    Can extend the concrete order purchase strategies. Adds a new logic for validating the order’s VAT Tax and adds the new tax to the total price.

Refactoring Purchase Strategies to Support Decorator Design Pattern

The base class for all concrete strategies and their decorators is the OrderPurchaseStrategy.

public abstract class OrderPurchaseStrategy {

  public abstract double calculateTotalPrice();

  public abstract void assertOrderSummary(double totalPrice);
}
public class TotalPriceOrderPurchaseStrategy extends OrderPurchaseStrategy {

  private final double itemsPrice;

  public TotalPriceOrderPurchaseStrategy(double itemsPrice) {
    this.itemsPrice = itemsPrice;
  }

  @Override
  public double calculateTotalPrice() {
    return itemsPrice;
  }

  @Override
  public void assertOrderSummary(double totalPrice) {
    var checkoutPage = new CheckoutPage();
    Driver.waitForAjax();
    Driver.waitUntilPageLoadsCompletely();
    checkoutPage.assertions().assertOrderTotalPrice(totalPrice);
  }
}

To be able to add a new behavior at runtime dynamically, all decorators need to derive from the class OrderPurchaseStrategyDecorator.

public class OrderPurchaseStrategyDecorator extends OrderPurchaseStrategy {

  protected final OrderPurchaseStrategy orderPurchaseStrategy;
  protected final PurchaseInfo purchaseInfo;
  protected final double itemPrice;

  public OrderPurchaseStrategyDecorator(
    OrderPurchaseStrategy orderPurchaseStrategy,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    this.orderPurchaseStrategy = orderPurchaseStrategy;
    this.itemPrice = itemPrice;
    this.purchaseInfo = purchaseInfo;
  }

  @Override
  public double calculateTotalPrice() {
    validateOrderStrategy();
    return orderPurchaseStrategy.calculateTotalPrice();
  }

  @Override
  public void assertOrderSummary(double totalPrice) {
    validateOrderStrategy();
    orderPurchaseStrategy.assertOrderSummary(totalPrice);
  }

  private void validateOrderStrategy() {
    if (orderPurchaseStrategy == null) {
      throw new NullPointerException(
        "The OrderPurchaseStrategy should be first initialized."
      );
    }
  }
}

This abstract class holds a couple of relevant variables. The most prominent one is orderPurchaseStrategy that is initialized in the constructor. It contains a reference to the object that is currently extended. The other variables are used for the computations of the different expected amounts.

Suppose we want to add logic to the above strategy, such as applying VAT Tax and its assertion. We can use the VatTaxOrderPurchaseStrategy, which in its essence is a decorator that is capable of extending other purchase strategies.

public class VatTaxOrderPurchaseStrategy
  extends OrderPurchaseStrategyDecorator {

  private final VatTaxCalculationService vatTaxCalculationService;
  private final CouponCodeCalculationService couponCodeCalculationService;
  private double vatTax;

  public VatTaxOrderPurchaseStrategy(
    OrderPurchaseStrategy orderPurchaseStrategy,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    super(orderPurchaseStrategy, itemPrice, purchaseInfo);
    vatTaxCalculationService = new VatTaxCalculationService();
    couponCodeCalculationService = new CouponCodeCalculationService();
  }

  @Override
  public double calculateTotalPrice() {
    var currentCountry = Arrays
      .stream(Country.values())
      .filter(country -> country.toString().equals(purchaseInfo.getCountry()))
      .toArray(Country[]::new)[0];
    vatTax =
      vatTaxCalculationService.calculate(
        (
          itemPrice -
          couponCodeCalculationService.calculate(
            itemPrice,
            purchaseInfo.getCouponCode()
          )
        ),
        currentCountry
      );
    return orderPurchaseStrategy.calculateTotalPrice() + vatTax;
  }
}

The VatTaxOrderPurchaseStrategy is a descendant of the OrderPurchaseStrategyDecorator. Further, it overrides its methods. The interesting part is that the total price is calculated through a method recursion. First, the total amount is determined by the concrete component (order purchase strategy), and then the computed VAT tax is added to it.

The same recursion technique is used for the validation of the order summary UI. Before anything else, the ValidateOrderSummary methods of all extended strategies are going to be executed, and after that, the VAT tax is verified.

The coupon code discount can be checked through a similar decorator.

public class VatTaxOrderPurchaseStrategy
  extends OrderPurchaseStrategyDecorator {

  private final VatTaxCalculationService vatTaxCalculationService;
  private final CouponCodeCalculationService couponCodeCalculationService;
  private double vatTax;

  public VatTaxOrderPurchaseStrategy(
    OrderPurchaseStrategy orderPurchaseStrategy,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    super(orderPurchaseStrategy, itemPrice, purchaseInfo);
    vatTaxCalculationService = new VatTaxCalculationService();
    couponCodeCalculationService = new CouponCodeCalculationService();
  }

  @Override
  public double calculateTotalPrice() {
    var currentCountry = Arrays
      .stream(Country.values())
      .filter(country -> country.toString().equals(purchaseInfo.getCountry()))
      .toArray(Country[]::new)[0];
    vatTax =
      vatTaxCalculationService.calculate(
        (
          itemPrice -
          couponCodeCalculationService.calculate(
            itemPrice,
            purchaseInfo.getCouponCode()
          )
        ),
        currentCountry
      );
    return orderPurchaseStrategy.calculateTotalPrice() + vatTax;
  }
}

The only difference between the latter and the former is how the total price is calculated.

Usage of Decorated Strategies PurchaseContext

public class PurchaseContext {

  private final OrderPurchaseStrategy orderPurchaseStrategy;
  private final ItemPage itemPage;
  private final ShoppingCartPage shoppingCartPage;
  private final CheckoutPage checkoutPage;

  public PurchaseContext(OrderPurchaseStrategy orderPurchaseStrategy) {
    this.orderPurchaseStrategy = orderPurchaseStrategy;
    itemPage = new ItemPage();
    shoppingCartPage = new ShoppingCartPage();
    checkoutPage = new CheckoutPage();
  }

  public void purchaseItem(String itemUrl, PurchaseInfo clientPurchaseInfo) {
    itemPage.navigate(itemUrl);
    itemPage.clickBuyNowButton();
    itemPage.clickViewShoppingCartButton();
    shoppingCartPage.clickProceedToCheckoutButton();
    checkoutPage.fillBillingInfo(clientPurchaseInfo);
    var expectedTotalPrice = orderPurchaseStrategy.calculateTotalPrice();
    orderPurchaseStrategy.assertOrderSummary(expectedTotalPrice);
  }
}

The following code is now missing in the improved version.

public void validatePurchaseInfo(PurchaseInfo purchaseInfo) {
    for (var currentStrategy : orderPurchaseStrategies) {
        currentStrategy.validatePurchaseInfo(purchaseInfo);
    }
}
public void validateOrderSummary(double itemPrice, PurchaseInfo purchaseInfo) {
    for (var currentStrategy : orderPurchaseStrategies) {
        currentStrategy.assertOrderSummary(itemPrice, purchaseInfo);
    }
}

The PurchaseContext holds only one reference to the OrderPurchaseStrategy and employs it to verify the total amount and all other prices on the order summary page.

Decorator Design Pattern Usages in Tests

public class StorePurchaseDecoratedStrategiesTests {

  @BeforeMethod
  public void testInit() {
    Driver.startBrowser();
  }

  @AfterMethod
  public void testCleanup() {
    Driver.stopBrowser();
  }

  @Test
  public void totalPriceCalculatedCorrect_when_AtCheckoutAndDecoratedStrategyPatternUsed() {
    var itemUrl = "falcon-9";
    var itemPrice = 50.00;
    var purchaseInfo = new PurchaseInfo();
    purchaseInfo.setEmail("info@berlinspaceflowers.com");
    purchaseInfo.setFirstName("Anton");
    purchaseInfo.setLastName("Angelov");
    purchaseInfo.setCompany("Space Flowers");
    purchaseInfo.setCountry("Germany");
    purchaseInfo.setAddress1("1 Willi Brandt Avenue Tiergarten");
    purchaseInfo.setAddress2("Lützowplatz 17");
    purchaseInfo.setCity("Berlin");
    purchaseInfo.setZip("10115");
    purchaseInfo.setPhone("+491888999281");
    purchaseInfo.setCouponCode("happybirthday");
    OrderPurchaseStrategy orderPurchaseStrategy = new TotalPriceOrderPurchaseStrategy(
      itemPrice
    );
    orderPurchaseStrategy =
      new VatTaxOrderPurchaseStrategy(
        orderPurchaseStrategy,
        itemPrice,
        purchaseInfo
      );
    orderPurchaseStrategy =
      new CouponCodeOrderPurchaseStrategy(
        orderPurchaseStrategy,
        itemPrice,
        purchaseInfo
      );
    new PurchaseContext(orderPurchaseStrategy)
      .purchaseItem(itemUrl, purchaseInfo);
  }
}

The most prominent part of the above code is how the order purchase strategies are decorated and utilized by the PurchaseContext.

OrderPurchaseStrategy orderPurchaseStrategy = new TotalPriceOrderPurchaseStrategy(itemPrice);
orderPurchaseStrategy = new VatTaxOrderPurchaseStrategy(orderPurchaseStrategy, itemPrice, purchaseInfo);
orderPurchaseStrategy = new CouponCodeOrderPurchaseStrategy(orderPurchaseStrategy, itemPrice, purchaseInfo);
new PurchaseContext(orderPurchaseStrategy).purchaseItem(itemUrl, purchaseInfo);

First, a TotalPriceOrderPurchaseStrategy is instantiated. Then it is passed to the constructor of the VatTaxOrderPurchaseStrategy. This way, it is extended, and the VAT tax is going to be added to the total price. The same is done for coupon code strategy; a new decorator is initialized. Finally, the total price is going to be equal to the item price plus the VAT tax minus the coupon code discount.

Summary – Pros and Cons of using Decorator Design Pattern

Pros and Cons

  • Provide a flexible alternative to subclassing for extending functionality.
  • Allow behavior modification at runtime rather than going back into existing code and making changes.
  • Help resolve the Class Explosion Problem.
  • Support the Open-Closed Principle.
  • Decorators can result in many small objects, and overuse can be complicated.
  • It can complicate the process of instantiating the component because you have to instantiate the component and wrap it in some decorators.
  • It can be complicated to have decorators keep track of other decorators because to look back into multiple layers of the decorator chain starts to push the decorator pattern beyond its actual intent.
  • Can cause issues if the client relies heavily on the components concrete type.

For more detailed overview and usage of many more design patterns and best practices in automated testing, check my book “Design Patterns for High-Quality Automated Tests, Java Edition, Clean Code for Bulletproof Tests”.  (+ with the book you will get an access to more than 20000+ lines of real-world code examples and video explanations to solidify your knowledge)

Related Articles

Java, Web Automation Java

Automate Telerik Kendo Grid with WebDriver with Java and JavaScript

Have you had this problem trying to automate custom-tuned web controls? Probably, your team has purchased these from some dedicated UI controls vendor. There ar

Automate Telerik Kendo Grid with WebDriver with Java and JavaScript

Design Architecture Java, Design Patterns Java, Java

Strategy Design Pattern in Automated Testing Java Code

In the previous articles from the series Design Patterns in Automated Testing, I explained how to make your test automation framework better through the impleme

Strategy Design Pattern in Automated Testing Java Code

Java, Kotlin, Mobile Automation Java

Getting Started with Appium for Android Kotlin on Windows in 10 Minutes

This is the first article from the new series dedicated to the mobile testing using Appium test automation framework. Here, I am going to show you how to config

Getting Started with Appium for Android Kotlin on Windows in 10 Minutes

Java, Mobile Automation Java

Getting Started with Appium for iOS Java on macOS in 10 Minutes

The second article from the Appium Series is going to be about testing iOS apps. I am going to show you how to configure your machine to test iOS applications –

Getting Started with Appium for iOS Java on macOS in 10 Minutes

AutomationTools, Free Tools, Java

Quick Guide GitHub Actions on Running Selenium Java Tests

In this article from the series Automation Tools, I am going to guide you on how you can set up a GitHub Actions job for a Selenium Java project, run your Selen

Quick Guide GitHub Actions on Running Selenium Java Tests

Design Architecture Java, Design Patterns Java, Java

Facade Design Pattern in Automated Testing Java Code

In the series of articles Design Patterns in Automated Testing, you can read about useful techniques for structuring the automation test code. Today’s publicati

Facade Design Pattern in Automated Testing Java Code
Anton Angelov

About the author

Anton Angelov is Managing Director, Co-Founder, and Chief Test Automation Architect at Automate The Planet — a boutique consulting firm specializing in AI-augmented test automation strategy, implementation, and enablement. He is the creator of BELLATRIX, a cross-platform framework for web, mobile, desktop, and API testing, and the author of 8 bestselling books on test automation. A speaker at 60+ international conferences and researcher in AI-driven testing and LLM-based automation, he has been recognized as QA of the Decade and Webit Changemaker 2025.