How to configure Capybara to run tests in a dockerized Selenium Grid

capybaradinghydocker-composerspecselenium-grid

I have a suite of tests that I want to execute in a dockerized Selenium Grid. The tests are written in Ruby using RSpec and Capybara. Also worth noting: I'm using dinghy as a wrapper for docker-machine.

A couple weeks ago I built a proof of concept but with Nightwatch instead of RSpec+Capybara. That works fine but getting Capybara to work with the dockerized Selenium Grid has proven difficult.

I've tried numerous configurations without success. I think the closest I've gotten to a successful configuration is the following…

# docker-compose.yml

web:
  image: web:latest  # built on my machine
  environment:
    VIRTUAL_HOST: app.under.test

rspec:
  build: .
  dockerfile: Dockerfile
  environment:
    APP_HOST: app.under.test
  volumes:
    - .:/usr/src/app
  links:
    - web
    - hub

hub:
  image: selenium/hub:latest
  environment:
    VIRTUAL_HOST: selenium.hub.docker
  ports:
    - 4444:4444

node-firefox:
  image: selenium/node-firefox:latest
  environment:
    VIRTUAL_HOST: firefox.docker
  links:
    - hub

node-chrome:
  image: selenium/node-chrome:latest
  environment:
    VIRTUAL_HOST: chrome.docker
  links:
    - hub
# Dockerfile for the rspec image

FROM instructure/ruby-passenger:2.3

USER root

ENV APP_HOME /usr/src/app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

COPY Gemfile Gemfile.lock $APP_HOME/
RUN chown -R docker:docker $APP_HOME

USER docker
RUN bundle install --quiet --jobs 8
USER root

COPY . $APP_HOME
RUN chown -R docker:docker $APP_HOME

USER docker
# spec/example_test.rb:

require 'rspec'
require 'capybara'
require 'capybara/dsl'
require 'selenium-webdriver'

RSpec.configure do |config|
  config.include Capybara::DSL
end

def setup
  url = 'http://selenium.hub.docker'
  capabilities = Selenium::WebDriver::Remote::Capabilities.firefox
  Capybara.app_host = ENV['APP_HOST']

  Capybara.register_driver :remote_browser do |app|
    Capybara::Selenium::Driver.new(
      app,
      :browser => :remote,
      url: url,
      desired_capabilities: capabilities
    )
  end

  Capybara.default_driver = :remote_browser
  Capybara.javascript_driver = :remote_browser
end

describe 'This is an example' do
  it 'and it works' do
    setup
    visit '/'
    expect(page.title).to eq 'Home'
  end
end

But this ^ configuration yields the following error upon starting the tests:

1) This is an example and it works
     Failure/Error: visit '/'

 Selenium::WebDriver::Error::WebDriverError:
   unexpected response, code=200, content-type="text/html"
   <html><head><title>Selenium Grid2.0 help</title></head><body>You are using grid 2.52.0<br>Find help on the official selenium wiki : <a href='https://github.com/SeleniumHQ/selenium/wiki/Grid2' >more help here</a><br>default monitoring page : <a href='/grid/console' >console</a></body></html>
 # /home/docker/.gem/ruby/2.3.0/gems/selenium-webdriver-2.52.0/lib/selenium/webdriver/remote/http/common.rb:85:in `create_response'
 # /home/docker/.gem/ruby/2.3.0/gems/selenium-webdriver-2.52.0/lib/selenium/webdriver/remote/http/default.rb:90:in `request'
 # /home/docker/.gem/ruby/2.3.0/gems/selenium-webdriver-2.52.0/lib/selenium/webdriver/remote/http/common.rb:59:in `call'
 # /home/docker/.gem/ruby/2.3.0/gems/selenium-webdriver-2.52.0/lib/selenium/webdriver/remote/bridge.rb:645:in `raw_execute'
 # /home/docker/.gem/ruby/2.3.0/gems/selenium-webdriver-2.52.0/lib/selenium/webdriver/remote/bridge.rb:123:in `create_session'
 # /home/docker/.gem/ruby/2.3.0/gems/selenium-webdriver-2.52.0/lib/selenium/webdriver/remote/bridge.rb:87:in `initialize'
 # /home/docker/.gem/ruby/2.3.0/gems/selenium-webdriver-2.52.0/lib/selenium/webdriver/common/driver.rb:59:in `new'
 # /home/docker/.gem/ruby/2.3.0/gems/selenium-webdriver-2.52.0/lib/selenium/webdriver/common/driver.rb:59:in `for'
 # /home/docker/.gem/ruby/2.3.0/gems/selenium-webdriver-2.52.0/lib/selenium/webdriver.rb:86:in `for'
 # /home/docker/.gem/ruby/2.3.0/gems/capybara-2.6.2/lib/capybara/selenium/driver.rb:13:in `browser'
 # /home/docker/.gem/ruby/2.3.0/gems/capybara-2.6.2/lib/capybara/selenium/driver.rb:45:in `visit'
 # /home/docker/.gem/ruby/2.3.0/gems/capybara-2.6.2/lib/capybara/session.rb:232:in `visit'
 # /home/docker/.gem/ruby/2.3.0/gems/capybara-2.6.2/lib/capybara/dsl.rb:51:in `block (2 levels) in <module:DSL>'
 # ./spec/example_test.rb:31:in `block (2 levels) in <top (required)>'

Any ideas? What am I missing?

Update

Got it!

def setup
  url = 'http://selenium.hub.docker/wd/hub'
  capabilities = Selenium::WebDriver::Remote::Capabilities.firefox
  Capybara.app_host = "http://#{ENV['APP_HOST']}"
  Capybara.run_server = false

  Capybara.register_driver :remote_browser do |app|
    Capybara::Selenium::Driver.new(
      app,
      :browser => :remote,
      url: url,
      desired_capabilities: capabilities
    )
  end

  Capybara.default_driver = :remote_browser
  Capybara.javascript_driver = :remote_browser
end

The key—in addition to Capybara.run_server = false and prepending http:// to the app_host—was specifying /wd/hub on the url.

Working solution here.

Best Answer

For a capybara setup there are a few things you need to worry about -

1 - where capybara connects to the browser - in most peoples cases thats locally but you're using remote and you appear to have that correctly configured

2 - where the browser attempts to connect to - the base url for that is set by Capybara.app_host - so Capybara.app_host = "http://<ip/domain name the app will be running on> - you have that set to ENV['APP_HOST'] which is set to app.under.test - I'm assuming thats an ip/domain name that resolves to an accessible interface on the machine capybara is being run on?

3 - where Capybara binds the app being tested - This is set through Capybara.server_host and Capybara.server_port - server_host defaults to 127.0.0.1 and the port is picked randomly. This is most likely where your issue lies since 127.0.0.1 on the machine running Capybara is usually not accessible from other machines You'll need to change that to be the externally available ip that matches to app.under.test from #2. You may also need to set the port if your firewall configuration requires it.

Related Topic