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.
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 🙂
is there any way to navigate to different page by chaining rather than separately initializing it
If you are moving to another page from a page, then I would consider that business flow. You can check that here – This might answer your question.
http://www.testautomationguru.com/selenium-webdriver-how-to-design-test-business-workflows-in-fluent-style/
Hi,
I’m trying to use fluent style pom in my organization. My app has lots of situations like below,
goToX()
If user logged in goto page x
Else goto login page
My goToX func has only one return type and i dont want to write distinct functions for each condition.
Do you have any solution for this?
I think you need to look into chain of responsibility design principles. http://www.testautomationguru.com/selenium-webdriver-design-patterns-in-test-automation-chain-of-responsibility-design-pattern/