6

I'm trying to write a page object in Selenium Webdriver using the page factory @FindBy annotations. The page object is for a sidebar, and the parent WebElement containing all elements the page object needs to interact with is initialized in this way:

@FindBy (xpath = "//div[contains(@class,'yui3-accordion-panel-content') and child::div[.='Sidebar']]")
WebElement sidebar;

I then want the search input relative to this sidebar element. Is there a way to do this referencing sidebar element? I could copy and paste the entire path to the beginning:

@FindBy (xpath = "//div[contains(@class,'yui3-accordion-panel-content') and child::div[.='Sidebar']]//input[@id='search']")

But I'd much rather make it relative to the first element. Is anything like this possible?

@FindBy (parent = "sidebar", xpath = ".//input[@id='search']")

The Selenium documentation on the @FindBy annotation is a bit lacking...

kesslern
  • 190
  • 2
  • 8

2 Answers2

13

The answer is to implement an ElementLocatorFactory that allows you to provide a search context (meaning, a driver or a WebElement).

public class SearchContextElementLocatorFactory
        implements ElementLocatorFactory {

    private final SearchContext context;

    public SearchContextElementLocatorFactory(SearchContext context) {
        this.context = context;
    }

    @Override
    public ElementLocator createLocator(Field field) {
        return new DefaultElementLocator(context, field);
    }
}

Then, when instantiating your page object, use this locator factory.

WebElement parent = driver.findElement(By.xpath("//div[contains(@class,'yui3-accordion-panel-content') and child::div[.='Sidebar']]"));
SearchContextElementLocatorFactory elementLocatorFactory = new SearchContextElementLocatorFactory(parent);
PageFactory.initElements(elementLocatorFactory, this);

Now your @FindBy annotations will be relative to parent. For example, to get the main sidebar WebElement:

@FindBy(xpath = ".")
WebElement sideBar;
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
kesslern
  • 190
  • 2
  • 8
  • Thanks, very useful, but I wonder if there is a way to instantiate the page object without finding the parent element (just set the locator of the parent and instantiate when element are being looked up, which matches the pattern better, imo)? – Len Oct 07 '15 at 15:05
  • The answer looks like a one-time configuration for all FindBys you have on a page. Is there any possibility to configure it for any single FindBy (like in the question >>@FindBy (parent = "sidebar", xpath = ".//input[@id='search']")<<) – Robert DROP TABLE STUDENTS Sep 24 '19 at 12:52
0

No, it is not possible this way. But you can use getter methods instead of annotation-based initialization:

public class SomePage {
    @FindBy (xpath = "//div[contains(@class,'yui3-accordion-panel-content') and   child::div[.='Sidebar']]")
    private WebElement sidebar;

    private WebElement getSomeChildElement() {
        return siderbar.findElement(By.id("somechild"));
    }
}
Lucas Wa
  • 530
  • 1
  • 6
  • 15