Java - 等待 Java 脚本事件(例如 onchange)使用 Selenium 完成

Java - Wait for JavaScript event (e.g. onchange) to complete using Selenium

一组逻辑相关的 input 字段有一个 onchange 事件。在遍历字段并修改它们的值后,由于 onchange 事件所做的事情,一些得到正确更新而一些没有正确更新。

onchange事件在字段上触发的那一刻,它开始一些处理(涉及其他相关字段),将值存储在某处并清除其他相关字段,如果它们以前没有被自己处理过onchange事件。

我可以让线程休眠任意时间,但这看起来不太好。它只是猜测处理需要多少时间,然后选择是在充足的睡眠中浪费时间,还是拥有一个可以因超时而中止的脚本。

有没有办法知道 JavaScript 代码(由 onchange 事件调用)何时完成工作?

原代码

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

输出

有睡眠:Field1:_w_ ... Field2:_x_ ... Field3:_y_ ... FieldN:_z_

没有睡眠:Field1:_w_ ... Field2:___ ... Field3:_y_ ... FieldN:___

备注:

我遇到了一些问题,所以我认为值得一提的是在简短的笔记中吸取的教训:

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 .

据我所知,您无法使用 Selenium Web Driver 等待异步事件。但是,您可以使用 WebDriverWait class 等待该事件的影响。如果您提到的事件,请在 DOM 中进行一些更改,您可以使用选定的 ExpectedConditions 检测这些更改。

   Wait<WebDriver> wait = new WebDriverWait(driver, 15, 500);

   // here you make some change to the input

   wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//input")));

这个简单的示例将等到按钮处于活动状态。 如果在接下来的 15 秒内不满足预期条件,将抛出异常。

万一提供的 ExpectedConditions 集不够用,您始终可以通过实施 ExpectedCondition 接口来创建自己的。 有关详细信息,请访问文档。

https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/package-summary.html https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/ui/package-summary.html

您可以尝试这种等待 JQuery 设置为不活动的方法:

/**
 * Wait until JQuery is inactive
 * @author Bill Hileman
 */
public void waitForJQueryToBeInactive() {

    Boolean isJqueryUsed = (Boolean) ((JavascriptExecutor) driver)
            .executeScript("return (typeof(jQuery) != 'undefined')");

    if (isJqueryUsed) {
        while (true) {
            // JavaScript test to verify jQuery is active or not
            Boolean ajaxIsComplete = (Boolean) (((JavascriptExecutor) driver)
                    .executeScript("return jQuery.active == 0"));
            if (ajaxIsComplete)
                break;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
    }

}

以下例程在页面完成时检查 "complete" 和 returns 的文档就绪状态 loading/changing。

/**
 * Wait for the web page to finish loading
 * @author Bill Hileman
 */
public void waitForPageToLoad() {

    WebDriverWait wait = new WebDriverWait(driver, 10);
    wait.until(new ExpectedCondition<Boolean>() {

        public Boolean apply(WebDriver wdriver) {
            return ((JavascriptExecutor) driver).executeScript("return document.readyState").equals("complete");
        }
    });
}

经过认真的重构和一些研究,我终于做到了。 onchange 事件在 input 字段的值更改且元素失去焦点时触发。使用 WebElement(例如 sendKeys())操作字段不是一种选择,因为您无法控制后台处理,因此使用 JavascriptExecutor 是一种选择。首先,我使用 JavaScript 更新了字段的值(这不会触发事件),之后,我触发了 onchange 事件,同样使用 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));
}

这里有一些关键方面。

  • 不要混用隐式和显式等待(我很难学到),这会导致意外结果。
  • 使用 WebDriverWaitFluentWait 的特化)而不是其超类,除非您有非常具体的要求。如果您使用 FluentWait 确保忽略适当的异常;否则你将开始获得 NoSuchElementException.
  • onchange 事件在 input 字段的值更改且元素失去焦点时触发。
  • dispatchEvent() dispatches an Event at the specified EventTarget, (synchronously) invoking the affected EventListeners in the appropriate order. This applies for custom events as well. This is a very nice post 事件。

我使用下面的代码来更好地掌握ExpectedConditions.javaScriptThrowsNoExceptions and ExpectedConditions.jsReturnsValue。这是一个 JavaScript 函数调用,只会让引擎忙几秒钟。这样您就可以看到显式等待与 JavaScript 的交互并检查 return 值。请注意,每个 ExpectedCondition:

的 JS 代码略有不同
//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);