Facade Design Pattern in Automated Testing Java Code

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 publication is going to be about the Facade Design Pattern. I have explained how to use the most famous pattern – Page Object Model Pattern in two previous publications. The Facade Design Pattern can bring even more abstraction over the page objects, so if you are not familiar with them, I advise you to read my articles on the matter.

Definition

A facade is an object that provides a simplified interface to a larger body of code, such as a class library. It makes a software library easier to use and understand, is more readable, and reduces dependencies on external or other code.

Benefits:

  • Make a software library easier to use, understand, and more readable.

  • Reduce dependencies of outside code.

  • Keeps the Principle of least knowledge.

  • Wrap a poorly designed APIs in a better one.

UML Class Diagram

classDiagram
    PurchaseFacade *-- BellatrixDemoItemPage
    PurchaseFacade *-- BellatrixDemoShoppingCartPage
    PurchaseFacade *-- BellatrixDemoCheckoutPage
    StorePurchaseTests --> PurchaseFacade
    class BellatrixDemoItemPage {
        #elements()
        +assertions()
        +navigate()
        +clickBuyNowButton()
        +clickViewShoppingCartButton()
    }
    class BellatrixDemoShoppingCartPage {
        #elements()
        +assertions()
        +navigate()
        +clickProceedToCheckoutButton()
    }
    class BellatrixDemoCheckoutPage {
        #elements()
        +assertions()
        +navigate()
        +fillBillingInfo()
    }
    class PurchaseFacade {
        -bellatrixDemoItemPage
        -bellatrixDemoShoppingCartPage
        -bellatrixDemoCheckoutPage
        +purchaseItem()
    }
    class StorePurchaseTests {
        +subtotalPriceOfFalcon9CalculatedCorrect()
        +subtotalPriceOfSaturnVCalculatedCorrect()
    }

Participants

The classes and objects participating in this pattern are:

  • Facade

    Holds methods that combine actions executed on multiple pages.

  • Page Objects (BellatrixDemoItemPage)

    Holds the actions that can be performed on the page like search and navigate. Exposes easy access to the page assertions through the assertions method. The best implementations of the pattern hide the elements’ usage, wrapping it through all action methods.

  • UI Tests (StorePurchaseTests)

    This class contains a group of tests related to the above facade. It can hold only a single instance of the facade.

Test’s Test Case

Bellatrix Demos Item Page

Bellatrix Demos View Shopping Cart Button

Bellatrix Demos Purchase Info Fill

Bellatrix Demos Assert Subtotal Price

The primary goal of the test classes’ design is to enable us to reuse the code for all different test cases. For example, you are purchasing other items with а different combinations of shipping data, thus other total and/or subtotal prices.

Facade Design Pattern Java Code

The following class structure is going to be used.

Classes Structure Facade Design Pattern

There is nothing unusual in most of the page objects. Probably, the most interesting logic is located in the BellatrixDemoCheckoutPage.

public class BellatrixDemoCheckoutPage
  extends BasePage<BellatrixDemoCheckoutElements, BellatrixDemoCheckoutAssertions> {

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

Note

The Driver class now contains an additional method called w****aitForAjax, responsible for handling the waiting of asynchronous web requests.

There are two public methods. The fillBillingInfo uses the data class P****urchaseInfo to fill in the current client’s purchase info.

The PurchaseInfo class holds mostly string properties and getters & setters for them.

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

After clicking the Place order button, the page loads asynchronously (it doesn’t reload the page) so we have to put explicit wait condition presenceOfElementLocated for the orderDetailsSubtotal element. The logic is located in the BellatrixDemoCheckoutElements class.

public class BellatrixDemoCheckoutElements extends BaseElements {

  public WebElement billingFirstName() {
    return browser.findElement(By.id("billing_first_name"));
  }

  public WebElement billingLastName() {
    return browser.findElement(By.id("billing_last_name"));
  }

  public WebElement billingCompany() {
    return browser.findElement(By.id("billing_company"));
  }

  public WebElement billingCountryWrapper() {
    return browser.findElement(By.id("select2-billing_country-container"));
  }

  public WebElement billingCountryFilter() {
    return browser.findElement(By.className("select2-search__field"));
  }

  public WebElement billingAddress1() {
    return browser.findElement(By.id("billing_address_1"));
  }

  public WebElement billingAddress2() {
    return browser.findElement(By.id("billing_address_2"));
  }

  public WebElement billingCity() {
    return browser.findElement(By.id("billing_city"));
  }

  public WebElement billingZip() {
    return browser.findElement(By.id("billing_postcode"));
  }

  public WebElement billingPhone() {
    return browser.findElement(By.id("billing_phone"));
  }

  public WebElement billingEmail() {
    return browser.findElement(By.id("billing_email"));
  }

  public WebElement couponCodeShowInputButton() {
    return browser.findElement(By.className("showcoupon"));
  }

  public WebElement couponCodeInput() {
    return browser.findElement(By.id("coupon_code"));
  }

  public WebElement couponCodeApplyButton() {
    return browser.findElement(By.name("apply_coupon"));
  }

  public WebElement createAccountCheckBox() {
    return browser.findElement(By.id("createaccount"));
  }

  public WebElement checkPaymentsRadioButton() {
    return browser.findElement(
      By.cssSelector("[for*='payment_method_cheque']")
    );
  }

  public WebElement placeOrderButton() {
    return browser.findElement(By.id("place_order"));
  }

  public WebElement orderDetailsSubtotal() {
    String locator = "//th[text()='Subtotal:']/following-sibling::td/span";
    browserWait.until(
      ExpectedConditions.presenceOfElementLocated(By.xpath(locator))
    );
    return browser.findElement(By.xpath(locator));
  }
}

We can then assert if the price located on the order page is correct.

Tests without Facade Design Pattern

If we wish to write two separate tests – buy two different products with varying information. We can perform them only with the above classes without a facade class.

public class StorePurchaseWithoutFacadeTests {

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

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

  @Test
  public void subtotalPriceOfFalcon9CalculatedCorrect_when_NoFacadePatternUsed() {
    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");
    var itemPage = new BellatrixDemoItemPage();
    var shoppingCartPage = new BellatrixDemoShoppingCartPage();
    var checkoutPage = new BellatrixDemoCheckoutPage();
    itemPage.navigate(itemUrl);
    itemPage.assertions().assertProductPrice(itemPrice);
    itemPage.clickBuyNowButton();
    itemPage.clickViewShoppingCartButton();
    shoppingCartPage.clickProceedToCheckoutButton();
    shoppingCartPage.assertions().assertShoppingCartSubtotalPrice(itemPrice);
    checkoutPage.fillBillingInfo(purchaseInfo);
    checkoutPage.assertions().assertOrderSubtotalPrice(itemPrice);
  }

  @Test
  public void subtotalPriceOfSaturnVCalculatedCorrect_when_NoFacadePatternUsed() {
    var itemUrl = "saturn-v";
    var itemPrice = 120.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");
    var itemPage = new BellatrixDemoItemPage();
    var shoppingCartPage = new BellatrixDemoShoppingCartPage();
    var checkoutPage = new BellatrixDemoCheckoutPage();
    itemPage.navigate(itemUrl);
    itemPage.assertions().assertProductPrice(itemPrice);
    itemPage.clickBuyNowButton();
    itemPage.clickViewShoppingCartButton();
    shoppingCartPage.clickProceedToCheckoutButton();
    shoppingCartPage.assertions().assertShoppingCartSubtotalPrice(itemPrice);
    checkoutPage.fillBillingInfo(purchaseInfo);
    checkoutPage.assertions().assertOrderSubtotalPrice(itemPrice);
  }
}

As you can see, the main problem in the examples is that we need to create instances of our pages for every test. Moreover, the whole workflow of the tests as method calls is copied. The practices mentioned above make our tests much harder to maintain. Also, it breaks one of the essential programming principles – DRY (Don’t Repeat Yourself).

Tests Using Facade Design Pattern

The solution to the above problems is to encapsulate our test’s logic/workflow in a f****acade class.

public class PurchaseFacade {

  private final BellatrixDemoItemPage bellatrixDemoItemPage;
  private final BellatrixDemoShoppingCartPage bellatrixDemoShoppingCartPage;
  private final BellatrixDemoCheckoutPage bellatrixDemoCheckoutPage;

  public PurchaseFacade(
    ItemPage itempage,
    ShoppingCartPage shoppingCartPage,
    CheckoutPage checkoutPage
  ) {
    this.bellatrixDemoItemPage = new BellatrixDemoItemPage();
    this.bellatrixDemoShoppingCartPage = new BellatrixDemoShoppingCartPage();
    this.bellatrixDemoCheckoutPage = new BellatrixDemoCheckoutPage();
  }

  public void purchaseItem(
    String itemUrl,
    double itemPrice,
    PurchaseInfo purchaseInfo
  ) {
    bellatrixDemoItemPage.navigate(itemUrl);
    bellatrixDemoItemPage.assertions().assertProductPrice(itemPrice);
    bellatrixDemoItemPage.clickBuyNowButton();
    bellatrixDemoItemPage.clickViewShoppingCartButton();
    bellatrixDemoShoppingCartPage.clickProceedToCheckoutButton();
    bellatrixDemoShoppingCartPage
      .assertions()
      .assertShoppingCartSubtotalPrice(itemPrice);
    bellatrixDemoCheckoutPage.fillBillingInfo(purchaseInfo);
    bellatrixDemoCheckoutPage.assertions().assertOrderSubtotalPrice(itemPrice);
  }
}

If the test case’s workflow is changed, it can be quickly updated only in a single place. Or, if you want to add additional assertions, they can be added to the purchaseItem method.

Find below the code of the same tests refactored to use the Facade Design Pattern.

public class StorePurchaseWithFacadeTests {

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

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

  @Test
  public void subtotalPriceOfFalcon9CalculatedCorrect_when_FacadePatternUsed() {
    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 ShoppingCart().purchaseItem(itemUrl, itemPrice, purchaseInfo);
  }

  @Test
  public void subtotalPriceOfSaturnVCalculatedCorrect_when_FacadePatternUsed() {
    var itemUrl = "saturn-v";
    var itemPrice = 120.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 ShoppingCart().purchaseItem(itemUrl, itemPrice, purchaseInfo);
  }
}

Improved Facade Design Pattern

The only issue with the previously presented code is that it doesn’t follow the Dependency Inversion Principle.

Definition

It suggests that our high-level components (the facades) should not depend on our low-level components (the pages); rather, they should both depend on abstractions.

UML Class Diagram

classDiagram
    ItemPage <|-- BellatrixDemoItemPage
    ShoppingCartPage <|-- BellatrixDemoShoppingCartPage
    CheckoutPage <|-- BellatrixDemoCheckoutPage
    PurchaseFacade *-- ItemPage
    PurchaseFacade *-- ShoppingCartPage
    PurchaseFacade *-- CheckoutPage
    StorePurchaseTests --> PurchaseFacade
    class BellatrixDemoItemPage {
        #elements()
        +assertions()
        +navigate()
        +clickBuyNowButton()
        +clickViewShoppingCartButton()
        +assertPrice()
    }
    class BellatrixDemoShoppingCartPage {
        #elements()
        +assertions()
        +navigate()
        +clickProceedToCheckoutButton()
        +assertSubtotalAmount()
    }
    class BellatrixDemoCheckoutPage {
        #elements()
        +assertions()
        +navigate()
        +fillBillingInfo()
        +assertSubtotal()
    }
    class ItemPage {
        +navigate()
        +clickBuyNowButton()
        +clickViewShoppingCartButton()
        +assertPrice()
    }
    class ShoppingCartPage {
        +clickProceedToCheckoutButton()
        +assertSubtotalAmount()
    }
    class CheckoutPage {
        +fillBillingInfo()
        +assertSubtotal()
    }
    class PurchaseFacade {
        -itemPage
        -shoppingCartPage
        -checkoutPage
        +purchaseItem()
    }
    class StorePurchaseTests {
        +subtotalPriceOfFalcon9CalculatedCorrect()
        +subtotalPriceOfSaturnVCalculatedCorrect()
    }

Find below the code of the improved version of the facade that holds the logic related to the creation of purchases.

public class PurchaseFacade {

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

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

  public void purchaseItem(
    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);
  }
}

Through the usage of the pages’ interfaces, the facade follows the Dependency Inversion Principle. You can replace the version of some of the pages without changing even a single code line in the facades.

The facade combines the different pages’ methods to complete the wizard of the order. If there is a change in the order of the executed actions, you can edit it only here. It will apply to tests that are using the facade. The different test cases are accomplished through the various parameters passed to the facade’s methods. You can read how to create these pages in my article Page Objects That Make Code More Maintainable.

These facades contain much less code because most of the logic is held by the pages instead of the facade itself.

Tests Using the Improved Facade Design Pattern

public class StorePurchaseWithFacadeTests {

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

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

  @Test
  public void subtotalPriceOfFalcon9CalculatedCorrect_when_FacadePatternUsed() {
    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");
    var itemPage = new BellatrixDemoItemPage();
    var shoppingCartPage = new BellatrixDemoShoppingCartPage();
    var checkoutPage = new BellatrixDemoCheckoutPage();
    new PurchaseFacade(itemPage, shoppingCartPage, checkoutPage)
      .purchaseItem(itemUrl, itemPrice, purchaseInfo);
  }

  @Test
  public void subtotalPriceOfSaturnVCalculatedCorrect_when_FacadePatternUsed() {
    var itemUrl = "saturn-v";
    var itemPrice = 120.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");
    var itemPage = new BellatrixDemoItemPage();
    var shoppingCartPage = new BellatrixDemoShoppingCartPage();
    var checkoutPage = new BellatrixDemoCheckoutPage();
    new PurchaseFacade(itemPage, shoppingCartPage, checkoutPage)
      .purchaseItem(itemUrl, itemPrice, purchaseInfo);
  }
}

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

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

Java, Web Automation Java

Selenium WebDriver Tor Network Integration Java Code

For a long time, I wanted to write automation using the Tor Web Browser. My preferred automation framework is Selenium WebDriver. However, I found out that ther

Selenium WebDriver Tor Network Integration 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

Java, Kotlin, Web Automation Java

30 Advanced WebDriver Tips and Tricks Kotlin 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 Kotlin Code

Design Architecture Java, Design Patterns Java, Java

Advanced 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

Advanced Page Object Pattern in Automated Testing Java Code

Design Architecture Java, Design Patterns Java, Java

Fluent Page Object Pattern in Automated Testing Java Code

In my previous articles from the series Design Patterns in Automated Testing, I explained in detail how to improve your test automation framework through the im

Fluent Page Object 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.