├── .dockerignore ├── .gitignore ├── .travis.yml ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── bin ├── build ├── start ├── start-debug ├── stop └── test ├── dependabot.yml ├── docker-compose.dev.override.yml ├── docker-compose.yml ├── grid └── testrunner.rb ├── scripts └── ci │ ├── build.sh │ └── upgrade_docker_compose.sh └── spec ├── features └── example_spec.rb └── spec_helper.rb /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .DS_Store 3 | .git 4 | .gitignore 5 | .idea 6 | .travis.yml 7 | 8 | Dockerfile 9 | docker-compose.* 10 | LICENSE.txt 11 | README.md 12 | 13 | scripts/ci/ 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /spec/reports/ 2 | /spec/examples.txt 3 | /tmp/ 4 | 5 | .ruby-version 6 | .DS_Store 7 | .idea 8 | 9 | /docker-compose.override.yml 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | notifications: 3 | email: false 4 | 5 | services: 6 | - docker 7 | 8 | addons: 9 | apt: 10 | packages: 11 | - docker-ce 12 | 13 | env: 14 | - DOCKER_COMPOSE_VERSION=1.25.5 15 | 16 | branches: 17 | only: 18 | - master 19 | 20 | before_install: ./scripts/ci/upgrade_docker_compose.sh 21 | 22 | install: true 23 | 24 | script: ./scripts/ci/build.sh 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM instructure/ruby:2.7 2 | 3 | USER root 4 | 5 | WORKDIR /usr/src/app/ 6 | 7 | COPY --chown=docker:docker Gemfile Gemfile.lock ./ 8 | 9 | USER docker 10 | RUN bundle install --quiet --jobs 8 11 | USER root 12 | 13 | COPY --chown=docker:docker . ./ 14 | 15 | USER docker 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rspec' 4 | gem 'capybara' 5 | gem 'selenium-webdriver', '3.142.7' # we pin this version to match that of the selenium docker images (see docker-compose.yml) 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.8.0) 5 | public_suffix (>= 2.0.2, < 5.0) 6 | capybara (3.35.3) 7 | addressable 8 | mini_mime (>= 0.1.3) 9 | nokogiri (~> 1.8) 10 | rack (>= 1.6.0) 11 | rack-test (>= 0.6.3) 12 | regexp_parser (>= 1.5, < 3.0) 13 | xpath (~> 3.2) 14 | childprocess (3.0.0) 15 | diff-lcs (1.4.4) 16 | mini_mime (1.0.2) 17 | mini_portile2 (2.8.1) 18 | nokogiri (1.14.0) 19 | mini_portile2 (~> 2.8.0) 20 | racc (~> 1.4) 21 | public_suffix (4.0.6) 22 | racc (1.6.2) 23 | rack (3.0.4.2) 24 | rack-test (2.0.2) 25 | rack (>= 1.3) 26 | regexp_parser (2.0.3) 27 | rspec (3.10.0) 28 | rspec-core (~> 3.10.0) 29 | rspec-expectations (~> 3.10.0) 30 | rspec-mocks (~> 3.10.0) 31 | rspec-core (3.10.1) 32 | rspec-support (~> 3.10.0) 33 | rspec-expectations (3.10.1) 34 | diff-lcs (>= 1.2.0, < 2.0) 35 | rspec-support (~> 3.10.0) 36 | rspec-mocks (3.10.2) 37 | diff-lcs (>= 1.2.0, < 2.0) 38 | rspec-support (~> 3.10.0) 39 | rspec-support (3.10.2) 40 | rubyzip (2.0.0) 41 | selenium-webdriver (3.142.7) 42 | childprocess (>= 0.5, < 4.0) 43 | rubyzip (>= 1.2.2) 44 | xpath (3.2.0) 45 | nokogiri (~> 1.8) 46 | 47 | PLATFORMS 48 | ruby 49 | 50 | DEPENDENCIES 51 | capybara 52 | rspec 53 | selenium-webdriver (= 3.142.7) 54 | 55 | BUNDLED WITH 56 | 1.17.3 57 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [2016] [Michael Hargiss] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/mycargus/rspec-capybara-docker-grid.svg?branch=master)](https://travis-ci.com/mycargus/rspec-capybara-docker-grid) 2 | 3 | # A Dockerized Selenium Grid with RSpec and Capybara 4 | 5 | I built this project to quickly provision a dockerized environment for running 6 | UI tests against a dockerized app. It employs a dockerized [Selenium Grid] 7 | which yields a far more cost-effective CI solution compared to purchasing and 8 | maintaining dedicated machines. 9 | 10 | I've included bash scripts in the `bin/` directory as wrappers for the 11 | `docker-compose` commands. Hopefully, once you've completed the initial setup, 12 | you won't have to recall any docker commands. :smiley: 13 | 14 | Both RSpec and Capybara are automatically provisioned in the `testrunner` docker 15 | image. You can easily customize their configurations in `spec/spec_helper.rb`. 16 | 17 | ## Dependencies 18 | 19 | - a clone of this repo on your machine 20 | - [Docker] 21 | 22 | ## Setup 23 | 24 | Here's the default workflow when writing RSpec tests in this project: 25 | 26 | - `bin/build && bin/start && bin/test` 27 | - make changes to files inside the spec/ directory 28 | - verify changes with `bin/build && bin/start && bin/test` 29 | 30 | :sadtrombone: 31 | 32 | To make your life easier, first do this: 33 | 34 | ```bash 35 | cp docker-compose.dev.override.yml docker-compose.override.yml 36 | ``` 37 | 38 | Now any changes you make within this repo on your host file system will 39 | automatically show up in the `testrunner` docker container. Here's your new 40 | workflow: 41 | 42 | - `bin/build && bin/start && bin/test` 43 | - make changes to files inside the spec/ directory 44 | - `bin/test` 45 | 46 | :party: 47 | 48 | Some folks have reported file permission issues with this workflow, so YMMV. 49 | 50 | ### Where do I add my app? 51 | 52 | By default this project will use a bare-bones Sinatra web [app] as the system 53 | under test (SUT). If you want to replace that default web app with your own, 54 | open the `docker-compose.yml` file, find the `web` service configuration, and 55 | replace `mycargus/hello_docker_world:master` with your app's docker image label. 56 | 57 | For example: 58 | 59 | ```yaml 60 | web: 61 | image: my-app-under-test:master 62 | ``` 63 | 64 | If you're not sure how to create or pull a docker image, I recommend working 65 | through the official Docker tutorial located on their website. 66 | 67 | ## How do I execute the tests? 68 | 69 | Start the Selenium hub, the SUT, and the Selenium browser nodes: 70 | 71 | ```bash 72 | bin/start 73 | ``` 74 | 75 | Execute the tests with Rspec and Capybara from inside the `testrunner` container: 76 | 77 | ```bash 78 | bin/test 79 | ``` 80 | 81 | When you're done, stop and remove the docker containers: 82 | 83 | ```bash 84 | bin/stop 85 | ``` 86 | 87 | ## I want to see the app under test. How can I do that? 88 | 89 | If you're using the default web app provided, then open your browser and go to 90 | . 91 | 92 | If you're using your own web app, make sure to expose a port in your web app's 93 | Dockerfile. For example, if you have `EXPOSE 9887` in your web app's Dockerfile, 94 | then you can view it at . 95 | 96 | ## Can I view the Selenium grid console? 97 | 98 | Yep! After having started the Selenium hub and nodes (`bin/start`), open a 99 | browser and go to , then click the 'console' link. 100 | 101 | ## A test is failing. How do I debug it? 102 | 103 | Start the Selenium hub, the app under test, and the Selenium _debug_ browser 104 | nodes: 105 | 106 | ```bash 107 | bin/start-debug 108 | ``` 109 | 110 | View the chrome debug node via VNC (password: `secret`): 111 | 112 | ```bash 113 | open vnc://localhost:5900 114 | ``` 115 | 116 | View the firefox debug node via VNC (password: `secret`): 117 | 118 | ```bash 119 | open vnc://localhost:5901 120 | ``` 121 | 122 | Next execute the tests against the browser nodes and watch them run in the VNC 123 | window(s): 124 | 125 | ```bash 126 | bin/test 127 | ``` 128 | 129 | Again, once you're finished: 130 | 131 | ```bash 132 | bin/stop 133 | ``` 134 | 135 | [app]: https://github.com/mycargus/hello_docker_world 136 | [docker]: https://docs.docker.com/ 137 | [selenium grid]: https://github.com/SeleniumHQ/docker-selenium 138 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Builds the testrunner docker image for rspec and capybara 4 | 5 | set -e 6 | 7 | docker-compose build --pull testrunner 8 | -------------------------------------------------------------------------------- /bin/start: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Starts the Selenium hub, the app under test, and the Selenium browser nodes 4 | 5 | set -e 6 | 7 | ./bin/stop 8 | 9 | docker-compose up -d hub web node-firefox node-chrome 10 | -------------------------------------------------------------------------------- /bin/start-debug: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Starts the Selenium hub, the app under test, and the Selenium *debug* browser 4 | # nodes. 5 | 6 | set -e 7 | 8 | ./bin/stop 9 | 10 | docker-compose up -d hub web node-chrome-debug node-firefox-debug 11 | 12 | # wait for the selenium grid browser nodes to register with the selenium grid hub 13 | sleep 5 14 | -------------------------------------------------------------------------------- /bin/stop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # kills and removes docker containers 4 | 5 | set -e 6 | 7 | docker-compose kill && docker-compose rm -vf 8 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Starts testrunner execution of all specs. 4 | # 5 | # See the 'testrunner' service config in docker-compose.yml to see which script 6 | # the container runs. 7 | 8 | set -e 9 | 10 | docker-compose up testrunner 11 | -------------------------------------------------------------------------------- /dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: 'bundler' 5 | directory: '/' 6 | schedule: 7 | interval: 'monthly' 8 | commit-message: 9 | # Prefix all commit messages 10 | prefix: 'bundler' 11 | # Specify labels for dependency pull requests 12 | labels: 13 | - 'bundler' 14 | - 'dependencies' 15 | # Set to 0 to disable version updates 16 | open-pull-requests-limit: 2 17 | -------------------------------------------------------------------------------- /docker-compose.dev.override.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "3.7" 3 | 4 | services: 5 | testrunner: 6 | volumes: 7 | - ./:/usr/src/app/ 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | hub: 5 | image: selenium/hub:3.8.1 6 | environment: 7 | GRID_BROWSER_TIMEOUT: 10000 # 10 seconds 8 | GRID_NEW_SESSION_WAIT_TIMEOUT: 20000 9 | GRID_NODE_POLLING: 300 10 | GRID_TIMEOUT: 10000 11 | ports: 12 | - "4444:4444" 13 | networks: 14 | - rspec-capybara-docker-grid-network 15 | 16 | node-chrome: 17 | image: selenium/node-chrome:3.8.1 18 | environment: &SELENIUM_NODE_ENV 19 | HUB_HOST: hub 20 | HUB_PORT: 4444 21 | ports: 22 | - "5901:5900" 23 | volumes: 24 | - /dev/shm:/dev/shm 25 | networks: 26 | - rspec-capybara-docker-grid-network 27 | 28 | node-chrome-debug: 29 | image: selenium/node-chrome-debug:3.8.1 30 | environment: 31 | <<: *SELENIUM_NODE_ENV 32 | ports: 33 | - "5901:5900" 34 | volumes: 35 | - /dev/shm:/dev/shm 36 | networks: 37 | - rspec-capybara-docker-grid-network 38 | 39 | node-firefox: 40 | image: selenium/node-firefox:3.8.1 41 | environment: 42 | <<: *SELENIUM_NODE_ENV 43 | ports: 44 | - "5900:5900" 45 | volumes: 46 | - /dev/shm:/dev/shm 47 | networks: 48 | - rspec-capybara-docker-grid-network 49 | 50 | node-firefox-debug: 51 | image: selenium/node-firefox-debug:3.8.1 52 | environment: 53 | <<: *SELENIUM_NODE_ENV 54 | ports: 55 | - "5900:5900" 56 | volumes: 57 | - /dev/shm:/dev/shm 58 | networks: 59 | - rspec-capybara-docker-grid-network 60 | 61 | testrunner: 62 | build: 63 | context: . 64 | command: ruby grid/testrunner.rb 65 | environment: 66 | APP_HOST: web:80 67 | HUB_HOST: hub:4444 68 | networks: 69 | - rspec-capybara-docker-grid-network 70 | 71 | web: 72 | image: mycargus/hello_docker_world:master 73 | networks: 74 | - rspec-capybara-docker-grid-network 75 | 76 | networks: 77 | rspec-capybara-docker-grid-network: 78 | driver: bridge 79 | name: rspec-capybara-docker-grid-network 80 | -------------------------------------------------------------------------------- /grid/testrunner.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'capybara/rspec' 3 | require 'selenium-webdriver' 4 | 5 | module Grid 6 | class TestRunner 7 | BROWSERS = ['firefox', 'chrome'].freeze 8 | 9 | def run 10 | BROWSERS.each do |browser| 11 | setup(browser) 12 | yield 13 | end 14 | end 15 | 16 | def setup(browser) 17 | puts "\n\n############################" 18 | puts "Starting #{browser.capitalize} test run..." 19 | puts "############################\n\n" 20 | 21 | Capybara.register_driver :remote_browser do |app| 22 | capabilities = Selenium::WebDriver::Remote::Capabilities.send(browser.to_sym) 23 | 24 | Capybara::Selenium::Driver.new( 25 | app, 26 | browser: browser.to_sym, 27 | url: "http://#{ENV['HUB_HOST']}/wd/hub", 28 | desired_capabilities: capabilities 29 | ) 30 | end 31 | end 32 | end 33 | end 34 | 35 | testrunner = Grid::TestRunner.new 36 | testrunner.run do 37 | RSpec::Core::Runner.run ['spec'] 38 | RSpec.clear_examples 39 | end 40 | -------------------------------------------------------------------------------- /scripts/ci/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ev 4 | 5 | docker-compose pull 6 | docker-compose build --pull testrunner 7 | 8 | docker-compose up -d node-chrome node-firefox hub web 9 | 10 | # wait for the selenium grid browser nodes to register with the selenium grid hub 11 | sleep 10 12 | 13 | docker-compose run --rm testrunner && echo $? 14 | 15 | exit_code=$? 16 | echo "testrunner container exited with: ${exit_code}" 17 | 18 | if [[ "${exit_code}" == "0" ]]; then 19 | echo ":: It's working!" 20 | else 21 | echo ":: Test Failed :(" 22 | exit_code=1 23 | fi 24 | 25 | exit ${exit_code} 26 | -------------------------------------------------------------------------------- /scripts/ci/upgrade_docker_compose.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | sudo rm /usr/local/bin/docker-compose 6 | curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose 7 | chmod +x docker-compose 8 | sudo mv docker-compose /usr/local/bin 9 | -------------------------------------------------------------------------------- /spec/features/example_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Loading the app under test' do 4 | it 'displays the home page' do 5 | puts 'Capybara test started' 6 | 7 | visit '/' 8 | expect(page).to have_content 'Hello, Docker World!' 9 | 10 | puts 'Capybara test finished' 11 | puts '¸¸♬·¯·♩¸¸♪·¯·♫¸¸Happy Dance¸¸♬·¯·♩¸¸♪·¯·♫¸¸' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'capybara/rspec' 3 | require 'capybara/dsl' 4 | require 'selenium-webdriver' 5 | 6 | RSpec.configure do |config| 7 | config.include Capybara::DSL 8 | config.color = true 9 | config.tty = true 10 | config.default_formatter = 'doc' 11 | end 12 | 13 | Capybara.configure do |config| 14 | config.default_max_wait_time = 5 15 | 16 | config.app_host = "http://#{ENV['APP_HOST']}" 17 | config.run_server = false 18 | 19 | config.default_driver = :remote_browser 20 | config.javascript_driver = :remote_browser 21 | end 22 | --------------------------------------------------------------------------------