Dockerizing System Tests With Selenium

Table Of Contents

Introduction

We are spinning up a new application for some end users to enter data, so we need to build a robust testing system. Unit tests are a must, but those only test to ensure that all the classes, methods, and functions do what we expect. On top of that, we also need to verify that the web app as a whole looks and behaves how we hope it does to have a complete end-to-end testing apparatus. My goal for these first few spikes in the project was to find a tool for system testing and see if we could make it modular and easily automate it to not interfere with our workflow. I believe I found the solution. Selenium is a suite of tools for creating and automating browser tests, and I think it is what I was looking for. Let’s dive in.

Selenium WebDriver

Selenium is a multi-language suite of tools for creating browser-based automation tests. This tool will automate opening a browser, looking for a test condition, and reporting a pass or fail. Selenium is a whole suite of tools, but I decided to focus on Selenium Web Driver.

Much like in the name, the software “drives” the web browser. Much like humans, we can bring up the webpage in a browser, enter in data, look up tags, and utilize all these capabilities to write tests using the API Selenium provides. A test opens a browser, and like magic, many things happen.

My goal is to automate as much of this process as possible. So it would not be ideal to have a bunch of browsers launch with with ghosts running tests. A better solution was to run headless browsers using container images provided by Selenium. This solution gives us a container with a built-in web browser which we don’t see that our script runs a test against. The results we can see in the terminal.

As our project utilizes Docker, the easiest way to start is to grab the Docker image for the browser and start making some scripts. How this will look in the final solution is still to be determined, but I can replicate what I did for the sake of this write-up.

Setup

To begin, I set up a docker-compose.yml file with the following:

docker-compose.yml file

This gave us two containers, our browser using the Chrome-based selenium image and a Ruby-based container to run the script. Our new stack uses a lot of Ruby. The system container is built using an image from the docker file in the test directory.

Dockerfile for Ruby based system

This build handles installing the gems we need, including selenium-webdriver and the chromedriver-helper. Both are used to run our Web Driver script and utilize Chrome capabilities.

Gemfile

Finally, the last bit is the script itself.

Test Script

Running Tests

Let’s look at this script. My Ruby is rusty, but I tried my best. We set all our requirements, set a timer to make the thread sleep (more on that later), and then we write our test. In the script, we are writing the tests using the RSpec. RSpec is a domain-specific language built using Ruby to test Ruby code. We use this to test our behaviors using the describe and it blocks.

We start by defining the capabilities we are looking for; in this case, we are testing a chrome browser, so we need to specify that.

Then we use a variant of WebDriver called the Remote WebDriver. Remote Driver is written identically to WebDriver, just with the caveat of the driver logic looking for the browser in another system. Here we set the address for the Chrome Selenium container so that our WebDriver knows to look for this remote machine to run the test against.

Both containers are in the same network provided by Docker Compose, so we use the hostnames. Also, note that we are mapping port 4444 as the WebDriver will use this port to communicate.

We then set the driver to navigate to our chosen website as an action. The following line sets what we expect to see using RSpec’s handy expect function. We expect the page’s title to be equal to a string we provide and fail if the title is mismatched. Then we’ll just take a screenshot and save the image to a local drive using one of the built-in save_screenshot functions. Finally, and this was important, use the quit function.

We run this just by running docker-compose up, and we can see the test passed as going to Google does indeed yield a page title of “Google.”

Test results in terminal

We can also see the screenshot taken from the remote browser.

Resulting image from test

Notes

Using the quit function was essential to kill the connection, but it also closed the browser we can’t see. Additionally, I mentioned I would come back to using the sleep function in Ruby. It turns out using the depends_on feature in Docker Compose is not enough to ensure services are available to each other. When I began running the network, the remote driver would continually fail to connect. It turns out we just needed a moment for everything to boot. Docker recommends creating a wait script but pausing the thread for a moment worked just as well.

Conclusion

This was a pretty simple example, but it answered my questions. We can use this to test website behavior, make it modular, and automate the tests. That checks enough boxes for me to keep going down this rabbit hole. The next goal is to develop an automated solution and possibly clean up the deployment a little. I’m thinking of having these two containers not run with the rest of the project and boot up the containers using a bash script specifically for testing. I might write about that too.

-George