Jenkins Configuration for a Yeoman based AngularJS web application

At Portland Webworks we use continuous integration, the process of frequent testing and deployment of the software, to deliver higher quality products at a lower cost.

  • Automation means less overhead during the development process
  • A tightened feedback loop means increased communication on multiple developer teams
  • Increased quality means better software and lower maintenance costs over the life of the product

This will be a brief overview of the configuration of a Jenkins build server for a Yeoman workflow based AngularJS web application.

We will be making use of headless testing courtesy of PhantomJS and Karma runner, then outputting the results in JUnit format. Jenkins will consume this format and provide test results in the project views.

Let's get started.

Ready the build server

We are running Jenkins on an AWS machine running Amazon Linux AMI.

At the time of this publication, there is no package in the Yum Package Manager for installing NodeJS by default, but you can enable the EPEL Repository maintained by Fedora if you like.

For this example, we will grab a binary and moved it into place like so:

wget http://nodejs.org/dist/v0.10.4/node-v0.10.4-linux-x64.tar.gz
tar -zxvf node-v0.10.4-linux-x64.tar.gz
sudo mv node-v0.10.4-linux-x64 /opt/node-v0.10.4
sudo ln -s node-v0.10.4/ node
sudo ln -s /opt/node/bin/node /usr/bin/node
sudo ln -s /opt/node/bin/npm /usr/bin/npm

Make sure your symlinks check out and Node runs:

[jstoltenborg@aws-x ~]$ node -v
v0.10.4

One other option to manage your Node installation is to clone the repo from Github and roll your own.

Globally install necessary Node packages

Now that we have Node installed, let's install packages that our project requires globally.

npm install
bower install --dev
grunt cibuild

You may run into dependency issues during this process. Some searching around will lead you to resolution, or you can define specific versions to install to make things happy. For example,

sudo npm -g install generator-angular generator-karma@0.4.1

Let's turn our attention back to our project workspace and do some configuration for the build.

Prepare your Gruntfile and project dependencies

In the Grunt configuration file, add a testing profile that uses PhantomJS and runs once. Note the JUnit reporter, which exports the test results in JUnit formatted XML for Jenkins to parse.

The config file we specify, karma.conf.js, provides defaults for other settings not defined here and is overridden by those we have defined.

wget http://nodejs.org/dist/v0.10.4/node-v0.10.4-linux-x64.tar.gz
tar -zxvf node-v0.10.4-linux-x64.tar.gz
sudo mv node-v0.10.4-linux-x64 /opt/node-v0.10.4
sudo ln -s node-v0.10.4/ node
sudo ln -s /opt/node/bin/node /usr/bin/node
sudo ln -s /opt/node/bin/npm /usr/bin/npm
npm install
bower install --dev
grunt cibuild
[jstoltenborg@aws-x ~]$ node -v
v0.10.4
sudo npm -g install yo bower grunt-cli generator-karma generator-angular phantomjs
sudo npm -g install generator-angular generator-karma@0.4.1
continuous: {
    configFile: 'karma.conf.js',
    singleRun: true,
    browsers: ['PhantomJS'],
    reporters: ['dots', 'junit'],
    junitReporter: {
        outputFile: 'test-results.xml'
    }
}
compress: {
    main: {
        options: {
            mode: 'tgz',
            archive: 'target/x_mobile.tgz'
        },
        files: [{
            expand: true,
            src: '**/*',
            cwd: 'dist/',
            dot: true
        }]
    }
}
grunt.registerTask('cibuild', [
    'karma:continuous',
    'build',
    'compress'
])
grunt.registerTask('build', [
    'clean:dist',
    'useminPrepare',
    'concurrent:dist',
    'concat',
    'copy',
    'ngmin',
    'uglify',
    'rev',
    'usemin'
])
export PHANTOMJS_BIN=/usr/lib/node_modules/phantomjs/lib/phantom/bin/phantomjs

... and configure compression of the dist directory into a target directory so we have an artifact to access through Jenkins as well as to copy to another environment for deployment.

continuous: {
    configFile: 'karma.conf.js',
    singleRun: true,
    browsers: ['PhantomJS'],
    reporters: ['dots', 'junit'],
    junitReporter: {
        outputFile: 'test-results.xml'
    }
}

Then, register a new task in the Grunt configuration file that leverages the new testing and compression functionality you defined,

compress: {
    main: {
        options: {
            mode: 'tgz',
            archive: 'target/x_mobile.tgz'
        },
        files: [{
            expand: true,
            src: '**/*',
            cwd: 'dist/',
            dot: true
        }]
    }
}

... and sandwiches our current build task, defined as,

grunt.registerTask('cibuild', [
    'karma:continuous',
    'build',
    'compress'
])

Now on to Jenkins to create the new job.

Call in the Butler

In Jenkins, let's create a new job and configure it to check out the appropriate repository. There are a host of settings to consider, such as polling the repo for changes and limiting build history, that you can explore outside the scope of this guide.

In the Build Environment section, check “Inject environment variables to the build process” and then in the “Properties Content” you’ll set the location of the phantomjs binary:

wget http://nodejs.org/dist/v0.10.4/node-v0.10.4-linux-x64.tar.gz
tar -zxvf node-v0.10.4-linux-x64.tar.gz
sudo mv node-v0.10.4-linux-x64 /opt/node-v0.10.4
sudo ln -s node-v0.10.4/ node
sudo ln -s /opt/node/bin/node /usr/bin/node
sudo ln -s /opt/node/bin/npm /usr/bin/npm
npm install
bower install --dev
grunt cibuild
[jstoltenborg@aws-x ~]$ node -v
v0.10.4
sudo npm -g install yo bower grunt-cli generator-karma generator-angular phantomjs
sudo npm -g install generator-angular generator-karma@0.4.1
continuous: {
    configFile: 'karma.conf.js',
    singleRun: true,
    browsers: ['PhantomJS'],
    reporters: ['dots', 'junit'],
    junitReporter: {
        outputFile: 'test-results.xml'
    }
}
compress: {
    main: {
        options: {
            mode: 'tgz',
            archive: 'target/x_mobile.tgz'
        },
        files: [{
            expand: true,
            src: '**/*',
            cwd: 'dist/',
            dot: true
        }]
    }
}
grunt.registerTask('cibuild', [
    'karma:continuous',
    'build',
    'compress'
])
grunt.registerTask('build', [
    'clean:dist',
    'useminPrepare',
    'concurrent:dist',
    'concat',
    'copy',
    'ngmin',
    'uglify',
    'rev',
    'usemin'
])
export PHANTOMJS_BIN=/usr/lib/node_modules/phantomjs/lib/phantom/bin/phantomjs

Then, under the Build menu in the build configuration area, add a build step "Execute Shell" with the following command:

export PHANTOMJS_BIN=/usr/lib/node_modules/phantomjs/lib/phantom/bin/phantomjs

This installs project specific Node packages, installs packages defined in Bower, then runs the Grunt 'cibuild' task that we registered in the Grunt configuration file.

Next, add a post-build action to archive the artifact. It will be accessible through the Jenkins web view for each build.

Lastly, add another post-build action to “Publish JUnit test result report” and give the filename of the xml output you defined in your Gruntfile. In our case it’s test-results.xml and its put by default into the root of the workspace.

Build Results

A successful build means that our tests pass, the grunt build task is run, and a compressed artifact is created. The results of the Karma tests are picked up by Jenkins, and we begin to see results in our build views:

After two builds, a trend graph will appear on the job page.

Conclusion

I hope aspects of this approach are useful for integration into your project. In the coming months I will be sharing more of the software development lifecycle conventions we employ to deliver high quality software products at a lower cost to our clients.

I welcome your feedback in the comments, or by email to john@ this domain.

About The Author: 

John Stoltenborg is experienced in the software development lifecycle as a software developer and as a PMP certified project manager. He has worked with Portland Webworks since 2009.

Facebook Twitter Vimeo Share to Stumble Upon More...

Comments

What is the shell command to run in Jenkins?

I saw this hidden in the html under the page source. I believe this is the answer your looking for:

export PHANTOMJS_BIN=/usr/lib/node_modules/phantomjs/lib/phantom/bin/phantomjs

This was buried in the page source but not being rendered. I think this might be the correct answer...

export PHANTOMJS_BIN=/usr/lib/node_modules/phantomjs/lib/phantom/bin/phantomjs

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Use [gist:####] where #### is your gist number to embed the gist.
  • Syntax highlight code surrounded by the <pre class="brush: lang">...</pre> tags, where lang is one of the following language brushes: as3, applescript, bash, csharp, coldfusion, cpp, css, delphi, diff, erlang, groovy, jscript, java, javafx, perl, php, plain, powershell, python, ruby, sass, scala, sql, vb, xml.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
6 + 1 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
By submitting this form, you accept the Mollom privacy policy.