Test Automation Blog

Tools, Tips and Thoughts for Playwright, Selenium UI and API automated testing

Implementing Method Chaining with a Page Object Model

When creating a test automation framework using the Page Object Model design pattern and Selenium, a useful feature to add is Method Chaining (also known as Fluid Syntax) to make your test steps more readable and therefore maintainable. So instead of your test code using a LoginPage object like this:

LoginPage loginPage = new LoginPage(driver);
loginPage.setUsername("JohnDoe");
loginPage.setPassword("he7s6eh!dy");
loginPage.clickLoginButton();

Using fluid syntax we can chain the method calls so that the code would look something like this:

LoginPage loginPage = new LoginPage(driver);
loginPage.setUsername("JohnDoe")
         .setPassword("he7s6eh!dy")
         .clickLoginButton();

As you can see, the little code snippet is a little more readable, however the cumulative effect of using this technique throughout our tests improves readability hugely, especially when we also chain method calls across multiple web pages / page objects and use ‘business methods’ – which we’ll cover further down this article. For now let’s explore how to implement method chaining / fluid syntax in a single page, the Login page, and then use it in our Selenium tests.

Originally a simplified LoginPage object without fluid syntax might be implemented using code like this:

public class LoginPage {

    public WebDriver driver;

    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }

    public void setUsername(String username) {
        driver.findElement(By.id("username")).sendKeys(username);
    }

    public void setPassword(String password)
    {
        driver.findElement(By.id("pwd")).sendKeys(password);
    }

    private void clickLoginButton() {
        driver.findElement(By.id("loginBtn")).click();
    }
}

To use fluid syntax we need to amend the setUsername() and setPassword() methods to return the current instance of the LoginPage object, i.e. return this; We therefore also need to change the method declaration so that instead of ‘private void methodName’ we have ‘private LoginPage methodName’:

public class LoginPage {

    public WebDriver driver;

    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }

    public LoginPage setUsername(String username) {
        driver.findElement(By.id("username")).sendKeys(username);
        return this;
    }

    public LoginPage setPassword(String password)
 {
        driver.findElement(By.id("pwd")).sendKeys(password);
        return this;

    }

    private void clickLoginButton() {
        driver.findElement(By.id("loginBtn")).click();
    }
}

This can be a difficult concept to initially understand, so one way I like to think of it is:  if the login page method you call – for example setUsername() – performs some action but after that action the browser focus still remains on the login page, then the method should return a login page object. Similarly when you call the setPassword() method you expect it to populate a password textbox with the password value parameter, but after it has finished doing that then the test session should still be on the login page, so that method should also return a login page object.

You will notice that the clickLoginButton() method does not have a “return this” statement. This is because after this method causes the Login button to be clicked, we expect to navigate to a different page. We will cover this in the “Chaining Across Multiple Pages / Page Objects” below.

One important point to note about method chaining is that when you implement it in the page classes using return this; it does not make its usage mandatory – you can still use the original non-fluid syntax in your tests if you want to. This flexibility also allows you to retro-fit fluid syntax to an existing framework since the existing code will still work, and then refactor the existing codebase incrementally in your own time.

Chaining Across Multiple Pages / Page Objects

We can also chain method calls across multiple web page objects, to improve readability further. For example imagine we have a home page, a login page and an account details page, and in our test we want to:

  1. Start by landing on the Home page
  2. Click on a link to go to the Login page
  3. Enter the user name and password and click on the Login button
  4. We then land on the Account Details page
  5. Click on a link to go back to the Home page

Let’s go back to where we left off above on steps 3-4, where we click on the Login button and expect to land on the Account Details page. In this situation we need the clickLoginButton() method to now return a new instance of an AccountDetailsPage object like so:

public class LoginPage {
    .
    .
    .
    private AccountDetailsPage clickLoginButton() {
        driver.findElement(By.id("loginBtn")).click();
        return new AccountDetailsPage(driver);
    }
}

So when we call clickLoginButton() on the LoginPage object we will get back an AccountDetailsPage object.

Let me now show you how the HomePage and AccountDetailsPage should be coded, and how the test which calls the methods on these objects should work.

public class HomePage {

    public WebDriver driver;

    public HomePage(WebDriver driver) {
        this.driver = driver;
    }

    public LoginPage navigateToLoginPage() {
        driver.findElement(By.id("gotoLoginPageLink")).click();
        return new LoginPage(driver);
    }
}

public class AccountDetailsPage {

    public WebDriver driver;

    public AccountDetailsPage(WebDriver driver) {
        this.driver = driver;
    }

    public HomePage navigateToHomePage() {
        driver.findElement(By.id("gotoHomePageLink")).click();
        return new HomePage(driver);
    }
}

As you can see the HomePage class has a method navigateToLoginPage() which when called clicks on a link to go to the login page, and returns a new LoginPage object. Similarly the AccountDetailsPage class has a navigateToHomePage() method which clicks on a link to go back to the home page, and so returns a new HomePage object.

Let’s now compare how this fluid syntax would compare to ‘normal’ test code:

// normal test code
HomePage homePage = new HomePage(driver);
homePage.navigateToLoginPage();
LoginPage loginPage = new LoginPage(driver);
loginPage.setUsername("JohnDoe");
loginPage.setPassword("he7s6eh!dy");
loginPage.clickLoginButton();
AccountDetailsPage accountDetailsPage = new AccountDetailsPage(driver);
accountDetailsPage.navigateToHomePage();

// fluid syntax code
(new HomePage(driver)).navigateToLoginPage()
                      .setUsername("JohnDoe")
                      .setPassword("he7s6eh!dy")
                      .clickLoginButton()
                      .navigateToHomePage();

As you can see, this Selenium test code is already much neater and more easy to read and understand, and that’s just with this small example.

One of the disadvantages of fluid syntax and method chaining is that you could chain dozens of methods from many pages into one big statement, and it could potentially be difficult to look at one of the method calls and know what page it was relating to, thereby actually reducing maintainability. However with experience and judicious naming of navigation methods you can overcome this problem. For example if a test fails when calling a particular method and it’s not immediately obvious what page that method related to, you only need to look at the return class type of the previous method to find out.

Advanced Chaining with Business Methods

The classes above are very simplistic, and I often use enhanced ‘business methods’ in my page objects instead of methods that directly relate to actions on individual elements on the web pages. For example with the LoginPage object, instead of separate setUsername(), setPassword() and clickLoginButton() methods, I would normally implement a single login() method that accepted a username and password parameters and would perform all three actions: populate the username and password textboxes and click on the Login button. And that method would return a new AccountDetailsPage object.

public class LoginPage {

    public WebDriver driver;

    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }

    public AccountDetailsPage login(String username, String password) {
        driver.findElement(By.id("username")).sendKeys(username);
        driver.findElement(By.id("pwd")).sendKeys(password);
        driver.findElement(By.id("loginBtn")).click();
        return new AccountDetailsPage(driver);
    }
}

This allows us to shorten the test code we have been using to:

(new HomePage(driver)).navigateToLoginPage()
                      .login("JohnDoe","he7s6eh!dy")
                      .navigateToHomePage();

This is particularly important as we make longer Selenium tests so the code remains succinct and understandable … and therefore much more maintainable.

Here’s a test for an e-commerce website which fluidly calls chained business methods on the page objects instead of methods manipulating individual web elements. I think you can easily understand what it does at a glance, yet can you imagine how difficult the equivalent code which didn’t use fluid syntax and business methods would be to understand and maintain?

WebDriver driver = new ChromeDriver();
driver.get("http://automationpractice.com/");

(new HomePage(driver)).navigateToLoginPage()
                      .login("JohnDoe","he7s6eh!dy")
                      .navigateToHomePage();
                      .searchForProduct("Adidas Samba Trainers")
                      .addProductToShoppingCart()
                      .proceedToCheckout()

// now verify we have 1 item in the shopping cart
ShoppingCartSummaryPage shoppingCartSummaryPage = new ShoppingCartSummaryPage(browser);
int numProducts = shoppingCartSummaryPage.getTotalQuantity();
assertEquals(numProducts, 1, "Expected 1 product in shopping cart but actual number was: " + Integer.toString(numProducts));

That’s the end of this article, I hope you’ve enjoyed it and found it useful for writing maintainable Selenium test automation code using method chaining / fluid syntax with a page object model and business methods to really supercharge your test automation suites.

Implementing Method Chaining with a Page Object Model
Scroll to top