How to wait on javascript executor call in @Keyword method

Hello,
I am new to Katalon and I have a question on the best approach for a @Keyword.

Basically, in the @Keyword I need to wait using a timeout period until a javascript executor call returns a true value.

In my test case, I will call a @Keyword that will make a javascript executor call which does “some evaluation” on the webpage. When the javascript executor call is done processing on
the webpage, it returns true and then the @Keyword returns true to the test case. If the javascript executor call does not return true before the time out period expires, then the
@Keyword will return false to the test case.

So in @Keyword:

  • make the javascript executor call and wait for a specified timeout period
  • if the javascript executor call returns true before the time out expires, return true to the test case
  • else if the time out expires before the javascript executor call returns a value, then return false to the test case

Thank you for any guidance on a best approach!

I can help you with this - I do this a lot. Unfortunately, I need to head out… if you can give me 10-12 hours, I’ll look in again.

Hello Russ_Thomas,
Any information you could provide would be greatly appreciated.

Thanks!

Does anyone have information on how to do this???

Sorry @jmartinelli, I completely forgot about this.

Before we go ahead, I have one very key question:

Is the JavaScript code running synchronously or asynchronously?

Hello! No problem, we all get busy.

It would be synchronous .

They both can be mastered, but synchronous is far easier. In fact, you don’t need any “special code”, you can just call the JavaScript function and read the response.

String js = 'return yourJavaScriptFunction();'
boolean result = (boolean) WebUI.executeJavaScript(js, null);
println result // true or false

I have a feeling this isn’t what you wanted. I have a strong feeling your JavaScript code is in fact asynchronous – at least, going by the way you worded your original post.

Let me know.

I have ever studied JavascriptExecutor.executeAsyncScript by reading the following article:

https://www.tutorialspoint.com/understanding-execute-async-script-in-selenium

I could modify its sample Java code into a Katalon Studio Test Case like this:

TC1:

import org.openqa.selenium.JavascriptExecutor
import org.openqa.selenium.WebDriver

import com.kms.katalon.core.webui.driver.DriverFactory
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI

WebUI.openBrowser('')
WebUI.navigateToUrl('http://demoaut-mimic.kazurayam.com/')

// get current system time
long s = System.currentTimeMillis()

WebDriver driver = DriverFactory.getWebDriver()
JavascriptExecutor j = (JavascriptExecutor)driver
j.executeAsyncScript("window.setTimeout(arguments[arguments.length - 1], 800);")

System.out.println("Time elapsed is:" + (System.currentTimeMillis()- s));

WebUI.closeBrowser()

When I ran this, I got the following output in the console.

2021-02-19 15:31:10.708 INFO  c.k.katalon.core.main.TestCaseExecutor   - --------------------
2021-02-19 15:31:10.713 INFO  c.k.katalon.core.main.TestCaseExecutor   - START Test Cases/TC1
2021-02-19 15:31:13.059 DEBUG testcase.TC1                             - 1: openBrowser("")
2021-02-19 15:31:13.633 INFO  c.k.k.core.webui.driver.DriverFactory    - Starting 'Chrome' driver
2 19, 2021 3:31:13 午後 org.openqa.selenium.remote.DesiredCapabilities chrome
情報: Using `new ChromeOptions()` is preferred to `DesiredCapabilities.chrome()`
2021-02-19 15:31:13.757 INFO  c.k.k.core.webui.driver.DriverFactory    - Action delay is set to 0 milliseconds
Starting ChromeDriver 88.0.4324.96 (68dba2d8a0b149a1d3afac56fa74648032bcf46b-refs/branch-heads/4324@{#1784}) on port 7362
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
2 19, 2021 3:31:17 午後 org.openqa.selenium.remote.ProtocolHandshake createSession
情報: Detected dialect: W3C
2021-02-19 15:31:18.087 INFO  c.k.k.core.webui.driver.DriverFactory    - sessionId = 39498e6e738c7d69c349bf448745b833
2021-02-19 15:31:18.129 INFO  c.k.k.core.webui.driver.DriverFactory    - browser = Chrome 88.0.4324.182
2021-02-19 15:31:18.130 INFO  c.k.k.core.webui.driver.DriverFactory    - platform = Mac OS X
2021-02-19 15:31:18.132 INFO  c.k.k.core.webui.driver.DriverFactory    - seleniumVersion = 3.141.59
2021-02-19 15:31:18.135 INFO  c.k.k.core.webui.driver.DriverFactory    - proxyInformation = ProxyInformation { proxyOption=NO_PROXY, proxyServerType=HTTP, username=, password=********, proxyServerAddress=, proxyServerPort=0, executionList="", isApplyToDesiredCapabilities=true }
[FINE] No subscribers registered for event class com.kms.katalon.core.event.TestingEvent
[FINE] No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
2021-02-19 15:31:18.234 DEBUG testcase.TC1                             - 2: navigateToUrl("http://demoaut-mimic.kazurayam.com/")
2021-02-19 15:31:22.001 DEBUG testcase.TC1                             - 3: s = System.currentTimeMillis()
2021-02-19 15:31:22.003 DEBUG testcase.TC1                             - 4: driver = getWebDriver()
2021-02-19 15:31:22.026 DEBUG testcase.TC1                             - 5: j = driver
2021-02-19 15:31:22.032 DEBUG testcase.TC1                             - 6: j.executeAsyncScript("window.setTimeout(arguments[arguments.length - 1], 800);")
2021-02-19 15:31:22.910 DEBUG testcase.TC1                             - 7: out.println("Time elapsed is:" + System.currentTimeMillis() - s)
Time elapsed is:908
2021-02-19 15:31:22.948 DEBUG testcase.TC1                             - 8: closeBrowser()
2021-02-19 15:31:23.175 INFO  c.k.katalon.core.main.TestCaseExecutor   - END Test Cases/TC1

What it does?

  1. test case TC1 passes a javascript fragment window.setTimeout(arguments[arguments.length - 1], 800);
    which would run on browser in asynchronous manner.
  2. test case TC1 itself runs synchronously. It blocks at the call j.executeAsyncScript until the javascript on browser finishes and returns.

I found this TC1 is useless. It does what WebUI.delay(n) does, and no more.

A Quote from https://ui.vision/rpa/docs/selenium-ide/executeasyncscript

What is executeAsyncScript good for?
Honestly, we don’t know. While asynchronous code in Javascript in general has plenty good uses, we have yet to find any good use case within any Selenium IDE.

@jmartinelli

Do you find executeAsyncScript useful ?

@Russ_Thomas

Please find the following line in the sample code above. I copied this from another source. I have a question about this.

I know what “Array-like” object arguments in JavaScript is. I know that the string as the 1st argument to JavascriptExecutor.executeScript(String script, Object... args) method will be executed as a body of anonymous JavaScript function.

In the above sample code there is no additional arguments. Therefore arguments.length - 1 will be equal to -1. In other words, I will effectively have

j.executeAsyncScript("window.setTimeout(arguments[-1], 800);")

arguments[-1] — what is this? I do not understand it. Could you give me a light?

I have no idea what’s going on there. It looks to me that the example is a poor one, or worse, broken. The wording on that site is lengthy, too, putting me into a coma trying to unravel what they’re talking about. Life is too short :confused:

But really, they’re making a moderately tricky task much more complicated than it needs to be. When I wrote my async handler (Groovy -> JS), I did something quite similar to this answer on stack overflow https://stackoverflow.com/a/33859447 but even that seems a bit overkill to me. If I needed it (which I don’t) then perhaps I would try to use it and make it a bit simpler.

In essence, all you need is WebUI.executeJavaScript and a sentinel (in the window of the AUT) which you can check from Groovy to know when the async operation is complete. When the operation is complete you can carry out the next step in your test which should reflect the new state of the AUT after the async code has completed.

Truly, that last paragraph - i.e. those last two sentences - explain all you need to know.

As with any tricky code, the subtleties can be hard to convey without posting the entire code section for evaluation/analysis. I was expecting that @jmartinelli needed the async code and I would have to extract it for 3rd party consumption AND document it. I was kind of glad I didn’t need to based on his insistence his requirement is synchronous. :upside_down_face:

@Russ_Thomas

Yes, I thought so, but was not sure. Thank you very much for explaining it clearly.

Sorry for the delayed response, I have been out of the office for a few days.

I checked with the developer that wrote the javascript function and his reply was “The function should be synchronous, but what it checks for are asynchronous AJAX calls.” I guess he means that when the test case calls the javascript function, it would be considered synchronous since we are waiting for it to return ‘true’ once it is done processing any requests on it’s end.
So in the custom Keyword I need to time out the javascript call in case it never comes back with a response. I guess it’s basically setting a timer when I make the javascript call and then if I do not get a response back within that time frame then the test case fails.

Like I suspected, that is ASYNCHRONOUS.

Like I said above, you need to set up a sentinel in the JavaScript code in the AUT. Your Groovy code in Katalon will need to wait for that sentinel to signal the AUT async process is complete.

Please pay attention to this next bit of info - it’s important.

It is far, far easier and more robust to do this without a sentinel approach. It is much better to wait for the state of the AUT to change in some way. That change would signal when the async stuff is completed. Perhaps, for example, a specific text box is filled with certain data. Or perhaps a grid/table appears.

Only if nothing like that happens would you need a sentinel approach.

The best (easiest) way to create the sentinel

Have the developers do it. Tell them to add these functions to the AUT page:

function setSentinel(name, value) {
  window[name] = value;
}
function getSentinel(name) {
 return window[name];
}

If they are unwilling, you can inject them yourself along with each of the following.

Tell them to write something like this before the ajax calls begin:

window.ajax_completed = false;

Again, if they are unwilling, inject it yourself, before the async process begins.

Next you need this to happen at the point where ALL ajax calls have resolved:

window.ajax_completed = true;

Your Groovy code needs to loop while getSentinel("ajax_completed") returns false.

The moment the sentinel returns true…

  1. Break out of the while loop.
  2. (Optional) call setSentinel("ajax_completed", false) so that it’s ready for a possible subsequent call.
  3. Continue with you test case.

Make your Groovy while loop delay for one second – WebUI.delay(1) – between iterations. You should throw an error when 20 or 30 iterations go by without the sentinel returning true. That’s a long time, I think, but only you know the behavior of your AUT.