Understanding execute async script in Selenium
Solution 1:
When should I use
execute_async_script()
instead of the regularexecute_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:
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).
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.
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
.
Solution 2:
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
[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.
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.
Since you've worked with protractor, I'll use that as example.
Protractor uses executeAsyncScript
in both get
and waitForAngular
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
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):
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
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).