How to implement WebDriver PageObject methods that can return different PageObjects How to implement WebDriver PageObject methods that can return different PageObjects selenium selenium

How to implement WebDriver PageObject methods that can return different PageObjects


Bohemian's answer is not flexible - you cannot have a page action returning you to the same page (such as entering a bad password), nor can you have more than 1 page action resulting in different pages (think what a mess you'd have if the Login page had another action resulting in different outcomes). You also end up with heaps more PageObjects just to cater for different results.

After trialing this some more (and including the failed login scenario), I've settled on the following:

private <T> T login(String user, String pw, Class<T> expectedPage){    username.sendKeys(user);    password.sendKeys(pw);    submitButton.click();    return PageFactory.initElements(driver, expectedPage);}public AdminWelcome loginAsAdmin(String user, String pw){    return login(user, pw, AdminWelcome.class);}public CustomerWelcome loginAsCustomer(String user, String pw){    return login(user, pw, CustomerWelcome.class);}public Login loginWithBadCredentials(String user, String pw){    return login(user, pw, Login.class);}

This means you can reuse the login logic, but prevent the need for the test class to pass in the expected page, which means the test class is very readable:

Login login = PageFactory.initElements(driver, Login.class);login = login.loginWithBadCredentials("bad", "credentials");// TODO assert login failure messageCustomerWelcome customerWelcome = login.loginAsCustomer("joe", "smith");// TODO do customer things

Having separate methods for each scenario also makes the Login PageObject's API very clear - and it's very easy to tell all of the outcomes of logging in. I didn't see any value in using interfaces to restrict the pages used with the login() method.

I'd agree with Tom Anderson that reusable WebDriver code should be refactored into fine-grained methods. Whether they are exposed finely-grained (so the test class can pick and choose the relevant operations), or combined and exposed to the test class as a single coarsely-grained method is probably a matter of personal preference.


You are polluting your API with multiple types - just use generics and inheritance:

public abstract class Login<T> {    @FindBy(id = "username")    private WebElement username;    @FindBy(id = "password")    private WebElement password;    @FindBy(id = "submitButton")    private WebElement submitButton;    private WebDriver driver;    private Class<T> clazz;    protected Login(WebDriver driver, Class<T> clazz) {        this.driver = driver;        this.clazz = clazz    }    public T login(String user, String pw){        username.sendKeys(user);        password.sendKeys(pw);        submitButton.click();        return PageFactory.initElements(driver, clazz);    }}

and then

public AdminLogin extends Login<AdminWelcome> {   public AdminLogin(WebDriver driver) {       super(driver, AdminWelcome.class);   }}public CustomerLogin extends Login<CustomerWelcome> {   public CustomerLogin(WebDriver driver) {       super(driver, CustomerWelcome.class);   }}

etc for all types on login pages


Note the work-around for type erasure of being able to pass an instance of Class<T> to the PageFactory.initElements() method, by passing an instance of the class into the constructor, which is known as the "type token" pattern.