Friday, 22 February 2019

Automate Angular Apps With Protractor/Jasmine

Hi All,

In this post I will show how you can automate Angular web application using Protractor and Jasmine.

Prerequisites:
NodeJS 8.x.x
npm package manager
Webdriver-manager
Protractor
Jasmine
JS-reporting Tool (To generate pretty formatted reports)

Framework Structure:

For demonstration I am using POM (Page Object Model) design pattern, where we will be separating Test flow from page locators and from operations to be performed on those locators.


As per above project structure : 
data - 
This package will hold test data like User Credentials, Url, other test data.
pages - 
This package will hold page classes containing page web-element locators and operations. 
specs - 
This package will hold test classes, which will be run using Jasmine.
Testreports & TestResults -
These folders will contains the test result reports in pretty HTML & XML format.
node_modules -
This folder will hold downloaded dependencies required to run protractor framework.
package.json -
This files content specifies what dependencies needs to be download in order to run tests.
Conf.js -
This file will contain information about webdriber, Base Url, Browser profiles, test suites and will serves as a main test runner file with command. "protractor conf.js"


Lets take an example of Login Page scenario for any working angular application.

Login Page Content would be

import BasePage from './basePage';

class LoginPage extends BasePage {
    /**
     * Custom constructor for Login Page.
     * will have page locaters, static and assertion data.
     */
 constructor() {
        super();
        //xpath related to login
        this.udns_title_xpath = element(by.xpath('//h1[contains(text(),"sometext")]'));
        this.username_input_xpath = element(by.xpath('//input[@formcontrolname="username"]'));
        this.password_input_xpath = element(by.xpath('//input[@formcontrolname="password"]'));
        this.login_Button_xpath = element(by.buttonText('LOG IN >'));
        
        //alert message xpath
        this.alert_message_xpath = element(by.xpath('/html/body/app-root/div/div[2]/div/app-login/div/div/div[1]/div[2]/div/form/div[2]/div[1]'));
        this.profileNameXpath = element(by.xpath('/html[1]/body[1]/app-root[1]/div[1]/app-udns-header[1]/nav[1]/div[1]/div[2]/ul[1]/li[14]/a[1]/span[1]'));
        
        //static data
        this.webPageTitle = 'WebPageTitle';
        this.url = '/login';
        
        //Assertion Data
        this.homePageTitle = 'sometext';
        this.pageLoaded = this.titleIs(this.webPageTitle);
    }
    
    /**
     * Type username in Username input box.
     * @param  {string} username
     * @return {promise}
     */
    typeUsername(username){
     return this.username_input_xpath.sendKeys(username);
    }
    
    /**
     * Type password in password input box.
     * @param  {string} password
     * @return {promise}
     */
    typePassword(password){
     return this.password_input_xpath.sendKeys(password);
    }
    
    /**
     * click login button.
     * @return {promise}
     */
    clickLoginButton(){
     return this.login_Button_xpath.click();
    }
    
    /**
     * Fetch user name from profile.
     * @return {promise}
     */
    getProfileTitle(){
     return this.profileNameXpath.getText();
    }
    
    /**
     * Alert text from login page.
     * @return {promise}
     */
    getAlertText(){
     //return this.login_Button_xpath.getText();
     this.alert_message_xpath.isDisplayed().then(function(result) {
      if ( result ) {
       return this.login_Button_xpath.getText(); 
      } else {
       return this.login_Button_xpath.getText();
      }
     });
    }
    
    /**
     * Check if UDNS title xpath is present
     * @return {promise}
     */
    isTitlePresent() {
     this.udns_title_xpath.isDisplayed().then(function(result){
      if(result) {
       return this.Bdns_title_xpath.getText();
      } else {
       return "MDNS";
      }
     });
    }
}
export default new LoginPage();

Specs/Tests class will be like:


import LoginPage from '../pages/LoginPage';
import UserData from '../data/userData';
import HomePage from '../pages/homePage';

describe ('User login scenarios->', () => {
 var wrong_creds = 'abc';
 var profileName = 'P K';
 
 beforeEach(() => {
        LoginPage.goto();
 });

 afterEach(()=>{
  HomePage.logout();
 });
 
 it('User should login with valid username and password', () => {
  LoginPage.typeUsername(UserData.getUserName());
        LoginPage.typePassword(UserData.getPassword());
        LoginPage.clickLoginButton();
  var myProfileText = LoginPage.getProfileTitle();
        expect(myProfileText).toEqual(profileName);
        });
 
 /*it('User should logout after successful login', () => {
  LoginPage.typeUsername(UserData.getUserName());
        LoginPage.typePassword(UserData.getPassword());
        LoginPage.clickLoginButton();
  expect(HomePage.logout()).toBe(null);
    });*/
 
});

Package.json file content
{
  "name": "protractor_example",
  "version": "1.1.0",
  "description": "An framework example Protractor project that makes use of page objects...",
  "engines": {
    "node": "8.12.0"
  },
  "scripts": {
    "prepare": "node_modules/.bin/webdriver-manager update",
    "update": "node_modules/.bin/webdriver-manager update",
  },
  "keywords": [
    "protractor",
    "page objects",
    "tests",
    "automation",
    "webdriver"
  ],
  "author": "Rohan",
  "license": "Pandhare",
  "bugs": {
    "url": "https://github.com/rkpandhare/protractor_example/issues"
  },
  "homepage": "https://github.com/rkpandhare/protractor_example",
  "dependencies": {
    "protractor": "5.4.1",
    "chance": "1.0.16",
    "jasmine-spec-reporter": "2.7.0",
    "protractor-flake": "3.3.0"
  },
  "devDependencies": {
    "babel-preset-es2015": "^6.24.1",
    "babel-register": "^6.24.1",
    "jasmine-reporters": "^2.3.2",
    "protractor-html-reporter": "^1.3.2",
    "protractor-html-reporter-extended": "^2.5.27"
  }
}

conf.js



// solves `SyntaxError: Unexpected token import`
require("babel-register")({
    presets: [ 'es2015' ]
});

exports.config = {
    directConnect: true,
 //seleniumAddress: 'http://localhost:4444/wd/hub',

    specs: ['specs/loginSpecs.js'], //
    baseUrl: 'http://YourAngularWebApplication.com',
    framework: 'jasmine',

    onPrepare: () => {
       // set browser size...
       browser.manage().window().setSize(2560, 1600);

       var jasmineReporters = require('jasmine-reporters');
       jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
           consolidateAll: false,
           savePath: './testResults',
           filePrefix: 'xmloutput'
       }));
    },
    
    onComplete: function() {
        var browserName, browserVersion;
        var capsPromise = browser.getCapabilities();
    
        capsPromise.then(function (caps) {
           browserName = caps.get('browserName');
           browserVersion = caps.get('version');
    
           var HTMLReport = require('protractor-html-reporter');
    
           testConfig = {
               reportTitle: 'Test Execution Report',
               outputPath: './testReports',
               screenshotPath: './testReports/screenshots',
               testBrowser: browserName,
               browserVersion: browserVersion,
               modifiedSuiteName: false,
               screenshotsOnlyOnFailure: true
           };
           new HTMLReport().from('testResults/xmloutputCreateandDeletedomainsfeatures.xml', testConfig);
           new HTMLReport().from('testResults/xmloutputUserloginscenarios.xml', testConfig);
           });
    },
    
    capabilities: {
        browserName: 'chrome',
        shardTestFiles: true,
        maxInstances: 1,
        chromeOptions: {
            args: [
                // disable chrome's wakiness
                //'--headless',
             '--disable-infobars',
                '--disable-extensions',
                'verbose',
                'log-path=/tmp/chromedriver.log'
            ],
            prefs: {
                // disable chrome's annoying password manager
                'profile.password_manager_enabled': false,
                'credentials_enable_service': false,
                'password_manager_enabled': false
            }
        }
    },    
    jasmineNodeOpts: {
        showColors: true,
        displaySpecDuration: true,
        // overrides jasmine's print method to report dot syntax for custom reports
        print: () => {},
        defaultTimeoutInterval: 50000000    }
};


Instructions to run:

Start webdriver-manager
$webdriver-manager start

**It will start listening on default port localhost:4444

Run specs/tests mentioned in conf.js file

$protractor conf.js

** Verify results html and xml file created under folder testResult and testReport.

Please leave comments in comment section if something is not working. Thanks.