Tuesday, December 15, 2015

Tech Post: Docker, PhantomJS, Selenium on a Jenkins host for pre-production website testing

I recently spent days trying to solve a technical problem when attempting to auto-test a website on my Jenkins server combined with Selenium, Selenium Grid, or PhantomJS on the same machine. 

I had previously been using a different approach for my automated testing and thought I'd do something new for this site :->


I figured out the solution and decided I would share it here should someone find themselves in the same or similar situation.

These instructions apply to Jenkins but will likely also apply for Bamboo, TeamCity or your CI server of choice.

Scenario:  

  • When testing on a developer workstation, the site is started up and tested at localhost: (127.0.0.1).

  • A Jenkins server which runs all tests locally instead of on slaves (the same problem would likely occur on a slave).

  • On a check-in of code, load the site up at localhost on the Jenkins server (as you would expect)

  • Attempt to run Selenium or PhantomJS in a Docker container......

-- FAIL --

  • Spent days researching "headless" build capabilities
  • Spent even more days reading about countless issues with dockerized containers working with Selenium. 

As it turns out, the problem is not related to Docker and Firewalls but the nature of containers.  

The easy (short) answer.....

If you set your tests to run at localhost: or 127.0.0.1, they will work no problems on a developer machine, but when the Dockerized container tries to reach localhost it will look to it's own localhost versus the CI server's localhost... 

Change the tests to use the local IP address of the host instead. I used an environment variable to do this on the host. 

The following sample makes some assumptions (modify as needed)

  • Jenkins Server
  • NodeJS
  • Docker
  • PhamtomJS (the same holds true for Docker versions of Selenium)

Docker:

docker run -d -t -p 4444:4444 --name phantom servebox/phantomjs phantomjs \
--webdriver=127.0.0.1:4444

To see the running container....

docker ps should show you something like.....

aa71e19beecd        servebox/phantomjs   "phantomjs --webdrive"   2 minutes ago       Up Less than a second   0.0.0.0:4444->4444/tcp   phantom

You can now start and stop the container by executing either  (making it easy to control from Jenkins)

docker stop phantom
docker start phantom

Environment Variable Setup (linux)

export TEST_IP_ADDRESS="x.x.x.x" (replace with host address)



NodeJS: (helper.js)


global.testPort = 9005;
var url = "http://" + process.env.TEST_IP_ADDRESS + ":" + testPort;
global.url = url;

NodeJS: (mocha test) 


var webdriver = require('selenium-webdriver');
var expect = require('chai').expect;

var driver = new webdriver.Builder().usingServer('http://127.0.0.1:4444/wd/hub').withCapabilities({
    'browserName': 'phantomjs'}).build();

describe('Click On CEC Logo should bring us to the SA CEC Page', function () {

    "use strict";
    it('should work', function (done) {

        driver.get(url);  //set in helper.js as global
        driver.findElement(webdriver.By.id('CECLink'))
                .then(function () {
                    return driver.findElement(webdriver.By.id('CECLink')).getAttribute("href");                })
                .then(function (linkString) {
                    expect(linkString).equals('https://www.scrumalliance.org/certifications/cec-certification');                    driver.quit();                })
                .then(function () {
                    done();                });    });});
(code not perfectly formatted in this blog post).


Jenkins:

Create a job step to execute a shell with the following commands....
(where x.x.x.x. is the ip address where the LOCAL TESTABLE WEB PAGE is located in pre-production).

docker start phantom
export TEST_IP_ADDRESS="x.x.x.x"      
npm run jenkins-mocha 
docker stop phantom


What Happens:


  • Jenkins launches the docker container called phantom that was previously defined
  • Docker starts up an instance of the phantomjs webdriver at port 4444 on the localhost (in a container)
  • NodeJS starts up the website locally (code not shown in this example) . This site starts at IP address x.x.x.x
  • NodeJS (mocha) loads Webdriver and requests PhantomJS from the Container
  • The Container loads PhantomJS and gives a session for Webdriver
  • NodeJS makes requests to x.x.x.x (instead of localhost)
  • When the tests are complete, Jenkins stops the phantom container