Search code examples
groovykatalon-studiozoho

How to handle a Zoho CRM autocomplete in Katalon Studio test cases


I'm at an impasse on this ever-present autocomplete widget in the Zoho app that I'm to test. It pops up on the CRM pages under test, and to handle it, I had the following util method:

public final class GeneralWebUIUtils {

   // ... other utils

   public static void HandleAutoComplete(TestObject textField, String input, TestObject loader, TestObject dropdownOption, boolean doScroll = false) {
        WebUI.click(textField)

        WebUI.setText(textField, input)

        TimeLoggerUtil.LogAction({
            WebUI.waitForElementVisible(loader, 3)

            // TODO: somehow this is not working for state; // it ends up selecting the first dropdown value
            return WebUI.waitForElementNotVisible(loader, 3)
        },
        "Loader",
        "appear and disappear");

        // TODO: do we really need if-condition here?
        if (doScroll) {
            this.ScrollDropdownOptionIntoView(dropdownOption)
        }

        WebUI.waitForElementVisible(dropdownOption, 3, FailureHandling.STOP_ON_FAILURE)

        WebUI.click(dropdownOption)
    }

   public static void ScrollDropdownOptionIntoView(TestObject to) {
        WebUI.executeJavaScript("arguments[0].scrollIntoView({block: 'center'})", [WebUiCommonHelper.findWebElement(to, 1)])
    }

    // ... other util methods
}

This seemed like the answer, until I have been dealing with, off and on for several month...

...it select "the wrong" dropdown option! For example, on a membership category autocomplete, we type in "Membership", and it would select [some other dropdown option with "Membership" in the name], before the first member category dropdown option became available.

Last night, I was up til 2am working on this and test cases that were using it. I ended up changing the code to:

public static void HandleAutoComplete(TestObject textField, String input, TestObject loader, TestObject dropdownOption, boolean doScroll = false) throws StepFailedException {
        WebUI.click(textField)

        WebUI.setText(textField, input)

        TimeLoggerUtil.LogAction({
            return WebUI.waitForElementNotVisible(loader, 3)
        },
        "Loader",
        "disappear");

        // TODO: do we really need if-condition here?
        if (doScroll) {
            this.ScrollDropdownOptionIntoView(dropdownOption)
        }

        TimeLoggerUtil.LogAction({
            WebUI.waitForElementVisible(dropdownOption, 3);
            return this.WaitForElementHasText(dropdownOption, input, 5, FailureHandling.STOP_ON_FAILURE);
        },
        "Dropdown option",
        "become visible")

        WebUI.verifyElementText(dropdownOption, input)

        WebUI.click(dropdownOption)
    }

   public static boolean WaitForElementCondition(Closure<Boolean> onCheckCondition, TestObject to, int timeOut, FailureHandling failureHandling = FailureHandling.STOP_ON_FAILURE) {
        final long startTime = System.currentTimeMillis()
        boolean isConditionSatisfied = false;
        while ((System.currentTimeMillis() < startTime + timeOut * 1000) && (!isConditionSatisfied)) {
            isConditionSatisfied = onCheckCondition(to);
        }
        if ((!isConditionSatisfied) && (failureHandling.equals(FailureHandling.STOP_ON_FAILURE))) {
            KeywordUtil.markFailedAndStop("Condition for TestObject '${to.getObjectId()}' not met after ${(System.currentTimeMillis() - startTime) / 1000} seconds");
        }
        return isConditionSatisfied;
    }

   public static boolean WaitForElementHasText(TestObject to, String expectedText, int timeOut, FailureHandling failureHandling = FailureHandling.STOP_ON_FAILURE) {
        return this.WaitForElementCondition({ TestObject testObj ->
            return WebUI.getText(testObj).contains(expectedText);
        },
        to,
        timeOut,
        failureHandling);
    }

This is way the hell faster, and instead of silently doing incorrect behavior (i.e. selecting the wrong dropdown option), it will throw exception. It should just work!!! (It seems like the actual dropdown option isn't ready to be checked by the time we go to check it...)

I tried remedying it by tweaking WaitForElementHasText():

public static boolean WaitForElementHasText(TestObject to, String expectedText, int timeOut, FailureHandling failureHandling = FailureHandling.STOP_ON_FAILURE) {
        return this.WaitForElementCondition({ TestObject testObj ->
            return WebUI.verifyElementPresent(testObj, 1) && WebUI.getText(testObj).contains(expectedText);
        },
        to,
        timeOut,
        failureHandling);
    }

No dice. It will still sometimes fail, and there doesn't seem to be a damn thing I can do about it....

....or is there?

How, outside of switching to modal view, which is more complicated to handle, can we handle the autocomplete once and for all?


Solution

  • No matter how hard I tried, I couldn't solve this the traditional way: using strategy patterns.

    Why? Because, when I went to log the initial states of dropdownOption and loader, they were the exact same, regardless of whether the autocomplete handler was going to end up succeeding, or failing.

    It looks like I have no control over the dropdownOption "flickers" even after we wait on it to exist!

    So, I had to get creative:

       public static boolean WaitForElementCondition(Closure<Boolean> onCheckCondition, Closure onContinue, TestObject to, int timeOut, FailureHandling failureHandling = FailureHandling.STOP_ON_FAILURE) {
            final long startTime = System.currentTimeMillis()
            boolean isConditionSatisfied = false;
            while ((System.currentTimeMillis() < startTime + timeOut * 1000) && (!isConditionSatisfied)) {
                isConditionSatisfied = onCheckCondition(to);
                onContinue(isConditionSatisfied, to);
            }
            if ((!isConditionSatisfied) && (failureHandling.equals(FailureHandling.STOP_ON_FAILURE))) {
                KeywordUtil.markFailedAndStop("Condition for TestObject '${to.getObjectId()}' not met after ${(System.currentTimeMillis() - startTime) / 1000} seconds");
            }
            return isConditionSatisfied;
        }
    
       public static boolean WaitForElementHasText(TestObject to, String expectedText, int timeOut, FailureHandling failureHandling = FailureHandling.STOP_ON_FAILURE) {
            return this.WaitForElementCondition({ TestObject testObj ->
                return WebUI.getText(testObj).contains(expectedText);
            },
            { boolean success, TestObject tObj ->
                if (!success) {
                    WebUI.waitForElementNotPresent(tObj, 1);
    
                    WebUI.waitForElementPresent(tObj, timeOut);
                }
            },
            to,
            timeOut,
            failureHandling);
        }
    

    re-ran the test several times, and all was well!