End-2-End UI Automation Framework

How to set up the UI automation from scratch

Naren Chejara

--

As a QA Engineer, I have been working in software automation including UI, API and customize automation framework. In this article, we are going to discuss configuring or set up UI automation using node libraries like Grunt (a JS task runner), Mocha (a test framework), protractor (a UI automation library) and reporting.

“Automation is a process or procedure which can be performed tasks/actions with less human assistance, reduce manual and repetitive tasks” — Wikipedia

I presume that you have a basic understanding of Node JS, Grunt, Mocha & protractor prior to reading this article.

I prefer to exclude UI automation from the application project but you are free to exclude/include from/in the project.

You can refer ‘end2end_automation_protractor’ repository in case you stuck in the article

Let’s get started!

Create an ‘end2end_automation_protractor’ folder and setup the NPM project by running below commands

$ mkdir end2end_automation_protractor
$ cd end2end_automation_protractor

$ npm init -y
// option -y create package.json with default configuration without prompt user input

$ npm install -g protractor

‘npm install -g protractor’ command installs the protractor and webdriver-manager CLI globally that can use to start, stop, update selenium server, download chrome & Firefox driver and run protractor tests. It’s recommended to run below command to make sure we are using the latest selenium and driver prior to starting the selenium server.

$ webdriver-manager update

Install the following dependencies

$ npm i --save grunt grunt-cli matchdep mocha mochawesome mochawesome-screeshots protractor grunt-protractor-runner should
  • grunt & grunt-cli | is a JavaScript Task Runner that helps to run repetitive tasks like minification, compilation, linting, unit testing, ene2end testing, etc. You can read more about grunt https://gruntjs.com
  • matchedep | match dependencies or dev dependencies from the package.json to perform
  • mocha & mochawesome | Mocha is a test framework running in node js and mochawesome is a custom report for the mocha test framework
  • grunt-protractor-runner | a grunt plugin that can help to run the protractor tests

Protractor Config & Test Files

Protractor requires to create a config file that contains the selenium settings, reports settings, etc, therefore let’s create a ‘conf.js’ and ‘home-spec.js’ file and copy and paste below codes

//conf.js
exports.config = {
seleniumAddress: process.env.SELENIUM_SERVER || "http://localhost:4444/wd/hub",
framework: "mocha",
suites: {
homePage: ["./homepage/home-spac.js"]
},

onPrepare: () => {
/**
* Maximize the browser window
*/
browser.manage().window().maximize();

/**
* Protractor waits for angular components to load completely on a web-page befor it begins any execution.
* However, I have used non-angular application, so the Protractor keeps waiting for 'angular' to load till the test fails with timeout.
* So,I explicitly tell the Protractor do not wait for the 'angular' components
* */
browser.ignoreSynchronization = true;
},
onComplete: () => {
// clean up code goes here
}
}

I have added two simple tests in the home-spec.js to make sure that the test passed and failed

//home-spec.js
const expect = require('expect');

describe("Home page", () => {
it("Navigate to 'https://www.linkedin.com/'", async () => {
await browser.get("https://www.linkedin.com/");
expect(await browser.getTitle()).toEqual("LinkedIn: Log In or Sign Up");
});
it("Navigate to 'https://www.linkedin.com/'", async () => {
await browser.get("https://www.linkedin.com/");
expect(await browser.getTitle()).toEqual("Dummy Title");
});
});

Grunt Configuration

Now It’s time to create a ‘Gruntfile.js’ file so let’s do that, copy/paste the below code

//Gruntfile.js
module.exports = grunt => {
"use strict";

let match = require("matchdep"),
path = require('path');

/**
* Load all grunt tasks which start with 'grunt-' in dependencies
*/
match.filter(['grunt-*', '!grunt-cli'], require('./package.json')).forEach(grunt.loadNpmTasks);

/**
* Load all grunt tasks which start with 'grunt-' in devDependencies
*/
match.filterDev(
['grunt-*', '!grunt-cli'],
require('./package.json')).forEach(grunt.loadNpmTasks);


/**
* Regiester grunt task for UI automation
*/
grunt.registerMultiTask('smoke_ui', 'Run protractor tests', function() {
let config = {};
config[this.target] = grunt.config.get('smoke_ui')[this.target];

grunt.config.set('protractor', config);
grunt.task.run(`protractor:${this.target}`);
});

/**
* Prepare test suites and config file
*/
function prepareTestSuiteOptions(suiteName){
let opts = {
options: {
configFile: path.resolve(__dirname, './tests/conf.js'),
args: { suite: suiteName }
}
};
return opts;
}

/*
* Set a grunt job
*/
grunt.config.set('smoke_ui', {
homepage: prepareTestSuiteOptions("homePage")
});
}

The code above will load NPM tasks from the grunt plugin, register grunt job and set the test options for the job

Global Configuration [Optional]

While working on the automation, you may come across many environment variables and global settings. I’d suggest configuring all environment variables and settings are in one file so you can access and maintain them easily.

Let’s create a global.config.js file that can be used to configure global settings for automation. You can expand and shrink as per your needs

//global.config.js
module.exports = (() => {
"use strict";
const _host = process.env.APPLICATION_HOST || "localhost",
_port = process.env.APPLICATION_PORT || '3000',
_seleniumHost = process.env.SELENIUM_SERVER || "localhost",
_seleniumPort = process.env.SELENIUM_PORT || '4444';

return {

applicationAddress: `http://${_host}:${_port}`,
seleniumAddress: `http://${_seleniumHost}:${_seleniumPort}/wd/hub`,
timeout: 60 * 1000
}

})();

Since we have configured environment and settings variables in the global file, therefore, do not forget to update test specs and conf files.

Tests Categorization

Tests Categorization is a way to split tests into several categories that can help to execute and maintain tests easily. For Instance, you can categories your tests based on the application pages such as home page, contact us page, about us page, etc.

It’s essential and recommended to categorize the UI tests so you can easily filter tests for the different CI jobs (such as commit & nightly jobs) or run them parallelly

Let’s create two more categories (grunt jobs), Open ‘Gruntfile.js’ file, copy & replace the code below

/*
* Set a grunt job
*/
grunt.config.set('smoke_ui', {
homepage: prepareTestSuiteOptions("homePage"),
profilePage: prepareTestSuiteOptions("profilePage"),
allTests: prepareTestSuiteOptions("allTests")

});

Open ‘conf.js’ file, copy & replace the code below

suites: {
homePage: ["./homepage/**/*.js"],
profilePage: ["./profilepage/**/*.js"],
allTests: ["./**/*.js"]
}

Dynamic Configuration

In general, UI tests need to run in several environments to make sure the application UI is working as expected in a different environment such as run UI tests in multi-browser with a different screen resolution

It’s essential that your automation accepts the dynamic configuration so you can get enough flexibility when running tests in different environments. Let’s add an option in the grunt job that can be used to define configuration files as a command-line parameter.

Open ‘conf.js’ file and copy/paste the code below

let testConfigFile = grunt.option('config-file') || path.resolve(__dirname, './tests/conf.js');
/**
* Prepare test suites and config file
*/
function prepareTestSuiteOptions(suiteName){
let opts = {
options: {
configFile: testConfigFile,
args: { suite: suiteName }
}
};
return opts;
}

Let’s create a new configuration file that can run UI tests in the headless mode. The headless mode is nothing but a way to run the UI tests without open the browser UI. The headless mode is really essential especially when you are running UI tests in the dockerize container or Linux environment which does not support browser UI. Running the test in the headless mode is much faster than the head mode.

let’s create a ‘headlessConf.js’ file and copy/paste the code below

//headlessConf.js
const config = require('./conf').config;
let browserOpts = {};
if(config.capabilities.browserName.toString() === "chrome"){
browserOpts.chromeOptions = {
args: ["--headless"]
}
}
if(config.capabilities.browserName.toString() === "firefox") {
browserOpts.firefoxOptions = {
args: ['--headless']
},
browserOpts['moz:firefoxOptions'] = {
args: [ '--headless' ]
}
}
config.capabilities = Object.assign(config.capabilities, browserOpts);
//console.dir(config);
exports.config = config;

Open CMD window and run the command below

$ set TEST_BROWSER=chrome
$ grunt smoke_ui:allTests --config-file=\end2end_automation_protractor\tests\headlessConf.js
$ set TEST_BROWSER=firefox
$ grunt smoke_ui:allTests --config-file=\end2end_automation_protractor\tests\headlessConf.js

Test Report

I am using Mochawesome modules to generate the HTML report for test execution. Open conf.js file and copy/paste below code after “framework: ‘mocha’” line

mochaOpts: {
timeout: globalSetting.timeout,
reporter: 'mochawesome',
reporterOptions: {
reportDir: 'ui_reports' // place to store report
overwrite: true,
reportName: 'ui-tests-report',
reportTitle: 'UI Tests Report'
}
},

Test Report With Screenshot

The mochawesome-screenshots module can be used to generate an HTML report with a screenshot. Open conf.js file and copy/paste below code right after the line framework: ‘mocha’.

mochaOpts: {
reporter: 'mochawesome-screenshots',
reporterOptions: {
reportDir: 'ui_reports',
reportName: 'ui-tests-report',
reportTitle: 'UI Tests Report',
reportPageTitle: 'UI Tests Report',
takePassedScreenshot: false,
clearOldScreenshots: true,
},
timeout: globalSetting.timeout
},

Each failed test will create a snapshot and add it in the HTML report as I have skipped to take snapshots for the passed test, Run a test job with the fail test in order to validate the snapshot is attached in the report when a test failed

You can get source code from Github. Hope this article will help to setup UI automation.

--

--