Knowing how to use testing frameworks is one of the most important skills any developer must learn. In particular with the Ruby programming language, testing is even more important because a compiler will not catch syntax errors or little slip ups! Such is life when using interpreted languages. Despite testing’s importance being obvious, it is surprising how many Ruby on Rails apps have deficient test coverage or no code coverage at all! Why is this?

I think one of the simplest explanations is that it is hard. I see discussion board’s like this one on Reddit where Rails practitioners are struggling to get full stack testing to work. Apart from the difficulty of even getting some test frameworks to work, it takes discipline to spend additional time writing tests for code instead of merely committing and shipping. Add these two factors together and I can see why so many developers just give up and move on.

With that said, I wanted to share my own Selenium/RSpec configuration. I use this configuration on all of my own personal Ruby on Rails projects. While it may not be perfect for you or your setup, it works well enough for us.

First is my Gemfile:

group :test do
  gem 'rspec-rails', '~> 4.0.0'
  gem 'capybara'
  gem 'webdrivers', '~> 4.0'
  gem 'selenium-webdriver'
  gem 'database_cleaner'
  gem 'launchy' # `save_and_open_page` in integration specs
end

With this set of gem’s I can easily program full stack, JavaScript enabled, Integration tests with my Rails application. Setup each gem according to its own README documentation. I will not go through each of them in this blog post!

Second is my Capybara Configuration:


# spec/support/capybara_config.rb

Capybara.register_driver :chrome_headless do |app|
  capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
    'goog:chromeOptions': {
      args: %w[ no-sandbox headless disable-gpu --window-size=1920,1080]
    }
  )
  Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities)
end

#Ensure Log directory exists
%x(mkdir -p tmp/selenium_logs)

Capybara.register_driver :chrome do |app|
  capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
    'goog:chromeOptions': { args: %w[ start-maximized ] }
  )

  Capybara::Selenium::Driver.new(
    app,
    browser: :chrome,
    desired_capabilities: capabilities,
    driver_opts: {
      log_path: "./tmp/selenium_logs/selenium-#{Time.now.to_i}.log",
      verbose: true
    }
  )
end

Capybara.javascript_driver = ENV.fetch('CAPYBARA_JAVASCRIPT_DRIVER', :chrome_headless).to_sym

#must allow this URL if Webdrivers needs to download a binary
WebMock.disable_net_connect!(allow: 'chromedriver.storage.googleapis.com', allow_localhost: true)

Whats going on in this file? Quite a bit and it took me a lot of trial and error to settle on this particular combination of settings.

First, this file sets up TWO capybara drivers. chrome_headless is useful for everyday testing and CI servers. It’s really annoying to have a Chrome window popup while you’re trying to work. Capybara will steal focus too so its not possible to merely hit or minimize the window easily either. This mode is useful on CI server’s too because those system’s usually don’t have anyone watching the tests run on them! The other driver, chrome, will open up a browser window and I can watch the tests run. I primarily use this for debugging and for writing new tests! In practice, if I wanted to run a test in a browser window and see whats happening I would merely prepend CAPYBARA_JAVASCRIPT_DRIVER=chrome to my rspec command, like this: CAPYBARA_JAVASCRIPT_DRIVER=chrome rspec spec/features

Second, Selenium’s Capabilities class is where things can get a touch confusing. When using chrome_headless, we have to pass options to chrome so it knows what mode to be in! The documentation for it is here but the options I’ve used above will do the trick. --window-size is one that really helped me out because it stops Capybara from not being able to see elements that may be too far down the screen. This opens up a pretty big browser window so it hopefully minimizes screen size related issues.

Third, if you have VCR or another gem that prevents leaking HTTP requests, you have to allow Webmock to download the chromedriver. This is mainly needed on CI systems, but you may need it in your local test environment also. If you have chromedriver installed already, you may not need it at all. This is one of those environmental configuration lines that could easily not apply to you - but I included it anyways just in case.

Lastly, controlling tests while they are running can be difficult too. I use the following Macro’s to help!

#spec/support/capybara_macros.rb

module CapybaraMacros
  def scroll_to_bottom
    page.execute_script 'window.scrollBy(0,10000)'
  end

  def wait_for_ajax
    Timeout.timeout(Capybara.default_max_wait_time) do
      loop until finished_all_ajax_requests?
    end
  end

  def finished_all_ajax_requests?
    page.evaluate_script('jQuery.active').zero?
  end

  #https://ricostacruz.com/til/pausing-capybara-selenium
  def pause_selenium #not to be confused with Kernel#pause
    $stderr.write 'Press enter to continue'
    $stdin.gets
  end

  # hack to make element visible on page
  def maximize_browser_window
    Capybara.current_session.current_window.resize_to(1000, 1000)
  end
end

RSpec.configure do |config|
  config.include CapybaraMacros, type: :feature
  config.include Warden::Test::Helpers
end

Let’s take a look at them one by one:

  • scroll_to_bottom - scrolls the browser window down to what is most likely the bottom. 10000 is a lot! Sometimes you need to test something at the very bottom of the page. This macro shows you how.
  • wait_for_ajax - this one saves my skin all the time. Use this after opening up a modal popup or anytime there’s some kind of transition. This merely tells Capybara to chill out while the browser finishes doing stuff. This works with jQuery and I’m not sure about other frontend frameworks. Odds are for Angular, Vue, etc, you’d just have to adjust the finished_all_ajax_requests? method to work with your particular setup!
  • pause_selenium - the equivalent of binding.pry while running a JS-enabled feature spec. This will pause the Ruby thread completely allowing you to pause your feature spec right where you want so you can debug. Do not commit this - it will freeze your CI server as it requires keyboard input to exit from.
  • maximize_browser_window - helps enlarge the browser window in case the size gets messed up for some reason.
  • save_and_open_page - This one is provided by the Launchy gem, but its a nice one to have if you want to save the current state and open it on a separate browser window.

Lastly, we need to configure Database Cleaner so RSpec doesn’t leave behind junk in your database after a JS-enabled Spec has completed. Without the following configuration, it’s possible your test suite or CI server might error randomly.

RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, type: :feature) do
    DatabaseCleaner.strategy = :truncation
  end

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

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

TL;DR - Too Long Didn’t Read

Testing is tough as-is, and the wrong settings can make it more difficult than it ought to be. The above settings work on all of our Ruby on Rails projects. Feel free to adapt to your projects!

👇👇👇 Does your organization struggle to write good tests? We can help fix that! Please contact us for your free consultation! 👇👇👇