理解在 Selenium 中执行异步脚本

IT技术 javascript python selenium selenium-webdriver protractor
2021-02-26 15:52:17

我一直在使用selenium(使用python 绑定protractor大部分时间)相当长的时间,每次我需要执行 javascript 代码时,我都使用了execute_script()方法。例如,滚动页面(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

    });
});

或者,为了获取所有元素属性字典(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的,但与语言无关。

2个回答

我什么时候应该使用execute_async_script()而不是常规的execute_script()

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

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

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

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

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

@pguardiario 回调由 Selenium 本身创建和传递。当您使用 时execute_async_script,JavaScript 端可用的最后一个参数是由 Selenium 创建的回调。假设您调用driver.execute_async_script(script, "foo", "bar"). 在 中运行的代码中scriptarguments[0]值是“foo”,arguments[1]是值“bar”,arguments[2]是 Selenium 创建的回调。您的脚本必须在完成后调用回调以向 Selenium 发出信号表示它已完成。否则,Selenium 无法知道它已完成。
2021-04-19 15:52:17
回调是什么,你为什么不通过它?
2021-04-22 15:52:17
谢谢,有道理。我以为我应该传递回调。
2021-05-05 15:52:17
高超。+1 详细说明何时仍然适合使用executeover executeAsync我要补充一点:这个错误对我来说(使用 selenium web 驱动程序和 javascript)似乎又回来了。
2021-05-09 15:52:17

这是对两个 API引用(嗯,它是 Javadoc,但功能是相同的),这是其中的摘录,突出了不同之处

[executeAsyncScript] 在当前选定的框架或窗口的上下文中执行一段异步 JavaScript。与执行同步 JavaScript 不同,使用此方法执行的脚本必须通过调用提供的回调显式表示它们已完成。这个回调总是作为最后一个参数注入到执行的函数中。

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


由于您使用过量角器,因此我将以此为例。量角器用途executeAsyncScriptgetwaitForAngular

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

在 中get,量角器需要轮询页面,直到全局window.angular被 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因为我们没有立即返回值。代码在这里


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

添加了一些示例。希望这是有道理的。
2021-04-27 15:52:17
太好了,这是一个有趣的旅程,了解量角器的客户端脚本和其他内部结构!再次感谢。理论上,让我们想象一下:如果我要使用 selenium python 绑定测试一个 Angular 应用程序 - 我需要加载类似(或相同)的客户端脚本并异步调用testForAngular()waitForAngular()函数,以检查并等待 Angular “变得稳定” ”。正确的?
2021-04-30 15:52:17
好吧,testForAngular 只是等待 'window.angular' 出现,而​​ waitForAngular 使用 'window.angular' 来检查应用程序是否稳定,所以你肯定需要它们,但你也需要很多其他代码 = )
2021-05-04 15:52:17
是的,谢谢 :) 我只是想考虑一个有效的现实世界非量角器/角度相关的 executeAsyncScript 示例用法,它无法用其他任何方法轻松解决。仅供参考,我会暂时保留这个话题(我们还将在几天内开始悬赏以引起注意)。
2021-05-14 15:52:17
感谢您提供指向 javaDoc 的链接 - 提到了一些示例。您个人是否需要执行异步脚本?如果是,您能描述一下用例吗?那真的很有帮助。
2021-05-16 15:52:17