Capybara, Selenium and SSL
I’ve been looking for a way to get Selenium set up with Capybara. We have a fairly standard dev config, nginx with passenger running the site, Rspec for unit tests, Capybara for integration tests. One bit that was missing was Javascript tests, so I decided to give it a go with Selenium.
First of all, Selenium did not like me trying to log in with SSL. To be honest, it wasn’t really Selenium as much as Webrick which Capybara starts for Selenium. I kept getting ssl_error_rx_record_too_long. Solution: let nginx deal with the SSL bit of the communication and send through a simple HTTP request to Webrick. Bonus points for setting a header which tells Rails that initially it was an SSL request.
Next up: database issues. Our tests rely on two things: seed data and transactions. The idea is to get the seed data in the db and then run each test within a transaction so that it’s quicker to reset. Unfortunately Selenium opens a new database connection, and a new transaction to go with it. Solution: database_cleaner. Strategy – truncation, as mentioned on numerous websites… Oh wait, leave the seed data in! You can pass an :except option to DatabaseCleaner, except that you then can’t create models which are seeded as they will just stay in the db. Oh well, Truncation and loading seeds before each test it is. After painful waiting for the test suite to finish (28min instead of the usual 10) it was clear that this is not a sensible solution. So I tried switching strategies in between tests, from transaction for most tests to truncation for Selenium tests. Worked like a charm.
Lastly, a minor thing compared to everything else – did you know that fixtures are cached? Well they are. So if you try loading them again while running the tests it won’t work. Need a special fix for that.
All in all, it was a painful experience. Below is the code I used, some of it will be deprecated once new Capybara is out, and I might have omitted something accidentally. If so, let me know in the comments. Other than that, enjoy!
nginx.conf:
http {
# your usual server config goes here
upstream testing {
server 127.0.0.1:9000; # Capybara will start Webrick on this port
}
server {
listen 80;
server_name myapp.testing;
location / {
proxy_pass http://testing;
proxy_set_header Host $host;
}
}
server {
listen 443;
server_name myapp.testing;
location / {
proxy_pass http://testing;
proxy_set_header Host $host; # forward the host so that urls use this
proxy_set_header X_FORWARDED_PROTO 'https'; # this is how Rails finds out if it's an https request
}
ssl on;
ssl_certificate /etc/ssl/certs/myssl.crt;
ssl_certificate_key /etc/ssl/private/myssl.key;
}
}
Gemfile
gem "capybara" gem "database_cleaner"
spec_helper.rb
config.use_transactional_fixtures = false config.before(:suite) do DatabaseCleaner.strategy = :transaction end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end Capybara.server_port = 9000 Capybara.app_host = 'http://myapp.testing'
We also have a special integration_helper.rb included only in integration tests. The DatabaseCleaner mess is to make sure switching from transaction to truncation and vice versa works without any issues.
integration_helper.rb
config.before(:each) do if example.metadata[:js] DatabaseCleaner.clean DatabaseCleaner.strategy = :truncation Capybara.current_driver = :selenium # this can be removed with new Capybara release end end config.after(:each) do if example.metadata[:js] DatabaseCleaner.clean load "#{Rails.root}/db/seeds.rb" DatabaseCleaner.strategy = :transaction DatabaseCleaner.start Capybara.use_default_driver # this can be removed with new Capybara release end end
You only need this bit if you’re using YAML fixtures/seed data and somewhere in your code you say something like Fixtures.create_fixtures(seed_dir, table).
db/seeds.rb
Fixtures.reset_cache
Rspec integration test
it "works with Selenium", :js => true do visit root_path end

Interesting stuff,
Any plans for JS unit testing?
How finely grained are your JS tests? IE do you have separate tests to hide/show a bit of content? Mousover popups?
Dan