fluent-api-header

Selenium WebDriver – How To Design Page Objects In Fluent Style

Overview:

As an automation engineer, you would have understood that creating an automated test for an application is not very difficult. Instead the difficult part is maintaining the existing tests!! That too, When you have thousands of automated tests. Oh Wait..on top of that, You have got Junior team members to maintain those thousands of tests!!

We do not work alone! We work with our fellow QA engineers with different skill sets! Some team members might find it very difficult to understand the code. The more time they spend to understand the code the less productive they would be as you could imagine! As a lead/architect of the automation framework, You need to come up with a proper design for the page objects/tests for easy maintenance and readability of the tests. So everyone in the team is very comfortable with the framework.

Lets see how we could design page objects for improving its readability.

Fluent Style:

Lets say,  You are given a task to calculate the sum of the first 2 numbers which are greater than 5, lesser than 20 and divisible by 3 from a list of integers!

The traditional for-loop approach:

List<Integer> list = Arrays.asList(1, 3, 6, 9, 11, 14, 16, 15, 21, 23);

int sum = 0;
int limit = 0;
for (Integer i : list) {
    if (i > 5 && i < 20 && (i % 3 == 0)) {
        limit++;
        sum = sum + (i * 2);
        if (limit == 2){
            break;
        }
    }
}
System.out.println(sum);

The Java8-Stream approach:

List<Integer> list = Arrays.asList(1, 3, 6, 9, 11, 14, 16, 15, 21, 23);

int sum = list.stream()
                .filter(i -> i > 5)
                .filter(i -> i < 20)
                .filter(i -> i % 3 == 0)
                .mapToInt(i -> i * 2)
                .limit(2)
                .sum();

System.out.println(sum);

As you could see, the number of lines could be more or less same in both approaches. But Java8 is definitely the winner when it comes to readability. When you use fluent style API and an IDE for development, the IDE itself will show all the possible methods you could invoke to proceed further on completing the task!

Sample Application:

I am going to consider the below mercury tours ticket booking workflow – check here.

mercury-fluent-style-001

Registration – Page Object:

public class RegistrationPage {

    private final WebDriver driver;

    @FindBy(name = "firstName")
    private WebElement firstName;

    @FindBy(name = "lastName")
    private WebElement lastName;

    @FindBy(name = "email")
    private WebElement userName;

    @FindBy(name = "password")
    private WebElement password;

    @FindBy(name = "confirmPassword")
    private WebElement confirmPassword;

    @FindBy(name = "register")
    private WebElement submit;

    private RegistrationPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public static RegistrationPage using(WebDriver driver) {
        return new RegistrationPage(driver);
    }

    public RegistrationPage launch() {
        driver.get("http://newtours.demoaut.com/mercuryregister.php");
        return this;
    }

    public RegistrationPage setFirstName(String firstName) {
        this.firstName.sendKeys(firstName);
        return this;
    }

    public RegistrationPage setLastName(String lastName) {
        this.lastName.sendKeys(lastName);
        return this;
    }

    public RegistrationPage setUserName(String userName) {
        this.userName.sendKeys(userName);
        return this;
    }

    public RegistrationPage setPassword(String password) {
        this.password.sendKeys(password);
        return this;
    }

    public RegistrationPage setConfirmPassword(String confirmPassword) {
        this.confirmPassword.sendKeys(confirmPassword);
        return this;
    }

    public void submit() {
        this.submit.click();
    }
}

Login – Page Object:

public class LoginPage {

    @FindBy(name = "email")
    private WebElement userName;

    @FindBy(name = "password")
    private WebElement password;

    @FindBy(name = "login")
    private WebElement loginBtn;

    private LoginPage(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }

    public static LoginPage using(WebDriver driver) {
        return new LoginPage(driver);
    }

    public LoginPage setUsername(String username) {
        this.userName.sendKeys(username);
        return this;
    }

    public LoginPage setPassword(String password) {
        this.password.sendKeys(password);
        return this;
    }

    public void login() {
        this.loginBtn.click();
    }

}

Tests:

I could call the entry points ‘using’ and chain the methods to create a fluent style code in my tests to test my pages.

RegistrationPage.using(driver)
                .launch()
                .setFirstName("fn")
                .setLastName("ln")
                .setUserName("abcd")
                .setPassword("abcd")
                .setConfirmPassword("abcd")
                .submit();

LoginPage.using(driver)
                 .setUsername("abcd")
                 .setPassword("abcd")
                 .login();

The above code is much better and cleaner than the below traditional approach.

RegistrationPage registrationPage = new RegistrationPage(driver);
registrationPage.launch();
registrationPage.setFirstName("fn");
registrationPage.setLastName("ln");
registrationPage.setUserName("abcd");
registrationPage.setPassword("abcd");
registrationPage.setConfirmPassword("abcd");
LoginPage loginPage = registrationPage.submit();                 

loginPage.setUsername("abcd")
loginPage.setPassword("abcd")
loginPage.login();

 

Summary:

Fluent Style API improves the code readability and gives a very clear view of what the page does.

Each and every design/approach has its own pros and cons.  Even if this approach improves the code readability, it sometimes make it difficult to debug the code!  For ex: You want to print some debug statements between ‘setUsername‘ and ‘setPassword‘.   It is not possible to do it without breaking the chain!

LoginPage.using(driver)
            .setUsername("abcd")   //Can I print something here for debugging??
            .setPassword("abcd")
            .login();

However I would not encourage you to write debug statements everywhere in your tests. Instead the methods themselves should write enough info for you to debug later  in a separate log file.

Happy Testing & Subscribe 🙂

 

Share This:

Categories: Articles, Best Practices, Framework, Page Object Design, Selenium

Leave a Reply

Your email address will not be published. Required fields are marked *