Angular E2E Testing with Protractor in Jenkins on Kubernetes

One of the more difficult pieces of automation to set up in the Jenkins Pipeline was the Angular Protractor end-to-end tests. This is a very rarely documented process and deserves special attention and I will try to record and explain all the pain points that I have met and how to overcome them.

The first is the set up of the JUnitReporter to get the test report to show up in Jenkins. For that we just add jasmine-reporters and the code below to the protractor config.

const { JUnitXmlReporter } = require('jasmine-reporters');
...
onPrepare() {
...
const junitReporter = new JUnitXmlReporter({
savePath: './e2e/test-results/E2E',
consolidateAll: false
});
jasmine.getEnv().addReporter(junitReporter);
}

Then we can just add junit ‘e2e/test-results/**/*.xml’ to our pipeline to have Jenkins pick up and show the results from the tests.

The next part is a little less obvious. Most local protractor configurations use the local chrome instance to run the selenium server. But that has not worked for me. Each time I would be met with the following error.

Running shell script
+ npm run e2e-ci
@jobrm/websiteui@1.0.0 e2e-ci /home/jenkins/workspace/JobRelationshipManager
ng e2e --prod --base-url http://jobrm-static:80
I/file_manager - creating folder /home/jenkins/workspace/JobRelationshipManager/node_modules/protractor/node_modules/webdriver-manager/selenium
I/config_source - curl -o/home/jenkins/workspace/JobRelationshipManager/node_modules/protractor/node_modules/webdriver-manager/selenium/chrome-response.xml https://chromedriver.storage.googleapis.com/
I/downloader - curl -o/home/jenkins/workspace/JobRelationshipManager/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.41.zip https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip
I/update - chromedriver: unzipping chromedriver_2.41.zip
I/update - chromedriver: setting permissions to 0755 for /home/jenkins/workspace/JobRelationshipManager/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.41
I/launcher - Running 1 instances of WebDriver
I/direct - Using ChromeDriver directly...
E/launcher - spawn /home/jenkins/workspace/JobRelationshipManager/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.41 ENOENT
[06:35:03] E/launcher - Error: spawn /home/jenkins/workspace/JobRelationshipManager/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.41 ENOENT
at Process.ChildProcess._handle.onexit (internal/child_process.js:231:19)
at onErrorNT (internal/child_process.js:406:16)
at process._tickCallback (internal/process/next_tick.js:63:19)
E/launcher - Process exited with error code 199
An unexpected error occurred: undefined
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! @jobrm/websiteui@1.0.0 e2e-ci: `ng e2e --prod --base-url http://jobrm-static:80`
npm ERR! Exit status 1

Big keywords that stand out are: spawn, ENOENT, selenium, webdriver-manager, chromedriver. And also none of them are helpful to debug the issue. After a while of research, some people said that the configuration on the Chrome driver is wrong, need to add some of these flags.

chromeOptions: {
args: ["--headless", "--disable-gpu", "--window-size=800x600"]
}

I like github issue suggestions as much as the next guy and have no other better alternatives, so I implemented it but this was not a successful solution.

Having no luck with various spawn and ENOENT issues and explanations, since that error happens a lot in NodeJS, I have started to search for anything to do with Protractor and Kubernetes. Eventually I came across this github issue of someone trying to run Protractor in Docker. One of the suggestions sounded like an interesting one to me because I did not know that Protractor could use a different server for its browser. And a helpful gist gives the general instruction on how one could accomplish this with docker compose. So thank you, Xotabu4.

I felt that I was getting closer! If I could just run the selenium container separately and then have my protractor config pointing to that server then I could solve this dilemma. The next big help was from finding a similar explanation with a kubectl yaml file for the full solution. The job in the example does magic, but at some point the job references the “SELENIUM_HOST” as localhost and “SELENIUM_PORT” as 4444. Since Jenkins slaves have similar structure to the job, where multiple containers are side loaded as part of the pod, we can use a similar configuration in the Jenkins pipeline.

...
containerTemplate(name: 'selenium', image: 'selenium/standalone-chrome:3.14', command: '', ttyEnabled: false, ports: [portMapping(containerPort: 4444)]),
...

Notice that I am not interested in communicating with the container so I do not need TTY, and the command is also irrelevant as long as entrypoint runs and the selenium process is running. I basically just need the container to be up and responding. It took some trial and error to figure out that, http://selenium:4444 or http://127.0.0.1:4444 does not work, but http://localhost:4444 does. I am a little mystified by this but have not had the chance to really get what’s going on. The changes to the protractor configuration are as follows.

...
directConnect: false,
seleniumAddress: 'http://localhost:4444/wd/hub',
...

At the end the solution is simple and somewhat elegant. There are very few extra steps that need to be taken and most of them are good practices to separate configurations for different environments.

Below is the full configuration of the pipeline that you can find at the JobRM/websiteui, but that configuration may change over time.

The command to e2e-ci is ng e2e — prod — base-url http://jobrm-static:80 — protractor-config ./protractor.local.conf.js.

This concludes the explanations and various errors I have run into.