So you want to test ‘localhost’ with Grunt?

In our previous blog post, we described how easy it is to connect to ngrok to allow Tenon to test your local environment. Today we’re going to add ngrok to the grunt-tenon-demo. Everything in this post is on Github and in the add-ngrok branch of the grunt-tenon-demo.

To follow along this post, you need a paid account on ngrok. Its only $5 a month for a paid version, so do it!

The goal of this tutorial is to create a custom Grunt task that will allow you to test all of your project’s files using Tenon, while connected to localhost. Because the task uses glob() to determine what files to test, you can actually just point it to whatever files you want to by choosing the right globbing pattern.

What makes this so powerful is that you can now take full advantage of Tenon’s powerful DOM-aware testing capabilities to test your project. If your site includes 3rd party CSS or JS frameworks or other content delivered via CDN, this allows you to test the full, final rendered DOM. Let’s take a look:

requires new modules

  1. The package.json file now includes new dependencies necessary for the new task, such as unirest, jsonfile, ngrok, fs, glob etc.
  2. Some of these need to be added with require() at the top of the Gruntfile

Adds new tasks

  1. Adds a clean task. We’ll talk about that later
  2. Adds grunt-contrib-connect to use as a static web server, which we used in the project for the previous tutorial.

Our custom task

Grunt allows you to create your own task. The goal of our custom task is to:

  • Connect to ngrok
  • Grab a list of all the pages in our project and use that list to create test URLs
  • POST each of those URLs to the Tenon API
  • Take each of the responses and write them to file.

Creating a custom task in Grunt is super easy. You simply use grunt.registerTask which takes 3 arguments: your task name, a description, and then a callback function. So ours will be called ‘tenonlive’. The whole thing starts at Line 134

    grunt.registerTask('tenonlive', 'Test the project URLs on localhost using NGrok', function () {
    // all our stuff goes here

Because everything this task does relies on being connected to ngrok, Line 138 contains:

       addr: '9001', // port or network address
       authtoken: '' // your authtoken from
    }, function (err, url) {
    // all the other stuff

The ‘addr’ needs to be the port address of our server and, of course, needs to match the port from our connect task.

The callback function from ngrok.connect returns ‘err’ which is any possible error message, and ‘url’, which is the randomly generated address we get when we connect to ngrok. If we get an error, the task fails. Otherwise, its time to move on.

Next, we need to get a list of all of our files:

    // read all the HTML files in the project
    filenames = glob.readdirSync('/**/*.html');
    filenames.forEach(function (fName) {
    // here's where we'll loop through them and test them

Once we have that list of files, we’re going to loop through them and test each one. We take each file, assemble a URL string, and POST that to the API using Unirest:

     // All of these 'config.*' items come from the same .tenonrc file that the grunt-tenon-client plugin uses{
         key: config.key,
         url: testUrl,
         projectID: config.projectID,
         certainty: config.certainty,
         level: config.level,
         priority: config.priority,
         ref: config.ref,
    }).header('Accept', 'application/json').end(function (response) {
    // handle the response from Tenon

Inside of that callback function above, we take the response object, parse it, and write the Tenon response data to a JSON file:

    grunt.log.writeln('Status: ' + response.status);
  if (response.status > 200) {'Tenon API response: ' + response.status);
    return done();

  var data = JSON.parse(JSON.stringify(response.body));
  grunt.log.writeln('responseID: ' + data.request.responseID);
  grunt.log.writeln('Total Errors: ' + data.resultSummary.issues.totalErrors);
  grunt.log.writeln('Total Warnings: ' + data.resultSummary.issues.totalWarnings);
  var file = 'tmp/' + data.request.responseID + '.json';

  // write the results to a JSON file
  jsonfile.writeFile(file, data, function (err) {
    if (err) {
      grunt.log.writeln('Error Writing File: ' + err);
    } else {
      grunt.log.writeln('Response Saved To: ' + file);

That’s it!

To use this task, you need to run the following tasks via command line

    $ grunt connect
    $ grunt tenonlive

This will start the server and run the tests. Let’s see a video:

Grunt Tenon demo with ngrok (video)

Start your free trial of Tenon today!