Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Run JavaScript QUnit Tests Using Jenkins and PhantomJS

DZone's Guide to

How to Run JavaScript QUnit Tests Using Jenkins and PhantomJS

There are many benefits to unit testing, but there is no QUnit plugin for Jenkins. This post shows you a workaround for QUnit results in Jenkins.

· DevOps Zone
Free Resource

Download “The DevOps Journey - From Waterfall to Continuous Delivery” to learn learn about the importance of integrating automated testing into the DevOps workflow, brought to you in partnership with Sauce Labs.

Unit testing is great. However, the real benefit of unit testing is only achieved when the tests are run before each deployment. In continuous integration (CI), it makes sense to run the tests automatically in the CI tool.

At Akanoo, we are using Jenkins, which can be extended by various plugins for several use cases. Unfortunately, there is no plugin for QUnit test results, so we have to utilize the existing plugin for JUnit. Two steps need to be done:

  • Find a way to run the tests in Jenkins.
  • Find a way to output the QUnit results as JUnit results.

Running QUnit Tests in Jenkins

Jenkins offers no service to open web pages upon deployment. I didn’t know of any plugin that offers such a thing, so I went out Googling. I found a guide in the repository of the HTML5 boilerplate on how to set up QUnit with Jenkins that suggested to use PhantomJS with a QUnit test runner.

Following that lead, the first thing I did was to download PhantomJS and try to run my test HTML file locally. PhantomJS can only run JavaScript files, so I needed a test runner. I took a look at the one mentioned above but found it a little too bold for our needs, so I came up with my own solution.

var system = require('system');
var fs = require('fs');
var page = require('webpage').create();

// argument 0 is always the file which is called (this)
if (system.args.length === 1) {
    console.log('Pass the path/to/testfile.js as argument to run the test.');
    phantom.exit();
} else {
    // path is relative to where phantomjs is started
    var url = system.args[1]; // e.g. 'test/unit/tests.html'
    console.log("Opening " + url);
}

page.open(url, function (status) {
    console.log("Status: " + status);
    if (status === "success") {
        setTimeout(function () {
            var path = 'results.xml';
            var output = page.evaluate(function () {
                return document.output;
            });

            fs.write(path, output, 'w');
            console.log("Wrote JUnit style output of QUnit tests into " + path);

            console.log("Tests finished. Exiting.");
            phantom.exit();
        }, 3000);
    } else {
        console.log("Failure opening" + url + ". Exiting.");
        phantom.exit();
    }
});

The runner takes an argument with the path to the HTML QUnit test file that is to be opened by PhantomJS. If the argument is missing, we can use  console.log()  to print the result into the console running PhantomJS. The main part of the script opens the page. If the given file doesn’t exist, an error message is logged and PhantomJS terminated. If the file can be opened, the JavaScript variable document.output of the test page is evaluated and written into a file called results.xml. The evaluation is done after a timeout of three seconds – the time the tests never exceeded on my local machine.

Output QUnit Results in JUnit Format

In the next step, we need to make sure the QUnit results can be interpreted by the Jenkins JUnit plugin. Luckily, there is already a plugin for QUnit to produce the results in a JUnit-style XML report. I installed the plugin and configured it to write the results in the document.output variable that we’ve already seen in the PhantomJS runner above.

The current setup is running fine on my local machine: PhantomJS is installed, can be started via shell to execute the runner script, opening the QUnit test HTML file and saving the JUnit-style report into results.xml.

Creating the Jenkins Pipeline

Let’s make sure the job is also running in Jenkins. At Akanoo, Jenkins lies inside a Docker image, so I edited the Dockerfile to download and unpack PhantomJS. Use the correct version (32bit or 64bit) — I first used 32bit on a 64bit machine and wondered why it didn’t work. Make sure to add PhantomJS to your PATH variable.

Jenkins allows by default to define multiple build steps for one build, but we want to achieve that the full build is terminated as soon as one step fails. Jenkins offers the Pipeline plugin to define multiple stages of a build, so I installed the pipeline and the JUnit plugin and restarted Jenkins.

I have configured several stages in the pipeline:

  1. Checkout the latest version of code from Git.
  2. Run the tests in PhantomJS, archive the test results and report results to the JUnit plugin.
  3. Build, if the previous step didn’t fail.

To make the build fail if an error in the unit tests occured, we can utilize a try-catch-block. The Groovy script in the pipeline also allows you to run shell scripts, which we need to run PhantomJS. I came up with the following script:

node {
    stage('Version Control') {
        // checkout the latest version from Git
    }
    stage('Test') {
        try {
            // run PhantomJS
            sh 'cd ${JENKINS_HOME}/path/to/unit/tests && phantomjs phantomjs-runner.js tests.html'

            // move result file into workspace
            sh 'mv ${JENKINS_HOME}/path/to/unit/tests/results.xml ${JENKINS_HOME}/workspace/${JOB_NAME}'

            // archive test results with relative path from ${JENKINS_HOME}/workspace
            step([$class: 'JUnitResultArchiver', testResults: '**results.xml'])

            // report to JUnit with relative path from ${JENKINS_HOME}/workspace
            junit '**results.xml'
        } catch(err) {
            throw err
        }
    }
    stage('Build') {
        // I would build now if the test didn't fail
    }
}

Let’s discuss the script line-by-line. At first, we have the Version Control stage. I assume you know how to checkout from Git. You may also omit this stage if the script is stored on the same machine as Jenkins.

In the Test stage, a shell script executes PhantomJS with two parameters: the phantomjs-runner.js file we discussed above and the QUnit HTML test file. The results of the test are stored in a file called results.xml in the same folder the tests lie in. In the next line, we move it into the Jenkins workspace of the current job. The step command is used to store the test results using the JUnitResultArchiver to be able to analyze the results of all tests later. We also send the results to the JUnit plugin to check for errors. This step will throw an exception if errors are found that is caught by the try-catch-block and re-thrown to stop the build before starting the Build step.

In the Build step, the actual build will run. This step depends on what you want to achieve. In our case, we run a Groovy script.

We managed to configure a Jenkins build pipeline that checks out the current version from Git (or any other version control system), runs the QUnit tests in a PhantomJS headless browser, returns the test results in JUnit-style format, archives the results, and only builds if the tests were successful.

It took me a couple of hours to figure out the single steps and bring everything together. I hope you found this useful. If you have any questions or ideas for optimization, please leave a comment below.

Discover how to optimize your DevOps workflows with our cloud-based automated testing infrastructure, brought to you in partnership with Sauce Labs

Topics:
unit testing ,javascript ,qunit ,devops ,continuous integration ,phantomjs

Published at DZone with permission of Bendix Saeltz. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}