March 11, 2018

Testing Rails Application via Browsers with System Tests

I'm developing a small application which is built on Ruby on Rails, React and Relay. Since the project is based on my personal hobby and just a prototype, I preferred to write application codes rather than to write tests at first. However, as the application grows larger, I had to test the application manually every time when I make changes to make sure it doesn't break other features. The lack of tests made development speed slower. So I decided to write automated tests.

Introducing System Tests

From Rails 5.1, you can use Capybara out-of-the-box. Capybara is an acceptance test framework for web applications written in ruby. All you have to do is just generate test class and write tests.

$ rails g system_test notes
      invoke  test_unit
      create    test/system/notes_test.rb

The generated file will appear like below.

require "application_system_test_case"

class NotesTest < ApplicationSystemTestCase
  # test "visiting the index" do
  #   visit notes_url
  #
  #   assert_selector "h1", text: "Note"
  # end
end

As the commented out example code suggests, you can use Capybara DSL here.

Docker and Selenium Remote Control

Capybara provides various drivers. You can test your application with :rack_test, :selenium, :poltergeist and so on. In system tests, the default driver is :selenium using Chrome browser. You can customize it in ApplicationSystemTestCase, the super class of all system test classes in the application. It is in test/application_system_test_case.rb and if you haven't edited it yet it will be like the following.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end

However, more and more people use Docker and Docker Compose as development environment today. In that case, you may want to use a selenium server which running on a container different from the application. To do that, I changed driven_by in the file.

  driven_by :selenium, using: :chrome, screen_size: [1400, 1400], options: {
    browser: :remote,
    url: ENV.fetch('SELENIUM_DRIVER_URL'),
    desired_capabilities: :chrome
  }

docker-compose.yml will be like following:

version: '3'

services:
  rails:
    ports: ['5000:5000']
    environment:
      # snip...
      SELENIUM_DRIVER_URL: http://selenium:4444/wd/hub
      # snip...

  selenium:
    image: selenium/standalone-chrome-debug:3.10.0
    ports: ['5900:5900']
    logging:
      driver: 'none'

You'll have to tell Capybara the server address and the port number of the Rails server for system tests. So I also added setup part in the file.

  def setup
    Capybara.app_host = 'http://rails:3001'
    Capybara.server_host = '0.0.0.0'
    Capybara.server_port = '3001'

    Selenium::WebDriver.logger.level = :warn # to make outputs silent
  end

Capybara.app_host will be used to enter location on Chrome's address bar. From the selenium container, "rails" can be used as host name of the rails container. So rails:3001 means “3001 port on the rails container”. Capybara.server_host and Capybara.server_port indicate what host and port number will be used by the Rails server.

JavaScript Errors

Additionally, You may want to make a test failure if there are any JavaScript errors. Unfortunately, it doesn't work by default, though. Anyway, you can check the browser logs and raise errors if you met SEVERE errors.

  def teardown
    page.driver.browser.manage.logs.get(:browser).each do |log|
      case log.message
      when /This page includes a password or credit card input in a non-secure context/, /Failed to load resource/
        next
      else
        message = "[#{log.level}] #{log.message}"
        if log.level == 'SEVERE'
          raise message
        else
          warn message
        end
      end
    end
  end

I added the teardown part on ApplicationSystemTestCase like above to manage it somehow.

Code

Finally, my ApplicationSystemTestCase became like following.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [1400, 1400], options: {
    browser: :remote,
    url: ENV.fetch('SELENIUM_DRIVER_URL'),
    desired_capabilities: :chrome
  }

  def setup
    Capybara.app_host = 'http://rails:3001'
    Capybara.server_host = '0.0.0.0'
    Capybara.server_port = '3001'

    Selenium::WebDriver.logger.level = :warn # to make outputs silent
  end

  def teardown
    page.driver.browser.manage.logs.get(:browser).each do |log|
      case log.message
      when /This page includes a password or credit card input in a non-secure context/,
           /Failed to load resource/
        next
      else
        message = "[#{log.level}] #{log.message}"
        if log.level == 'SEVERE'
          raise message
        else
          warn message
        end
      end
    end
  end
end

See Also

© Hibariya 2020

Powered by Hugo & Kiss.