thinksimple.pl

Capybara, Selenium and SSL

14.03.2011 22:22

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

Comments

Dan Carper 17 Mar 18:17

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

squil 19 Mar 08:37

JS unit testing is relatively simple, you can use Jasmine or QUnit. Provided you have units in your app, which we don't - we just have a bunch of events. Also, most likely we won't have too many JS tests because they're incredibly slow, so we'll probably be testing the crucial stuff, and everything else will stay in rspec + progressive enhancement.

At least, until capybara-zombie becomes usable.

Comments are closed.