Johannes Thönes

Software-Developer, ThoughtWorker, Permanent Journeyman, Ruby Enthusiast, Java and Devils Advocate.

Waiting for a JavaScript Event With Selenium/Capybara

I came a cross the question, how I could wait for a JavaScript Event in Capybara using the Selenium Webdriver.

This is what I came up with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected
def wait_for_javascript_event event_name
  selenium_bridge.setScriptTimeout(Capybara.default_wait_time * 1000)

  selenium_driver.execute_async_script(
    "var callback = arguments[arguments.length - 1];
    $(window).one('#{event_name}', function() {setTimeout(callback, 1); return true;});"
    )
end

private
def selenium_driver
  page.driver.browser
end

def selenium_bridge
  selenium_driver.send(:bridge)
end

So, what is going on?

  • Capybara does not provide the feature to execute asynchronous JavaScript. So I get the selenium driver in and grab the private Selenium bridge. The bridge gives me access to this API: JavascriptExecutor

  • I set the script timeout to the timeout I set in Capybara. I found, that otherwise there is a timeout of 2ms, which was not enough for me.

  • Then I execute asynchronous javascript. This Javascript will get a callback as last parameter of arguments injected, which I need to call, once I’m finished with waiting for the event.

  • Inside the JavaScript, I set up a listener for the event I’m interested in. When I get the event I’ll immediately return true - so the event propagation goes on and I don’t change anything in the actual website.

  • A millisecond later, I execute the callback. Why later? Because without it, the execution of the normal JavaScript in the browser stopped. I guess, but haven’t verified, that otherwise the Ruby code will directly come back - therefore the JavaScript in the browser is in a weird state.

Is there a better solution?

I don’t know. I googled a bit and found none - so I came up with this solution. If there is a better one, I would be really glad. The code is not really obvious, has a lot of technical details and is tied quite strongly to the selenium bridge. I cannot switch to another driver, say poltergeist.

Do you have a better solution? Tell me!

Why do we need to do that at all?

When looking for solutions, I found a lot of people who asked how you actually can wait for events in Capybara. The answers were mostly around changing something in the websites JavaScript itself, e.g. showing a hidden element.

That’s not possible for us. We are building a smoke test. The event is coming from a different system, which is not under our control. In theory, I could try to get the other guys to change something in their page. But that’s not something to be done easily and fast.