Strategy Design Pattern in Automated Testing Java Code

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 implementation of page objects and facades. When we have to write tests for more complex use case scenarios, it usually gets harder and harder to follow the Open Close Principle, one of SOLID’s primary principles. In this part of the series, I will use the Strategy Design Pattern to create extendable assertions for an e-commerce module.

Definition

In computer programming, the strategy pattern (also known as the policy pattern) is a software design pattern that enables an algorithm‘s behavior to be selected at runtime.

Benefits:

  • Defines a family of algorithms.

  • Encapsulates each algorithm.

  • Makes the algorithms interchangeable within that family.

  • The code is easier to maintain as modifying or understanding strategy does not require you to understand the whole main object.

UML Class Diagram

classDiagram
    PurchaseContext --> OrderPurchaseStrategy
    OrderPurchaseStrategy <|-- VatTaxOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- NoTaxOrderPurchaseStrategy
    OrderPurchaseStrategy <|-- CouponCodeOrderPurchaseStrategy
    class PurchaseContext {
        +PurchaseContext(OrderPurchaseStrategy orderPurchaseStrategy)
        +purchaseItem()
    }
    class OrderPurchaseStrategy {
        +assertOrderSummary()
    }
    class VatTaxOrderPurchaseStrategy {
        +assertOrderSummary()
    }
    class NoTaxOrderPurchaseStrategy {
        +assertOrderSummary()
    }
    class CouponCodeOrderPurchaseStrategy {
        +assertOrderSummary()
    }

Participants

The classes and objects participating in this pattern are:

  • Strategy (OrderPurchaseStrategy)

    Defines an interface common to all algorithms. The context class calls the interface to perform the algorithm, identified by the concrete strategy.

  • ConcreteStrategy (VatTaxOrderPurchaseStrategy)

    Implements the algorithm using the strategy interface.

  • Context (PurchaseContext)

    Holds a dependency on Strategy. Wraps the calls to the concrete strategies, may provide an interface to the strategies to access its data.

Test’s Test Case

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 primary goal of the test-related classes’ design is to enable a decoupled and extendable assertion of the different prices on the last page of the purchasing process.

Strategy Design Pattern Java Code

The following class structure will be used.

Classes Structure Strategy Design Pattern

As you can see, the design of the tests uses the Page Object design pattern heavily. There is nothing unusual in the different pages, so I’m not going to paste every single page’s code here because it is not relevant to the article’s theme. Probably, the most interesting logic is located in the CheckoutPage.

public class CheckoutPage
  extends BasePage<CheckoutElements, CheckoutAssertions> {

  @Override
  protected String getUrl() {
    return "http://demos.bellatrix.solutions/checkout/";
  }

  public void fillBillingInfo(PurchaseInfo purchaseInfo) {
    if (purchaseInfo.getCouponCode() != null) {
      elements().couponCodeShowInputButton().click();
      Driver
        .getBrowserWait()
        .until(
          ExpectedConditions.elementToBeClickable(elements().couponCodeInput())
        );
      elements().couponCodeInput().sendKeys(purchaseInfo.getCouponCode());
      elements().couponCodeApplyButton().click();
    }
    elements().billingFirstName().sendKeys(purchaseInfo.getFirstName());
    elements().billingLastName().sendKeys(purchaseInfo.getLastName());
    elements().billingCompany().sendKeys(purchaseInfo.getCompany());
    elements().billingCountryWrapper().click();
    elements().billingCountryFilter().sendKeys(purchaseInfo.getCountry());
    elements().getCountryOptionByName(purchaseInfo.getCountry()).click();
    elements().billingAddress1().sendKeys(purchaseInfo.getAddress1());
    elements().billingAddress2().sendKeys(purchaseInfo.getAddress2());
    elements().billingCity().sendKeys(purchaseInfo.getCity());
    elements().billingZip().sendKeys(purchaseInfo.getZip());
    elements().billingPhone().sendKeys(purchaseInfo.getPhone());
    elements().billingEmail().sendKeys(purchaseInfo.getEmail());
    if (purchaseInfo.getShouldCreateAccount()) {
      elements().createAccountCheckBox().click();
    }
    if (purchaseInfo.getShouldCheckPayment()) {
      elements().checkPaymentsRadioButton().click();
    }
    Driver.waitForAjax();
    Driver
      .getBrowserWait()
      .until(
        ExpectedConditions.elementToBeClickable(elements().placeOrderButton())
      );
    Driver
      .getBrowserWait()
      .until(
        ExpectedConditions.invisibilityOfElementLocated(
          By.xpath("//div[@class='blockUI blockOverlay']")
        )
      );
    elements().placeOrderButton().click();
  }
}

The PurchaseInfo class holds the data about the client’s purchase. Most of the data is populated through string properties.

public class PurchaseInfo {

  private String firstName;
  private String lastName;
  private String company;
  private String country;
  private String address1;
  private String address2;
  private String city;
  private String zip;
  private String phone;
  private String email;
  private Boolean shouldCreateAccount = false;
  private Boolean shouldCheckPayment = false;
  private String couponCode = null;

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getCompany() {
    return company;
  }

  public void setCompany(String company) {
    this.company = company;
  }

  public String getCountry() {
    return country;
  }

  public void setCountry(String country) {
    this.country = country;
  }

  public String getAddress1() {
    return address1;
  }

  public void setAddress1(String address1) {
    this.address1 = address1;
  }

  public String getAddress2() {
    return address2;
  }

  public void setAddress2(String address2) {
    this.address2 = address2;
  }

  public String getCity() {
    return city;
  }

  public void setCity(String city) {
    this.city = city;
  }

  public String getZip() {
    return zip;
  }

  public void setZip(String zip) {
    this.zip = zip;
  }

  public String getPhone() {
    return phone;
  }

  public void setPhone(String phone) {
    this.phone = phone;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public Boolean getShouldCreateAccount() {
    return shouldCreateAccount;
  }

  public void setShouldCreateAccount(Boolean shouldCreateAccount) {
    this.shouldCreateAccount = shouldCreateAccount;
  }

  public Boolean getShouldCheckPayment() {
    return shouldCheckPayment;
  }

  public void setShouldCheckPayment(Boolean shouldCheckPayment) {
    this.shouldCheckPayment = shouldCheckPayment;
  }

  public String getCouponCode() {
    return couponCode;
  }

  public void setCouponCode(String couponCode) {
    this.couponCode = couponCode;
  }
}

Implementation without Strategy Design Pattern

The use cases can be automated easily via the usage of the Facade Design Pattern.

public class PurchaseFacade {

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

  public PurchaseFacade(
    ItemPage itempage,
    ShoppingCartPage shoppingCartPage,
    CheckoutPage checkoutPage
  ) {
    this.itemPage = itempage;
    this.shoppingCartPage = shoppingCartPage;
    this.checkoutPage = checkoutPage;
  }

  public void purchaseItemNoTax(
    String itemUrl,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    purchaseItemInternal(itemUrl, itemPrice, purchaseInfo);
    checkoutPage.assertVatTax(0);
  }

  public void purchaseItemVatTax(
    String itemUrl,
    double itemPrice,
    double vatTax,
    PurchaseInfo purchaseInfo
  ) {
    purchaseItemInternal(itemUrl, itemPrice, purchaseInfo);
    checkoutPage.assertVatTax(vatTax);
  }

  public void purchaseItemCouponCode(
    String itemUrl,
    double itemPrice,
    double discount,
    PurchaseInfo purchaseInfo
  ) {
    purchaseItemInternal(itemUrl, itemPrice, purchaseInfo);
    checkoutPage.assertDiscount(discount);
  }

  private void purchaseItemInternal(
    String itemUrl,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    itemPage.navigate(itemUrl);
    itemPage.assertPrice(itemPrice);
    itemPage.clickBuyNowButton();
    itemPage.clickViewShoppingCartButton();
    shoppingCartPage.clickProceedToCheckoutButton();
    shoppingCartPage.assertSubtotalAmount(itemPrice);
    checkoutPage.fillBillingInfo(purchaseInfo);
    checkoutPage.assertSubtotal(itemPrice);
  }
}

The main drawback of such a solution is the number of various methods for the different tax verification cases. If there is a need to introduce a new tax assertion, a new function should be added, which will break the Open Close Principle.

Strategy Design Pattern Implementation

There are hundreds of test cases that you can automate in this sample e-commerce module. Some of the most important are related to the prices’ correctness on the last page of the purchasing process. So the primary goal of the assertions in the tests will be to test if the correct prices are displayed. There are a couple of things that can modify the price – VAT, discounts from coupon codes, etc. The page objects can be combined in the PurchaseContext class to perform a new purchase. The strategy design pattern can be applied to pass the specific validation strategy.

The following interface can define the primary method of the validation strategy.

public interface OrderPurchaseStrategy {
void assertOrderSummary(double itemPrice, PurchaseInfo purchaseInfo);
}

The PurchaseContext class is almost identical to the already discussed facade classes; the only difference is that it holds a dependency on the OrderPurchaseStrategy.

The concrete validation strategy is passed to its constructor.

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);
  }
}

Note

In most cases, I believe that the best approach to assert the tax prices and similar money amounts is to call the real production web services used to power the actual e-commerce module. They should be already entirely tested via unit and integration tests, and their output should be guaranteed.

The primary goal of the E2E tests is to ensure that the correct prices are visualized on the page rather than to check the real calculation logic. So in my concrete implementation of the VatTaxOrderPurchaseStrategy, I have created a dummy sample implementation. In actual tests, instead of calculating the taxes manually, we should call the production web service. If the module doesn’t use web services, you can always create your test web-service and call the production code in wrapper methods. I had already done that for an old legacy module that I had to validate automatically.

public class VatTaxOrderPurchaseStrategy implements OrderPurchaseStrategy {

  private final VatTaxCalculationService vatTaxCalculationService;

  public VatTaxOrderPurchaseStrategy() {
    vatTaxCalculationService = new VatTaxCalculationService();
  }

  @Override
  public void assertOrderSummary(double itemPrice, PurchaseInfo purchaseInfo) {
    var currentCountry = Arrays
      .stream(Country.values())
      .filter(country -> country.toString().equals(purchaseInfo.getCountry()))
      .toArray(Country[]::new)[0];
    var vatTax = vatTaxCalculationService.calculate(
      itemPrice,
      currentCountry,
      purchaseInfo
    );
    var checkoutPage = new CheckoutPage();
    Driver.waitForAjax();
    Driver.waitUntilPageLoadsCompletely();
    checkoutPage.assertions().assertOrderVatTaxPrice(vatTax);
  }
}

I have implemented a similar logic for CouponCodeOrderPurchaseStrategy.

public class CouponCodeOrderPurchaseStrategy implements OrderPurchaseStrategy {

  private final CouponCodeCalculationService couponCodeCalculationService;

  public CouponCodeOrderPurchaseStrategy() {
    couponCodeCalculationService = new CouponCodeCalculationService();
  }

  @Override
  public void assertOrderSummary(double itemPrice, PurchaseInfo purchaseInfo) {
    var discount = couponCodeCalculationService.calculate(
      itemPrice,
      purchaseInfo.getCouponCode()
    );
    var checkoutPage = new CheckoutPage();
    Driver.waitForAjax();
    Driver.waitUntilPageLoadsCompletely();
    checkoutPage.assertions().assertOrderDiscountPrice(discount);
  }
}

One of the essential benefits of using the Strategy Design Pattern is that if a new tax is introduced in the application, you don’t have to modify your existing pages or the PurchaseContext. You will need only to create a new concrete strategy.

For example, if Online Store announces that starting from tomorrow, a supermodel can deliver you the desired items directly to your door, only for 100 bucks. You can handle the new tax validation by creating a new SuperModelOrderPurchaseStrategy.

Tests Using Strategy Design Pattern

public class StorePurchaseStrategyTests {

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

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

  @Test
  public void totalPriceCalculatedCorrect_when_AtCheckoutAndStrategyPatternUsed() {
    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");
    new PurchaseContext(new VatTaxOrderPurchaseStrategy())
      .purchaseItem(itemUrl, itemPrice, purchaseInfo);
  }
}

The use of the Strategy Design Pattern to create automated tests is easy. The only thing you have to do is pass the desired algorithm to the newly created PurchaseContext.

Summary

As a consequence of these actions applied through the Strategy Design Pattern’s use, your tests’ architecture will follow the Open-Closed Principle, which states that the code should be open for extension but closed for modification.

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

Design Architecture Java, Design Patterns Java, Java

Page Object Pattern in Automated Testing Java Code

In the series of articles Design Patterns in Automated Testing, I am presenting the most useful techniques for structuring the code of your automation tests. On

Page Object Pattern in Automated Testing Java Code

Java, Mobile Automation Java

Getting Started with Appium for Android Java 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 Java on Windows in 10 Minutes

Design Architecture Java, Design Patterns Java, Java

Advanced Strategy Design Pattern in Automated Testing Java Code

In this part of the Design Pattern Series, I’m going to extend my ideas about the application of the Strategy Design Pattern in automation tests. In my previous

Advanced Strategy Design Pattern in Automated Testing Java Code

Java, Web Automation Java

30 Advanced WebDriver Tips and Tricks Java Code

This is the next article from the WebDriver Series where I will share with you 30 advanced tips and tricks using Java code. I wrote similar articles separated i

30 Advanced WebDriver Tips and Tricks Java Code

Java, Mobile Automation Java

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

The third article from the Appium Series is going to be about testing Android apps on macOS machine. I am going to show you how to configure your macOS machine

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

AutomationTools, Free Tools, Java

Healenium: Self-Healing Library for Selenium-based Automated Tests

In this article, we're going to review a library called Healenium. It is an AI-powered open-source library for improving the stability of Selenium-based tests,

Healenium: Self-Healing Library for Selenium-based Automated Tests
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.