0

We want to send some keys to an element identified by name. In the application there might be multiple elements with the same name but in that case just one will be visible. To do that we have a code snippet like that (simplistic code, no production code):

List<WebElement> list = driver.findElements(By.xpath("//[@name='title']"));
for (WebElement elem : list) {
    try {
         elem.sendKeys(value);
         break;
    } catch (Exception e) {
         // ignore
    }
}

If the title element is not yet there we wait for it to appear using implicit wait. So usually this will work fine. Anyway we sometimes have the case that there are already elements with that name (but are hidden) and the correct one will just be created by async code. But in this case the code will not work. As the findElements() will return immediately (no implicit wait) just returning invisible elements. In that case sendKeys() will wait for the element to become visible but this never happens (ignoring new elements created after findElements) and so it fails after the implicit wait timeout.

Basically we need the possibility to tell findElements() that we just want to have visible elements. If there are no visible elements Selenium should wait for the implicit wait duration. Is this possible?

DebanjanB
  • 118,661
  • 30
  • 168
  • 217
Werzi2001
  • 1,745
  • 1
  • 13
  • 32
  • I would change the xpath to include `hidden` attribute as `//[@name='title'][@hidden='false']`. Provided there is hidden attribute assigned to the hidden element. – supputuri Jun 19 '19 at 19:48
  • There is no `hidden` attribute. – Werzi2001 Jun 19 '19 at 21:28
  • Can you please share the html of hidden and visible elements. – supputuri Jun 19 '19 at 21:38
  • In the worst case you can get the coordinates of the element and check if it's values are >0, to make sure you are working on the visible element. – supputuri Jun 19 '19 at 21:38
  • 1
    You will need to write a custom wait that waits for the count of found items that are visible to be > 0. I can't do that right now without an editor so I'll try to remember to take a look at this when I get home if you haven't already found the answer. – JeffC Jun 19 '19 at 21:48

2 Answers2

1

As your usecase involves:

  • there might be multiple elements with the same name but in that case just one will be visible
  • send some keys to an element identified by name
  • wait for it to appear
  • using implicit wait

A multipurpose solution to cater to all the above mentioned condition would be to use WebDriverWait in conjunction with ExpectedConditions set as elementToBeClickable().

  • elementToBeClickable(): An expectation for checking an element is visible and enabled such that you can click it.

  • Code Sample:

    try {
        new WebDriverWait(driver, 20).until(ExpectedConditions.elementToBeClickable(By.xpath("//button[@class='nsg-button']"))).sendKeys(value);
    }
    catch (TimeoutException e) {
        System.out.println("Desired element was not present");
    }
    

Additionally, you have to remove all the instances of ImplicitWait

Note: Do not mix implicit and explicit waits. Doing so can cause unpredictable wait times. For example setting an implicit wait of 10 seconds and an explicit wait of 15 seconds, could cause a timeout to occur after 20 seconds.

You can find a relevant discussion in Replace implicit wait with explicit wait (selenium webdriver & java)

DebanjanB
  • 118,661
  • 30
  • 168
  • 217
  • Not sure if I did it wrong but this also just seems to wait to wait for `elementToBeClickable` for the element/elements that already existed and matched `//button[@class='nsg-button']` at the time the `WebDriverWait` was created. I need it to find elements that are created after the `WebDriverWait` was created. – Werzi2001 Jun 19 '19 at 21:28
  • 1
    This doesn't answer the question. The question states that there are *more than one* element that matches the locator but only one of them is visible. None of your code or explanation addresses this main problem. – JeffC Jun 19 '19 at 21:34
  • 1
    this answer is part-way there I think... you'll also want expected condition for the list of elements you get... (and also, sigh... catching stale reference and re-running...) then use the above try/catch with the proper xpath selector. Something like this for getting the elements: "List list = wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(By.xpath("//[@name='title']")));" The reason you need to catch the stale reference is that "presenceOfAllElements..." only waits until the 1st item appears... if more come, stale ref is thrown. – pcalkins Jun 19 '19 at 21:57
  • @pcalkins To understand a usecase and answer accordingly it needs a lot of understanding of how _Selenium_ is designed and how it works. Each and every contributor can't understand each and every scenario and at times may react adversely. We need to allow them to co-exist ignoring them. Hope you understand. – DebanjanB Jun 19 '19 at 22:00
0

Based on the answer from DebanjanB and JeffC I was able to create my own wait implementation that waits for the first visible element but also takes into consideration elements that are created during the wait:

new WebDriverWait(driver, 5).until(drv -> {
    List<WebElement> elements = drv.findElements(By.xpath("//[@name='title']"));
    for (WebElement element : elements) {
        if (element.isDisplayed()) {
            return element;
        }
    }
    return null;
});

Or with streams ;-)

new WebDriverWait(driver, 5).until(drv -> {
    List<WebElement> elements = drv.findElements(By.xpath("//[@name='title']"));
    return elements.stream()
        .filter(WebElement::isDisplayed)
        .findFirst()
        .orElse(null);
});
Werzi2001
  • 1,745
  • 1
  • 13
  • 32