Automated Testing with BrowserStack & Selenium

Ensuring high quality software requires a lot of testing. Whether it’s code peer-review, unit testing, integration testing, system testing or exploratory user testing – it all has to be done! Therefore we look to automate our testing where possible. This blog post explores the automation of browser compatibility testing in BrowserStack using Selenium WebDriver.

BrowserStack is a cloud-based platform for testing applications in various Operating Systems and browsers. It is used for both Continuous Integration (CI) and cross-browser testing. Selenium WebDriver is a tool for driving browsers to replicate and automate user journeys and assert presence and functionality of page elements.

bstackWe aim to integrate automated testing into our Continuous Integration pipeline, however a number of our more established products are not yet part of this pipeline. One such product is Location Centre which is used by many local authorities where the end-user often has limited control over the choice of OS and browser. This often restricts the development to suit the majority of browsers used and their degree of modernity. Despite not being part of our CI pipeline testing our software to ensure high quality is just as important. Therefore I’ll now provide an example of how simple and straightforward it is to automate tests for Location Centre using BrowserStack.

BrowserStack offers a 100 minute trial so I recommend you also try it out! The site provides a range of multi-system environments for manual and automated testing as shown in the image below. bw_mobile_devices

BrowserStack runs the tests using Selenium WebDriver and these code examples serve as a great starting point. The tests can be scripted manually or by using Selenium IDE plugin for Firefox – the extension allows you to record the user steps with minimum coding, thus minimising hand-written code and saving you time. It provides hundreds of commands for element assertion, mouse position and many more. The tool decides by itself how to locate the element. The plugin is user-friendly and well-supported and the process of recording is quick and intuitive.

I have recorded the simple process of logging in to Location Centre, selecting the ‘Mapping’ tab and then zooming and panning the map.

selelnium_ide

Once created test cases can be exported in your language of choice – in our case it’s Python.

(File> Export Test As> Python 2  / unittest / WebDriver)

We can add the exported test case to our IDE. All we need to do now is modify the setup function to add the BrowserStack keypass and specify the browser and OS requirements.

We find it’s always worth starting the tests with the high risk browsers which is traditionally Internet Explorer which is used by the majority of Location Centre customers. Gov.UK recommends testing from IE8 and up, so we will run the test in IE8.

IE8 runs on Windows 7 and Windows XP, making it difficult to test locally. You could get IE8 from Microsoft virtual machine but BrowserStack makes it much easier. Using the BrowserStack tool we can get the capabilities and add them to the setUp function:

system_capabilities

System capabilities and the authorisation information can be input into the setup:

class WebDriver(unittest.TestCase):
    def setUp(self):
       <strong> self.verificationErrors = []
        url = 'http://USERNAME:PASSCODE@hub.browserstack.com:80/wd/hub'
        self.driver = webdriver.Remote(command_executor=url, desired_capabilities= {'browser': 'IE', 'browser_version': '8.0', 'os': 'Windows', 'os_version': '7', 'resolution': '1024x768'})</strong>

    def test_leics_l_c(self):
        driver = self.driver
        driver.get("https://lctrial.locationcentre.co.uk/")
        driver.find_element_by_id("ContentPlaceHolder1_loginControl_Password").clear()
        driver.find_element_by_id("ContentPlaceHolder1_loginControl_Password").send_keys("password")
        driver.find_element_by_id("ContentPlaceHolder1_loginControl_UserName").clear()
        driver.find_element_by_id("ContentPlaceHolder1_loginControl_UserName").send_keys("username")
        driver.find_element_by_id("ContentPlaceHolder1_loginControl_LoginButton").click()
        driver.find_element_by_id("Header1_lnkMap").click()
        driver.find_element_by_id("map-size-toggle").click()
        driver.find_element_by_id("map-size-toggle").click()
        driver.find_element_by_id("OpenLayers.Control.PanZoomBar_17_zoomin_innerImage").click()
        driver.find_element_by_id("OpenLayers.Control.PanZoomBar_17_zoomin_innerImage").click()
        driver.find_element_by_xpath("//div[@id='toolsPanel']/divg").click()

    def is_element_present(self, how, what):
        try:
            self.driver.find_element(by=how, value=what)
        except NoSuchElementException as e:
            return False
        return True

    def is_alert_present(self):
        try:
            self.driver.switch_to_alert()
        except NoAlertPresentException as e:
            return False
        return True

    def close_alert_and_get_its_text(self):
        try:
            alert = self.driver.switch_to_alert()
            alert_text = alert.text
            if self.accept_next_alert:
                alert.accept()
            else:
                alert.dismiss()
            return alert_text
        finally:
            self.accept_next_alert = True

    def tearDown(self):
        self.driver.quit()
        self.assertEqual([], self.verificationErrors)

    if __name__ == "__main__":
        unittest.main()

You can now run the automated test with BrowserStack as shown in the GIF below.

ice_video_20170105-102548

The test can be played back and each step can be reviewed in turn. BrowserStack will provide you with a screenshot for any failed step (I have broken the one below on purpose!).

bs_failed_test

This service offers many useful features including localhost testing, ability to upload files and many more. There is no or very little coding required – although you may want to change an elements location or add some custom assertions, cursor movements or text inputs etc. Furthermore, there are no (or not as many) compatibility issues between the Selenium methods and the webdrivers which one experiences when testing locally.

You can utilise the vast Selenium library for replicating user journeys in the required system at the desired speed. BrowserStack also has integrated Cucumber and Behave used in Behaviour Driven Development (BDD) and testing. However, at the time of the writing this post, there were issues running those on Windows platforms. Overall, it is a great framework for compatibility testing.

Alongside the automation, exploratory tests still need to be in place to avoid kaleidoscopic bugs like this one found in our product mapTrunk…good job we caught this before Go-Live!

openlayers_bug-1

AngularJS and OpenLayers: creating a scale bar module

Intro

We’ve recently released a new product called mapTrunk. The app is built using the open source libraries AngularJS and OpenLayers 3 (among many others!). As part of our development efforts we looked into creating reusable modules. This blog post offers a high level introduction to AngularJS and OpenLayers 3 and shows how they can work together to create a reusable map scale bar module example.

maptrunk

AngularJS and OpenLayers 3

AngularJS is an open source JavaScript framework for creating web apps. It provides tools for making apps modular. AngularJS handles binding data which means the view (HTML) automatically updates when the model (JavaScript) updates. Other benefits of AngularJS include form validation in the browser, the ease of wiring an app up to a backend and the testability of the code. AngularJS also lets you extend the syntax of HTML and inject components into your HTML. This feature comes in handy when creating the scale bar module.

OpenLayers 3 is an open source mapping library. It provides tools for adding dynamic maps to an app. Commonly used mapping controls provided by OpenLayers include zooming in/out control, a mouse position control and a scale bar control.

The following example shows how to create a basic map with OpenLayers and AngularJS. The result is a map and a button to recenter the map. It also shows the user how many times they have centred the map.

HTML

Firstly we need to include the AngularJS and OpenLayers 3 libraries, add a div for the map and add a button. We also need to include the Angular app called “app”, which is created in JavaScript.

<html ng-app="app">
<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.18.2/ol.css"/>
    <a href="https://code.angularjs.org/1.4.12/angular.js">https://code.angularjs.org/1.4.12/angular.js</a>
    <a href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.18.2/ol.js">https://cdnjs.cloudflare.com/ajax/libs/ol3/3.18.2/ol.js</a>
    <a href="http://main.controller.js">http://main.controller.js</a>
</head>
<body ng-controller="mainController as main">
    <div id="map"></div>
    <button ng-click="main.centerMap()">Button</button>
    <div>You have centered the map on coordinate [0,0] {{main.counter}} times.</div>
</body> </html>

JavaScript

Here we create our own Angular controller; mainController, which initialises the map and contains the function which is called on clicking the button, updating the counter.

var app = angular.module('app', []);

(function () {

    'use strict';

    /**
     * The main Angular controller which initialises the mapping
     */
    angular
        .module('app')
        .controller('mainController', [mainController]);

    function mainControllerblockquote {
        var vm = this;
        vm.counter = 0;
        vm.map = new ol.Map({
            layers: [
                new ol.layer.Tile({
                    source: new ol.source.OSM()
                })
            ],
            target: 'map',
            view: new ol.View({
                center: [0, 0],
                zoom: 2
            })
        });
        vm.centerMap = function () {
            vm.map.getView().setCenter([0, 0]);
            vm.counter++;
        }
    }
})();

Creating the scale bar module

The OpenLayers library already has a scale bar module called ‘scale line’ built-in. An example can be found here. One of the requirements for mapTrunk was to create a scale line that can display distances in two units at the same time, metric and imperial.

To create a reusable module we can create a custom Angular directive. Angular directives basically let us create our own HTML syntax and inject components by using that HTML syntax. It makes the HTML code easier to read and hides the complexity of the component. In this blog we’re not going to go into the details of Angular directives so please see AngularJS’s documentation on directives for a full explanation.

First we need to create the Angular directive and decide what the HTML syntax is going to be. In the code snippet below we called the directive scaleLineControl. This translates into the HTML tag . The directive needs to have access to an OpenLayers map object to be able to add a scale line control to the map. The map object can be passed into the directive by adding it as a property to the HTML ‘map=”main.map”‘. The OpenLayers scale line control needs a HTML target ID so this ID can be given to the HTML directive as well. The scale line control is added to the OpenLayers map object by using the addControl function. The units of the first control are specified as metric. To create a scale line module which also shows imperial measurements, a second scale line control is added to the map with imperial units. OpenLayers takes care of listening out for changes on the map and updates the controls accordingly. Now we should see two scale lines on the map, but they are positioned on top of each other so we need some CSS to fix this.

(function () {

'use strict';

/**
 * @fileoverview This file provides a scaleLineDirective. 
 * It add a scaleLine showing both metric and imperial units. 
 * CSS is needed to display the elements nicely
 *
 * Example usage:
 * 
 * 
 */

    angular
        .module('app')
        .directive('scaleLineControl', scaleLineDirective);

    function scaleLineDirective() {
        return {
            restrict: 'E',
            link: function(scope, element, attributes) {
                var attr = 'map';
                var prop = attributes[attr];
                var map = scope.$eval(prop);
 
                var scaleLineControl = new ol.control.ScaleLine({
                    target: 'scale-line-container',
                    className: 'scale-line-top',
                    minWidth: 100,
                    units: 'metric'
                });
                map.addControl(scaleLineControl);

                var scaleLineControl2 = new ol.control.ScaleLine({
                    target: 'scale-line-container',
                    className: 'scale-line-bottom',
                    minWidth: 100,
                    units: 'imperial'
                });
                map.addControl(scaleLineControl2);
            }
        };
    }
})();

Making it look good!

We can specify the CSS class names when creating the OpenLayers scale line controls. By doing so we can customise the default look of the scale line controls. Here we have added the class ‘scale-line-top’ to the metric control and ‘scale-line-bottom’ to the imperial control.

#map {
    width: auto;
    height: 100%;
    position: relative;
    overflow: hidden;
}

#scale-line-container {
    border-radius: 2px;
    background: white none repeat scroll 0 0;
    bottom: 8px;
    left: 8px;
    font-size: 10px;
    position: absolute;
    z-index: 1000;
    padding: 5px;
    text-align: center;
}

.scale-line-top-inner {
    border-style: none solid solid solid;
    border-width: medium 2px 2px 2px;
}

.scale-line-bottom-inner {
    border-style: solid solid none;
    border-width: 2px 2px medium;
    margin-top: -2px;
}

Result

The result is an Angular directive which can be injected into HTML and easily be used in other applications.

scaleline.PNG

For a full working example, see this Plnkr.