Javascript – Java – Use Selenium to wait for JavaScript events such as onchange to complete

Java – Use Selenium to wait for JavaScript events such as onchange to complete… here is a solution to the problem.

Java – Use Selenium to wait for JavaScript events such as onchange to complete

A set of logically related input fields has an onchange event. After traversing the fields and modifying their values, some fields are updated correctly and others are not updated correctly because of what the onchange event does.

The moment the onchange event fires on a field, it starts some processing (involving other related fields), stores the values somewhere and clears other related fields (if they haven’t been handled by itself before) with the onchange event.

I can let threads hibernate any time, but that doesn’t look good. It simply guesses how much time processing will take and then chooses whether to waste time in plenty of sleep or have a script that can abort the timeout.

Is there a way to know when the JavaScript code (invoked by the onchange event) has done its job?

Original code

Wait<WebDriver> wait = new WebDriverWait(driver, 25, 500);
for(int i = 1; i <= fieldCount; i++) {
    elementId = "field$" + i;
    wait.until(ExpectedConditions.elementToBeClickable(By.id(elementId)));
    driver.findElementById(elementId).sendKeys(data);
    The mess happens if I don't sleep
    Thread.sleep(3000);
}

Output

Sleep time: Field1: _w_Field2:_x_Field3 :_y_FieldN:_z_

No sleep :Field1:_w_Field2:___Field3 :_y_FieldN:___

Notes:

I

ran into some issues, so I think it’s worth mentioning the lessons learned in the short note:

WARNING: Do not mix implicit and explicit waits.

Use WebDriverWait (specialization of FluentWait) instead of FluentWait, unless you have a very specific requirement. E.g., WebDriverWait ignores NotFoundException (NoSuchElementException‘s superclass) by default. See recomendation.

Solution

After a serious refactoring and some research, I finally did it. The onchange event fires when the value of the input field changes and the element loses focus. Manipulating fields using WebElements (e.g. sendKeys()) is not an option because you have no control over background processing, so using JavascriptExecutor is a choice. First, I used JavaScript (which doesn’t fire an event) to update the value of the field, and then I fired the onchange event, also using JavaScript:

//Setting implicit wait to 0 to avoid mess with explicit waits
driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS);
Use WebDriverWait instead of FluentWait (it's superclass)
Wait<WebDriver> wait = new WebDriverWait(driver, 25, 500);
for(int i = 1; i <= fieldCount; i++) {
    String elementId = "field$" + i;
    String javaScript = String.format("document.getElementById('%s').value='%s';" , elementId , myValue);
    Object jsResult = wait.until(ExpectedConditions.javaScriptThrowsNoExceptions(javaScript));
    javaScript = String.format("return document.getElementById('%s').dispatchEvent(new Event('change'));" , elementId);
    jsResult = wait.until(ExpectedConditions.jsReturnsValue(javaScript));
}

Here are some key aspects.

  • Don’t mix implicit and explicit waiting (which I’ve learned from the hard lesson), which can lead to unexpected results.
  • Use WebDriverWait (a specialization of FluentWait) instead of its parent class (super class), unless you have very specific requirements. If you use FluentWait, make sure to ignore the appropriate exceptions; Otherwise, you will start receiving NoSuchElementException.
  • The onchange event fires when the value of the input field changes and the element loses focus.
  • dispatchEvent() in the specified EventTarget ( synchronously ) dispatch an event, Call the affected EventListeners in the appropriate order. This applies to custom events as well. This is very nice post for activity.

I use the following code to get a better grasp of ExpectedConditions.javaScriptThrowsNoExceptionsand ExpectedConditions.jsReturnsValue This is a JavaScript function call that only keeps the engine busy for a few seconds. This allows you to see the explicit wait for interaction with JavaScript and examine the return value. Note that the JS code for each ExpectedCondition is slightly different:

//ExpectedConditions.jsReturnsValue
String javaScript = "(function watcher(ms){var start=new Date().getTime(); var end = start; while(end<start+ms){end=new Date().getTime(); }; return 'complete'; })(5000); return 'success'; ";
log.trace("javaScript={}", javaScript);
Object jsResult = wait.until(ExpectedConditions.jsReturnsValue(javaScript));
log.trace("jsResult={}", jsResult);

ExpectedConditions.javaScriptThrowsNoExceptions
javaScript = "(function watcher(ms){var start=new Date().getTime(); var end = start; while(end<start+ms){end=new Date().getTime(); }; return 'complete'; })(5000); ";
log.trace("javaScript={}", javaScript);
jsResult = wait.until(ExpectedConditions.javaScriptThrowsNoExceptions(javaScript));
log.trace("jsResult={}", jsResult);

Related Problems and Solutions