PageObject and PageObjectInterface

Overview

Selenium references: PageObjects

PageObject is a central concept of the Bobcat. It’s a term derived from Selenium framework and it means a class that encapsulates page HTML and exposes page features.

PageObjectInterface extends the idea of PageObject. It allows to define interface with API that can be bind with specific PageObject implementation. We can inject interface marked by PageObjectInterface annotation and Bobcat will found bound implementation and inject it instead.

What is important that class marked with PageObject doesn’t require corresponding interface. You can still inject class marked by PageObject It is additional feature to create one API for changing markup.

Example:

Interface with PageObjectInterface

@PageObjectInterface
public interface LoginComponent{
     
    public void login(String username, String password);
    
}

Implementation of this interface:

@PageObject(css = ".login-box")
public class LoginComponentImpl extends LoginComponent{
     
    @FindBy(id = "username")
    private WebElement usernameField;
      
    @FindBy(id = "password")
    private WebElement passwordField;
        
    @FindBy(css = "button[type=submit]")
    private WebElement submitButton;
     
    public void login(String username, String password) {
         usernameField.sendKeys(username);
         passwordField.sendKeys(password);
         submitButton.click();
    }
    
}

Somewhere in guice module:

public class LoginModule extends AbstractModule {

  @Override
  protected void configure() {
    bind(LoginComponent.class).to(LoginComponentImpl.class);
  }
}

Page using component:

@PageObject
public class LoginPage {
    
    @FindPageObject
    private LoginComponent loginComponent;
 
    public void login(String username, String password) {
        loginComponent.login(username, password);
    }
}

@PageObject locators and @FindPageObject

@PageObject annotation can have locators that allows to find object in html markup (css or xpath). If our page object has locator set and we inject it using @FindPageObject then Bobcat will find it for us.

@PageObject(xpath = "//div[contains(@class, 'x-grid3-body')]/div")
public class SomeElement {
//... 
}

@PageObject
public class TestObject {

  @FindPageObject
  private SomeElement someElement;

  @FindPageObject
  private List<SomeElement> items;

}

As we can see in example in Overview this mechanism will work if we are injecting interface bound to class with @PageObject and locator

@FindBy annotations

Writing webDriver.findElement(...) is not very efficient. Luckily, Selenium provides the PageFactory util class that allows us to use @FindBy annotations. You don’t have to call the PageFactory manually. If you annotate the class with @PageObject, Bobcat will do it for you:

@PageObject // we need this so Bobcat takes care of @FindBy fields
public class LoginPage {
 
    @FindBy(id = "username")
    private WebElement usernameField;
 
    @FindBy(id = "password")
    private WebElement passwordField;
 
    @FindBy(css = "button[type=submit]")
    private WebElement submitButton;
 
    public void login(String username, String password) {
        usernameField.sendKeys(username);
        passwordField.sendKeys(password);
        submitButton.click();
    }
}

In this case we use FindBy annotation in the LoginPage to inject three WebElements.

Nested elements

Sometimes it may happen that HTML structure is too complicated to be represented by one class and it makes sense to split it into multiple classes. Consider a following page: Login Page

Generally it’s a good practice and one of basic Object Oriented principals to have classes with limited responsibility. But FindBy, FindPageObject annotation and webdriver.findBy(...) method uses global browser scope when seeking for elements. That is why, you may want to narrow scope for locating elements in PageObject class. As we can see in example in Overview we have page with login component. We splitted implementation to LoginPage which represents outer html and LoginComponentImpl for login component nested in html element with class login.

@PageObject(css = ".login-box")
public class LoginComponentImpl extends LoginComponent{
     
    @FindBy(id = "username")
    private WebElement usernameField;
      
    @FindBy(id = "password")
    private WebElement passwordField;
        
    @FindBy(css = "button[type=submit]")
    private WebElement submitButton;
     
    public void login(String username, String password) {
         usernameField.sendKeys(username);
         passwordField.sendKeys(password);
         submitButton.click();
    }
    
}

Why is it so cool? The coolest part is under the hood: usernameField, passwordField and submitButton WebElements are located in scope limited to div#login-box element!

@CurrentScope

What about a WebElement? Is it possible to have div#login-box as a WebElement that I can access to? Something like this object but for WebElements?

Let’s go back to our login page once more, but this time bare in mind that with Bobcat we can limit the scope for locating elements: Login Page With @CurrentScope annotation we have an access to WebElement which represents the same limited scope that was used for locating @FindBy elements. In simple words: WebElement scope = webdriver.findElement(By.cssSelector("div#login-box")) will represent the same element as WebElement annotated with @CurrentScope:

@PageObject(css = ".login-box")
public class LoginBox {
 
    @Inject
    @CurrentScope
    private WebElement currentScope; // it will be the div#login-box
 
 }

@Global annotation

@Global annotation is the exact opposite to @CurrentScope. You may want to use it when there is a need to ignore scope limitation. But be careful and use it wisely. Possible use case: floating dialog.

@PageObject(css = ".login-box")
public class LoginBox {
 
    @Global
    @FindBy(css = ".floating.dialog")
    private WebElement floatingDialog;
 
 }

Bobcat will search floatingDialog in whole html not only in div#login-box