了解Selenium中的执行异步脚本

时间:2022-12-06 09:06:59

I've been using selenium (with 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):

我一直在使用selenium(主要是python绑定和通过量角器)很长一段时间,每次我需要执行javascript代码时,我都使用了execute_script()方法。例如,对于滚动页面(python):

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

Or, for infinite scrolling inside an another element (protractor):

或者,对于另一个元素(量角器)内的无限滚动:

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

    });
});

Or, for getting a dictionary of all element attributes (python):

或者,获取所有元素属性的字典(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)

But, WebDriver API also has execute_async_script() which I haven't personally used.

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

What use cases does it cover? When should I use execute_async_script() instead of the regular execute_script()?

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

The question is selenium-specific, but language-agnostic.

问题是硒特异性,但与语言无关。

2 个解决方案

#1


13  

Here's the reference to the two APIs (well it's Javadoc, but the functions are the same), and here's an excerpt from it that highlights the difference

这是对两个API的引用(它是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.

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

Basically, execSync blocks further actions being performed by the selenium browser, while execAsync does not block and calls on a callback when it's done.

基本上,execSync阻止了由selenium浏览器执行的进一步操作,而execAsync在完成后不会阻塞和调用回调。


Since you've worked with protractor, I'll use that as example. Protractor uses executeAsyncScript in both get and waitForAngular

既然您使用了量角器,我将以此为例。量角器在get和waitForAngular中使用executeAsyncScript

In waitForAngular, protractor needs to wait until angular announces that all events settled. You can't use executeScript because that needs to return a value at the end (although I guess you can implement a busy loop that polls angular constantly until it's done). The way it works is that protractor provides a callback, which Angular calls once all events settled, and that requires executeAsyncScript. Code here

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

In get, protractor needs to poll the page until the global window.angular is set by Angular. One way to do it is driver.wait(function() {driver.executeScript('return window.angular')}, 5000), but that way protractor would pound at the browser every few ms. Instead, we do this (simplified):

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

Again, that requires executeAsyncScript because we don't have a return value immediately. Code here

同样,这需要executeAsyncScript,因为我们没有立即返回值。代码在这里


All in all, use executeAsyncScript when you care about a return value in a calling script, but that return value won't be available immediately. This is especially necessary if you can't poll for the result, but must get the result using a callback or promise (which you must translate to callback yourself).

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

#2


20  

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

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

When it comes to checking conditions on the browser side, all checks you can perform with execute_async_script can be performed with execute_script. Even if what you are checking is asynchronous. I know because once upon a time there was a bug with execute_async_script that made my tests fail if the script returned results too quickly. As far as I can tell, the bug is gone now so I've been using execute_async_script but for months beforehand, I used execute_script for tasks where execute_async_script would have been more natural. For instance, performing a check that requires loading a module with RequireJS to perform the check:

在检查浏览器端的条件时,可以使用execute_script执行可以使用execute_async_script执行的所有检查。即使你正在检查的是异步的。我知道因为曾经有一个execute_async_script的错误导致我的测试失败,如果脚本返回的结果太快了。据我所知,这个bug现在已经消失,所以我一直在使用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;"))

The require call is asynchronous. The problem with this though, besides leaking a variable into the global space, is that it multiplies the network requests. Each execute_script call is a network request. The wait method works by polling: it runs the test until the returned value is true. This means one network request per check that wait performs (in the code above).

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

When you test locally it is not a big deal. If you have to go through the network because you are having the browsers provisioned by a service like Sauce Labs (which I use, so I'm talking from experience), each network request slows down your test suite. So using execute_async_script not only allows writing a test that looks more natural (call a callback, as we normally do with asynchronous code, rather than leak into the global space) but it also helps the performance of your tests.

当你在本地测试时,这不是什么大问题。如果你必须通过网络,因为你正在使用像Sauce Labs这样的服务提供的浏览器(我使用它,所以我说的是经验),每个网络请求都会降低你的测试套件的速度。因此,使用execute_async_script不仅允许编写看起来更自然的测试(调用回调,就像我们通常使用异步代码一样,而不是泄漏到全局空间中),但它也有助于测试的性能。

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

The way I see it now is that if a test is going to hook into asynchronous code on the browser side to get a result, I use execute_async_script. If it is going to do something for which there is no asynchronous method available, I use execute_script.

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

#1


13  

Here's the reference to the two APIs (well it's Javadoc, but the functions are the same), and here's an excerpt from it that highlights the difference

这是对两个API的引用(它是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.

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

Basically, execSync blocks further actions being performed by the selenium browser, while execAsync does not block and calls on a callback when it's done.

基本上,execSync阻止了由selenium浏览器执行的进一步操作,而execAsync在完成后不会阻塞和调用回调。


Since you've worked with protractor, I'll use that as example. Protractor uses executeAsyncScript in both get and waitForAngular

既然您使用了量角器,我将以此为例。量角器在get和waitForAngular中使用executeAsyncScript

In waitForAngular, protractor needs to wait until angular announces that all events settled. You can't use executeScript because that needs to return a value at the end (although I guess you can implement a busy loop that polls angular constantly until it's done). The way it works is that protractor provides a callback, which Angular calls once all events settled, and that requires executeAsyncScript. Code here

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

In get, protractor needs to poll the page until the global window.angular is set by Angular. One way to do it is driver.wait(function() {driver.executeScript('return window.angular')}, 5000), but that way protractor would pound at the browser every few ms. Instead, we do this (simplified):

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

Again, that requires executeAsyncScript because we don't have a return value immediately. Code here

同样,这需要executeAsyncScript,因为我们没有立即返回值。代码在这里


All in all, use executeAsyncScript when you care about a return value in a calling script, but that return value won't be available immediately. This is especially necessary if you can't poll for the result, but must get the result using a callback or promise (which you must translate to callback yourself).

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

#2


20  

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

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

When it comes to checking conditions on the browser side, all checks you can perform with execute_async_script can be performed with execute_script. Even if what you are checking is asynchronous. I know because once upon a time there was a bug with execute_async_script that made my tests fail if the script returned results too quickly. As far as I can tell, the bug is gone now so I've been using execute_async_script but for months beforehand, I used execute_script for tasks where execute_async_script would have been more natural. For instance, performing a check that requires loading a module with RequireJS to perform the check:

在检查浏览器端的条件时,可以使用execute_script执行可以使用execute_async_script执行的所有检查。即使你正在检查的是异步的。我知道因为曾经有一个execute_async_script的错误导致我的测试失败,如果脚本返回的结果太快了。据我所知,这个bug现在已经消失,所以我一直在使用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;"))

The require call is asynchronous. The problem with this though, besides leaking a variable into the global space, is that it multiplies the network requests. Each execute_script call is a network request. The wait method works by polling: it runs the test until the returned value is true. This means one network request per check that wait performs (in the code above).

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

When you test locally it is not a big deal. If you have to go through the network because you are having the browsers provisioned by a service like Sauce Labs (which I use, so I'm talking from experience), each network request slows down your test suite. So using execute_async_script not only allows writing a test that looks more natural (call a callback, as we normally do with asynchronous code, rather than leak into the global space) but it also helps the performance of your tests.

当你在本地测试时,这不是什么大问题。如果你必须通过网络,因为你正在使用像Sauce Labs这样的服务提供的浏览器(我使用它,所以我说的是经验),每个网络请求都会降低你的测试套件的速度。因此,使用execute_async_script不仅允许编写看起来更自然的测试(调用回调,就像我们通常使用异步代码一样,而不是泄漏到全局空间中),但它也有助于测试的性能。

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

The way I see it now is that if a test is going to hook into asynchronous code on the browser side to get a result, I use execute_async_script. If it is going to do something for which there is no asynchronous method available, I use execute_script.

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