FluentWait not checking at intervals correctly unless ExpectedConditions is used


#1

I am trying to wait on a set of elements using FluentWait, however it’s not correctly waiting/polling for the specified element if I were to use Function<AppiumDriver, List<MobileElement>
I am able to correctly wait for the elements when using ExpectedConditions with FluentWait.
Is there something that I might be overlooking?

FluentWait Code
    public static Wait<AppiumDriver<MobileElement>> fluentWait(
            int timeoutTime,
            int pollTime,
            Class<? extends Throwable>... ignoredExceptions)
    {
        return new FluentWait<>(DriverSingleton.getDriver())
                .withTimeout(Duration.ofSeconds(timeoutTime))
                .pollingEvery(Duration.ofSeconds(pollTime))
                .ignoreAll(Arrays.asList(ignoredExceptions));
    }

    public static Function<AppiumDriver<MobileElement>, List<MobileElement>> fluentWaitFunctionMany(By by)
    {
        return new AppiumFunction<AppiumDriver<MobileElement>, List<MobileElement>>()
        {
            @NullableDecl
            @Override
            public List<MobileElement> apply(@NullableDecl AppiumDriver<MobileElement> driver)
            {
                if (driver == null)
                {
                    throw new IllegalStateException("Driver parameter is null");
                }
                return driver.findElements(by);
            }
        };
    }

    public static List<MobileElement> fluentlyWaitForMany(
            By by,
            int timeoutTime,
            int pollTime,
            Class<? extends Throwable>... ignoredExceptions)
    {
        logger.info("Fluently Waiting for many {} | Timeout: {} | Polling: {}", by.toString(), timeoutTime, pollTime);
        return fluentWait(timeoutTime, pollTime, ignoredExceptions).until(fluentWaitFunctionMany(by));
    }

    public static List<MobileElement> fluentlyWaitForManyWithDefaultExceptions(By by, int timeoutTime, int pollTime)
    {
        return fluentlyWaitForMany(by, timeoutTime, pollTime, NoSuchElementException.class, StaleElementReferenceException.class);
    }

The element that I’m waiting on:

private static final By contentItemsBy = By.xpath("//*[@class='android.widget.LinearLayout and count(android.widget.FrameLayout)=2]");

FluentWait with Function<AppiumDriver, List<MobileElement>:

public void deleteAllContentItems()
{
            logger.info("=====Started Deleting Library Content====");
            List<MobileElement> contentItems = WaitUtil.fluentlyWaitForManyWithDefaultExceptions(contentItemsBy, 10, 1);
            logger.info("Initial Content Item Size: {}", contentItems.size());

            while (contentItems.isEmpty())
            {
                contentItems = WaitUtil.fluentlyWaitForManyWithDefaultExceptions(contentItemsBy, 10, 1);
                logger.info("Testing: {}", contentItems.size());
            }
            logger.info("=====Finished Deleting Library Content====");
}

Results:

| 2019-05-28 | 15:13:51:111 | [main] INFO PageObject - =====Started Deleting Library Content====
| 2019-05-28 | 15:13:51:111 | [main] INFO WaitUtil - Fluently Waiting for many By.xpath: //*[@class='android.widget.LinearLayout' and count(android.widget.FrameLayout)=2] | Timeout: 10 | Polling: 1
| 2019-05-28 | 15:13:51:561 | [main] INFO PageObject - Initial Content Item Size: 0
| 2019-05-28 | 15:13:51:562 | [main] INFO WaitUtil - Fluently Waiting for many By.xpath: //*[@class='android.widget.LinearLayout' and count(android.widget.FrameLayout)=2] | Timeout: 10 | Polling: 1
| 2019-05-28 | 15:13:52:411 | [main] INFO PageObject - Testing: 2
| 2019-05-28 | 15:13:52:411 | [main] INFO PageObject - =====Finished Deleting Library Content====

As you can see for the results above, it was supposed to wait for 10 seconds, but it does not seem to be doing so and has to go through the second while loop.

FluentWait with ExpectedConditions:

 public void deleteAllContentItems()
 {
            logger.info("=====Started Deleting Library Content====");
            List<WebElement> contentItems = WaitUtil.fluentWait(10, 1, NoSuchElementException.class)
                    .until(ExpectedConditions.presenceOfAllElementsLocatedBy(contentItemsBy));
            logger.info("Initial Content Item Size: {}", contentItems.size());

            while (contentItems.isEmpty())
            {
                contentItems = WaitUtil.fluentWait(10, 1, NoSuchElementException.class)
                        .until(ExpectedConditions.presenceOfAllElementsLocatedBy(contentItemsBy));
                logger.info("Testing: {}", contentItems.size());
            }
            logger.info("=====Finished Deleting Library Content====");
 }

Results:

| 2019-05-28 | 14:44:33:472 | [main] INFO PageObject - =====Started Deleting Library Content====
| 2019-05-28 | 14:44:34:918 | [main] INFO PageObject - Initial Content Item Size: 1
| 2019-05-28 | 14:44:34:918 | [main] INFO PageObject - =====Finished Deleting Library Content====

The ExpectedConditions is doing its job properly by waiting and does not need to go through the second while loop.

Extra Details:
I’ve also added an implicit wait to each PageObject class, if that is somehow conflicting with FluentWait.

    public BasePage(AppiumDriver<MobileElement> driver, Class c) {
        this.driver = (AndroidDriver) driver;
        logger = LoggerFactory.getLogger(c);
        PageFactory.initElements(new AppiumFieldDecorator(this.driver, Duration.ofSeconds(10)), this);
    }

Environment:
Appium Java Client - 7.0.0
Appium Server Desktop Client - Version 1.13.0 (1.13.0.20190502.1)
Mobile Platform: Samsung Galaxy Tablet S4


#2

I realized that the Functions need to return a Falsey value and since driver.findElements returns an empty List when it can’t find any elements, it will always return as a True statement thus making the FluentWait believe that the elements have been found. So I needed to return null instead if the list is empty.