Menu
Alfredo Motta
  • Home
  • Newsletter
  • Articles
  • About me
  • Events
  • Books
  • Research
  • Learn to code in London
Alfredo Motta

Dockerized Rails Capybara tests on top of Selenium

Posted on May 31, 2016May 18, 2022

If you use Docker to deploy your Rails application you may want to use the same infrastructure to run your tests.

However the setup of your Selenium browser tests is far from obvious with Rails and Docker and may generate some confusion 1 2 3 4 5.

The short answer is available in this repository on Github. For the long answer keep reading this blog post for a step by step tutorial!

Dockerize Rails

Let’s start by creating a Docker container to run your application. First we create a simple rails hello world app with the following commands:

Create the rails app
1
2
# In your console
rails new test_app

Our hello world route
Ruby
1
2
# routes.rb
root 'hello_world#index'

The hello world controller
Ruby
1
2
3
# app/controller/hello_world_controller.rb
class HelloWorldController < ApplicationController
end

1
2
# app/views/hello_world/index.html.erb
Hello World

then we add the following to your Gemfile :

Add capybara and the selenium webdriver gem
Ruby
1
2
3
4
5
group :test do
  gem 'capybara'
  gem 'rspec-rails'
  gem 'selenium-webdriver'
end

Run bundle install  and bundle exec rails server  to make sure that our app is accessible at http://locahost:3000 . Also run  rails generate rspec:install to initialize the spec directory.

We will now use the  Gemfile  and the  Gemfile.lock to set up of our Docker container. Let’s create a Dockerfile in the root of the project with the following:

Dockerfile to install rails and the app dependencies
1
2
3
4
5
6
7
8
9
10
FROM ruby:latest
RUN apt-get update -qq && apt-get install -y build-essential nodejs
 
RUN mkdir /app
WORKDIR /app
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install
 
CMD ["rails", "server", "-b", "0.0.0.0"]

The Dockerfile downloads the Ruby image called ruby:latest  and installs  build-essential ,  nodejs and the gems specified in our Gemfile including Rails.

Connect to a remote Selenium server

To run our tests we need a Selenium server running somewhere. By default the  capybara  and  selenium-webdriver gems spawn the selenium server automatically for us when we run our specs. But nothing stops us to select a different remote selenium server! Let’s do that.

First go to http://www.seleniumhq.org/download/, download the latest tar and run the server with:

1
java -jar selenium-server-standalone-.jar

Now connect Capybara with the Selenium server we just started. Inside  rails_helper.rb add the following:

Ruby
1
2
3
4
Capybara.javascript_driver = :selenium_remote_firefox
Capybara.register_driver "selenium_remote_firefox".to_sym do |app|
  Capybara::Selenium::Driver.new(app, browser: :remote, url: "http://localhost:4444/wd/hub", desired_capabilities: :firefox)
end

This will instruct  Capybara to connect to the remote Selenium instance we are running. Finally add to the RSpec configuration the following lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
RSpec.configure do |config|
  [...other conf...]
 
  config.before(:each) do
    Capybara.app_host = "http://localhost:#{Capybara.current_session.server.port}"
  end
 
  config.after(:each) do
    Capybara.reset_sessions!
    Capybara.use_default_driver
    Capybara.app_host = nil
  end
end

This will tell the  Selenium server that the app it must visit is the one spawned by Capybara in our localhost. If you run  bundle exec rspec you should see the following in the Selenium server output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
19:02:02.207 INFO - Selenium Server is up and running
19:09:42.174 INFO - Executing: [new session: Capabilities [{rotatable=false, nativeEvents=false, browserName=firefox, takesScreenshot=true, javascriptEnabled=true, version=, platform=ANY, cssSelectorsEnabled=true}]])
19:09:42.198 INFO - Creating a new session for Capabilities [{rotatable=false, nativeEvents=false, browserName=firefox, takesScreenshot=true, javascriptEnabled=true, version=, platform=ANY, cssSelectorsEnabled=true}]
19:09:46.069 INFO - Done: [new session: Capabilities [{rotatable=false, nativeEvents=false, browserName=firefox, takesScreenshot=true, javascriptEnabled=true, version=, platform=ANY, cssSelectorsEnabled=true}]]
19:09:46.081 INFO - Executing: [get: http://localhost:57193/])
19:09:46.940 INFO - Done: [get: http://localhost:57193/]
19:09:46.953 INFO - Executing: [find elements: By.xpath: /html])
19:09:47.046 INFO - Done: [find elements: By.xpath: /html]
19:09:47.059 INFO - Executing: [is displayed: 0 [[FirefoxDriver: firefox on MAC (e536aaa9-930d-e544-849f-35739e1519c5)] -> xpath: /html]])
19:09:47.088 INFO - Done: [is displayed: 0 [[FirefoxDriver: firefox on MAC (e536aaa9-930d-e544-849f-35739e1519c5)] -> xpath: /html]]
19:09:47.094 INFO - Executing: [get text: 0 [[FirefoxDriver: firefox on MAC (e536aaa9-930d-e544-849f-35739e1519c5)] -> xpath: /html]])
19:09:47.108 INFO - Done: [get text: 0 [[FirefoxDriver: firefox on MAC (e536aaa9-930d-e544-849f-35739e1519c5)] -> xpath: /html]]
19:09:47.122 INFO - Executing: [delete all cookies])
19:09:47.136 INFO - Done: [delete all cookies]
19:09:47.142 INFO - Executing: [get: about:blank])
19:09:47.178 INFO - Done: [get: about:blank]
19:09:47.184 INFO - Executing: [find elements: By.xpath: /html/body/*])
19:09:47.194 INFO - Done: [find elements: By.xpath: /html/body/*]
19:09:47.201 INFO - Executing: [delete session: bde150ad-b376-45ea-b46b-5e6c82af97fa])
19:09:47.349 INFO - Done: [delete session: bde150ad-b376-45ea-b46b-5e6c82af97fa]

The server correctly received the calls from Capybara, visited the test app and completed the test successfully.

Dockerize Selenium

The next step is to dockerize our Selenium server to run our browser tests inside a Docker container instead of our localhost . First, we need to create a docker-compose  file to run our application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '2'
services:
  test:
    build: .
    command: bundle exec rspec
    container_name: test_app
    environment:
      - SELENIUM_REMOTE_HOST=selenium
    volumes:
      - .:/app
    depends_on:
      - selenium
  selenium:
    image: selenium/standalone-firefox
    container_name: selenium

The app has two services running inside the corresponding containers. The test service is running the rails container specified in our Dockerfile . The build entry specifies the location of the  Dockerfile. The command entry specifies the command to execute when the service is invoked. In our case we want to run our tests. container_name,  environment and depends_on  are self explicative. Finally the volumes instruction link our code into the container. Finally, the Selenium service is pretty easy to set up because there is a dockerized version of the server ready for us in  selenium/standalone-firefox !

Let’s now go back to our rails_helper . We need the following to use the Selenium remote server:

Ruby
1
2
3
4
5
6
7
8
9
10
if ENV['SELENIUM_REMOTE_HOST']
  Capybara.javascript_driver = :selenium_remote_firefox
  Capybara.register_driver "selenium_remote_firefox".to_sym do |app|
    Capybara::Selenium::Driver.new(
      app,
      browser: :remote,
      url: "http://#{ENV['SELENIUM_REMOTE_HOST']}:4444/wd/hub",
      desired_capabilities: :firefox)
  end
end

Notice that Docker by default makes the Selenium service available at the  http://selenium host using the name specified in the docker-compose file. The port is the default one.

The RSpec configuration changes as follows:

Ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
RSpec.configure do |config|
  [...other config...]
 
  config.before(:each) do
    if /selenium_remote/.match Capybara.current_driver.to_s
      ip = `/sbin/ip route|awk '/scope/ { print $9 }'`
      ip = ip.gsub "\n", ""
      Capybara.server_port = "3000"
      Capybara.server_host = ip
      Capybara.app_host = "http://#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}"
    end
  end
 
  config.after(:each) do
    Capybara.reset_sessions!
    Capybara.use_default_driver
    Capybara.app_host = nil
  end
end

You may be surprised by these four magic lines:

Ruby
1
2
3
4
ip = `/sbin/ip route|awk '/scope/ { print $9 }'`
ip = ip.gsub "\n", ""
Capybara.server_port = "3000"
Capybara.server_host = ip

these instructions retrieve the ip address of the container and pass it to Capybara and in turn to Selenium such that it will be able to retrieve the pages to test. In the basic Selenium remote example this configuration was not necessary because our test app was running on our localhost.

Lunch the tests!

To run our tests at this point all we need is:

1
docker-compose up

and you should see something similar to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
:capybara-on-dockerized-selenium (master *+)$ docker-compose up
Creating network "capybaraondockerizedselenium_default" with the default driver
Creating selenium
Creating test_app
Attaching to selenium, test_app
selenium    | 11:33:18.295 INFO - Launching a standalone Selenium Server
selenium    | 11:33:18.413 INFO - Java: Oracle Corporation 25.03-b03
selenium    | 11:33:18.417 INFO - OS: Linux 4.4.8-boot2docker amd64
selenium    | 11:33:18.471 INFO - v2.53.0, with Core v2.53.0. Built from revision 35ae25b
selenium    | 11:33:18.825 INFO - Driver provider org.openqa.selenium.ie.InternetExplorerDriver registration is skipped:
selenium    | registration capabilities Capabilities [{ensureCleanSession=true, browserName=internet explorer, version=, platform=WINDOWS}] does not match the current platform LINUX
selenium    | 11:33:18.836 INFO - Driver provider org.openqa.selenium.edge.EdgeDriver registration is skipped:
selenium    | registration capabilities Capabilities [{browserName=MicrosoftEdge, version=, platform=WINDOWS}] does not match the current platform LINUX
selenium    | 11:33:18.837 INFO - Driver class not found: com.opera.core.systems.OperaDriver
selenium    | 11:33:18.840 INFO - Driver provider com.opera.core.systems.OperaDriver is not registered
selenium    | 11:33:18.850 INFO - Driver provider org.openqa.selenium.safari.SafariDriver registration is skipped:
selenium    | registration capabilities Capabilities [{browserName=safari, version=, platform=MAC}] does not match the current platform LINUX
selenium    | 11:33:18.860 INFO - Driver class not found: org.openqa.selenium.htmlunit.HtmlUnitDriver
selenium    | 11:33:18.861 INFO - Driver provider org.openqa.selenium.htmlunit.HtmlUnitDriver is not registered
selenium    | 11:33:19.151 INFO - RemoteWebDriver instances should connect to: http://127.0.0.1:4444/wd/hub
selenium    | 11:33:19.157 INFO - Selenium Server is up and running
test_app    | Running via Spring preloader in process 17
test_app    | /app/db/schema.rb doesn't exist yet. Run `rake db:migrate` to create it, then try again. If you do not intend to use a database, you should instead alter /app/config/application.rb to limit the frameworks that will be loaded.
selenium    | 11:33:33.592 INFO - Executing: [new session: Capabilities [{rotatable=false, nativeEvents=false, browserName=firefox, takesScreenshot=true, javascriptEnabled=true, version=, platform=ANY, cssSelectorsEnabled=true}]])
selenium    | 11:33:33.613 INFO - Creating a new session for Capabilities [{rotatable=false, nativeEvents=false, browserName=firefox, takesScreenshot=true, javascriptEnabled=true, version=, platform=ANY, cssSelectorsEnabled=true}]
selenium    | 11:33:37.954 INFO - Done: [new session: Capabilities [{rotatable=false, nativeEvents=false, browserName=firefox, takesScreenshot=true, javascriptEnabled=true, version=, platform=ANY, cssSelectorsEnabled=true}]]
selenium    | 11:33:37.966 INFO - Executing: [get: http://172.18.0.3:3000/])
selenium    | 11:33:39.078 INFO - Done: [get: http://172.18.0.3:3000/]
selenium    | 11:33:39.105 INFO - Executing: [find elements: By.xpath: /html])
selenium    | 11:33:39.289 INFO - Done: [find elements: By.xpath: /html]
selenium    | 11:33:39.303 INFO - Executing: [is displayed: 0 [[FirefoxDriver: firefox on LINUX (178058bb-7190-472a-b65c-69b21bbdff3e)] -> xpath: /html]])
selenium    | 11:33:39.402 INFO - Done: [is displayed: 0 [[FirefoxDriver: firefox on LINUX (178058bb-7190-472a-b65c-69b21bbdff3e)] -> xpath: /html]]
selenium    | 11:33:39.410 INFO - Executing: [get text: 0 [[FirefoxDriver: firefox on LINUX (178058bb-7190-472a-b65c-69b21bbdff3e)] -> xpath: /html]])
selenium    | 11:33:39.452 INFO - Done: [get text: 0 [[FirefoxDriver: firefox on LINUX (178058bb-7190-472a-b65c-69b21bbdff3e)] -> xpath: /html]]
selenium    | 11:33:39.464 INFO - Executing: [delete all cookies])
selenium    | 11:33:39.485 INFO - Done: [delete all cookies]
selenium    | 11:33:39.494 INFO - Executing: [get: about:blank])
selenium    | 11:33:39.576 INFO - Done: [get: about:blank]
selenium    | 11:33:39.588 INFO - Executing: [find elements: By.xpath: /html/body/*])
selenium    | 11:33:39.617 INFO - Done: [find elements: By.xpath: /html/body/*]
test_app    | .
test_app    |
test_app    | Finished in 7.03 seconds (files took 13.39 seconds to load)
test_app    | 1 example, 0 failures
test_app    |
selenium    | 11:33:39.631 INFO - Executing: [delete session: 859dc787-21dc-4715-80f2-d0305f06bce3])
selenium    | 11:33:39.758 INFO - Done: [delete session: 859dc787-21dc-4715-80f2-d0305f06bce3]
test_app exited with code 0

Conclusion

In this blog post I have presented how to run your Selenium tests in a Docker environment. For people new to Docker it may be quite hard to fit all the pieces together and I hope this will be helpful to some fellow engineer.

If you enjoyed this blog post you can also follow me on twitter.
Looking forward to hearing your comments!

References

  1. StackOverflow: Dockerized selenium browser cannot access Capybara test url
  2. StackOverflow: How to configure Capybara to run tests in a dockerized Selenium Grid?
  3. A gist file with the same setup presented here
  4. Another blog post on the same topic
  5. Docker compose tutorial

3 thoughts on “Dockerized Rails Capybara tests on top of Selenium”

  1. Pingback: ‘In Container’ Rails Development With Docker | Varyonic
  2. Jaron Gao says:
    November 14, 2016 at 8:52 am

    This was very helpful. Thank you!

    Reply
    1. mottalrd says:
      November 15, 2016 at 7:56 am

      I am glad you found it useful!

      Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Answer the question * Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Tweets by mottalrd

Recent Comments

  • mottalrd on Not So Random Software #21 – Technical debt
  • mottalrd on Not So Random Software #24 – Climate Change Tech
  • mottalrd on A/B Testing, from scratch
  • mottalrd on A/B Testing, from scratch
  • Karim on A/B Testing, from scratch
©2023 Alfredo Motta | Powered by SuperbThemes & WordPress