了解在 Selenium 中执行异步脚本

Understanding execute async script in Selenium

我一直在使用 selenium(与 python bindings and through protractor mostly) for a rather long time and every time I needed to execute a javascript code, I've used execute_script() method. For example, for scrolling the page (python):

driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

或者,对于 (量角器):

var div = element(by.css('div.table-scroll'));
var lastRow = element(by.css('table#myid tr:last-of-type'));

browser.executeScript("return arguments[0].offsetTop;", lastRow.getWebElement()).then(function (offset) {
    browser.executeScript('arguments[0].scrollTop = arguments[1];', div.getWebElement(), offset).then(function() {
        // assertions

    });
});

或者,为了获得 dictionary of all element attributes (python):

driver.execute_script('var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) { items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value }; return items;', element)

但是,WebDriver API 还有 execute_async_script() 我没有亲自使用过。

它涵盖了哪些用例?我什么时候应该使用 execute_async_script() 而不是常规的 execute_script()

问题是特定于 selenium 的,但与语言无关。

这是两个 API 的 reference(好吧,它是 Javadoc,但功能是相同的),这里是突出显示差异的摘录

[executeAsyncScript] Execute an asynchronous piece of JavaScript in the context of the currently selected frame or window. Unlike executing synchronous JavaScript, scripts executed with this method must explicitly signal they are finished by invoking the provided callback. This callback is always injected into the executed function as the last argument.

基本上,execSync 会阻止 selenium 浏览器执行的进一步操作,而 execAsync 不会阻止并在完成后调用 callback


由于您使用过量角器,我将以它为例。 量角器在 get and waitForAngular

中都使用 executeAsyncScript

waitForAngular中,量角器需要等到angular宣布所有事件都已解决。你不能使用 executeScript 因为它需要在最后 return 一个值(尽管我猜你可以实现一个繁忙的循环,不断轮询 angular 直到它完成)。它的工作方式是量角器提供一个回调,Angular 在所有事件都解决后调用,这需要 executeAsyncScript。代码 here

get中,量角器需要轮询页面,直到Angular设置全局window.angular。一种方法是 driver.wait(function() {driver.executeScript('return window.angular')}, 5000),但这样量角器每隔几毫秒就会敲击浏览器。相反,我们这样做(简化):

functions.testForAngular = function(attempts, callback) {
  var check = function(n) {
    if (window.angular) {
      callback('good');
    } else if (n < 1) {
      callback('timedout');
    } else {
      setTimeout(function() {check(n - 1);}, 1000);
    }
  };
  check(attempts);
};

同样,这需要 executeAsyncScript,因为我们没有立即获得 return 值。代码 here


总而言之,当您关心调用脚本中的 return 值时,请使用 executeAsyncScript,但该 return 值不会立即可用。如果您无法轮询结果,但必须使用回调或承诺(您必须自己将其转换为回调)来获取结果,这尤其必要。

When should I use execute_async_script() instead of the regular execute_script()?

在浏览器端检查条件时,您可以使用 execute_async_script 执行的所有检查都可以使用 execute_script 执行。即使您正在检查的是异步的。 我知道,因为曾几何时 execute_async_script 有一个错误,如果脚本返回结果太快,我的测试就会失败。据我所知,错误现在已经消失,所以我一直在使用 execute_async_script 但几个月前,我使用 execute_script 来处理 execute_async_script 会更自然的任务。例如,执行一个需要用 RequireJS 加载模块来执行检查的检查:

driver.execute_script("""
// Reset in case it's been used already.
window.__selenium_test_check = undefined;
require(["foo"], function (foo) {
    window.__selenium_test_check = foo.computeSomething();
});
""")

result = driver.wait(lambda driver: 
    driver.execute_script("return window.__selenium_test_check;"))

require 调用是异步的。但是,除了将变量泄漏到全局 space 之外,这个问题还在于它会增加网络请求。每个 execute_script 调用都是一个网络请求。 wait 方法通过轮询工作:它运行测试直到返回值为真。这意味着 wait 执行的每次检查一个网络请求(在上面的代码中)。

当你在本地测试时,这没什么大不了的。如果你必须通过网络,因为你的浏览器是由像 Sauce Labs 这样的服务提供的(我使用过,所以我是根据经验谈的),每个网络请求都会减慢你的测试套件。 所以使用 execute_async_script 不仅允许编写看起来更自然的测试(调用回调,就像我们通常对异步代码所做的那样,而不是泄漏到全局 space),而且还有助于测试的性能。

result = driver.execute_async_script("""
var done = arguments[0];
require(["foo"], function (foo) {
    done(foo.computeSomething());
});
""")

我现在的看法是,如果一个测试要挂接到浏览器端的异步代码中以获得结果,我会使用 execute_async_script。如果它要做一些没有可用异步方法的事情,我使用 execute_script.