I've just started using WebDriver, and I'm trying to learn the best practices, in particular using PageObjects and PageFactory.
It's my understanding that PageObjects should expose the various operations on a web page, and isolate the WebDriver code from the test class. Quite often, the same operation can result in navigating to different pages depending on the data used.
For example, in this hypothetical Login scenario, providing admin credentials takes you to the AdminWelcome page, and providing Customer credentials takes you to the CustomerWelcome page.
So the easiest way to implement this is to expose two methods which return different PageObjects...
Login PageObject
package example;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class Login {
@FindBy(id = "username")
private WebElement username;
@FindBy(id = "password")
private WebElement password;
@FindBy(id = "submitButton")
private WebElement submitButton;
private WebDriver driver;
public Login(WebDriver driver){
this.driver = driver;
}
public AdminWelcome loginAsAdmin(String user, String pw){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, AdminWelcome.class);
}
public CustomerWelcome loginAsCustomer(String user, String pw){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, CustomerWelcome.class);
}
}
And do the following in the test class:
Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome = loginPage.loginAsAdmin("admin", "admin");
or
Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome = loginPage.loginAsCustomer("joe", "smith");
Alternative approach
Instead of duplicating code, I was hoping there was a cleaner way of exposing a single login()
method which returned the relevant PageObject.
I thought about creating a hierarchy of pages (or having them implement an interface) so that I could use that as the return type, but it feels clumsy. What I came up with was the following:
public <T> T login(String user, String pw, Class<T> expectedPage){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, expectedPage);
}
Which means you can do the following in the test class:
Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome =
loginPage.login("admin", "admin", AdminWelcome.class);
or
Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome =
loginPage.login("joe", "smith", CustomerWelcome.class);
This is flexible - you could add an ExpiredPassword page and not have to change the login()
method at all - just add another test and pass in the appropriate expired credentials and the ExpiredPassword page as the expected page.
Of course, you could quite easily leave the loginAsAdmin()
and loginAsCustomer()
methods and replace their contents with a call to the generic login()
(which would then be made private). A new page (e.g. the ExpiredPassword page) would then require another method (e.g. loginWithExpiredPassword()
).
This has the benefit that the method names actually mean something (you can easily see that there are 3 possible results of logging in), the PageObject's API is a bit easier to use (no 'expected page' to pass in), but the WebDriver code is still being reused.
Further improvements...
If you did expose the single login()
method, you could make it more obvious which pages can be reached from logging in by adding a marker interface to those pages (this is probably not necessary if you expose a method for each scenario).
public interface LoginResult {}
public class AdminWelcome implements LoginResult {...}
public class CustomerWelcome implements LoginResult {...}
And update the login method to:
public <T extends LoginResult> T login(String user, String pw,
Class<T> expectedPage){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, expectedPage);
}
Either approach seems to work well, but I'm not sure how it would scale for more complicated scenarios. I haven't seen any code examples like it, so I'm wondering what everyone else does when actions on a page can result in different outcomes depending on the data?
Or is it common practice to just duplicate the WebDriver code and expose lots of different methods for each permutation of data/PageObjects?
See Question&Answers more detail:
os