10

Quick question about page objects in selenium webdriver. our site is very dynamic with lots of ajax and various authentication states. It is tough to figure out how to define each page object BUT lets say I have figured that out and defined several page objects that represent our site.

How do you handle crossing from page to page. So I get a page object for my home page and one for my account page and one for my results page. Then I need to write a test that traverses all my pages to simulate a user performing multiple actions.

How do you say give me a HomePage object to create a new use -> then get a account page object to go perform some user actions - then get a result page object to verify those actions all from a single script.

How are people doing this?

thanks

Ripon Al Wasim
  • 36,924
  • 42
  • 155
  • 176
ducati1212
  • 855
  • 9
  • 18
  • 29

5 Answers5

10

When you're simulating having the user enter a new URL into the URL bar of the browser, then it's the responsibility of the test class to create the page object it needs.

On the other hand, when you're doing some operation on the page that would cause the browser to point to another page -- for example, clicking a link or submitting a form -- then it's the responsibility of that page object to return the next page object.

Since I don't know enough about the relationships between your home page, account page, and result page to tell you exactly how it'd play out in your site, I'll use an online store app as an example instead.

Let's say you've got a SearchPage. When you submit the form on the SearchPage, it returns a ResultsPage. And when you click on a result, you get a ProductPage. So the classes would look something like this (abbreviated to just the relevant methods):

public class SearchPage {

    public void open() {
        return driver.get(url);
    }

    public ResultsPage search(String term) {
        // Code to enter the term into the search box goes here
        // Code to click the submit button goes here
        return new ResultsPage();
    }

}

public class ResultsPage {

    public ProductPage openResult(int resultNumber) {
        // Code to locate the relevant result link and click on it
        return new ProductPage();
    }

}

The test method to execute this story would look something like this:

@Test
public void testSearch() {

    // Here we want to simulate the user going to the search page
    // as if opening a browser and entering the URL in the address bar. 
    // So we instantiate it here in the test code.

    SearchPage searchPage = new SearchPage();
    searchPage.open(); // calls driver.get() on the correct URL

    // Now search for "video games"

    ResultsPage videoGameResultsPage = searchPage.search("video games");

    // Now open the first result

    ProductPage firstProductPage = videoGameResultsPage.openResult(0);

    // Some assertion would probably go here

}

So as you can see, there's this "chaining" of Page Objects where each one returns the next one.

The result is that you end up with lots of different page objects instantiating other page objects. So if you've got a site of any considerable size, you could consider using a dependency injection framework for creating those page objects.

Dave Leeds
  • 698
  • 6
  • 7
  • This is what I thought I needed to do. it just seems a little clunky as our site is very ajaxy and actions on the site return to different places depending on what state the user is currently in. – ducati1212 Apr 30 '12 at 12:23
  • The problem with this code is that each page needs to call PageFactory.initObjects() and the driver instance cannot initialize all objects on 2 different pages (lets say they are 2 different windows or an embedded frame). So, while I agree this is a great design pattern to many things, this pattern quickly breaks down to only working for certain applications. The way WebDriver PageFactory is designed, its much better to open a page object , use it, mark it for garbage collection, then instantiate the next page , use it, etc. Perhaps you could extend PageFactory, as a workaround, maybe. – djangofan Mar 22 '13 at 16:36
4

Well, I created my own Java classes which represent the pages:

Say, the below is code to represent home page. Here user can login:

public class HomePage{
  private WebDriver driver;
  private WebElement loginInput;
  private WebElement passwordInput;
  private WebElement loginSubmit;

  public WebDriver getDriver(){
    return driver;
  }

  public HomePage(){
    driver = new FirefoxDriver();
   }

  public CustomerPage login(String username, String password){
     driver.get("http://the-test-page.com");
     loginInput = driver.findElement(By.id("username"));
     loginInput.sendKeys(username);
     passwordInput = driver.findElement(By.id("password"));
     passwordInput.sendKeys(password);
     loginSubmit = driver.findElement(By.id("login"));
     loginSubmit.click();
     return new CustomerPage(this);
  }


}

And the page for Customer can look like this. Here I am demonstrating, how to get, say, logged in user:

public class CustomerPage{
    private HomePage homePage;
    private WebElement loggedInUserSpan;

 public CustomerPage(HomePage hp){
    this.homePage = hp;
  }

 public String getLoggedInUser(){
      loggedInUserSpan = homePage.getDriver().findElement(By.id("usrLongName"));
      return loggedInUserSpan.getText();
 }

}

And the test can go like this:

@Test
public void testLogin(){
  HomePage home = new HomePage();
  CustomerPage customer = home.login("janipav", "extrasecretpassword");
  Assert.assertEquals(customer.getLoggedInUser(), "Pavel Janicek");
}
Pavel Janicek
  • 14,128
  • 14
  • 53
  • 77
2

You generally want to model what a user actually does when using your site. This ends up taking the form of a Domain Specific Language (DSL) when using page objects. It gets confusing with reusable page components though.

Now that Java 8 is out with default methods, reusable page components can be treated as mixins using default methods. I have a blog post with some code samples found here that explains this in more detail: http://blog.jsdevel.me/2015/04/pageobjects-done-right-in-java-8.html

jsdevel
  • 1,539
  • 1
  • 12
  • 8
1

I suggest you use a framework that provides support for these patterns. Geb is one of the best one out there. Below is an example taken from their manual

Browser.drive {
    to LoginPage
    assert at(LoginPage)
    loginForm.with {
        username = "admin"
        password = "password"
    }
    loginButton.click()
    assert at(AdminPage)
}

class LoginPage extends Page {
    static url = "http://myapp.com/login"
    static at = { heading.text() == "Please Login" }
    static content = {
        heading { $("h1") }
        loginForm { $("form.login") }
        loginButton(to: AdminPage) { loginForm.login() }
    }
}

class AdminPage extends Page {
    static at = { heading.text() == "Admin Section" }
    static content = {
        heading { $("h1") }
    }
}
Aravind Yarram
  • 78,777
  • 46
  • 231
  • 327
1

I enjoy writing Selenium Webdriver tests using the Page Object pattern. But was personally annoyed at the verbosity and repetition of having to always explicitly instantiate and return the next page or page component. So with the benefit of Python's metaclasses I wrote a library, called Keteparaha, that automatically figures out what should be returned from a selenium page object's method calls.

aychedee
  • 24,871
  • 8
  • 79
  • 83