Selecting by Option Value using Capybara

Capybara’s select command allows searching a HTML select field and selecting an option that matches the supplied value by name, id or label text. Additionally, the match option allows the user to indicate the behaviour if more than one option is found. For example, match: :first will select the first item out of multiple matches, while match: :one will throw an error if more than one item is found.

I was recently dealing with a time zone select list based on ActiveRecord::TimeZone. I was receiving an error when Capybara tried to select “Samoa” from the option list (“Samoa” due to test randomisation), since there were items named “American Samoa” and “Samoa”. I ended up having to select the option via its ‘value’ attribute instead:

time_zone = ActiveRecord::TimeZone.all.sample
find('select#time_zone').find("option[value='#{time_zone.name}']").select_option

Mixing DatabaseCleaner transaction and truncation strategies

RSpec by default issues a database transactional rollback that is executed after the completion of a block. When using Capybara with the :js option (to enable testing with Selenium or Webkit), the transactional rollback offered by RSpec is unusable as the browser is loaded via a separate thread and has a separate database connection. Thus any uncommitted data cannot be read by the browser.

Most people suggest employing DatabaseCleaner using a truncation strategy to overcome this deficiency. The performance of cleaning via truncation vs transaction depends on the size of data – with smaller fixtures, transactions are preferable.
Instead of using one or the other, we can mix strategies to have the best of both worlds:

config.use_transactional_fixtures = false # Using DatabaseCleaner instead

config.before(:suite) do
  DatabaseCleaner.strategy = :transaction # Default strategy
  DatabaseCleaner.clean_with(:truncation) # Initially clean with truncation
end

config.before(:each, type: :request) do
  # Swap to truncation as Selenium runs in separate thread with a different
  # database connection
  DatabaseCleaner.strategy = :truncation
end

config.after(:each, type: :request) do
  # Reset so non-request specs can use transaction
  DatabaseCleaner.strategy = :transaction
end

config.before(:each) do
  DatabaseCleaner.start
end

config.after(:each) do
  DatabaseCleaner.clean
end

On my current test suite of around 461 tests with 73 request specs, this dropped the run time by just over a minute.