Implement WebUI.waitForElementText for non static Web Services


#1

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());  
}  

}


#2

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.


#3

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.