JavaScript multi module project – Continuous Integration
JavaScript multi module project
Few days ago, I wrote blog post about JavaScript multi module project with Grunt. This approach allows you to split application into various modules. But at the same time it allows you to create one deployment out of these modules. It was inspired by Maven multi module concept (Maven is build system from Java world).
But configuration of the project is just a half of the puzzle. Testing is must for me. And tests have to be executed. Execution must be automatic. So I needed to figure out Continuous Integration story for this project configuration.
Project parameters to consider
Let me quickly summarize base attributes of project configuration:
- Two Github repositories/sub-projects
- primediser-server – Node.JS/Express based back-end
- primediser-client– Angular based front-end
- One main project called primediser
- builds sub-projects
- gathers deployment
- runs Protractor end-to-end tests against deployment
- gathers code coverage stats for client side code
Choosing CI server
First of all I had to pick CI server. Obviously it has to support chaining of builds (one build would be able to kick off another build). I have Java background and experience with Jenkins. So it would be natural choice. I bet that Jenkins can handle it quite easily. But I wanted to try workflow closer to majority of JavaScript community. So I tried TravisCI. Straight away not suitable because it’s not possible to chain builds.
Next I tried drone.io. Relatively new service. Initially seemed not very feature reach. But closer look actually showed that this is the right choice. It provides these features necessary for me:
- Temporary Docker Linux instance (is deleted after build) with pre-installed Node.JS
- Web hook for remote triggering builds
- Web interface for specifying Bash build commands
- Web interface for specifying private keys or credentials needed during build
This combination of features turned to be very powerful. There are few unpolished problems (e.g. visualization of build process makes Firefox unresponsive, missing option for following build progress – so need to scroll manually), but I can live with these.
I was also able to execute Protractor tests against Sauce Labs from drone.io and measure the code coverage. Measuring Protractor tests code coverage is described in separate blog. Any change against sub-project triggers also build of main project. Does this sound interesting? Let me describe this CI configuration in details.
Documentation
I wouldn’t dive into drone.io, Sauce Labs or Protractor basic usage. They were new for me and I easily and quickly came through their docs. Here is list of docs I used to put together this CI configuration.
- drone.io is very intuitive. Here is their short Node.JS doc.
- Sauce Labs
- Check out Node.JS section in getting started section
- Their Protractor example
Protractor – Souce Labs integration
Important part of this setup is Protractor integration with Sauce Labs. Sauce Labs provide Selenium server with WebDiver API for testing. Protractor uses Sauce Labs by default when you specify their credentials. So credentials are the only special configuration in test/protractor/protractorConf.js (bottom of the snippet). Other configuration was taken from grunt-protractor-coverage example. I am using this grunt plug-in for running Protractor tests and measuring code coverage.
// A reference configuration file. exports.config = { // ----- What tests to run ----- // // Spec patterns are relative to the location of this config. specs: [ 'test/protractor/*Spec.js' ], // ----- Capabilities to be passed to the webdriver instance ---- // // For a full list of available capabilities, see // https://code.google.com/p/selenium/wiki/DesiredCapabilities // and // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js capabilities: { 'browserName': 'chrome' // 'browserName': 'firefox' // 'browserName': 'phantomjs' }, params: { }, // ----- More information for your tests ---- // // A base URL for your application under test. Calls to protractor.get() // with relative paths will be prepended with this. baseUrl: 'http://localhost:3000/', // Options to be passed to Jasmine-node. jasmineNodeOpts: { showColors: true, // Use colors in the command line report. isVerbose: true, // List all tests in the console includeStackTrace: true, defaultTimeoutInterval: 90000 }, sauceUser: process.env.SAUCE_USERNAME, sauceKey: process.env.SAUCE_ACCESS_KEY };
You may ask now, how can I use localhost in the configuration, when remote selenium server is used for testing. Good question. Sauce Labs provide very useful feature called Sauce Connect. It is a tunnel that emulates access to your machine from Selenium server. This is super useful when you need to bypass company firewall. It will be used later in main project CI configuration.
Setting up CI for sub-projects
The idea is that each change in sub-project would trigger a build of main project. That is why we need to copy a Build Hook of main project from Settings -> Repository section.
Main project kicked off by hitting the Web hook via wget Linux command from sub-project. As you can see in following picture, sub-project informs main project about a change and process its own build afterwards. Drone.io doesn’t provide concurrent builds (not sure if this is limitation of open source projects only), so main project will wait for sub-project’s build to finish. After that main project is build.
CI Configuration of Main Project
So build of main project is now triggered from sub-projects. Main project configuration is slightly more complicated. I uses various commands:
- First of all we need to specify Sauce Labs credentials in Environment Variables section
export SAUCE_USERNAME=******** export SAUCE_ACCESS_KEY=****************************
- Download, extract and execute Sauce Connect tunnel for Linux. This will make accessible some ports on build image for Selenium testing. Notice that execution of the tunnel is done with & (ampersand). This means that tunnel execution is done in forked process and current console can continue executing build.
wget https://d2nkw87yt5k0to.cloudfront.net/downloads/sc-latest-linux.tar.gz tar -xzvf sc-latest-linux.tar.gz cd sc-4.2-linux/bin ./sc & cd ../..
- Now standard Node.JS/Grunt build workflow is executed. Downloading and installing all NPM dependencies creates enough time for Sauce Connect tunnel to start.
npm install phantomjs time npm install -g protractor time npm install -g grunt-cli bower time npm install grunt
- Last command is closing the Sauce Labs tunnel after build, so that Docker build image wouldn’t hang.
kill %1
Such configured job now takes 9 minutes. drone.io limit is 15 minutes. Hopefully there will be enough space for all end-to-end tests I am going to create. If you are curious about build output/progress, take a look at my drone.io jobs below.
Project Links
Github repositories:
- primediser – main project
- primediser-server – Node.JS/Express based back-end
- primediser-client– Angular based front-end
Drone.io jobs:
Reference: | JavaScript multi module project – Continuous Integration from our JCG partner Lubos Krnac at the Lubos Krnac Java blog blog. |