How to represent Page Object navigation in a Selenium WebDriver project using java

Jamie Piltz picture Jamie Piltz · Apr 26, 2013 · Viewed 8k times · Source

I have read in a few places that Navigating between Pages when using WebDriver API is typically done by the Page Object returning the next page that should be returned when the action that causes the navigation to occur.

The solution works great for actions that always return the same page (eg, SearchPage.search() returning new ResultPage) but what happens when an action may put the user on a different page depending on some input or state. Java does not allow overloading of methods based on return type. So how can this be done?

I had a rough attempt at implementing a Navigator object that could be used to return a generic Page object based on what the expected result was.

public class Navigator {

    private WebDriver driver;
    private String baseUrl;
    private Map<Class, String> pagesToRelativePaths;


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

    public Navigator(WebDriver driver, String baseUrl, Map<Class, String> pagesToRelativePaths) {
        this.driver = driver;

        setBaseUrl(baseUrl);
        setPagesToRelativePaths(pagesToRelativePaths);
    }

    public void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public void setPagesToRelativePaths(Map<Class, String> pagesToRelativePaths) {
        this.pagesToRelativePaths = pagesToRelativePaths;
    }

    public <T extends PageBase> T navigateTo(Class<T> type) {
        driver.get(baseUrl + pagesToRelativePaths.get(type));

        return type.cast(new PageBase(driver));
    }

    public <T extends PageBase> T expectedLandingPage(Class<T> type) {
        return type.cast(new PageBase(driver));
    }
}

Action that would result in the page moving from one Page Object to another would implement a method like so:

public Navigator signIn(String username, String password) {
    usernameField.type(username);
    passwordField.type(password);
    signInButton.click();

    return new Navigator(driver);
}

and a Test would look like this:

@Test
public void SignInWithInvalidCredentialsTest() {
    SignInPage signInPage = navigator.navigateTo(SignInPage.class);

    signInPage = signInPage.signIn("invalid", "invalid").expectedLandingPage(SignInPage.class);

    assertThat(signInPage.getTitle(), is(equalTo(driver.getTitle())));
}

But unfortunately I couldn't get the Navigator to go to any page based on user input. Is there a way to do this that works or is there an alternative way to do this?

Source:

Answer

Jamie Piltz picture Jamie Piltz · Apr 29, 2013

I did a bit more research into returning Generic Types and found a solution to the problem. The key was to create and instance of the type that is being provided. There are a few exceptions that could occur so I've tried my best to outline them here:

public <T extends PageBase> T navigateTo(Class<T> type) {
    driver.get(baseUrl + pagesToRelativePaths.get(type));

    return getPage(type);
}

public <T extends PageBase> T expectedLandingPage(Class<T> type) {
    return getPage(type);
}

private <T extends PageBase> T getPage(Class<T> type) {
    T page = null;

    try {
        page = type.getDeclaredConstructor(WebDriver.class).newInstance(driver);
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }

    return page;
}