Automated web test using Protractor

Protractor is a neat test framework for AngularJS apps and since the web app we made in previous post is made using Angular, we’re going to create a simple Protractor test. You find the previous post here.

What do you need to start using Protractor

  • An Angular based web app uploaded to a web server. We’ll be using the Angular app we made in previous post.
  • Protractor is a Node.js program so you will have to have Node.js installed. You can download it here if you haven’t installed it.

Uploading our Angular web app to the Domoticz server

Since Protractor is made for testing web apps located on remote web servers, we upload our Angular web app to the Domoticz server in the “www” directory in a folder we call “angular_frontend”. For this we use a SFTP client (FileZilla) on our laptop and upload our CSS, fonts, img, JS folders along with the index, sensorApp.css and sensorController.js files.

angular_frontend

Installing Protractor

Make sure you have Node.js installed on the laptop by typing “node –version” in a Terminal window. If you have Node.js installed it will print out the version number you have. To install Protractor type the following:

npm install -g protractor

This will install Protractor and the Webdriver Manager. You can test your Protractor installation by typing “protractor –version”. Next type:

webdriver-manager update

Writing the Protractor test

We will write two files to create the Protractor test: one spec.js and one conf.js. Place these two files in the same directory on your laptop. Our conf.js file looks like this:

[code language=”javascript”]
exports.config = {
framework: ‘jasmine’,
seleniumAddress: ‘http://localhost:4444/wd/hub’,
specs: [‘spec.js’],
}
[/code]

In the conf file you can add a lot more configuration parameters but in this case we stick with the basics. The “seleniumAddress” parameter specifies the local web address we will use when running our test. The “specs” parameter tells Protractor where to find the test file i.e. “spec.js”. Our spec.js file looks like this:

[code language=”javascript”]
describe(‘Test of Home Security Frontend’, function() {
it(‘should verify the title and count the sensors’, function() {
browser.get(‘http://192.168.1.99:8080/angular_frontend/index.html’);

expect(browser.getTitle()).toEqual(‘Home Security Frontend’);

var sensorlist = element.all(by.repeater(‘sensor in sensorData’));
expect(sensorlist.count()).toEqual(4);

});
});
[/code]

It will retrieve the “index.html” file from the Domoticz web server and run two tests:

  1. Verify that the page title is “Home Security Frontend”
  2. Count the sensor boxes and verify that all four sensor are loaded (including the dummy sensor)

The “spec.js” file can be expanded with simulated user actions such as clicking on buttons and typing in text boxes. For more references how to use Protractor go to angular.github.io/protractor/.

Running the test

Before we run our test, we need to change the $timeout function to $interval in the sensorController.js file. This is because Protractor will timeout if using the $timeout repeatedly. In the Angular controller services listing, replace $timeout with $interval:

[code language=”js”]app.controller(‘sensorCtrl’, function($scope, $http, $interval) {[/code]

Next, replace the call to $timeout with $interval and set the repeat count to 1:

[code language=”js”]//$timeout(updateSensorData(), 1000); //Poll the sensor data every second
$interval(updateSensorData, 1000, 1); //Poll the sensor data every second[/code]

Save and upload the sensorController.js file to the Domoticz web server. Now we’re ready to run the test. Open a Terminal window and type:

webdriver-manager start

This will start the Selenium server that will be used for the Protractor test. Let the server run in this Terminal window and open another Terminal window. Run the Protractor test by typing:

protractor {path to your conf.js file}/conf.js

The output of a successful test looks like:

protractor_output

You can try running the test with intentionally wrong test criteria such as misspelt page title or incorrect number of sensors, just to see how a test result looks like with failed tests. You find all the source code at gitlab.com/yellington.

Creating an MVC structured UI using Angular

Angular is a nice JavaScript framework for creating more structured web solutions and presenting data. As shown in previous post, jQuery works just as good, but some advantages of Angular are that your code is easier to read, you can piggy-back on the functionality of Angular meaning less development time, and it is meant for separating the view (i.e. your HTML) from the data handling – i.e. Model-View-Controller based (MVC).

Things you need to have a fair understanding of to follow this post

  • A fair knowledge of HTML, CSS, and JavaScript
  • The basics of Angular suchs “ng directives” and “controller”
  • It’s good to check this previous post since we will go from a jQuery based solution to Angular

Breaking our web app into html, css, and JavaScript

In the end we will have an identical user interface with the same frontend functionality as we did with jQuery.

front-end_design

However one of the main end benefits of using Angular will be a cleaner structure when it comes to separating the presentation layer (i.e the html view) and the data handling (i.e. the Angular part). We will start by breaking our web app into three files:

  • index.html – this is the clean html frontend
  • sensorApp.js – this is where we implement the controller logic to update the data in the html view
  • sensorApp.css – this is simply the css styles we used previously, unchanged but broken out in a separate file

Updating our index.html with adequate ng directives

We start by “Angular-ify” our html view by adding necessary ng directives and bindings. This will allow us to use these spaces from our Angular controller to populate the html view with sensor data. We add the “ng-app” and “ng-controller” directives in the <html> tag, and link our css file, the Angular framework and our sensorController.js file in the <head>.

[code language=”html”]
<!DOCTYPE html>
<html ng-app="mySensorApp" ng-controller="sensorCtrl">
<head>
<title>Home Security Frontend</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="http://code.jquery.com/jquery.js"></script>

<!– Bootstrap –>
<script src="js/bootstrap.min.js"></script>
<link href="css/bootstrap.min.css" rel="stylesheet" media="screen">

<!– Angular –>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="sensorController.js"></script>

<link href="sensorApp.css" rel="stylesheet" media="screen">
</head>
<body>

<div class="container">

[/code]

Next we structure the “container” part of our html file in three sections corresponding to the Bootstrap rows: the top row, the home security status row, and the sensor boxes row.

[code language=”html”]
<div class="container">
<!– The top row for the logo and the time –>
<div class="row">
<div class="col-sm-12" id="top-bar">
<img id="yellington_logo" src="https://www.yellington.com/yellington_logo.png">
<div id="clock">{{clock | date: ‘HH:mm:ss’}}</div>
</div>
</div>

<!– The home security status row –>
<div class="row">
<div class="col-sm-6 v-align" style="text-align: right;">
<span class="glyphicon glyphicon-home" id="house-image"></span>
</div>
<div class="col-sm-6 v-align" id="house-status-text"><!– keep the opening and closing div tags together to avoid white space causing line breaks in the user interface–>
<h5>Home Security Status</h5>
<h2>{{homeSecurityStatus}}</h2>
</div>
</div>

<!– The row for the sensor boxes –>
<div class="row" id="sensor-container">
<div ng-repeat="sensor in sensorData" class="col-sm-6 col-md-4 col-lg-3 sensor-outer-box">
<div class="sensor-box">
<img class="sensor-icon" ng-src={{getSensorImage(sensor.Type)}}>
<div class="sensor-data">
<h4>{{ sensor.Name }}</h4>
<h5>Status: {{ sensor.Data }}</h5>
Updated: {{ sensor.LastUpdate }}
</div>
</div>
</div>
</div>

</div><!– Container –>
</body>
</html>
[/code]

In the top row, we have added an Angular expression for binding clock data to our html, likewise in the home security status row, and in the sensor data row for sensor data. The ng-repeat directive is a neat thing that will assist us going through the retrieved sensor data from the server. The html view has no clue where this data comes from or how or when it should be updated. This logic is handled by the controller.

Adding an Angular controller for updating the data

Angular uses a “controller” to take care of the logic for updating the data in the presentation view i.e. the html. We create a controller that looks like this and is linked together with our html file.

[code language=”javascript”]
var app = angular.module(‘mySensorApp’, []);
app.controller(‘sensorCtrl’, function($scope, $http, $timeout) {
var domoticzURL = "http://192.168.1.99:8080/"; //use this format if you want to place this file on another computer on your home network.
//var domoticzURL = "../"; //use this format if you place this file in a folder in the www folder on your Domoticz server.

updateSensorData(); //Start the polling of sensor data

function updateSensorData() {
//Publish the time in our user interface
$scope.clock = new Date();

//We request a list of all sensor data and loop through it to publish on our web page
$http.get(domoticzURL + "json.htm?type=devices&filter=all&used=true&order=Name")
.then(function(response) {

$scope.sensorData = response.data.result;

//We retrieve the status of the security system, basically Disarmed, Armed Away, or Armed Home
$http.get(domoticzURL + "json.htm?type=command&param=getsecstatus")
.then(function(response) {

if (response.data.secstatus==0) $scope.homeSecurityStatus = "Disarmed";
else if (response.data.secstatus==1) $scope.homeSecurityStatus = "Armed Home";
else if (response.data.secstatus==2) $scope.homeSecurityStatus = "Armed Away";
else $scope.homeSecurityStatus = "Unknown";

$timeout(updateSensorData(), 1000); //Poll the sensor data every second
});
});
}

$scope.getSensorImage = function(sensorType){
//This function returns the image of the sensor that is stored in the image folder on your Domoticz server
//The list of sensor types can be expanded dependant on what type of sensors you have
switch(sensorType) {
case "Light/Switch":
return domoticzURL + "images/Light48_On.png";
break;
case "Temp + Humidity":
return domoticzURL + "images/temp-20-25.png";
break;
default:
return domoticzURL + "images/Light48on.png";
}
};
});
[/code]

What’s inside the curly brackets of the app.controller is the constructor of the controller class and will execute at the time of launching the web app. You will probably recognize most of the code from the jQuery version. We have an updateSensorData() function that retrieves the sensor data through http requests along with a timer to poll the server every second. The getSensorImage() is used by the html view to retrieve the corresponding url for the src attribute.

Final conclusion

Using Angular might require some time to get a feeling for how it works and how to utilize the built-in functionality. Once over that learning curve, it’s a nice framework to use. Counting the lines of code from the jQuery version and the Angular version gives us that  jQuery required 142 lines of code whereas Angular required 117 – an 18% improvement. However, the biggest improvement is an increase in readability and structure and thus maintainability.

Starting with Arduino

If you love playing around with hardware, you’ll love Arduino. And if you don’t like hardware, you’ll love Arduino equally as much since you don’t have to be a hardware engineer to get that LED blink or program your Arduino to act as a thermometer. Arduino simply is a super quick and cheap way to have a demo or a prototype up and running in half a day. This includes having a complete hardware module, your software code flashed into it and time to make a cup of coffee to drink when you admire your creation.

What do you need to get started with Arduino?

  • An Arduino module (google Arduino module and you’ll certainly find an e-shop near you)
  • A laptop to run the Arduino programming environment
  • A fairly good knowledge of C or C++ programming language (any other programming language suffices as well)

Setting up your Arduino module

In our case, we have an Arduino compatible module called “Bean” (www.punchthrough.com). Our module communicates wirelessly with our laptop through a Bluetooth Low Energy chip, but if your module connects through a USB cable is basically the same procedure to set up and download your code.

bean

We unpack the small match box size product box and remove the battery isolating plastic film. This powers up our module.

Setting up the Arduino programming environment on our laptop

We start by downloading and installing the Arduino programming environment on our laptop: www.arduino.cc/en/Main/Software. There are versions for both Windows, Mac and Linux.

arduino

In our case we need to download the program “Bean Loader” which will help us load our program code that we write in the Arduino programming environment to our Arduino module. We download it from here: punchthrough.com/files/bean/loader/latest.php?download.

bean_loader

First time you open the Bean Loader, it will ask you to associate Bean Loader with your Arduino app you just installed. Locate the Arduino app in your Applications folder and click “Associate”. This association will make the Arduino pass your program code to the Bean Loader in order for it to be downloaded to the module. If you now restart the Arduino app, you should see the Lightblue Bean module in the list of Boards under the “Tools” menu.

board_list

Connecting our Arduino module with our laptop

We make sure our Bean is powered on and then we start the Bean Loader app. We click refresh to find our Bean module in the list. Next we right click on it and select “Connect”.

connect

Once connected you can try the connection by once again right clicking on the module in the list and select “Blink LED”. Now, everything is set up to create your first Arduino program. This we will go through in upcoming posts.