Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return a lazily-instantiated dynamic webelement

I've been using @FindBy for a while now, and I love the fact that the element doesn't get located until its necessary (not on instantiation).

However, the webpage may have anywhere from 2-10 of a certain element, and the id's on the elements are numbered (so the first element has an id of "element1" and so forth)

I would like to write a function where I can pass in an integer, and it will return a WebElement with the appropriate ID, AND is lazily instantiated. That means having a function like the following won't work:

public WebElement getElement(int numOnPage){
    return driver.findElement(By.id("element"+numOnPage));
}

Because the instant I call that function the WebElement gets located. (The reason why it can't be instantiated is because I have a function that waits until it the element exists by calling isDisplayed() over and over on it, catching NoSuchElementExceptions).

I also realize that I could create a List<WebElement> that selects via CSS every element whose ID starts with "element" but I have had other cases where I've wanted to return a dynamically generated element, and had to use a workaround there as well.

Thanks!

like image 794
Nathan Merrill Avatar asked Nov 03 '22 18:11

Nathan Merrill


2 Answers

First, I don't really understand why you absolutely need to get a WebElement reference before the element is really in the page. In a normal case, you could check that the page is completely loaded and then find for a WebElement. First would be typically done with a loop and a catch for NoSuchElementException as you mentioned.

However, if you need a reference for a WebElement before it can't be found in the page, I would simply create a proxy which loads lazily (only when first time needed) the real WebElement instance. Something like this:

public WebElement getElement(final int numOnPage) {
        return (WebElement) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[] { WebElement.class }, new InvocationHandler() {
            // Lazy initialized instance of WebElement
            private WebElement webElement;

            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                if (webElement == null) {
                    webElement = driver.findElement(By.id("element" + numOnPage));
                }
                return method.invoke(webElement, args);
            }
        });
    }

By calling getElement, you retrieve an object of type WebElement. As soon as you call one of its method, it will be retrieved using WebDriver.findElement. Note that, if you call a method on the proxy instance, the element must be in the page otherwise you get of course a NoSuchElementException.

like image 62
LaurentG Avatar answered Nov 15 '22 04:11

LaurentG


If i'm understanding the question correctly, you can't do this with the @FindBy annotation. The problem is that Annotations in Java are processed during compile time and as a consequence you cannot modify them on the fly:

http://docs.oracle.com/javase/tutorial/java/annotations/

It does however sound like your problem could be easily fixed by using an explicit wait:

public WebElement getElement(int numOnPage){
    WebDriverWait waiting= new WebDriverWait(driver, 15, 100);
    return waiting.until(ExpectedConditions.visibilityOfElementLocated(By.id("element"+numOnPage)));
}

This would scan the page waiting for the element to exist and be visible and when it is return a WebElement to you.

like image 39
Ardesco Avatar answered Nov 15 '22 06:11

Ardesco