Build a Docker Image for Ruby, Watir Webdriver, Shopify API, Chrome, and Firefox
Docker containers have become a really convenient way of running an automated test suite. Containers can be quickly spun up, easily torn down, and since you only pay for the time they’re running, can be cheaply hosted in the cloud.
Lets take a look at how to build a Docker container that includes everything we need to run a UI test framework using Ruby and Selenium WebDriver.
When building a container, Docker follows instructions within a file called a Dockerfile. The Dockerfile outlines the individual commands needed to install and setup all the stuff we want in our container.
Start With a Base Image
There are a ton of different Unix distributions you can use to build your Docker container. Even though running your Docker container can be cheap when run in the cloud, or free when run within your own data center or test lab, you still need to “pay” a resource price when it comes to things like bandwidth to copy the container file and time to start it.
It’s in our best interest to keep our Docker container as small as possible. Alpine Linux is a relatively small Linux distribution. It has a small footprint that makes it perfect for use with Docker because it helps to keep the container file size small.
Next we need to add specific Alpine package repositories. The stock Alpine image only knows about the repositories for the Alpine OS itself. If you want to install third-party packages, you’ll have to set the location of those repositories yourself.
If you’re used to using Ubuntu, you’ll know that the default package manager is apt-get. In the Alpine Linux world, the default package manager is called apk.
# Point to specific Alpine Package repositories for Chromium and Firefox. RUN apk update && apk upgrade \ && echo @latest-stable http://nl.alpinelinux.org/alpine/latest-stable/community >> /etc/apk/repositories \ && echo @latest-stable http://nl.alpinelinux.org/alpine/latest-stable/main >> /etc/apk/repositories \ && echo @edge-testing http://dl-cdn.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories \ && echo @edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories
Next lets install the Ruby development environment.
# Install Ruby dev environment RUN apk add --no-cache build-base RUN apk add --no-cache libffi-dev RUN apk add --no-cache ruby-dev RUN apk add --no-cache ruby-rdoc RUN apk add --no-cache ruby-webrick@latest-stable RUN apk add --no-cache zlib-dev
Install The Browsers
When running your tests within Docker, you won’t have access to a windowed Desktop. This forces you to run your test in headless mode.
The only two desktop browsers you can install in Docker that support headless mode are Firefox and Chrome.
# Install Chromium RUN apk add --no-cache chromium-chromedriver RUN apk add --no-cache chromium@latest-stable RUN apk add --no-cache harfbuzz@latest-stable RUN apk add --no-cache nss@latest-stable # Install Firefox and the Gechodriver RUN apk add --no-cache icu-libs@edge-main RUN apk add --no-cache firefox@edge-testing RUN wget https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux64.tar.gz && \ tar -zxvf geckodriver-v0.24.0-linux64.tar.gz && \ mv ./geckodriver /usr/local/bin/ && \ chmod a+x /usr/local/bin/geckodriver
Ruby Gems Needed By Our Tests
The test framework we built at Rewind needs to interact with Shopify and BigCommerce. We do this using their respective APIs.
The UI tests also use Selenium WebDriver. In this case, we’re using the Watir library, which wraps WebDriver and offers improved usability.
Lets run the Ruby gem commands to install those dependencies.
# Install Ruby gems required by the test framework. RUN gem install rake RUN gem install minitest RUN gem install minitest-reporters RUN gem install watir:6.16 RUN gem install shopify_api RUN gem install bigcommerce RUN gem install bigdecimal RUN gem install aws-sdk-s3
Now lets delete any leftover packages and other temporary files from inside the image.
# Clean up leftovers to help keep the final image small. RUN rm -rf /var/lib/apt/lists/* \ /var/cache/apk/* \ /usr/share/man \ /tmp/*
Include Our Ruby Test Scripts
Lets pull into the container the WebDriver tests and any Ruby code we’ve written. This assumes the Dockerfile is sitting in the same directory as our WebDriver tests.
This command will copy a local directory called ‘webdriver-tests‘ to the root (/) of the Docker container.
# Copy the automated tests into the container. ADD . webdriver-tests
Create a Unix User
We want to avoid running our tests as the Unix ‘root’ user, so lets create a user our Ruby tests will run as. Lets call the user ‘webdriver‘ and make him the owner of the ‘webdriver-tests’ directory.
# Add 'webdriver' as a user RUN adduser -D webdriver \ && chown -R webdriver:webdriver /webdriver-tests # Switch to non-privileged user 'webdriver' USER webdriver WORKDIR /webdriver-tests
Once we’ve switched to the ‘webdriver’ user, we need to set some environment variables to help the Ruby code find the Chrome browser.
# Set the environment variables for the Chromium browser. ENV CHROME_BIN=/usr/bin/chromium-browser \ CHROME_PATH=/usr/lib/chromium/
The Final Product
You’ll notice how I was using a bunch of individual RUN commands when adding each package. This is pretty inefficient and causes the Docker image to be larger than it needs to be.
So why do it? Docker caches the result of each line. When running the build command on the Dockerfile, Docker will check its cache if it has previously run that command. If so, it will simply apply the cached result of a previous run for that command. This reduces the time it takes to build the Docker file. This is very useful when debugging problems with your Dockerfile.
If we had used a single RUN command to install all of our packages, Docker would have had to reinstall each package on that line even if a single item had been changed.
Once we’ve got our Dockerfile producing the container that we want, we can condense all of those RUN commands into just a few lines. This will reduce the number of cached layers further reducing the size of our container.
Here’s our optimized Dockerfile.
|# Point to specific Alpine Package repositories for Chromium and Firefox.|
|RUN apk update && apk upgrade \|
|&& echo @latest-stable http://nl.alpinelinux.org/alpine/latest-stable/community >> /etc/apk/repositories \|
|&& echo @latest-stable http://nl.alpinelinux.org/alpine/latest-stable/main >> /etc/apk/repositories \|
|&& echo @edge-testing http://dl-cdn.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories \|
|&& echo @edge-main http://dl-cdn.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories|
|# Install Ruby dev environment|
|RUN apk add --no-cache build-base libffi-dev ruby-dev ruby-rdoc ruby-webrick@latest-stable zlib-dev|
|# Install Chromium|
|RUN apk add --no-cache chromium-chromedriver chromium@latest-stable harfbuzz@latest-stable nss@latest-stable|
|# Install Firefox and the Gechodriver|
|RUN apk add --no-cache icu-libs@edge-main firefox@edge-testing|
|RUN wget https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux64.tar.gz && \|
|tar -zxvf geckodriver-v0.24.0-linux64.tar.gz && \|
|mv ./geckodriver /usr/local/bin/ && \|
|chmod a+x /usr/local/bin/geckodriver|
|# Install Ruby gems required by the test framework.|
|RUN gem install rake minitest minitest-reporters watir:6.16 shopify_api bigdecimal aws-sdk-s3|
|# Clean up leftovers to help keep the final image small.|
|RUN rm -rf /var/lib/apt/lists/* \|
|# Copy the automated tests into the container.|
|ADD . webdriver-tests|
|# Add 'webdriver' as a user|
|RUN adduser -D webdriver \|
|&& chown -R webdriver:webdriver /webdriver-tests|
|# Switch to non-privileged user 'webdriver'|
|# Set the environment variables for the Chromium browser.|
|ENV CHROME_BIN=/usr/bin/chromium-browser \|