Testing Rails Application via Browsers with System Tests
Posted on by Gentaro "hibariya" Terada
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