Introduction
While Rails makes testing a breeze, JavaScript testing requires a bit of extra work. This post was written for minitest, but it should be easy to adapt for RSpec.
Initial Setup: Capybara and Poltergeist
For integration testing, I use Capybara, an acceptance test framework that can be used to simulate user interaction. While this works great for testing standard pages, the default driver does not execute JavaScript. The built-in Selenium JavaScript driver launches Firefox and performs testing by clicking around inside the GUI, but this is less performant than a headless browser like PhantomJS. I use Poltergeist, a Capybara driver for PhantomJS.
To set up Capybara and Poltergeist, add them both to your gemfile and add the following lines to your test helper.
require 'capybara/rails'
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist
class ActionDispatch::IntegrationTest
# Make the Capybara DSL available in all integration tests
include Capybara::DSL, JavaScriptUtilities
end
I like to create a JavaScriptUtilities module to make it easier to write JavaScript tests. All I have to do is call require_js in the test setup, and it takes care of switching the driver back to default afterwards.
module JavaScriptUtilities
def require_js
Capybara.current_driver = Capybara.javascript_driver
end
def after_teardown
super
Capybara.use_default_driver
end
end
Ajax Test Failures
I quickly found that Capybara was not always waiting for Ajax requests to complete. Ryan Bigg had a similar problem, but I was unable to get his solution to work. I came up with a kludge using HTML attributes. I set a waiting attribute to be true, wait for the ajaxSuccess event to be fired, and then set the waiting attribute to be false. The loop will not break until this attribute is set to false or it times out.
module JavaScriptUtilities
...
def wait_for_ajax
waiting_attr = "waiting_#{SecureRandom.hex}"
page.execute_script("$('body').attr('#{waiting_attr}', true)")
yield
page.execute_script("$(document).ajaxSuccess(function( event, xhr, settings ) { $('body').attr('#{waiting_attr}', false); });")
waiting(waiting_attr)
end
private
def waiting(waiting_attr)
counter = 0
while page.has_xpath?("//body[@#{waiting_attr}='true']")
counter += 1
sleep(0.1)
raise "Ajax request took longer than 5 seconds." if counter >= 50
end
end
end
Conclusion
I hope this is helpful for those of you trying to write JavaScript tests. If you have any questions or comments, please let me know. You can find me on Twitter.