Extend Selenium WebDriver WebElement?

Viewed 47045

I'm following the Page Object pattern suggested by Selenium, but how would I create a more specialized WebElement for a page. Specifically, we have tables on our pages and I have written some helper functions to get specific rows of a table, return the contents of a table, etc.

Currently, here is a snippet of a page object I created that has a table:

public class PermissionsPage  {

    @FindBy(id = "studyPermissionsTable")
    private WebElement permissionTable;

    @FindBy(id = "studyPermissionAddPermission")
    private WebElement addPermissionButton;

    ... 

}

So, what I'd like to do is have that permissionsTable to be a more customized WebElement that has some of those methods I mentioned earlier.

For example:

public class TableWebElement extends WebElement {
    WebElement table;
    // a WebDriver needs to come into play here too I think

    public List<Map<String, String>> getTableData() {
        // code to do this
    }

    public int getTableSize() {
        // code to do this
    }

    public WebElement getElementFromTable(String id) {
        // code to do this
    }
}

I hope that this makes sense what I'm trying to explain. I guess what I'm looking for is a way to have this custom WebElement to do some additional stuff that's table-specific. Add this custom element to a Page and take advantage of the way Selenium wires the webelements to the page based on the annotations.

Is it possible? And if so, does anyone know how this can be done?

10 Answers

I also created custom WebElement implementation by extending DefaultFieldDecorator and it works great with PageFactory pattern. However I have concerns about using PageFactory itself. It seems to work great only with 'static' applications (where user interaction does not change component layout of the app).

But whenever you need to work with something little more complex, like if you have a page with table, that contains list of Users and delete button next to each user, using PageFactory becomes problematic.

Here's an example to illustrate what I am talking about:

public class UserList
 {
private UserListPage page;

UserList(WebDriver driver)
{
    page = new UserListPage(driver);
}

public void deleteFirstTwoUsers()
{
    if (page.userList.size() <2) throw new RuntimeException("Terrible bug!");  

    page.deleteUserButtons.get(0).click();
    page.deleteUserButtons.get(0).click();
}

class UserListPage {

@FindBy(xpath = "//span[@class='All_Users']")
List<BaseElement> userList;

@FindBy(xpath = "//span[@class='All_Users_Delete_Buttons']")
List<BaseElement> deleteUserButtons;

UserListPage(WebDriver driver)
 {
    PageFactory.initElements(new ExtendedFieldDecorator(driver), this);
 }
}

So, in the above scenario, when one calls deleteFirstTwoUsers() method, it will fail when trying to delete second User with "StaleElementReferenceException: stale element reference: element is not attached to the page document". This happens because 'page' was instantiated only once in the Constructor and PageFactory has 'no way to know' :) that amount of Users has decreased.

The workarounds that I found so far, are ether:

1) Do not use Page Factory altogether for methods like this, and simply use WebDriver directly with some sort of while loop: driver.findElementsBy()....

2) or do something like this:

public void deleteFirstTwoUsers()
{
    if (page.userList.size() <2) throw new RuntimeException("Terrible bug!");  

    new UserListPage(driver).deleteUserButtons.get(0).click();
    new UserListPage(driver).deleteUserButtons.get(1).click();
}

3) or this:

public class UserList {
  private WebDriver driver;

UserList(WebDriver driver)
{
    this.driver = driver;
}

UserListPage getPage { return new UserListPage(driver);})
public void deleteFirstTwoUsers()
{
    if (getPage.userList.size() <2) throw new RuntimeException("Terrible bug!");  

    getPage.deleteUserButtons.get(0).click();
    getPage.deleteUserButtons.get(0).click();
}

But in the first examples, you can't benefit from using your wrapped BaseElement. And in second and third one - you end up creating new instances of Page Factory for every single action you do on the page. So I guess it all comes down to two questions:

1) Do you think it really worth to use PageFactory for anything, really? It would be much 'cheaper' to create each element on the fly like so:

class UserListPage
{
private WebDriver driver;

UserListPage(WebDriver driver)
{
    this.driver = driver;
}

List<BaseElement> getUserList() {
    return driver.findElements(By.xpath("//span[@class='All_Users']"));
}

2) Is it possible to @Override driver.findElements method so it'll return an instance of my wrapped BaseElement object and not WebElement? If not, are there any alternatives?

Related