Grunt-and-phantom For most of the development projects, web performance tracking is often counted as the most significant one; especially when considering from the client perspective. Faster the performance tracking, better becomes the end result. Many a number of tools are now available to execute this. Yslow, and Loadreport.js are indeed the most popular ones in the list. While there are tools that can provide flexible reports, there are also tools such as Grunt and Ant, meant to serve the purpose of task automation. Thanks to the Google I/O extended Kochi session, I had an opportunity to take a session on "Grunt-The task Manager". This post is an attempt to explain how the task automation tool can be combined with the performance tracking tool, by best leveraging the Loadreport and Grunt applications. At the outset, Grunt is a nodejs plugin intended to handle the JavaScript based tasks. Though its main purpose is often confined to bootstrapping the front end application, there are a variety of plugins available today to handle various other operations too. The two major plugins that I would like to emphasize here are "grunt-execute" and "grunt-contrib-watch". While the grunt-execute is used for executing nodeJs code, the grunt-contrib-watch is used for running predefined tasks whenever certain watched file patterns are added, changed or deleted. Considering the automation of the Loadreport.js for different sites, we would require a configuration file so as to store the sites details. This can be best accomplished by using a JSON file(sites.json) as shown here.


{
"name":"test",
"sites":[
{"name":"Site1","url":"https://www.cabotsolutions.com"},
{"name":"Site2","url":"https://www.cabotsolutions.com"},
{"name":"Site3","url":"https://www.cabotsolutions.com"}
]
}

The next step would be to use a nodeJs code for reading the JSON and hence executing the Loadreport function. For reading a JSON file, let us use the readJSON function of the node grunt module.

var grunt = require('grunt');
var fileJSON = grunt.file.readJSON('sites.json');

Using Loadreport.js, it is possible for us to carry out the resource performance test for a site and thus keep the latest test reports of each site. Loadreport will consider only one site at a time and will create/update reports/loadreport.json as soon as it completes the execution. For this, we would need to move the loadreport.json file every time it completes the execution. Nodejs also provides a ChildProcess module for executing system commands. For testing a website, it is always better that we have a headless browser to carry out the performance test. Browsers such as PhantomJS can be extremely powerful in this regard. Once the PhantomJS is installed to execute the loadreport, the test execution can be carried out using the following command:

phantomjs loadreport.js https://www.cabotsolutions.com performancecache json

If we execute the above command from CLI, it will do a perforance test for the url "https://www.cabotsolutions.com" and thus create a json report. This would help us in  creating a nodeJs function that can handle the execution.

var exec = require('child_process').exec;
function executeReport(fileName, url) {
exec('phantomjs loadreport.js '+ url+' performancecache json', function (error, stdout, stderr) {
exec('find repo -name '+fileName+'_3.js', function(error, stdout, stderr) {
if(stdout) {
exec('rm repo/'+fileName+'_1.js', function(error, stdout, stderr) {
if(!stderr) {
exec('mv repo/'+fileName+'_2.js repo/'+fileName+'_1.js', function(error, stdout, stderr) {
if(!stderr) {
exec('mv repo/'+fileName+'_3.js repo/'+fileName+'_2.js', function(error, stdout, stderr) {
if(!stderr) {
exec('mv reports/loadreport.json repo/'+fileName+'_3.js', function(error, stdout, stderr) {
checkLimit();
});
}
});
}
});
}

});
}
else {
exec('find repo -name '+fileName+'_2.js', function(error, stdout, stderr) {
if(stdout) {
exec('mv reports/loadreport.json repo/'+fileName+'_3.js', function(error, stdout, stderr) {
checkLimit();
});
}
else {
exec('find repo -name '+fileName+'_1.js', function(error, stdout, stderr) {
if(stdout) {
exec('mv reports/loadreport.json repo/'+fileName+'_2.js', function(error, stdout, stderr) {
checkLimit();
});
}
else {
exec('mv reports/loadreport.json repo/'+fileName+'_1.js', function(error, stdout, stderr) {
checkLimit();
});
}
});
}
});
}
});
});
}

function checkLimit() {
grunt.file.delete("reports/");
inc++;
if (inc >= fileJSON.sites.length) {
process.exit(code=0);
}
executeReport(fileJSON.sites[inc].name, fileJSON.sites[inc].url);
}
var inc = 0;
executeReport(fileJSON.sites[inc].name, fileJSON.sites[inc].url);

When we execute the above code, it will generate a report JSON for the site under consideration and thus move it into a different folder with a new name. For instance, let the file name at the very first run be sitename_1.js, sitename_2.js for the second run and sitename_3.js for the third run. From fourth run onwards, remove the sitename_1.js and replace sitename_2.js as sitename_1.js. Similarly, rename sitename_3.js as sitename_2.js and thus keep the newly generated report.json as sitename_3.js. This would help us access the three latest report files for all sites instantaneously. To run the above file, use the command

node run.js

Once the script is completed, check the repo folder to see the generated reports for the specified sites. Next in line, we can try automating the file execution whenever a change is made in the site's configuration file. It is at this point that grunt's watch plugin comes into action. With this, it is possible to configure the tasks that we need to run whenever a file is changed.

watch: {

reporter: {
files: ['sites.json'],
tasks: ['execute']
},
}

This means that whenever we change the file sites.json, the execute task will be automatically executed. Shown below is the structure of a grunt file.

module.exports = function(grunt) {
grunt.initConfig({
execute: {
target: {
src: ['run.js']
}
},
watch: {

reporter: {
files: ['sites.json'],
tasks: ['execute']
},
}
});
grunt.loadNpmTasks('grunt-execute');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['execute', 'watch']);
};

The grunt.initConfig is a configuration section wherein we can configure any task that needs to be executed. In this example, there are two tasks namely, "execute" and "watch". The grunt.loadNpmTasks() function used here serves the purpose of loading grunt modules. Here, both 'grunt-execute' and 'grunt-contrib-watch' modules are to be loaded. The grunt.registerTask() is used to register any number of custom tasks. In this context, the registerTask function is used to register a custom task 'default', with ['execute', 'watch'] as its dependencies. While running the command "grunt" or "grunt default" from the root folder, the "default" tasks will be automatically executed (If the task name is not "default", it needs to specified as a second argument). Once the "execute" task is triggered, it will generate the reports for the sites. Simultaneously, the "watch" task would create a listener to foresee whether changes are made in the sites.json.This is to let the "execute" task become functional automatically whenever the sites.json is subjected to a change. The same procedure holds good in the case of Yslow tool also. Out there, experiments and researches are still on; thus letting new doors open for the developer community to explore the newest possibilities of the Grunt ecosystem. It is up to us to learn and implement these so as to be in the technology front-line.

SHARE THIS BLOG ON

STAY UP-TO-DATE WITH US

Subscribe to our newsletter and know all that’s happening at Cabot.

SUBSCRIBE
SHARE