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

4
一组逻辑相关的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:___

注:

我在使用过程中遇到了一些问题,所以我认为值得简要记录一下学到的教训:

警告:不要混合使用隐式等待和显式等待。

除非您有非常特定的需求,否则请使用{{link2:WebDriverWait}}(FluentWait的专业化版本)而不是FluentWait。例如,WebDriverWait默认忽略NotFoundExceptionNoSuchElementException的超类)。请参见建议

4个回答

3
经过大量的重构和研究,我终于做到了。当输入字段的值改变并且元素失去焦点时,onchange事件将触发。使用WebElement(例如sendKeys())操纵字段不是一个选项,因为您无法控制后台处理,所以使用JavascriptExecutor是更好的选择。首先,我使用JavaScript更新了字段的值(这不会触发事件),然后,我使用JavaScript触发了onchange事件。
//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() 在指定的 EventTarget 上分派一个事件,(同步)按照适当的顺序调用受影响的 EventListeners。这同样适用于自定义事件。这是一个关于事件的非常好的文章

我使用以下代码更好地理解了 ExpectedConditions.javaScriptThrowsNoExceptionsExpectedConditions.jsReturnsValue。这是一个 JavaScript 函数调用,只需让引擎忙碌几秒钟。这样你就可以看到显式等待与 JavaScript 的交互并检查返回值。请注意,每个 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);

1
您可以尝试使用此方法,它会等待 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) {
            }
        }
    }

}

谢谢。没有使用jQuery。有趣的jQuery.active。之前不知道它。根据问题,它只能在执行代码是ajax调用时才起作用,我理解得对吗?是否有类似但适用于JavaScript的东西? - CamelCamelius
我确实有一个更通用的“waitForPageToLoad”方法,它使用JavaScript检查文档准备完毕状态是否为“complete”。它可能会有所帮助,因为在Web元素“繁忙”时,该标志可能会发生改变。我将其发布为单独的答案。 - Bill Hileman

0
据我所知,使用Selenium Web Driver无法等待异步事件。但是,您可以使用WebDriverWait类来等待该事件的影响。如果您提到的事件导致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


1
明白了。字段为空,我设置了一个值。然后它运行一些JavaScript。如果我立即填充下一个相关字段,它会丢失内容,但是如果我给JavaScript足够的时间完成它正在做的事情,我就可以更新下一个相关字段而不会出现问题。在onchange事件之前和之后它们看起来都一样...只是后台处理会出现问题,想检查是否有一种方法可以在不硬编码睡眠时间的情况下完成它。谢谢。 - CamelCamelius
好的,我现在明白了。您有一组连接的输入字段,如果更改其中一个,则异步任务会禁止您更改下一个字段,但它不会更改其原始值(即下一个字段)。 - Mateusz
第一条评论的答案:我认为异步任务修改其他字段而不是禁止您更改它们,因为如果您手动很快地执行此操作,则会看到该值消失。我认为它可能正在存储它们。第二条评论的答案:我没有看到任何可见的处理指示。 - CamelCamelius
我假定你无法影响 JavaScript 代码,但是你所描述的情况似乎是一个糟糕的用户界面问题,所以报告一下可能是个好主意。我也想让您手动尝试以下步骤:(1)在第二个字段上设置虚拟数据。(2)在第一个字段上设置期望的数据。(3)几秒钟后,查看第一个值是否已设置,而第二个值是否已重置或恢复默认值。如果情况是这样的话,我可能可以提供一些解决方法。 - Mateusz
没错,我对JS没有影响力。我尝试先手动设置第二个字段,结果出现了同样的问题。如果我快速操作,值会消失,但如果我慢慢操作,值会保持不变...在移动代码后,我得到了不同的结果...我正在编辑问题以添加发现...经过一些调试,我将您的代码包含在原始问题中,因为它非常好用 :) - CamelCamelius
显示剩余3条评论

0
以下例程检查文档的就绪状态是否为“complete”,并在页面完成加载/更改时返回。
/**
 * 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");
        }
    });
}

谢谢Bill!不幸的是,我已经执行了return document.readyState,所以所有东西都已经加载完毕。有时候我甚至必须每次想要执行操作时都要获取元素,否则就会出现元素不再缓存的错误,因此我会验证元素是否存在。 - CamelCamelius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接