We have recently started the development of a new application for a new client. One of the features requires the user to be able to select words in an input that, under the hood, uses the Select2 tagging system.

Writing a test to validate that this feature works would look something like this:

create_tags_in_database

sign_in user
visit a page

when I type "ta" in my tag_list field
and I choose the tag "tag1" in my tag_list field
And press save
Then my record tag_list should contains "tag1"

Since we are using Rails 5.1 and the latest rspec-rails, we are now able to use system tests with Rspec. And since this particular test relies on JavaScript they are a very good candidate.

While trying to get this test to work, I encountered a few quirks, mainly my created records not being available in my system tests and trying to select something inside select2.

Let’s dive into how to solve them.

Prerequisite

First, you will need to have the latest rspec-rails and capybara versions.

You will need to install 2 new gems in your :test group

gem "chromedriver-helper", group: :test

gem 'selenium-webdriver', group: :test

Loading different drivers

Now we will create a way to load different drivers, so when we’re running system tests that won’t require JavaScript we will use the fastest rack server, and when we need javascript we will use Selenium chrome headless. Create a new file in spec/support/system/driver.rb:

# spec/support/system/driver.rb

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    ActiveRecord::Base.establish_connection
    driven_by :selenium_chrome_headless
  end
end

A new system test file would now look something like this:

require 'rails_helper'

RSpec.describe "Creating a post with tags", type: :system do

end

And you need JavaScript to be enabled – you will just need to add a js: true to the describe block.

require 'rails_helper'

RSpec.describe "Creating a post with tags", type: :system, js: true do

end

If at that stage, you get complaints like “cannot find :selenenium_chrome_headless” please make sure you have the latest capybara version.

And, if you’re using Devise, make sure that you add this line to your rails_spec.rb config:

config.include Devise::Test::IntegrationHelpers, type: :system

But, I can’t view my record on the page!

If you’re using Puma, and you were writing a test like this…

require 'rails_helper'

RSpec.describe "Creating a post with tags", type: :system, js: true do

  context "visiting the new post page" do
    it "shows me a list of tags" do
      Tag.create(name: "tag1")

      visit new_post_page
   
      expect(page).to have_content("tag1")
    end
  end

end

… then there is a big chance that your test will fail. The reason is that the Tag record is created in another thread.

To remedy that, you need to ensure that Puma starts in 0 workers mode and 1 thread only.

Our current Puma config looks like this:

# config/puma.rb

workers Integer(ENV.fetch("WEB_CONCURRENCY", 2))
threads_count = Integer(ENV.fetch("MAX_THREADS", 2))
threads(threads_count, threads_count)

preload_app!

rackup DefaultRackup
environment ENV.fetch("RACK_ENV", "development")

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

You now need to create a support/puma.rb file:

ENV["WEB_CONCURRENCY"] = "0"
ENV["MAX_THREADS"] = "1"

Now, you should have a green test!

Let’s select a tag with Select2 in Capybara

Select2 hijacks your normal form select[multiple=true] and creates a fake input driven by JavaScript. So I have created a small helper and put it in support/helpers/select2_choose_tag.rb

def select2_choose_tag(field_class, options = {})
  within(".form-group.#{field_class}") do
    first('.select2-container', minimum: 1).click
    first('.select2-search__field').send_keys(options.fetch(:choose, ""), :enter)
  end
end

Now your final test can look like this:

require 'rails_helper'

RSpec.describe "Creating a post with tags", type: :system, js: true do

  context "visiting the new post page" do
    it "shows me a list of tags" do
      Tag.create(name: "tag1")

      visit new_post_page

      fill_in "Title", with: "Rspec & System tests are fun"

      select2_choose_tag "tag_list", choose: "tag1"

      click_on "save"
   
      expect(Post.last.tag_list).to eql(["tag1"])
    end
  end

end

Closing thoughts

System tests are now the defaults in Rails, and the Rspec team recommends that you move your feature tests to system tests. Doing so is fairly easy. You will just need to amend the describe block to include type: :system and change .feature for a .describe.

You also need to make sure to only load js: true when you really need JavaScript and allow for non-JavaScript dependent tests to remain as snappy as possible.