Setting Up The Rig

A practical guide to developing powerful web applications with AngularJS

In this article by Vinci Rufus, the author of the book AngularJS Web Application Development Blueprints, we will see the process of setting up various tools required to start building AngularJS apps. I'm sure you would have heard the saying, "A tool man is known by the tools he keeps." OK fine, I just made that up, but that's actually true, especially when it comes to programming. Sure you can build complete and fully functional AngularJS apps just using a simple text editor and a browser, but if you want to work like a ninja, then make sure that you start using some of these tools as a part of your development workflow.

Do note that these tools are not mandatory to build AngularJS apps. Their use is recommended mainly to help improve the productivity.

In this article, we will see how to set up and use the following productivity tools:

  • Node.js
  • Grunt
  • Yeoman
  • Karma
  • Protractor

Since most of us are running a Mac, Windows, Ubuntu, or another flavor of the Linux operating system, we'll be covering the deployment steps common for all of them.

(For more resources related to this topic, see here.)

Setting up Node.js

Depending on your technology stack, I strongly recommend you have either Ruby or Node.js installed.

In case of AngularJS, most of the productivity tools or plugins are available as Node Package Manager (npm), and, hence, we will be setting up Node.js along with npm. Node.js is an open source JavaScript-based platform that uses an event-based Input/output model, making it lightweight and fast.

Let us head over to www.nodejs.org and install Node.js. Choose the right version as per your operating system.

The current version of Node.js at the time of writing this article is v0.10.x which comes with npm built in, making it a breeze to set up Node.js and npm.

Node.js doesn't come with a Graphical User Interface (GUI), so to use Node.js, you will need to open up your terminal and start firing some commands. Now would also be a good time to brush up on your DOS and Unix/Linux commands.

After installing Node.js, the first thing you'd want to check is to see if Node.js has been installed correctly.

So, let us open up the terminal and write the following command:

node –-version

This should output the version number of Node.js that's installed on your system. The next would be to see what version of npm we have installed. The command for that would be as follows:

npm –-version

This will tell you the version number for your npm.

Creating a simple Node.js web server with ExpressJS

For basic, simple AngularJS apps, you don't really need a web server. You can simply open the HTML files from your filesystem and they would work just fine. However, as you start building complex applications where you are passing data in JSON, web services, or using a Content Delivery Network (CDN), you would find the need to use a web server.

The good thing about AngularJS apps is that they could work within any web server, so if you already have IIS, Apache, Nginx, or any other web server running on your development environment, you can simply run your AngularJS project from within the web root folder.

In case you don't have a web server and are looking for a lightweight web server, then let us set one up using Node.js and ExpressJS.

One could write the entire web server in pure Node.js; however, ExpressJS provides a nice layer of abstraction on top of Node.js so that you can just work with the ExpressJS APIs and don't have to worry about the low-level calls.

So, let's first install the ExpressJS module for Node.js.

Open up your terminal and fire the following command:

npm install -g express-generator

This will globally install ExpressJS. Omit the –g to install ExpressJS locally in the current folder.

When installing ExpressJS globally on Linux or Mac, you will need to run it via sudo as follows:

sudo npm install –g express-generator

This will let npm have the necessary permissions to write to the protected local folder under the user. The next step is to create an ExpressJS app; let us call it my-server. Type the following command in the terminal and hit enter:

express my-server

You'll see something like this:

create : my-server
   create : my-server/package.json
   create : my-server/app.js
   create : my-server/public
   create : my-server/public/javascripts
   create : my-server/public/images
   create : my-server/public/stylesheets
   create : my-server/public/stylesheets/style.css
   create : my-server/routes
   create : my-server/routes/index.js
   create : my-server/routes/user.js
   create : my-server/views
   create : my-server/views/layout.jade
   create : my-server/views/index.jade

   install dependencies:
     $ cd my-server && npm install

   run the app:
     $ DEBUG=my-server ./bin/www

This will create a folder called my-server and put in a bunch of files inside the folder.

The package.json file is created, which contains the skeleton of your app. Open it and ensure the name says my-server; also, note the dependencies listed.

Now, to install ExpressJS along with the dependencies, first change into the my-server directory and run the following command in the terminal:

cd my-server
npm install

Now, in the terminal, type in the following command:

npm start

Open your browser and type http://localhost:3000 in the address bar. You'll get a nice ExpressJS welcome message. Now to test our Address Book App, we will copy our index.html, scripts.js, and styles.css into the public folder located within my-server.

I'm not copying the angular.js file because we'll use the CDN version of the AngularJS library.

Open up the index.html file and replace the following code:

<script src= "angular.min.js" type="text/javascript"> </script>

With the CDN version of AngularJS as follows:

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js"></script>

A question might arise, as to what if the CDN is unreachable. In such cases, we can add a fall back to use a local version of the AngularJS library.

We do this by adding the following script after the CDN link is called:

<script>window.angular || document.write('<script src="lib/angular/angular.min.js"><\/script>');</script>

Save the file in the browser and enter localhost:3000/index.html. Your Address Book app is now running from a server and taking advantage of Google's CDN to serve the AngularJS file.

Referencing the files using only // is also called the protocol independent absolute path. This means that the files are requested using the same protocol that is being used to call the parent page. For example, if the page you are loading is via https://, then the CDN link will also be called via HTTPS.

This also means that when using // instead of http:// during development, you will need to run your app from within a server instead of a filesystem.

Setting up Grunt

Grunt is a JavaScript-based task runner. It is primarily used for automating tasks such as running unit tests, concatenating, merging, and minifying JS and CSS files. You can also run shell commands. This makes it super easy to perform server cleanups and deploy code. Essentially, Grunt is to JavaScript what Rake would be to Ruby or Ant/Maven would be to Java.

Installing Grunt-cli

Installing Grunt-cli is slightly different from installing other Node.js modules. We first need to install the Grunt's Command Line Interface (CLI) by firing the following command in the terminal:

npm install -g grunt-cli

Mac or Linux users can also directly run the following command:

sudo npm install –g grunt-cli

Make sure you have administrative privileges. Use sudo if you are on a Mac or Linux system. If you are on Windows, right-click and open the command prompt with administrative rights. An important thing to note is that installing Grunt-cli doesn't automatically install Grunt and its dependencies.

Grunt-cli merely invokes the version of Grunt installed along with the Grunt file. While this may seem a little complicated at start, the reason it works this way is so that we can run different versions of Grunt from the same machine. This comes in handy when your project has dependencies on a specific version of Grunt.

Creating the package.json file

To install Grunt first, let's create a folder called my-project and create a file called package.json with the following content:

{
  "name": "My-Project",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-contrib-jshint": "~0.10.0",
    "grunt-contrib-concat": "~0.4.0",
    "grunt-contrib-uglify": "~0.5.0",
"grunt-shell": "~0.7.0"

  }
}

Save the file. The package.json is where you define the various parameters of your app; for example, the name of your app, the version number, and the list of dependencies needed for the app.

Here we are calling our app My-Project with Version 0.1.0, and listing out the following dependencies that need to be installed as a part of this app:

  • grunt (v0.4.5): This is the main Grunt application
  • grunt-contrib-jshint (v0.10.0): This is used for code analysis
  • grunt-contrib-concat (v0.4.0): This is used to merge two or more files into one
  • grunt-contrib-uglify (v0.5.0): This is used to minify the JS file
  • grunt-shell (v0.7.0): This is the Grunt shell used for running shell commands

Visit http://gruntjs.com/plugins to get a list of all the plugins available for Grunt and also their exact names and version numbers.

You may also choose to create a default package.json file by running the following command and answering the questions:

npm init

Open the package.json file and add the dependencies as mentioned earlier.

Now that we have the package.json file, load the terminal and navigate into the my-project folder. To install Grunt and the modules specified in the file, type in the following command:

npm install --save-dev

You'll see a series of lines getting printed in the console, let that continue for a while and wait until it returns to the command prompt. Ensure that the last line printed by the previous command ends with OK code 0.

Once Grunt is installed, a quick version check command will ensure that Grunt is installed. The command is as follows:

grunt –-version

There is a possibility that you got a bunch of errors and it ended with a not ok code 0 message. There could be multiple reasons why that would have happened, ranging from errors in your code to a network connection issue or something changing at Grunt's end due to a new version update.

If grunt --version throws up an error, it means Grunt wasn't installed properly. To reinstall Grunt, enter the following commands in the terminal:

rm –rf node_modules
npm cache clean
npm install

Windows users may manually delete the node_modules folder from Windows Explorer, before running the cache clean command in the command prompt.

Refer to http://www.gruntjs.com to troubleshoot the problem.

Creating your Grunt tasks

To run our Grunt tasks, we'll need a JavaScript file. So, let's copy our scritps.js file and place it into the my-projects folder.

The next step is to create a Grunt file that will list out the tasks that we need Grunt to perform.

For now, we will ask it to do four simple tasks, first check if our JS code is clean using JSHint, then we will merge three JS files into one and then minify the JS file, and finally we will run some shell commands to clean up.

Until Version 0.3, the init command was a part of the Grunt tool and one could create a blank project using grunt-init. With Version 0.4, init is now available as a separate tool called grunt-init and needs to be installed using the npm install –g grunt-init command line. Also note that the structure of the grunt.js file from Version 0.4 onwards is fairly different from the earlier versions you've used.

For now, we will resort to creating the Grunt file manually. Refer to the following screenshot:

In the same location as where you have your package.json, create a file called gruntfile.js as shown earlier and type in the following code:

module.exports = function(grunt) {
    // Project configuration.
    grunt.initConfig({
 
 jshint:{
    all:['scripts.js']
 }
     });

    grunt.loadNpmTasks('grunt-contrib-jshint');
// Default task.
grunt.registerTask('default', ['jshint']);
};

To start, we will add only one task which is jshint and specify scripts.js in the list of files that need to be linted. In the next line, we specify grunt-contrib-jshint as the npm task that needs to be loaded. In the last line, we define the jshint as the task to be run when Grunt is running in default mode. Save the file and in the terminal run the following command:

grunt

You would probably get to see the following message in the terminal:

So JSHint is saying that we are missing a semicolon on lines 18 and 24. Oh! Did I mention that JSHint is like your very strict math teacher from high school.

Let's open up scripts.js and put in those semicolons and rerun Grunt. Now you should get a message in green saying 1 file lint free. Done without errors.

Let's add some more tasks to Grunt. We'll now ask it to concatenate and minify a couple of JS files. Since we currently have just one file, let's go and create two dummy JS files called scripts1.js and scripts2.js.

In scripts1.js we'll simply write an empty function as follows:

// This is from script 1
function Script1Function(){
   //------//
}

Similarly, in scripts2.js we'll write the following:

// This is from script 2
function Script2Function(){
   //------//
}

Save these files in the same folder where you have scripts.js.

Grunt tasks to merge and concatenate files

Now, let's open our Grunt file and add the code for both the tasks—to merge the JS file, and minify them as follows:

module.exports = function(grunt) {
 
    // Project configuration.
    grunt.initConfig({
 
 jshint:{
    all:['scripts.js']
 },
 concat: {
   dist: {
       src: ['scripts.js', 'scripts1.js','scripts2.js'],
       dest: 'merged.js'
            }
       },
uglify: {
   dist: {
   src: 'merged.js',
   dest: 'build/merged.min.js'
            }
       } 
    });
grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-uglify');
// Default task. grunt.registerTask('default', ['jshint','concat','uglify']); };

As you can see from the preceding code, after the jshint task, we added the concat task. Under the src attribute, we define the files separated by a comma that need to be concatenated. And in the dest attribute, we specify the name of the merged JS file.

It is very important that the files are entered in the same sequence as they need to be merged. If the sequence of the files entered is incorrect, the merged JS file will cause errors in your app.

The uglify task is used to minify the JS file and the structure is very similar to the concat task. We add the merged.js file to the src attribute and in the dest attribute, we will place the merged.min.js file into a folder called build.

Grunt will auto create the build folder.

After defining the tasks, we will load the necessary plugins, namely the grunt-contrib-concat and the grunt-contrib-uglify, and finally we will register the concat and uglify tasks to the default task.

Save the file and run Grunt. And if all goes well, you should see Grunt running these tasks and informing the status of each of the tasks.

If you get the final message saying, Done, without any errors, it means things went well, and this was your lucky day!

If you now open your my-project folder in the file manager, you should see a new file called merged.js. Open it in the text editor and you'll notice that all the three files have been merged into this. Also, go into the build/merged.min.js file and verify whether the file is minified.

Running shell commands via Grunt

Another really helpful plugin in Grunt is grunt-shell. This allows us to effectively run clean-up activities such as deleting .tmp files and moving files from one folder to another.

Let's see how to add the shell tasks to our Grunt file. Add the following highlighted piece of code to your Grunt file:

module.exports = function(grunt) {
 
    // Project configuration.
    grunt.initConfig({
 
 jshint:{
    all:['scripts.js']
 },
 
 concat: {
   dist: {
       src: ['scripts.js', 'scripts1.js','scripts2.js'],
       dest: 'merged.js'
            }
       },

uglify: {
   dist: {
   src: 'merged.js',
   dest: 'build/merged.min.js'
            }
       } ,


shell: {
    multiple: {
        command: [
            'rm -rf merged.js',
            'mkdir deploy',
            'mv build/merged.min.js deploy/merged.min.js'
        ].join('&&')
    }
}
    });

grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-shell');
// Default task.
grunt.registerTask('default', ['jshint','concat','uglify',
'shell'
]); 
};

As you can see from the code we added, we are first deleting the merged.js file, then creating a new folder called deploy and moving our merged.min.js file into it. Windows users would need to use the appropriate DOS commands for deleting and copying the files.

Note that .join('&&') is used when you want Grunt to run multiple shell commands. The next steps are to load the npm tasks and add shell to the default task list. To see Grunt perform all these tasks, run the Grunt command in the terminal.

Once it's done, open up the filesystem and verify whether Grunt has done what you had asked it to do. Just like we used the preceding four plugins, there are numerous other plugins that you can use with Grunt to automate your tasks.

A point to note is while the default Grunt command will run all the tasks mentioned in the grunt.registerTask statement, if you would need to run a specific task instead of all of them, then you can simply type the following in the command line:

grunt jshint

Alternatively, you can type the following command:

grunt concat

Alternatively, you can type the following command:

grunt ugligy

At times if you'd like to run just two of the three tasks, then you can register them separately as another bundled task in the Grunt file. Open up the gruntfile.js file, and just after the line where you have registered the default task, add the following code:

grunt.registerTask('concat-min', ['concat','uglify']);

This will register a new task called concat-min and will run only the concat and uglify tasks.

In the terminal run the following command:

grunt concat-min

Verify whether Grunt only concatenated and minified the file and didn't run JSHint or your shell commands.

You can run grunt --help to see a list of all the tasks available in your Grunt file.

Yeoman – the workflow tool

Yeoman prefers to be known as a workflow rather than just a tool. It is actually a collection of three tools that help you manage your workflow efficiently. The tools that come as a part of Yeoman are as follows:

  • Yo : This is a scaffolding tool and using the numerous generators available, one can quickly create the skeleton of your project. Yo has a generator to build AngularJS apps.
  • Grunt : This is used to run the tasks that will help you preview, test, and build the app.
  • Bower : This is an ideal tool for dependency management. Yeoman uses it to automatically search and download the necessary scripts.

Let's go about installing Yeoman and playing around with it a bit.

Installing Yeoman

To install Yeoman, make sure you are running it with administrative privileges. Enter the following command in the terminal:

sudo npm install –g yo

Next, let's install the AngularJS generator using the following command:

sudo npm install - g generator-angular 

Now, let's create our project directory and create the skeleton for our project. We will call our app Yoho; so, first let's create a folder called yoho. Enter the following command in the terminal:

mkdir yoho
cd yoho
yo angular

It's now going to start asking a series of questions, answer Y for all except the question, "Would you like to use Sass (with Compass)?". Answer N for this one.

The reason we say no here is because for now we will use vanilla CSS. Using Saas and Compass is however strongly recommended while building large applications.

Once yo-angular has finished doing whatever it had to do, go into your yoho folder and you'll notice a whole bunch of files and folders, as shown in the following screenshot:

Yeoman has created the skeleton of your AngularJS app along with everything you will need for this project.

Before we go into the details of the different files, one thing to note is that your node_modules folder is empty. This means Yeoman has only created the package.json file with all devdependencies listed out, but hasn't downloaded them yet.

We will need to run the following command:

npm install

This will download and install all the dependencies listed out in the package.json file. Once it's finished installing, verify that the node_modules folder now has folders such as grunt-contrib-clean and grunt-contrib-concat within it.

Ok, now, let's try and make sense of all the files that Yeoman has created. Refer to the following table:

Filename

Description

app/404.html

This is the 404 error page that will show up, when the user types in a wrong URL or the Angular app couldn't find the page mentioned in the URL.

app/favicon.ico

This is the icon that will show up in the browser tab of your app. Make sure you replace this default one with an icon that represents your app. Feel free to use any of those numerous online favicon generators to create your favicon. Remember that this favicon helps users to quickly identify your app within the multiple tabs of an open browser.

app/index.html

This will be the home page for your app. You can open it in a text editor to see what it contains. As you would have noticed, other than that one line of code with the ng-views directive, the rest of the file is mostly browser checks and inclusion of the various JavaScript files. Note that there is no actual AngularJS code other than ng-views and that's how it needs to be kept too.

robots.txt

This is the file where you set rules for the search engine robots or crawlers, telling them what pages they can index and which sections of the app should not be indexed.

scripts/app.js

This is the route's file where you'll define the template view and the controller that should load for a given URL. Controllers and views are loosely coupled in AngularJS; this means you can have a single controller talk to different views, or swap the templates for a controller by simply editing the routes in this app.js file.

scripts/controllers/main.js scripts/controllers/about.js

This is where you'll be writing the controllers for this app. As a part of the scaffolding, Yeoman would have already created a default MainCtrl and AboutCtrl controllers with a model created in each. Feel free to modify it and/or write the rest of your controllers in the same file.

styles/main.css

All your CSS code would go into this file. The styles folder would also contain the bootstrap.css file. This contains all the Bootstrap classes. Make sure you don't modify any of the code here or add any additional CSS in the bootstrap file.

views/about.html views/main.html

The views folder would contain all the template views or partials as they are also called to load within the ng-views tag of the index.html file. The routes defined in the scripts/apps. js file would control what view will be displayed for the given URL.

Bower_components:

This contains folders for the various vendor libraries such as AngularJS, Angular Animate, jQuery, and Bootstrap and it is used for dependency management of these libraries.

bower.json

The bower.json file keeps a track of the dependencies and dev dependencies of the various modules and plugins for this app.

Gruntfile.js

You know what the Grunt file is for right? Open it in a text editor and be overwhelmed by the 300 plus lines of code in the file. You'll see a bunch of predefined tasks and you'll also notice that besides the default bundled task, you also have tasks called test and build. These can be used to preview your app and finally build it ready for deployment.

test/karma.conf.js

The karma.conf.js file is the configuration file for running Karma unit tests. Opening this file will show the testing framework we are using, list files that we want to include within the scope of these unit tests, what port to use, the browser to use, and so on.

node_modules

As self-explanatory as it can be, this folder contains the various Node.js modules that were defined in the package.json file. Note that these modules do not get installed when you run the yo-angular command. These would download and install only after you run the npm-install command after the creation of the package.json file.

package.json

This file lists out all the Node.js modules that need to be installed. You may edit this file with caution to add more or remove dependencies that you think are not needed for your app.

test/spec/controllers/main.js

This is the file where you'd write the unit tests for your controllers. The main.js file would already have a simple unit test in it.

The default configuration of Yeoman uses Jasmine; you can change the configurations to use Mocha or Qunit frameworks.

Let's install our Node.js modules by running the following command in the terminal:

npm install

Running your app

Earlier in this article, we saw how to set up a server using Node.js and ExpressJS. Yeoman comes with its own server and running it is as simple as running the following command in your terminal:

grunt serve

The grunt server command is deprecated although it might still work for some.

This will open up a new browser window and will show you the default welcome screen, as shown in the following screenshot:

If you recollect looking at the main.js file (under scripts/controllers) and the main.html file (under views), you'll notice the page that is being rendered. Let's play around a bit.

Open the scripts/controllers/main.js file and you'll find a controller called MainCtrl and a model called awesomeThings. Let's add some more items to this array as follows:

'use strict';

angular.module('yohoApp')
  .controller('MainCtrl', function ($scope) {
    $scope.awesomeThings = [
      'HTML5 Boilerplate',
      'AngularJS',
      'Karma',

      'E2E',


      'Protractor'


    ];
  });

Let's display our awesomeThings array in the view. Please add the following code to the main.html file as follows:

<ul class="row">
    <li ng-repeat="things in awesomeThings">{{things}}</li>
</ul>

Save the file and switch to the browser. VOILA! The page updated on its own, you didn't have to reload the page in the browser.

The browser updates the moment you save your file, no more hitting the refresh button. Isn't that a big relief and a productivity boost!

This works thanks to a nifty module called LiveReload. You'll find this being installed as a part of devDependencies in the package.json file. You'll also notice Grunt tasks for it created in your gruntfile.js file.

So now you can have the server running and place your browser window and your text editor arranged side-by-side, and watch your app update as you write your code and save the file.

Unit testing with Karma

Writing automated unit tests for your AngularJS app is one of the best practices, the AngularJS team has been strongly advocating this right from the start. Every sample code on the www.Angularjs.org site has automated test cases along with it.

Keeping in line with the same philosophy, Yeoman too bakes in some sample unit tests using Karma. While Yeoman would automatically install Karma and its dependencies, let us, nevertheless, make sure the following modules are present in the node_modules folder:

  • karma
  • karma-chrome-launcher
  • karma-jasmine

In case you don't find them in your node_modules folder, install them using the npm install command. Next, make sure your karma.conf.js file looks like the following:

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: [
  'bower_components/angular/angular.js',
  'bower_components/angular-mocks/angular-mocks.js',
  'bower_components/angular-animate/angular-animate.js',
  'bower_components/angular-resource/angular-resource.js',
  'bower_components/angular-cookies/angular-cookies.js',
  'bower_components/angular-route/angular-route.js',
  'bower_components/angular-sanitize/angular-sanitize.js',
  'bower_components/angular-touch/angular-touch.js',
  'app/app.js',
  'app/scripts/*.js',
  'app/scripts/**/*.js',
  'test/spec/**/*.js'
    ],
    exclude: [
      
    ],
    preprocessors: {
    
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

To run your unit tests, simply run the following command in the terminal:

grunt test

This will fire up a new Chrome browser window and in the terminal start running through your tests. Refer to the following screenshot:

Oh! We got an error in MainCtrl. It would say something like Expected 5 to be 3 and point you to an error in the file located at test/spec/controllers/main.js.

Let's open up this file and see what's going on in there. The test case that's failing is as follows:

it('should attach a list of awesomeThings to the scope', function() {
    expect(scope.awesomeThings.length).toBe(3);
});

If you recollect, earlier we had modified our awesomeThings controller and added some additional elements to the array, the preceding test case is expecting the length of that array to be 3. Let's now modify that statement to the following code:

expect(scope.awesomeThings.length).toBeGreaterThan(3);

Let's save this file and rerun the following command:

grunt test

The test cases should run fine with a message saying Executed 2 of 2 SUCCESS……. Done , without errors.

Using Protractor for End-to-End tests

As we saw earlier with Karma, we can write unit tests that will make sure that our code in the controllers is working well; however, it would fall short if you wanted to run automated User Acceptance Tests (UAT) or run tests that simulate user-driven interactions with the browser. For running such tests, we'll need to use another new tool called Protractor.

Protractor replaces the earlier AngularJS scenarios as the default End-to-End testing framework for testing AngularJS apps.

Protractor runs on WebDriver.js, which in turn makes use of the Selenium Server. Selenium is one of the most popular browser-automation tools. In this section, we will see how to set up a standalone instance of Selenium Server, install Protractor, and run a default set of End-to-End tests with it.

Let's first install Protractor by running the following command in the terminal:

sudo npm install –g protractor

This will install Protractor globally.

Installing Selenium Server

Protractor comes with a handy script called webdriver-manager that can be used to download and run the Selenium Server.

In the terminal, type in the following command to download Selenium Server:

webdriver-manager update

To start the server, type in the following command:

webdriver-manager start

This should start your Selenium Server. Now open your browser and type in the http://localhost:4444/wd/hub/static/resource/hub.html address in the address bar. This will show you the current status of the Selenium Server.

The protractor folder within the node_modules comes with a couple of default tests that can help you jump start, writing your End-to-End tests. These tests are located in the usr/local/lib/node_modules/protractor/example folder.

Depending on what you are comfortable with, you can choose between Jasmine and Mocha for writing your test cases. From the example folder under protractor, copy the conf.js and the example_spec.js files and paste them into a new test/protractor-tests folder.

Understanding the example_spec.js file

The example_spec.js file is the specs file where the test cases are written. Let's open this file in an editor and try and make sense of what it is going to do.

We first describe our test suite as follows:

describe('angularjs homepage', function() { })

Next, let's look at the test case. The first test case looks something like the following:

  it('should greet the named user', function() {
    browser.get('http://www.angularjs.org');

    element(by.model('yourName')).sendKeys('Julie');

    var greeting = element(by.binding('yourName'));

    expect(greeting.getText()).toEqual('Hello Julie!');
  });

The first line describes the test case. In the next line, we navigate to the defined URL; in this case we load the www.angularjs.org home page.

Once the home page is loaded, in the next step the code is trying to locate an input field bound to a model called yourName and type in the text Julie. It then looks for the expression called Hello{{yourName}} and verifies that the text reads Hello Julie.

Let's look at the second test suite, which has two test cases as follows:

  describe('todoList', function() {
    var todoList;

We define the suite name and initialize todoList. Now since both the test cases need to go to the same URL and will be based on todoList, we use beforeEach to set the URL and load the content into our todoList array as follows:

    beforeEach(function() {
        browser.get('http://www.angularjs.org');

        todoList = element.all(by.repeater('todo in todos'));
    });

The first test case checks to see if we have two items in todoList and that the second item is called build an angular app. Refer to the following code:

    it('should list todos', function() {
        expect(todoList.count()).toEqual(2);
        expect(todoList.get(1).getText()).toEqual('build an angular app');
    });

The second test case will simulate adding an item to todolist by locating the todoText model, and adding in and verifying the text being added as follows:

    it('should add a todo', function() {
        var addTodo = element(by.model('todoText'));
        var addButton = element(by.css('[value="add"]'));

        addTodo.sendKeys('write a protractor test');
        addButton.click();
        expect(todoList.count()).toEqual(3);
        expect(todoList.get(2).getText()).toEqual('write a protractor test');
    });

})

Understanding the conf.js file

The conf.js file is the configuration file. Open it in an editor and you'll see the configuration settings such as the default address for the Selenium Server, the base URL, the browser to be used for testing, and the location of your test cases. Now, since we have the example_spec.js file in the same path as our conf.js file, let's correct the path of the specs to read as follows:

  specs: [example_spec.js'],

To run our test suite, open the terminal and navigate to the yoho folder and type in the following command:

protractor test/protractor-tests/conf.js

This will launch a browser instance and see the steps being performed by the script. The browser will automatically close once the script is executed and the terminal window should display the following message:

Finished in xxx.xxxx seconds

2 tests, 2 assertions, 0 failures

Sometimes you might start getting errors like Error: ECONNREFUSED connect ECONNREFUSED. In such a case restart your Selenium Standalone Server and web server.

Writing your own Protractor test cases

Now that we know how test cases are written in Protractor and how to run them, let's quickly write a couple of our own test cases against the default scaffold application that Yeoman created for us.

Let's create a new file called mytests.js in the test folder under yoho/protractor-tests. Now we can start writing out our test cases as follows:

describe('our homepage', function() { })

The first test case we will write is the one where we'll check to see if the page heading within the h1 tags says 'Allo Allo!'. The test case for that would look something like the following:

it('should say Allo', function() {
    browser.get('http://localhost:9000/#/');
    var heading = element(by.tagName('h1'))
    expect(heading.getText()).toEqual("'Allo, 'Allo!")
});

The browser.get function defines the URL of the page you'd want Protractor to navigate to. We use the by.tagName selector to locate the h1 tag and get the text within it and verify that it matches "'Allo, 'Allo!".

Let's write our second test case. Here, we want to ensure that the width of our page is within the recommended limits and isn't going beyond the screen size of most common users. Refer to the following code:

it('should not be greater than 940px', function() {
    browser.get('http://localhost:9000/#/')
    element(by.className('container')).getSize().then(function(size) {
        expect(size.width).toBeLessThan(950)
    })
})

Here, we are using the by.className selector to identify the container div and using the getSize property we check to make sure that the width is not greater than 950px.

Notice that the .then() function is a part of a promise, which ensures that the code waits for the result to be returned.

Now that we have our test case's specs file ready, we need to call it within the protractor configuration file. Let's open the conf.js file and change the filename in the specs array to point to our mytest.js file as follows:

specs: ['mytests.js'],

We are now set to test our scripts. First, let's start our server by running the following command:

cd yoho
grunt serve

This should start our server. Now, let's run our protractor test script by running the following command from within the yoho folder:

protractor test/protractor-tests/conf.js

Make sure you have your Selenium Standalone Server running; if not, start it using the following command:

webdriver-manager start

If all goes well, you could see the Chrome browser being launched, and see the script navigate to the localhost:9000/#/ URL and the browser window closing down after some time.

Switch to your terminal to see the status saying the following in green that means our test cases worked:

Finished in x.xxx seconds

2 tests, 2 assertions, 0 failures

In case you'd like to run your test cases without having to launch the browser window each time or if you'd want to run it headless on a server, then have a look at PhantomJS (http://phantomjs.org/), which is an excellent headless browser that runs on the WebKit engine.

Summary

This completes our article on setting up your rig. We worked through quite a few tools, namely Node.js, Grunt, Yeoman, Karma, and Protractor. While I strongly recommend making use of all of them when you build your AngularJS projects, you may feel free to choose the ones that suit your project the best.

Another thing to note is that most of these tools such as Node.js, ExpressJS, and Grunt can be used for any non-AngularJS projects. So getting familiar with these tools is surely beneficial for all frontend developers.

Resources for Article:



Further resources on this subject:


Books to Consider

comments powered by Disqus