Tuesday, June 28, 2016

Testing Webapps with Docker and Selenium

Functional testing of web applications within your continuous integration pipeline has huge advantages to streamlining delivery. Often considered the final vote before promotion of a build, function test suites constantly flex the customer use-cases and protect the user experience from regression. Testing every user workflow in an application sequentially takes time however -- a lot of time -- and only continues to grow.

In order to scale a functional test suite, the work must be divided into parallel processes with the full stack of each isolated from the rest. At the CI server, this amounts to having a centralized Master Job that invokes parameterized Child Jobs in a fork-join pattern (using Jenkins Multijob Plugin for example) to install the application and run the tests. With each Child Job being assigned a portion of the total tests, the eventual join provides the Master Job the ability to collect the test results and coverage metrics from all jobs for centralized reporting.

Docker provides a great platform for isolating the necessary components of each Child Job. If you are already using docker images as your managed build artifacts, this process is a piece of cake. If instead your artifacts are in another form you would need to install those artifacts into an isolated db/app server pair.

Thanks to the fine contributions of folks at SeleniumHQ, the docker-selenium project provides the docker images needed to setup a selenium grid with ease. A centralized Selenium Hub container listens on a specific host port for Selenium API calls from your tests (over JSONWP). From there, the hub forwards the commands to one of several Selenium Node containers that have registered with the Hub. The Node is responsible for managing a Selenium session, browser profile, opening the browser, and any other browser interactions.

First, to create the Selenium Hub:
[root@localhost ~]# docker run -d -P --name selenium-hub selenium/hub
44274517aebdf04a6517e604275fced2b0db134375c631241da2644dcdb077b5
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS                     NAMES
44274517aebd        selenium/hub                  "/opt/bin/entry_point"   10 seconds ago      Up 5 seconds        0.0.0.0:32770->4444/tcp   selenium-hub

Next, add selenium nodes for the desired browser/version (here Firefox 32.0):
[root@localhost ~]# docker run -d -P -e FIREFOX_VERSION=32.0 --link selenium-hub:hub --name selenium-node-ff32 selenium/node-firefox
cdd369b95f1a5c631241da2644dcdb077b5f04a6517e604275fced2b0db13437
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS                     NAMES
cdd369b95f1a        selenium/node-firefox         "/opt/bin/entry_point"   10 seconds ago      Up 5 seconds                                  selenium-node-ff32
44274517aebd        selenium/hub                  "/opt/bin/entry_point"   1 minute ago        Up 1 minute         0.0.0.0:32770->4444/tcp   selenium-hub

Keep in mind that by default each selenium-node container will run only one browser session at a time. You will want to scale your number of nodes to match (or more likely, exceed) the number of parallel Child Jobs in your CI server.

Finally, point your tests to the Selenum Hub URL http://192.168.1.50:32770/wd/hub. The Hub also provides a GUI console where you can review the configuration and monitor browser use at http://192.168.1.50:32770/grid/console.

Using lightweight containers like this to divide and conquer massive functional test suites will have a profound impact on your delivery pipeline, and help reign-in the developer feedback loop. Further, with Docker's ubiquitous nature, developers often run large Selenium Grids in their local environment and farm out test groups to it before checkin.

No comments:

Post a Comment