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?