Tag Archives: Javascript

How to call asynchronously web servers with Javascript ?

Problem :

We wanted to display or hide information from more than five web servers when loading a homepage. Instead of waiting until all the servers reply to us ( synchronous method) the customer wanted to see the information as soon as the server would reply. This solution presents a better user experience because the webpage is more responsive.

Solution

I will decompose the solution in several parts and explain each steps

  • A HTML/W3.CSS webpage with a button calling the javascript method
  • The Javascript code calling multiple web servers
  • The Javascript class that call a web server
  • The callback method which handle the response of the web server
  • The Javascript function which updates the webpage after obtaining a response from the server.

HTML web page

The html webpage contains a button and a text input. The text input is sent to the web servers when we click the button.

main_webpage
This is a simple example of webpage

The link to my javascript file :

<script type="text/javascript" src="./webserver.js">
<script type="text/javascript" src="./request.js">

When we click on the button we call a javascript function which will use the text content of the input “myinput”.

 <input name="myinput" id="myinput" class="w3-input w3-border w3-light-grey" type="text">
<button class="w3-btn w3-blue-grey" onclick="callWeb()">Load Web Servers</button>

callWeb() is a Javascript function which will retrieve the input value and call web servers.

function callWeb() {
    var input = document.getElementById('myinput'),
       myinput = input.value;
    if (myinput) {
       loadWebServers(input);
    } else {
       alert('Please enter an input!');
       input.focus();
    }
}

To avoid the error “The character encoding of the HTML document was not declared” : I have added these lines in the HTML :

<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">

For information in this example I use W3.CSS for the style. It is a modern CSS framework with built-in responsiveness. It is an equivalent of Bootstrap. More information at https://www.w3schools.com/w3css/.

Javascript : Calling multiple web servers

I will present the javascript function which send the request and the callback function which handle the response .

function loadWebServers(input)
{
    var value1 = input
    var value2 = ''
    var value3 = ''
    loadWebABC(value1);
    loadWebX(value1);
}

In this example we call only two web servers. loadWebABC call a web service with XML request. loadWebX call another web service with a JSON request.

function loadWebABC(value1)
{
 updateLoading();
MyClassRequest.sendXmlRequest("http://example.com/service", "myinput=”+ value1, "callbackWebABC");
}

When the server replies, the callback will handle the response :

function callbackWebABC(xml)
{
    updateLoading();
    var rootInfo = xml.getElementsByTagName("root-info");
    //parse more information
   infoextracted=…..
    showOrDisplayInfo(infoextracted);
}

Javascript : Detail of the class that call the web server

XmlHttpRequest is the class used to call distant web servers.
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest

If you use XMLHttpRequest from an extension, you should use it asynchronously. In this case, you receive a callback when the data has been received, which lets the browser continue to work as normal while your request is being handled.

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests

MyClassRequest is the equivalent of a class in Javascript world. It is defined in a file called request.js :

var MyClassRequest = {
	loading : false,

updaters : new Array(),

	Updater : function(request, backFunction, param, isXml, isJson) {
		this.request = request;
		this.backFunction = backFunction;
		this.param = param;
		this.isXml = isXml;
		this.cancelled = false;
		this.isJson = isJson;
	},

	createRequest : function() {
		var request = false;
		try {
			request = new XMLHttpRequest();
		} catch (trymicrosoft) {
			try {
				request = new ActiveXObject("Msxml2.XMLHTTP");
			} catch (othermicrosoft) {
				try {
					request = new ActiveXObject("Microsoft.XMLHTTP");
				} catch (failed) {
					request = false;
				}
			}
		}
		return request;
	},

	sendXmlRequest : function(url, urlParam, backFunction, param) {
		return this
				.sendRequest(url, urlParam, backFunction, param, true, false);
	},

	sendRequest : function(url, urlParam, backFunction, param, isXml, isJson, timeout) {
		 if (typeof(timeout)==='undefined') {
			 timeout = 0;
		 }
		var request = this.createRequest();

		if (!request) {
			alert("Request not supported");
			return -1;
		} else {
			var index = this.updaters.length;
			this.updaters[index] = new MyClassRequest.Updater(request,
					backFunction, param, isXml, isJson);

			request.open("POST", url, true);
			request.onreadystatechange = MyClassRequest.receiveRequest;
			if (timeout != null && timeout > 0) {
				request.timeout = timeout;
				request.ontimeout = MyClassRequest.timeoutRequest;
			}
			request.setRequestHeader("Content-Type",
					"application/x-www-form-urlencoded; charset=UTF-8");
			request.send(urlParam);

			this.setLoading("true");
			return index;
		}
	},

As you can see the function sendXmlRequest is a sub function of sendRequest. sendRequest will use the object XMLHttpRequest or an equivalent for microsoft supported browser. This important part in this function is :

request.onreadystatechange = MyClassRequest.receiveRequest;

It is receiveRequest function which will trigger the callback function defined previously.

receiveRequest : function() {
		var stillLoading = false;
		for ( var i = MyClassRequest.updaters.length - 1; i >= 0; i--) {
			var updater = MyClassRequest.updaters[i];
			if (updater != null) {
				if (updater.request.readyState == 4) {
					MyClassRequest.updaters[i] = null;
					if (updater.cancelled == false) {
						if (!updater.request.status == 200) {
							alert("No response from server");
						} else {
                                                        if (updater.backFunction) {
							callbackFunction(updater);
                                                        }
						}
					}
				} else {
					stillLoading = true;
				}
			}
			MyClassRequest.setLoading(stillLoading);
		}
	},

callbackFunction is the function which will actually call our callback function when we receive correctly a response form the distant web server.

function callbackFunction(updater)
{

var func = new Function("response", "param",
			updater.backFunction
					+ "(response, param)");
	if (updater.isXml) {
		var xml = updater.request.responseXML;
		if (MyClassRequest.checkErrors(xml)) {
			func(false, updater.param);
		} else {
			func(xml, updater.param);
		}
	} else {
		func(updater.request.responseText,
				updater.param);
	}

}

Javascript : update the webpage

In the previous callback method we use a function showOrDisplayInfo(). This function will display or hide element in the web page. This is a simplified version of the function but as you can see it modifies an HTML element to show or hide this element.

function showOrDisplayInfo(infoextracted) {
for (var infoin infoextracted)
    {
      var idElementToShow ="elementX"+ infoin;
         show( idElementToShow );
    }
}

function show(element)
{
  if (element)
  {
    element.style.display="";
  }
}

function hide(element)
{
  if (element)
  {
    element.style.display="none";
  }
}
Advertisements

Automate Unit testing of Javascripts with Karma Runner

The problem :

Working on TDD/BDD method with Javascripts is more tricky than with Java language. In the Java world you have Junit to test your unit tests and maven to execute all of them and create easily reports in Jenkins. In the Javascript world  the equivalent to run Junit is Qunit,Jasmine, Mocha . There is more tools because there is more complexity and these tools do not work perfectly . Not only you have different style of Unit testing but also different Test runner too. In Java you used just the one given by default.

Initially I was using Qunit with a maven plugin to run the tests. But it meant i could not use Jasmine 2 or Mocha. Qunit has limitations too that’s why i moved to Jasmine 2.0.

Now I use Karma test runner to run the Jasmine Unit Tests . But i can also run Qunit tests if I would like to. Karma is really flexible and can also run Mocha. It can also exclude or include some files to test them.  The other complexity of Javascript testing is the browser. Indeed Javascript can be run inside the Chrome,Firefox, Internet Explorer Browsers . For CI testing I used a headless browser called PhantomJs.

Also, Karma has a plugin to analyse Javascript code coverage.
This is the  best generic tool runner i found so far to run Javascript unit tests.

The solution :

I will present the solution I implemented to launch Javascript tests from Jenkins platform. Karma is launched From Jenkins. Also Karma is configured to find Javascripts Jasmine tests and generate a Junit report style, code coverage report.

Realise Unit test with Jasmine 2.0

You need to write specification .spec file describing the test. Example for CommonUtilSpec.js

describe("CommonUtil", function() {

it("trim testing ", function() {
var trimmed = trimFunc('tri');

expect(trimmed).toBe("tri");
expect(trimmed).not.toBe(null);
});

});

The documentation for Jasmine 2 :http://jasmine.github.io/2.0/introduction.html

Install tools on the Jenkins machine

Install Node.js like in this link :
http://karma-runner.github.io/1.0/intro/installation.html

Get as well yeoman and bower to get javascript packages
https://inviqa.com/blog/testing-javascript-get-started-jasmine-0

NOTE : Bower is an equivalent de maven for javascript http://stackoverflow.com/questions/12334346/dependency-management-and-build-tool-for-javascript

Configure Karma configuration for Jenkins

I have used the following link to configure Karma for Jenkins https://karma-runner.github.io/1.0/plus/jenkins.html. The configuration for Karma of this link have compilation errors.

I have installed EnvInject in order to specify some environment variables for Jenkins.

Configure Karma for Jenkins. I have used the information for the junit reporter at https://www.npmjs.com/package/karma-junit-reporter . It does junit style unit test reporting. It is very practical. I have also used the coverage plugin in order to know which part of the code have been tested https://www.npmjs.com/package/karma-coverage .

The content of my file :

module.exports = function(config) {
config.set({

// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',

// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],

// list of files / patterns to load in the browser
files: [
'/myproject/src/test/**/*.js',
'/myproject/src/main/Javascripts/commonUtil.js'
],

// list of files to exclude
exclude: [
],

// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'/myproject/src/main/**/*.js' : ['coverage']
},

// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter

reporters: ['progress', 'junit','coverage'],

// the default configuration
junitReporter: {
outputDir: '', // results will be saved as $outputDir/$browserName.xml
outputFile: 'test_jasmine_js.xml', // if included, results will be saved as $outputDir/$browserName/$outputFile
suite: '', // suite will become the package name attribute in xml testsuite element
useBrowserName: true, // add browser name to report and classes names
nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element
classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element
properties: {} // key value pair of properties to add to the section of the report
},

// web server port
port: 9876,

// enable / disable colors in the output (reporters and logs)
colors: true,

// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,

// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,

// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['PhantomJS'],

plugins : [
'karma-phantomjs-launcher',
'karma-jasmine',
'karma-html-reporter',
'karma-junit-reporter',
'karma-coverage'
],

// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,

// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
}

Configure and launch Jenkins

I configured jenkins to launch karma with shell script plugin. This solution is not optimized because the npm plugins are being installed at each launch. These plugins should be installed once.

npm install karma-jasmine --save-dev
npm install jasmine-core --save-dev
npm install karma-phantomjs-launcher --save-dev
npm install karma-junit-reporter --save-dev
npm install karma-jasmine-html-reporter --save-dev
npm install karma-html-reporter --save-dev
cp /home/myuser/myapp my.jenkins.conf.js .
karma start my.jenkins.conf.js

Here is a picture of the configuration in Jenkins :

config_jenkins_karma

Once you have finished the configuration, launch the tests. The result of the report should look like that

result_phantomsjs

The plugin coverage is really neat and show in details where the test were not covered  :

javascript_coverage

 

TROUBLESHOOTING

Error 1 : No provider for “framework:jasmine”! (Resolving: framework:jasmine
Correction : npm install karma-jasmine –save-dev

Error 2 : Error: Cannot find module ‘jasmine-core’
Correction : -npm install ‘jasmine-core –save-dev

Error 3 : Cannot load browser “PhantomJS”: it is not registered! Perhaps you are missing some plugin?
Correction: npm install karma-phantomjs-launcher –save-dev

Error 4 : Can not load reporter “junit”,
Correction:

plugins : [
        'karma-phantomjs-launcher',
        'karma-jasmine',
        'karma-junit-reporter'
    ],

Error 5 : Cannot find plugin “karma-junit-reporter
Correction: -npm install ‘karma-junit-reporter –save-dev

Error 6 : reporter.junit Cannot write JUnit xml
Problem was specific to where my conf file was located.The fix was to copy the conf file locally in working directory of the slave.