【问题标题】:Java - Wait for JavaScript event (e.g. onchange) to complete using SeleniumJava - 使用 Selenium 等待 JavaScript 事件(例如 onchange)完成
【发布时间】:2018-11-07 22:56:32
【问题描述】:

一组逻辑相关的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: 不要混合隐式和显式等待。

使用WebDriverWaitFluentWait 的特化)代替FluentWait,除非您有非常具体的要求。例如,WebDriverWait 默认忽略 NotFoundExceptionNoSuchElementException 的超类)。见recomendation

【问题讨论】:

    标签: javascript java selenium events onchange


    【解决方案1】:

    经过认真的重构和一些研究,我终于做到了。 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() 在指定的 EventTarget 处分派一个事件,(synchronously) 以适当的顺序调用受影响的 EventListener。这也适用于custom events。这是用于活动的very nice post

    我使用以下代码来更好地掌握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);
    

    【讨论】:

      【解决方案2】:

      你可以试试这个方法,它会等到 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。不知道。根据question,它只有在执行的代码是ajax调用时才有效,我做对了吗?有没有类似的东西,但它适用于 JavaScript ?
      • 我确实有一个更通用的 waitForPageToLoad 方法,它使用 javascript 检查文档就绪状态是否“完成”它可能会有所帮助,因为当 Web 元素“忙碌”时该标志可能会发生变化我'将其作为单独的答案发布。
      【解决方案3】:

      以下例程检查文档就绪状态是否“完成”,并在页面完成加载/更改时返回。

      /**
       * 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");
              }
          });
      }
      

      【讨论】:

      • 谢谢比尔!不幸的是,我已经在执行 return document.readyState 所以一切都已经加载了。有时我什至每次想要对其执行操作时都必须获取该元素,否则我最终会收到一条错误消息,指出该元素不再在缓存中,因此我验证元素是否存在。
      【解决方案4】:

      据我所知,您不能等待使用 Selenium Web 驱动程序的异步事件。但是,您可以使用 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")));
      

      这个简单的例子会等到一个按钮被激活。 如果在接下来的 15s 内没有满足预期的条件,就会抛出异常。

      如果提供的一组 ExpectedCondition 不足,您始终可以通过实现 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

      【讨论】:

      • 谢谢。顺便说一句,我正在使用输入字段而不是按钮。我会使用ExpectedConditions.elementToBeClickable,但我得到了一个例外,所以我只检查ExpectedConditions.visibilityOf。问题是字段上的onchange 事件似乎正在清除其他字段,如果它们尚未经过验证,所以我想看看一种方法来确定事件何时结束
      • 这只是一个想法,也许我可以提供更好的解决方案,但我仍然不清楚您要实现的目标。如果想法是查看字段是否为空,您可以编写一些自定义 ExpectedCondition 来等待特定情况。
      • 知道了。字段为空,我设置了一个值。之后它运行一些 JavaScript。如果我立即填充下一个相关字段,它会丢失它的内容,但是如果我给一些时间让 JavaScript 完成它正在做的事情,我可以毫无问题地更新下一个相关字段。而且它们在onchange 事件之前和之后看起来都一样......这只是后台处理问题的原因,并想检查是否有办法在不硬编码睡眠时间的情况下做到这一点。谢谢
      • 好的,我想我现在明白了。您有一组连接的输入字段,如果您更改一个,异步任务会禁止您更改下一个,但是,它不会更改其原始值(下一个)。
      • 我还必须问一下,在后台发生此类过程的用户是否有任何可见的迹象?
      猜你喜欢
      • 2016-10-10
      • 2014-05-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-06
      • 1970-01-01
      • 2018-11-28
      • 1970-01-01
      相关资源
      最近更新 更多