Implement WebUI.waitForElementText for non static Web Services

We have a Vaadin based web service and HTML pages are non static.

E.g. changing some selection will cause a replacement of DOM instead of a “page reload”.

There are HTML tags like this where the element attributes stay the same, only the text part will be changed:

Before clicking on web page:

<div class="v-label v-widget register-contentbox-heading v-label-register-contentbox-heading v-has-width" id="lbl_contentbox_heading" style="width: 361px;">This is the FIRST text</div>

After changing some selection on page the text will be relaced by server:

<div class="v-label v-widget register-contentbox-heading v-label-register-contentbox-heading v-has-width" id="lbl_contentbox_heading" style="width: 361px;">This is the SECOND text I want to wait for</div>

Those available Keywords do not work:

  1. WebUI.waitForPageLoad(5) – is not usable, as the browser does not initially load the page
  2. WebUI.waitForElementVisible(findTestObject(‘lbl_element_om_page’), 5) – this one waits until the element is visible. But the element was already visible before, too
  3. WebUI.verifyElementText(findTestObject(‘lbl_element_om_page’), ‘This is the SECOND text I want to wait for’) – The element is already present, but still with old text value ‘This is the FIRST text’
  4. WebUI.waitForElementAttributeValue() – This is not usable, because the Element does not have a ‘text’ attribute

That’s why I would like to request a new build-in “WebUI.waitForElementText()” with timeout and selection of failure handling.

Currently I have created my own CustomKeyword to search in complete HTML body for a given text fragment with a timeout condition:

CustomKeywords.'guiResources.guiSupportFunctions.waitForTextDisplayed'('This is the SECOND text I want to wait for' , 5, FailureHandling.CONTINUE_ON_FAILURE)

The corresponding CustomKeyword code looks like this in my file guiSupportFunctions.groovy

package guiResources

import org.openqa.selenium.WebDriver
import com.kms.katalon.core.webui.driver.DriverFactory
import org.openqa.selenium.By

class guiSupportFunctions {

private static final KeywordLogger LOG = new KeywordLogger();  

/**  
 \* Wait until the given text is visible on page  
 *  
 \* @param input  
 *            Der String, auf dessen Anzeige gewartet wird  
 \* @param timeout  
 *            timeout value, wie lange gewartet wird  
 \* @param failureHandling  
 *            failureHandling to control the test step result  
 \* @return true, if "input" is found within the given timeout value,  
 *            else return false  
 */  

@Keyword  
public static boolean waitForTextDisplayed(String input) {  
    return waitForTextDisplayed(input, 5, FailureHandling.STOP\_ON\_FAILURE)  
}  

@Keyword  
public static boolean waitForTextDisplayed(String input, Integer timeout) {  
    return waitForTextDisplayed(input, timeout, FailureHandling.STOP\_ON\_FAILURE)  
}  

@Keyword  
public static boolean waitForTextDisplayed(String input, Integer timeout, FailureHandling failureHandling) {  
    WebDriver driver = DriverFactory.getWebDriver()  
    long startWaitTime = java.util.Calendar.getInstance().getTimeInMillis();  
    long elapsedTime = 0;  
    long timeoutms = timeout * 1000;  

    LOG.logInfo("waitForTextDisplayed('" + input + "') - wait for max " + (timeoutms / 1000) + " secs");  

    while (!isTextPresentOnPage(driver, input) && (elapsedTime < timeoutms)) {  
        try {  
            Thread.sleep(100);  
            elapsedTime = java.util.Calendar.getInstance().getTimeInMillis() - startWaitTime;  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        (!isTextPresentOnPage(driver, input) && (elapsedTime < timeoutms))  
    }  
    elapsedTime = java.util.Calendar.getInstance().getTimeInMillis() - startWaitTime;  
    LOG.logInfo("elapsed time: " + (elapsedTime / 1000) + "s");  
    boolean found = isTextPresentOnPage(driver, input);  

    // For negativ tests it possible \*not\* to find the given text  
    if (!found) {  
        LOG.logInfo("Did wait for " + (timeoutms / 1000) + " seconds for text '" + input  
                \+ "' to be displayed - without success. Giving up.");  
        if ( failureHandling == FailureHandling.STOP\_ON\_FAILURE) {  
            throw new StepFailedException("Did wait for " + (timeoutms / 1000) + " seconds for text '" + input  
            \+ "' to be displayed - without success. Giving up.");  
        } else {  
            KeywordUtil.markFailed("Did wait for " + (timeoutms / 1000) + " seconds for text '" + input  
                    \+ "' to be displayed - without success. Giving up.")  
        }  
        return false;  
    }  
    LOG.logInfo("Text is displayed. Continue with test.");  
    return true;  
}  

/**  
 \* Check if given text is present directly in html-body  
 *  
 \* @param text  
 *            The text to be find  
 \* @return true, if "text" is visible/found,  
 *            else false  
 */  
private static boolean isTextPresentOnPage(WebDriver driver, String text) {  
    String bodyText = "";  
    try {  
        bodyText = driver.findElement(By.tagName("body")).getText();  
    } catch (Exception e) {  
        LOG.logInfo("isTextPresentOnPage(): Giving up. Could not read body text because of Exception: " + e.getLocalizedMessage());  
        KeywordUtil.markFailed("isTextPresentOnPage(): Giving up. Could not read body text because of Exception: " + e.getLocalizedMessage());  
        return false;  
    }  
    return bodyText.contains(text.trim());  
}  

}

Hi Patrik

FWIW, here is my jQuery/JavaScript version:

  /**
   * Makes multiple attempts to verify the element identified by <code>selector</code> has
   * innerText <code>expected</code>.
   * @param selector (String) CSS selector to be checked.
   * @param expected (String) The expected text.
   * @param isRegex (boolean) Optional. Whether <code>expected</code> is to be treated as a regex.
   * @param timeoutSeconds (int) Optional. How long to wait in seconds (default 60).
   */
  static void jQ_waitText(String selector, String expected, boolean isRegex = false, int timeoutSeconds = 60) {
    comment('jQ_waitText checking innerText on ' + selector)
    boolean match = false
    def result
    String js = '''
      var re, selector = arguments[0],
          expected = arguments[1],
          text = $(selector).text(),
          isRegex = arguments[2];
      if(isRegex) {
        re = new RegExp(expected);
        return {
          match: re.test(text),
          actual: text
        }
      }
      return {
        match: text.toString() === expected.toString(),
        actual: text.toString()
      }
    '''
    int count = 0;
    while (!match) {
      count++
      WebUI.comment('jQ_waitText checking innerText on ' + selector + ' ' + count)
      if(count > timeoutSeconds) {
        markFailed('jQ_waitText "' + selector + '" is "' + result["actual"] + '" which does not match "' + expected + '" (timeout).')
        return
      }

      try {
        result = jsexec(js, Arrays.asList(selector, expected, isRegex))
        match = result["match"]
      } catch (Exception e) {
        match = false
      }

      if(match) {
        break
      }
      WebUI.delay(1)
    }
    markPassed('Success: ' + selector + ' text is ' + expected + '!')
  }

Where:
* jsexec is a wrapper over WebUI.executeJavaScript
* markPassed and markFailed are wrappers over KeywordUtil.markFailed and KeywordUtil.markFailed.

1 Like

Bump!

After developing Katalon tests for more than six weeks, this has to be my favorite suggestion. Why is there no waitForElementText keyword? We have many dynamic pages where the text of an element changes based on the user’s gestures. Sometimes there is an AJAX call to the server that causes it (or multiple calls), and sometimes it is a simple DOM update by the JavaScript. In either case, how does the tester know what to “wait” for?

I will try the @Russ_Thomas suggestion custom keyword next.

After doing dozens and dozens of tests for several months, it just occurred to me an easy way to accomplish “waitForElementText” without making a custom keyword.

Suppose you define a Test Object detected by
xpath=//label[starts-with(text(),'Whatever')]

Then you can use WebUI.waitForElementVisible with that Test Object! It will wait until the element with that text becomes visible! (Not sure why this did not occur to me sooner.)

Thank you, with some more modifications it might be another way to find any text on any pages.

But I still want to have a build in WebUI one line operation in Katalon to do this task. :thinking:

//label[starts-with… will search for labels only, since not all text parts are linked to a label and to really find ANY text block, I need to change the xpath to: //*[starts-with

With your approach it looks like this now:

  1. creating a common dummy Test Object, just to have a TO working on it during test execution.
    This dummy TO will be placed directly under Object Repository, I call it “textDisplayed”
    The Object Property will contain only one entry with
    Name = xpath
    Condition = contains
    Value = //*[starts-with(text(), ‘this is a dummy text, will be replaces in script during execution’)]
    Detect object by? = true

  2. Since the text value is hardcoded at this point :disappointed_relieved:, I found a solution by modifing the Object Property in that Thread Using variables in Test Objects - #18 by Jaikumar_Kesav
    :grinning:

In Test Case code, the usage will be:
( “CustomKeywords.‘getTextByResouceBundleKey’(‘TextKeyEntry_ABC’)” is another Custom Keyword to fetch the page content delivered by a resource bundle)


// First get the dummy Test Object once to find any text in given page:
checkTextElement = findTestObject(‘textDisplayed’)

// Change test object’s property and set its value to the target text to be found at this point of test
checkTextElement = WebUI.modifyObjectProperty(checkTextElement, ‘xpath’, ‘contains’, ‘//*[starts-with(text(), '’ + CustomKeywords.‘getTextByResouceBundleKey’(‘TextKeyEntry_ABC’) + ‘')]’ ,true)

// Now we can waitForElementVisible() for our target text
WebUI.waitForElementVisible(checkTextElement, GlobalVariable.Timeout_Short, FailureHandling.STOP_ON_FAILURE)