Katalon and Javascript Promises Revisited

@Russ_Thomas ; @mwarren04011990 :

I know how to do the following in Selenium; however one of our projects that uses Katalon would like to leverage the below functionality as well and I can’t get it to work so asking for what I’m missing here.

A year ago I worked with the wonderful folks on this forum to capture a number of web metrics using the browser console in thread How does one get Katalon to handle asynchronous executeJavaScript calls?.

Now we’re capturing the largest CLS value following a 10 second timeframe, so I went about leveraging the below code utilizing a promise with a built in 10 second timer followed by a wait statement to force Katlon to be patient for there to be enough time to return the indicated value and then a call to retrieve the stored value in Katalon and report my findings; however it’s only returning 0.0 and when I run just the JavaScript (without the slashes for Groovy, any way around those?) I get a larger return result in decimal format (ex: 0.0005).

I can’t tell if Katalon just isn’t getting a result or if my variable type is incorrect; before this call the page has already been open for several seconds performing other checks so there should be a decent sized CLS entry array by this point:

//Declare initial Katalon and JS Variables
double chromeCLS
WebUI.executeJavaScript("largestCLS = 0;", null)

//Execute Javscript to return the Chrome calculated CLS value
jsCommandString = "  //create function for retrieving the largest CLS value\
					function getLargestCLS() {\
					  return new Promise((resolve) => {\
					    let largestCLS = 0;\
					    const observer = new PerformanceObserver((entryList) => {\
					      const entries = entryList.getEntries();\
					      entries.forEach(entry => {\
					        if (entry.entryType === 'layout-shift' && !entry.hadRecentInput && entry.value > largestCLS) {\
					          largestCLS = entry.value;\
					        }\
					      });\
					    });\
					\
					    // Start observing layout-shifts\
					    observer.observe({ type: 'layout-shift', buffered: true });\
					\
					    // Stop the observer after 10 seconds\
					    setTimeout(() => {\
					      observer.disconnect();\
					      resolve(largestCLS); // Resolve the promise with the largest CLS\
					    }, 10000);\
					  });\
					}\
					\
					// Call above function\
					getLargestCLS().then(largestCLS => {\
					  return(largestCLS);\
					});" 

WebUI.executeJavaScript(jsCommandString, null)

//Allow time for largest CLS to be recorded
WebUI.delay(12)
chromeCLS = WebUI.executeJavaScript("return largestCLS", null)

One other way I could is to perform the find largest loop on the Katalon side, but I’m not 100% sure that would work the way I’m intending either.

Thoughts?

1 Like

Hi @kevin.jackey,

Just do some quick research.

You’re encountering an issue because of how Katalon handles asynchronous JavaScript execution. The problem is that your getLargestCLS function returns a Promise, but Katalon’s executeJavaScript method does not inherently support handling JavaScript Promises directly.

Why is Katalon Returning 0.0?

  1. JavaScript Executes Asynchronously

    • The getLargestCLS function runs asynchronously and returns a Promise.
    • Katalon executes WebUI.executeJavaScript(jsCommandString, null), but since it doesn’t wait for the Promise to resolve, largestCLS is not set before the next command executes.
  2. Katalon Executes return largestCLS Too Early

    • Since largestCLS is not updated immediately (due to the 10-second delay in JS), WebUI.executeJavaScript("return largestCLS", null) retrieves 0.0, which was set initially.

Workaround
Instead of using a Promise that Katalon can’t handle, I suggest that you should wait for the Promise to resolve inside JavaScript and then return the value synchronously.

Updated JavaScript Code

Modify your script to use an async function that waits for the largest CLS value and then immediately returns it.

// Declare initial Katalon and JS Variables
double chromeCLS
WebUI.executeJavaScript("window.largestCLS = 0;", null)

// Modified JavaScript Execution
jsCommandString = """
    (async function() {
        let largestCLS = 0;
        const observer = new PerformanceObserver((entryList) => {
            entryList.getEntries().forEach(entry => {
                if (entry.entryType === 'layout-shift' && !entry.hadRecentInput && entry.value > largestCLS) {
                    largestCLS = entry.value;
                }
            });
        });

        observer.observe({ type: 'layout-shift', buffered: true });

        // Wait for 10 seconds before resolving
        await new Promise(resolve => setTimeout(resolve, 10000));

        observer.disconnect();
        window.largestCLS = largestCLS; // Store value in global scope
    })();
"""

WebUI.executeJavaScript(jsCommandString, null)

// Allow time for largest CLS to be recorded
WebUI.delay(12)

// Retrieve the recorded CLS value
chromeCLS = WebUI.executeJavaScript("return window.largestCLS", null)

println("Largest CLS Value: " + chromeCLS)

Key Fixes

  1. Stores CLS Value in window.largestCLS

    • JavaScript now assigns window.largestCLS, which persists in the page context.
  2. Uses async function

    • await new Promise(resolve => setTimeout(resolve, 10000)); makes JavaScript pause execution for 10 seconds before assigning the value.
  3. Ensures JavaScript Finishes Before Retrieval

    • WebUI.delay(12) ensures the script has enough time to complete before Katalon retrieves the CLS value.

Hope this can help

That worked perfectly! Thank you so much!

1 Like