Capybara provides execute_script
and evaluate_script
to execute JavaScript code in a Selenium-controlled browser. This however is not a good solution for asynchronous JavaScript.
Enter evaluate_async_script
, which allows you to execute some asynchronous code and wait until it finishes. There is a timeout of a couple of seconds, so it will not wait forever.
Use it like this:
page.evaluate_async_script(<<~JS)
let [done] = arguments
doSomethingAsynchronous().then(() => {
done() // call this to indicate we're done
})
JS
You can return results to Ruby by passing them to the done
callback:
result = page.evaluate_async_script(<<~JS)
let [done] = arguments
doSomethingAsynchronous().then(() => {
done("some result")
})
JS
Finally, you can pass additional object from Ruby to Javascript:
result = page.evaluate_async_script(<<~JS, arg1, arg2)
let [arg1, arg2, done] = arguments
doSomethingAsynchronous().then(() => {
done("some result")
})
JS
Handling Errors
If Errors happen within your JavaScript code, execution stops but the done
callback helper doesn't get called. Selenium unfortunately also doesn't recognize that an error happens and fails with a Timeout Exception after a while. To fix this, append a .catch
block that calls the callback method with some information about the Error. The inclusion of error: true
in the return value tells Selenium that an error happened:
result = page.evaluate_async_script(<<~JS, arg1, arg2)
let [arg1, arg2, done] = arguments
doSomethingAsynchronous().then(() => {
done("some result")
}).catch((error) => {
error => done({error: true, message: error.message, stacktrace: error.stack})
})
JS
Returning a promise
Warning
The following is not documented anywhere, it may be incorrect.
In newer Capybara versions you may also wrap a promise inside an IIFE and use evaluate_script
:
result = page.evaluate_script(<<~JS)
(function() {
return doSomethingAsynchronous()
})()
JS
Of course, you may also add an async
-modifier to this function declaration to enable the usage of await
. Please check, if this works for you. If not, use evaluate_async_script
and chain your calls.