Four Picks #4

After a long pause I am restarting the “Four Picks” series back again and hope you find it useful. Here is 4th edition of “Four Picks”

  1. Josh Glade has written an excellent article Five Sins of Mobile Testing on Stickyminds. Josh has given some very important tips for building an effective mobile testing strategy and avoid some common mistakes. His point on testing mobile apps on Emulator Software is valid, however emulators also provide first line of defence before apps are tesed on real devices. If you’re interested in learning more about mobile testing, I also recommend reading Jonathan Kohl’s book Tap into Mobile Application Testing I’m currently reading this book. It is packed with some real practical advice on testing mobile apps, I’ll post a review soon.
  2. Testability and Cost of Change I found this post while watching a video lesson. A well researched post with examples from Google. It provides very useful tips on testability and associated costs. There is an interesting example on cost savings with a bug found during TDD (again a solid reason to adopt TDD)
  3. From One Expert to Another: Dale Emery  Dale Emery talks about his recent talk on Test Automation Zombie Apocalypse I loved this presentation, though I do not agree to all points. This provides great stuff to reflect on your test automation efforts and assess the good and bad things. I also recommend reading Tales of Test Automation Gone Wrong by Elisabeth Hendrickson, in fact we built a assessment out of this presentation to check health of our test automation projects.
  4. Mindmaps! Here is a beautiful mind map on testing tools I found on Twitter

[tweet https://twitter.com/BugFinders/status/353179395822854145 ]

 

[Tutorial] Creating a Selenium IDE extension for Web Table commands

Selenium IDE is a great tool to record and replay simple tests within the Firefox browser. It provides all the commands that are supported by Selenium out of the box. However, we might need to build custom commands that are not part of Selenium. Selenium IDE provides a way to implement custom commands in three-column format by building extensions in the JavaScript language. These are stored in separate .js files.

Selenium IDE does not have built-in commands to work with web tables and perform verification on tables as needed in tests. This post explains how to implement custom commands to test Web Tables using Selenium IDE.

Setup

Create a user-extensions.js file and save it to a location where Selenium IDE can find it easily

Adding custom commands

To create an extension, we need to use the selenium.prototype object and follow a particular pattern for naming the functions. As we are creating an extension to verify the table properties and contents, we need to prefix assert to the function names.

Create a command to verify the number of columns in a table. Copy the following function to the user-extensions.js file:

Selenium.prototype.assertTableCols = function(locator, value) {
  var table = this.browserbot.findElement(locator);
  Assert.matches(value, table.rows[0].cells.length.toString());
}

To verify the number of rows, create the following function in the user-extensions.js file:

Selenium.prototype.assertTableRows = function(locator, value) {
  var table = this.browserbot.findElement(locator);
  Assert.matches(value, table.rows.length.toString());
}

Finally, to verify data in a specific cell of a table, create the following function in the user-extensions.js file:

Selenium.prototype.assertTableCellData = function(locator, value) {
	var args = new Array();
	args = value.split("|");
	var rowIdx = parseInt(args[0]) - 1
	var colIdx = parseInt(args[1]) - 1
	var table = this.browserbot.findElement(locator);
	if(null != table.rows[rowIdx].cells[colIdx].childNodes[0].text)
		Assert.matches(args[2], table.rows[rowIdx].cells[colIdx].childNodes[0].text);
}

Adding User Extensions to Selenium IDE Options

Now let’s start using this user extension with Selenium IDE by adding to the Selenium IDE Options. Click on the Options Menu and then click on Options Submenu. This will launch the Selenium IDE Options Dialog box as shown in the following screenshot:

Selenium IDE Options dialog box
Selenium IDE Options dialog box

Select the Path for user-extensions.js file by clicking the Browse… button in front of Selenium Core extensions (user-extenions.js). Restart the Selenium IDE. You can see the newly added commands in Selenium IDE

New commands in Selenium IDE
New commands in Selenium IDE

Extending Selenium IDE is pretty simple. We can add new commands by adding functions to the Selenium object prototype, and the PageBot object prototype. On startup, Selenium IDE will automatically look through functions in user-extensions.js on these prototypes, using name patterns to recognize which ones are actions, assertions, and locators.

Selenium IDE commands are restricted to a three-column format. However, some commands might need more information to execute the command. For example, we created an extension for verifying the Cell data. This command needs the row, column, and expected value. We can specify these parameters in value argument by separating parameter values using a pipe character “|”. Inside the function, we can then split this string using “|” and pass the values to appropriate variables.

Sample Test

Here is a sample test which navigates to a Shopping Cart page and performs verification on table displayed on the page:

Sample test with new Selenium IDE command
Sample test with new Selenium IDE command

 

iOS Automation with Appium & Selenium

Note: This post is not up to date with latest release of Appium. An update coming soon…

Yesterday I saw a tweet on Appium release from Sauce Labs and immediately started exploring it. This post summarizes my initial experience with Appium.

Appium (http://appium.io/) is an open source tool/framework for automating iOS Native and Hybrid Apps. It uses the WebDriver JSON wire protocol to drive iOS apps.

Appium server is written in Node.js and talks to iOS using UIAutomation via Instruments. You can use the Selenium WebDriver API for writing tests which talk to Appium via JSON wire protocol for running the Selenium commands. This also gives you advantage of writing tests in your language of preference.

Installation

I found installing Appium quite easy on a local machine. You need Node.js installed before using Appium.

1. Install Node.js from http://nodejs.org/

2. Install WebDriver package for Node.js with the following command

sudo npm install wd

3. Install Appium with the following command

sudo npm install appium -g

4. Start the Appium server with the following command

appium &

Appium server will start at http://localhost:4723

Implementing test using Selenium WebDriver

I am using a sample BMI Calculator App developed with native iOS SDK for this example

Bmi Calculator App
Bmi Calculator App

Build the app using xcodebuild command (In this example the BmiCalc app)

xcodebuild -sdk iphonesimulator6.1

I am using Maven to setup a Java project for this test and here is pom.xml with the following dependencies added. For this example I have used IntelliJ IDEA. For more information on using Maven for Selenium script development refer bonus Chapter Integration with other Tools from my Selenium Testing Tools Cookbook

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.bmicalc.test</groupId>
    <artifactId>bmi-ios-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>LATEST</version>
        <scope>test</scope>
    </dependency>
    </dependencies>
</project>

And here is BmiCalcTest class

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.By;

import java.io.File;
import java.net.URL;
import java.util.List;

import static org.junit.Assert.assertEquals;

public class BmiCalcTest {

    private WebDriver driver;

    @Before
    public void setUp() throws Exception {

        //Appium needs the path of app build
        //Set up the desired capabilities and pass the iOS SDK version and app path to Appium
        File app = new File("/Users/upgundecha/Desktop/AppExamples/BmiCalculator/build/Release-iphonesimulator/BmiCalculator.app");
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability(CapabilityType.BROWSER_NAME, "iOS");
        capabilities.setCapability(CapabilityType.VERSION, "6.1");
        capabilities.setCapability(CapabilityType.PLATFORM, "Mac");
        capabilities.setCapability("app", app.getAbsolutePath());

        //Create an instance of RemoteWebDriver and connect to the Appium server.
        //Appium will launch the BmiCalc App in iPhone Simulator using the configurations specified in Desired Capabilities
        driver = new RemoteWebDriver(new URL("http://localhost:4723/wd/hub"), capabilities);
    }

    @Test
    public void testBmiCalc() throws Exception {

        //iOS controls are accessed through WebElement class
        //Locate the Height & Weight textField by their accessibility labels using By.name()
        WebElement heightTextField = driver.findElement(By.name("Height"));
        heightTextField.sendKeys("181");

        WebElement weightTextField = driver.findElement(By.name("Weight"));
        heightTextField.sendKeys("80");

        //Locate and tap on Calculate button using the click() method
        WebElement calculateButton =  driver.findElement(By.name("Calculate"));
        calculateButton.click();

        //Locate all the label elements using By.tagName()
        List<WebElement> labels = driver.findElements(By.tagName("staticText"));

        //Check the calculated Bmi and Category displayed on labels
        //Label with index 8 has value of the Bmi and index 9 has the value for category
        assertEquals("24.42",labels.get(8).getText());
        assertEquals("Normal",labels.get(9).getText());
    }

    @After
    public void tearDown() throws Exception {
        //Close the app and simulator
        driver.quit();
    }
}

I really liked using Selenium WebDriver API for writing iOS tests with Appium. I can add iOS support to my existing Selenium Framework with minimal changes. Appium presently supports locating elements using the tag name (i.e type of iOS control) and accessibility labels.

Running tests in Cloud

You can also run Appium with Sauce Labs Cloud, for more details read http://sauceio.com/index.php/2013/02/announcing-appium-on-sauce-native-hybrid-ios-testing-in-the-cloud/

Overall Appium is a great tool to start with.

References:
Getting Started – http://appium.io/getting-started.html
Appium on GitHub – http://sauceio.com/index.php/2013/02/announcing-appium-on-sauce-native-hybrid-ios-testing-in-the-cloud/
Samples – https://github.com/appium/appium/tree/master/sample-code
Wiki – https://github.com/appium/appium/wiki
Google Group – https://groups.google.com/forum/?hl=en&fromgroups=#!forum/appium-discuss

Data Driven Testing with Selenium IDE

Selenium IDE does not have in-built features to create data-driven tests. However there are multiple options available in the form of Selenium IDE User Extension or Add-On for creating Data Driven tests.

This post will explore using a Selenium IDE Add-on called Sel Blocks to create Data Driven tests on a sample BMI Calculator application. This is a simplest option available for Selenium IDE for data-driven tests.

The Sel Blocks Add-on provides various flow control elements such as conditional handling, looping, variables and data-driven testing in Selenium IDE tests.

Setup
Install the Sel Blocks for Selenium IDE Add-On using the Add-On Manager in Firefox from https://addons.mozilla.org/en-US/firefox/addon/selenium-ide-sel-blocks/?src=search

Creating Data Driven Tests
First we need to identify test data for the script. The Sel Blocks Add-on needs the test data in XML format. Create a XML file with following format:

<testdata>
	<vars height="160" weight="45" bmi="17.6" bmi_category="Underweight" />
	<vars height="168" weight="70" bmi="24.8" bmi_category="Normal" />
	<vars height="181" weight="89" bmi="27.2" bmi_category="Overweight" />
	<vars height="178" weight="100" bmi="31.6" bmi_category="Obesity" />
</testdata>

You can have data rows defined as <vars> element under the root element <testdata>. In <vars> element you can define all the parameters needed for the test. Create the following script in Selenium IDE:

SelIDE

How it works
In this test the type and verifyValue commands are parameterized by substituting values from the test data specified in above XML file.

The forXml command of Sel Blocks is supplied with the name of XML file where test data is stored. In this example path of data.xml file is passed to the forXml command.

When this test is executed, Sel Blocks loads the content of XML file in memory and executes the script for number of times <vars> element present in the XML file. In this example it will execute the test for four times.

For all the attributes mentioned in <vars> element, Sel Blocks create internal variables that we can substitute as Selenium command parameters.

You can analyse the Selenium IDE Log tab for  results. If the expected data and actual data do not match, an error is reported in the Selenium IDE Log tab.

There’s more
You can also use datadriven user extension to create Data Driven tests. You need to install flowControl andinclude user extensions to use the dataddriven extension. Sel Blocks is easier option as it does not have dependency on any other extension. For more info visit http://wiki.openqa.org/display/SEL/datadriven

This post covers Data Driven testing with Selenium IDE. If you are looking for Data Driven testing with Selenium WebDriver, grab my book Selenium Testing Tools Cookbook published by PacktPub

Implementing ATDD/BDD with QTP – Rough Cuts

Recently I started working with SpecFlow.NET a very popular BDD tool available for .NET. It does a impressive job of automating acceptance testing in agile projects.

Out of curiosity I started a small proof-of-concept to build a similar framework with HP QTP, a popular GUI automation tool. This post covers the basics of the framework I built and as I indicated in the title, this framework does not have all the features like SepcFlow or Cucumber. This is just a rough implementation to prove that tools like QTP can also be used in BDD.

There is enough written about Behaviour Driven Development (BDD), and you can know more about it from Dan North’s legendary article http://dannorth.net/introducing-bdd/ and Wikipedia entry http://en.wikipedia.org/wiki/Behavior-driven_development

Behaviour Driven Development focuses on obtaining a clear understanding of desired application behaviour through discussion with stakeholders. In a nutshell using BDD technique you author automated tests in the language of the business while maintaining a connection to implemented system. Users describe features and scenarios to test these features in plain text files using Gherkin language in Given, When and Then structure. For more information on Gherkin language see http://en.wikipedia.org/wiki/Behavior-driven_development and https://github.com/cucumber/cucumber/wiki/Gherkin

  • Given [Precondition]
  • When [Actor + Action]
  • Then [Observable Result]

In BDD process starts with users of the system and development team discussing features, user stories and scenarios. These are documented in feature or story files using the Gherkin language. Developers then use the red green re-factor cycle to run these features using the BDD framework then writing step definition files mapping the steps from scenarios to the automation code and re-running, until all the acceptance criteria are met.

Here is a list of some popular BDD tools:

  • Cucumber, RSpec – Ruby
  • Cucumber-JVM, JBehave – Java
  • SpecFlow.NET – .NET

This example demonstrates acceptance testing of Fund Transfer feature from an on-line banking application using BDD. Using the fund transfer application a bank account holder can transfer or wire money to a registered payee. There are some validations around this feature which are captured in the specification.

Step 1 – Creating Feature Files

The first step is to create a feature file. A feature file defines specifications using Gherkin language for a feature under development. It’s a simple text file which starts with the feature description in a user story format. Then acceptance criteria is listed in the form of scenarios in the feature file. In below feature file, sample scenarios are described to validate the fund transfer application:

Feature: Customer Transfers Fund
	As a customer,
	I want to transfer funds
	so that I can send money to my friends and family

Scenario: Valid Payee
	Given the user is on Fund Transfer Page
	When he enters "Jim" as payee name
	And he enters "100" as amount
	And he Submits request for Fund Transfer
	Then ensure the fund transfer is complete with "$100 transferred to Jim successfully!!" message

Scenario: Invalid Payee
	Given the user is on Fund Transfer Page
	When he enters "Jack" as payee name
	And he enters "100" as amount
	And he Submits request for Fund Transfer
	Then ensure a transaction failure message "Transfer failed!! 'Jack' is not registered in your List of Payees" is displayed

Scenario: Account is overdrawn past the overdraft limit
	Given the user is on Fund Transfer Page
	When he enters "Tina" as payee name
	And he enters "1000000" as amount
	And he Submits request for Fund Transfer
	Then ensure a transaction failure message "Transfer failed!! account cannot be overdrawn" is displayed

Feature files are kept in a Feature folder in QTP script folder. Separate feature files are created for each feature based on number of features under test. The feature file should use .feature extension.

Test data values needed to execute steps can be embedded using double quotes. In above feature there is a step which says When he enters “Jim” as payee name. Here name of the payee is quoted.

Step 2 – Creating Step Definition File
A step definition file is created which maps the steps from a feature file to the automation code. Here is a step definition file implemented for fund transfer feature as follows:

'//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Script Name:	FundTransferStepDefs.vbs
' Author:		Unmesh Gundecha
'
' Description:  This file contains step definitions for Fund Transfer Feature
'				While defining function, please specfiy Step definition as comment above the function name in square brackets []
'//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

'[Given the user is on Fund Transfer Page]
Function GivenUserIsOnFundTransferPage()
 	GivenUserIsOnFundTransferPage = Browser("Browser").CheckProperty("title","Fund Transfer Page")
End Function

'[When he enters "name" as payee name]
Function WhenUserEneteredIntoThePayeeNameField(strPayeeName)
	Browser("Browser").Page("Fund Transfer Page").WebEdit("payeeTextBox").Set strPayeeName
End Function

'[And he enters "amount" as amount]
Function AndUserEneteredIntoTheAmountField(amount)
	Browser("Browser").Page("Fund Transfer Page").WebEdit("amountTextBox").Set amount
End Function

'[And he Submits request for Fund Transfer]
Function AndUserPressTransferButton()
	Browser("Browser").Page("Fund Transfer Page").WebButton("Transfer").Click
End Function

'[Then ensure the fund transfer is complete with "message" message]
Function ThenFundTransferIsComplete(message)
	ThenFundTransferIsComplete = Browser("Browser").Page("Fund Transfer Page").WebElement("message").CheckProperty("innerText", message)
End Function

'[Then ensure a transaction failure message "message" is displayed]
Function ThenFundTransferIsFailed(message)
  ThenFundTransferIsFailed = Browser("Browser").Page("Fund Transfer Page").WebElement("message").CheckProperty("innerText", message)
End Function

For each unique step in the feature file, a function is written which calls QTP code as per the behaviour specified in the step. This file is implemented as a function library file. These functions are mapped to steps using a special commenting standard. For example there is a step When he enters “Jim” as payee name in feature file. For this step a WhenUserEneteredIntoThePayeeNameField() function is defined in the step definition file . A special comment is written on top of the function declaration. This will tell driver script to map the matching step from feature file to this function.

'[When he enters "name" as payee name]
Function WhenUserEneteredIntoThePayeeNameField(strPayeeName)
	Browser("Browser").Page("Fund Transfer Page").WebEdit("payeeTextBox").Set strPayeeName
End Function

Step 3 – Creating Driver

The BDD_Driver script is core of this framework. This script reads feature file and step definition file and creates a map of steps along with their corresponding functions. It then executes scenarios from feature file using this map.

QTP BDD_Framework Architecture
QTP BDD_Framework Architecture

BDD_Driver script also provides test data embedded in a step from feature file to the function called from step definition file. A step definition function internally calls QTP code which runs the automated step on the application emulating user behaviour.

'//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
' Script Name:	BDD_Driver
' Author:		Unmesh Gundecha
'
' Description:  This is the main script of QTP-BDD Framework. This drives the execution of features and steps
'				Script first reads Step Definition File and creates a dictionary of available steps and functions.
'
'				For executing a Feature file it refer the dictionary matching steps from feature file and calling the respective function
'				While calling the step function it passes the test data embedded in Feature file steps to the function
'//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	Option Explicit

	Dim arrStepDefs, objStepDict, intLineIdx, strLine, strNextLine, arrFeatureFile, intScenarios, strArgs, varResult
	Dim strKey, arrArgs, strTestStep, strStepStatus, intCnt

	'Open the Test Application, by directly providing URL, QTP will launch the Browser and navigate to the specified URL
	SystemUtil.Run "http://dl.dropbox.com/u/55228056/fundTransfer.html"

	'Go through the Step Definition file and create dictionary of step definition and corresponding functions
	arrStepDefs = Split(DotNetFactory.CreateInstance("System.IO.File").ReadAllText(PathFinder.Locate("FundTransferStepDefs.vbs")),vbCrLf)
	Set objStepDict = CreateObject("Scripting.Dictionary")
	For intLineIdx = 0 To UBound(arrStepDefs)
		strLine = arrStepDefs(intLineIdx)
		If strLine <> "" Then
			If Left(strLine,2) = "'[" And Right(strLine,1) = "]" Then
				strLine = Replace(Replace(strLine,"'[",""),"]","")
				strLine = ParseLine("Step", strLine)
				strNextLine = Trim(arrStepDefs(intLineIdx+1))
				strNextLine = Replace(Replace(strNextLine,"Function",""),"Sub","")
				strNextLine = Mid(strNextLine,1,InStr(strNextLine,"(")-1)
				objStepDict.Add strLine, strNextLine
			End If
		End If
	Next

	'Read Feature file and match step definitions in objStepDict
	arrFeatureFile = Split(DotNetFactory.CreateInstance("System.IO.File").ReadAllText(PathFinder.Locate("FundTransfer.feature")),vbCrLf)
	For intLineIdx = 0 To UBound(arrFeatureFile)
		If InStr(arrFeatureFile(intLineIdx),"Feature:") <> 0 Then
			Print "Feature: " & Trim(Replace(arrFeatureFile(intLineIdx),"Feature:",""))
			intScenarios = 0
		ElseIf InStr(arrFeatureFile(intLineIdx),"Scenario:") <> 0 Then
			intScenarios = intScenarios + 1
			Print vbTab & "Scenario: " & Trim(Replace(arrFeatureFile(intLineIdx),"Scenario:",""))
		Else
			strArgs = ""
			varResult = ""
			strLine = Replace(Trim(arrFeatureFile(intLineIdx)),vbTab,"")
			If strLine <> "" Then
				strKey = ParseLine("Step",strLine)
				'After a step is found in dictionary generate the function call using the test data embedded in step from feature file
				If objStepDict.Exists(strKey) Then
					'Get Test Data Values from Feature File Steps and create an Argument Array
					If ParseLine("Args",strLine) <> "" Then
						arrArgs = Split(ParseLine("Args",strLine),";")
						For intCnt = 0 to UBound(arrArgs)
							strArgs = strArgs & "arrArgs(" & intCnt & "),"
						Next
						strArgs = Mid(strArgs,1,Len(strArgs)-1)
						strTestStep = "varResult = " & objStepDict(strKey) & "(" & strArgs & ")"
					Else
						strTestStep = "varResult = " & objStepDict(strKey)
					End If
					'Execute the function call
					Execute(strTestStep)
					If varResult <> "" Then
						If varResult Then
							strStepStatus = " [PASS]"
						Else
							strStepStatus = " [FAIL]"
						End If
					End If
					Print vbTab & vbTab & strLine & strStepStatus
				Else
					If InStr(strLine,"Given") <> 0 Or InStr(strLine,"When") <> 0 Or InStr(strLine,"Then") <> 0 Or InStr(strLine,"And") <> 0  Then
						Print vbTab & vbTab & strLine & " [PENDING]"
					Else
						Print vbTab & vbTab & strLine
					End If
				End If
			End If
		End If
	Next

	Print vbCrLf & "Total " & intScenarios & " Scenarios Executed!!"

	Function ParseLine(ByVal strWhatToParse, ByVal strLine)
		Dim objRegEx, objMatches, objMatch, strResult
		Set objRegEx = New RegExp
		objRegEx.Pattern = "\""(.*?)\"""
		objRegEx.IgnoreCase = True
		objRegEx.Global = True
		Set objMatches = objRegEx.Execute(strLine)
		For Each objMatch in objMatches
			If strWhatToParse = "Step" Then
				strResult = strResult & Replace(strLine,objMatch,vbNullString)
			ElseIf strWhatToParse = "Args" Then
				strResult = strResult & Replace(objMatch,"""","") & ";"
			End If
		Next
		If strResult <> "" Then
			If strWhatToParse = "Step" Then ParseLine = strResult
			If strWhatToParse = "Args" Then ParseLine = Mid(strResult,1,Len(strResult)-1)
		Else
			If strWhatToParse = "Step" Then ParseLine = strLine
			If strWhatToParse = "Args" Then ParseLine = ""
		End If
	End Function

This code might look little odd due to the language constraints that we have with VBScript. However it proves that BDD is possible with tools like QTP, however a better solution can be built in detail. Here is a sample report generated by the BDD_Driver

Next steps:
This is not complete framework and needs to be enhanced further as a mature BDD framework. Here are some key items that can be added/improved in next iteration:

  • Support multiple Feature and Step Definition files
  • 100% Gherkin syntax support
  • Suggest step definitions where functions are not implemented for steps in log/report
  • Support for ignoring steps where step definition is not implemented
  • Support for reading tabular data from steps
  • Report Generation like SpecFlow

Comparison with Keyword Driven testing
Implementing tests with BDD is pretty much similar to Keyword driven testing. In Keyword driven testing, Keywords or Actions are defined and mapped to functions which execute code related to the Keyword. Tests are written in tabular format which is easy to understand for users without having knowledge of the test tool or automation code. The driver script orchestrates the entire test execution.

In BDD, steps are written in a feature file can be compared to Keywords or actions and step definitions are the underlying functions created for the Keywords. However tests are expressed in a non tabular format, in a more natural plain text or domain specific language.

I’ll be happy to know your comments or suggestion to improve this further.

[Tutorial] Building a QTP Add-In for jQueryUI Widget using HP Web Extensibility Accelerator

jQuery UI is a jQuery user interface library. It provides interactions, widgets, effects, and theming for building Rich Internet Applications. jQuery UI provides a number of UI widgets such as Accordion, Datepicker, Slider, Dialog, Tabs etc.

These widgets are built using a number of low-level HTML elements such as DIVs, Unordered Lists, and Input Tags etc. While QTP can recognize these elements individually, we can build an add-in to recognize these controls as native jQuery UI widgets using HP Extensibility Accelerator for QTP. We can then perform native operations supported by jQuery framework.

In this post I’ll explain step by step instructions to build an add-in which will support identification of jQuery UI Tab widget. For more information on jQuery UI Tab visit http://jqueryui.com/demos/tabs/

Step 1 – Create a Web Extensibility Project

  1. Open HP Extensibility Accelerator
  2. Select File → New → Project option from HP Extensibility Accelerator main menu
  3. On New Project dialog box select “Web Add-in Extensibility” in Project types: list
  4. In Templates: select “Web Add-In Extensibility Project”
  5. Enter “jQueryUI” in Name field
  6. Click on OK button

A new Web Add-In Extensibility Project will be created in HP Extensibility Accelerator

Step 2 – Define a new Test Object Class

Click Add [+] button in Test Object Classes list. A new entry will be added to Test Object Classes list as “Control1” and a new Tab will be added with “Control1” title under the Workflow Section

Step 3 – General Settings

  1. On “Control1” tab select the General Tab
  2. Enter “jQueryUITab” in Name field. This will change the Tab name as well as entry in Test Object Classes List
  3. Enter “jQuery UI Tab Control” in Description field
  4. You can provide a custom icon using Icon File options. I have kept this as default icon displayed for WebElement Test Object
  5. In Class Type section, select “WebElement” in Base class dropdown
  6. Click on Advanced Options Section
  7. Go to the Record Options Section
  8. Enter “AddEventHandler” in Event registration function name: field
  9. Uncheck Handle events using standard Web event configuration checkbox
  10. Check Handle events on child elements using standard Web event configuration checkbox

Step 4 – Identification
Next, we need to configure the identification of this control type. Before performing below steps, open the Page containing jQueryUI Tab widget in the Internet Explorer. I have built this example on jQuery UI Tab demo page at http://jqueryui.com/demos/tabs/default.html

  1. Select the Map to Controls tab
  2. Under the Identify Control section, click on Select Controls… button. This will hide the HP Extensibility Accelerator window and a small panel will appear in the center of Desktop with Create Rules and Cancel Button
  3. Activate the Internet Explorer window where the http://jqueryui.com/demos/tabs/default.html  page is displayed
  4. Move the mouse over jQuery UI Tab widget displayed on the page. This control will be heighted by a Box with red border and small box containing tag and class information
  5. Hover mouse until the box shows following details
tag: DIV
class: ui-tabs ui-widget ui-widget-content…
  1. Once you locate the object with above details click on the Red box. A small popup will be displayed with Node hierarchy and classname, id, tagname details. Click on Select button. Tab control will be highlighted
  2. Click on Create Rules button
  3. Following rule will be displayed in the Identify Control Area


We need unique attributes to identify this control. Let’s remove attribute which are not unique or whose value will change based on instance of the widget.

Select the “id Equal tabs” node in the Rule and click on Delete [X] button above.  Value for id attribute will be provided by developers and will change for each instance.

Select the jQuery node in the Rule and click on Delete [X] button above.  Value for jQuery attribute is assigned dynamically.

Following will be the final rule to identify jQuery UI Tab widget

Test this rule by clicking Test Rule button. Widget will be highlighted in light green in Internet Explorer, this means that the rule is correct.QTP will use these attributes and their values to identify jQuery UI Tab widget

Step 5 – Adding Operations

We will add following operations to the jQuery UI Tab Test Object Class

Operation Description Parameter Return Value
Select(Item) Selects a specified Tab on jQuery UI Tab widget Index or Title of the Tab True
GetItems() Returns title for all the Tabs separated by semicolon None String
GetSelectedItem() Returns title of selected Tab None String
GetItemCount() Returns number of Tabs on widget None Int

To add operation to this control, click on Operations tab

Select Operation

  1. Click Add [+] button in Operations list
  2. Enter “Select” in Name field
  3. Enter “Select a specified Tab” in Description field
  4. Check Default Operation checkbox. This will be the default operation for Tab widget
  5. Select “Boolean” in Return Type dropdown
  6. In Operation Arguments list add new “Item” argument with type as “String”. Uncheck the Optional checkbox
  7. We need to write JavaScript code for this operation. Click on Implementation Code… button next to the Name field. This will open the Code editor. A empty function for Select operation will be added by the code editor
  8. Copy following code to the Select function
////////////////////////////////////////////////////////////////////////////////
// Selects specified Tab on a widget
//
// Parameters: Item (Index or Title of the Tab, 
//					 while passing Index prefix # before index value)
// Returns:   Boolean
///////////////////////////////////////////////////////////////////////////////////
function Select(Item) {
	var details = null;
	var success = false;

	if (!Item || typeof (Item) != "string") {
		details = "Cannot select invalid item: " + Item;
		_util.Report(micFail, "Select", toSafeArray(new Array()), details);
		throw Error(details);
	}

	var tabToSelect = -1;

	// Retrieve tab elements.
	var tabs = window.$(_elem).find(".ui-tabs-nav > li");

	if(Item.charAt(0) == '#') {
	// The tab item is specified by index.
	var index = parseInt(Item.substring(1)) - 1;
	if (index < tabs.length && index >= 0)
		tabToSelect = index;
	}
	else {
		// The tab item is specified by name.
		for(var i = 0; i < tabs.length; i++) {
			if (Item == tabs[i].innerText) { 
				tabToSelect = i; 
				break; 
			} 
		} 
	} 
	
	if (tabToSelect >= 0) {
		// Call the Select method of jQueryUI Tab.
		window.$(_elem).tabs().tabs('select',tabToSelect);
		details = "Selected " + Item;
	_util.Report(micDone, "Select", toSafeArray(new Array(Item)), details);
	}
	else {
		details = "Item " + Item + " does not exist.";
		_util.Report(micFail, "Select", toSafeArray(new Array(Item)), details);
		throw Error(details);
	}
	return true;
}

GetItems Operation

  1. Click Add [+] button in Operations list
  2. Enter “GetItems” in Name field
  3. Enter “Returns name of all the Tabs” in Description field
  4. Select “String” in Return Type dropdown
  5. Click on Implementation Code… button next to the Name field. This will open the Code editor. A empty function for Select operation will be added by the code editor
  6. Add following code to the GetItems function
////////////////////////////////////////////////////////////////////////////////
// This returns title of all the Tab on a widget separated by a semicolon 
//
// Parameters: None
// Returns:   String
///////////////////////////////////////////////////////////////////////////////////
function GetItems() {
	var tabLabels = new Array();
	var tabs = window.$(_elem).find(".ui-tabs-nav > li");
	for (var i = 0; i < tabs.length; i++)
		tabLabels.push(tabs[i].innerText);
	return tabLabels.join(";");
}

GetSelectedItem Operation

  1. Click Add [+] button in Operations list
  2. Enter “GetSelectedItem” in Name field
  3. Enter “Returns the name of selected Tab” in Description field
  4. Select “String” in Return Type dropdown
  5. Click on Implementation Code… button next to the Name field. This will open the Code editor. A empty function for Select operation will be added by the code editor
  6. Add following code to the GetSelectedItem function
////////////////////////////////////////////////////////////////////////////////
// This returns title of current selected Tab on widget
//
// Parameters: None
// Returns:   String
///////////////////////////////////////////////////////////////////////////////////
function GetSelectedItem() {
	return window.$(_elem).find(".ui-tabs-nav > li[class*='ui-tabs-selected']")[0].innerText;
}

GetItemCount Operation

  1. Click Add [+] button in Operations list
  2. Enter “GetItemCount” in Name field
  3. Enter “Returns count of Tabs” in Description field
  4. Select “Integer” in Return Type dropdown
  5. Click on Implementation Code… button next to the Name field. This will open the Code editor. A empty function for Select operation will be added by the code editor
  6. Add following code to the GetItemCount function
////////////////////////////////////////////////////////////////////////////////
// This returns count of Tabs on widget
//
// Parameters: None
// Returns:   Integer
///////////////////////////////////////////////////////////////////////////////////
function GetItemCount() {
	return window.$(_elem).find(".ui-tabs-nav > li").length;
}

Step 6 – Adding Properties
To add properties to this control, click on Properties tab

Property Description
all items Gets all tab titles separated by semicolon
items count Gets number of tabs
logical name Gets id of the DIV tag containing Tab widget
selected Gets the title of selected Tab
  1. Click Add [+] button in Properties list. Enter the name of Property. Repeat this for all the properties.
  2. Select the “logical name” property and open Object Identification – Mandatory Section. Click  >> button in middle to move this property in mandatory section
  3. Click on Implementation Code… button . This will open the Code editor. A empty function for Select operation will be added by the code editor
////////////////////////////////////////////////////////////////////////////////
// This returns property value for specified property
//
// Parameters: Name of the Property
// Returns:   String
///////////////////////////////////////////////////////////////////////////////////
function get_property_value(property) {
	if (property == "selected") {
		return window.$(_elem).find(".ui-tabs-nav > li[class*='ui-tabs-selected']")[0].innerText;
	}

	if (property == "items count") {
		return window.$(_elem).find(".ui-tabs-nav > li").length;
	}

	if (property == "all items") {
		var tabLabels = new Array();
		var tabs = window.$(_elem).find(".ui-tabs-nav > li");
		for (var i = 0; i < tabs.length; i++)
			tabLabels.push(tabs[i].innerText);
		return tabLabels.join(";");
	}

	if (property == "logical name")	{
		return _elem.id;
	}
}

Step 7 – Adding functions for Recording Select Operation
In Code editor create following functions. These function will add support for recording Select operation on Tab widget in QTP Record Mode

////////////////////////////////////////////////////////////////////////////////
// This function registers to listen for events that are specific to this
// control to support recording.
// It registers for clicks on all tabs.
//
// Parameters: none.
// Returns:   Boolean. (In this implementation always true.)
///////////////////////////////////////////////////////////////////////////////////
function AddEventHandler() {
	// Retrieve all of the tabs in the tab strip.
	var tabs = window.$(_elem).find(".ui-tabs-nav > li");

	for (var i = 0; i < tabs.length; i++) {
		var tab = window.$(tabs[i]);
		_util.RegisterForEvent(tab.find("a")[0], "onclick", "onTabClick", tabs[i]);
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////////////
// This is the event handler function called during a recording session when a
// user clicks a tab.
// It retrieves the name of the tab that was clicked and records a step that
// selects the tab.
//
// Name:        onTabClick
// Parameters:  selectedItem - The parameter passed to the RegisterForEvent function,
//              in this case the tab that the user clicked.
//              eventObj - The event object for the onclick event.
// Returns:     Boolean. true if the operation was recorded, otherwise false.
///////////////////////////////////////////////////////////////////////////////////
function onTabClick(selectedItem, eventObj) {
	var tabLabel = trim(window.$(selectedItem).text());
	if (!tabLabel)
		return false;
	
	var arr = new Array();
	arr.push(tabLabel);
	_util.Record("Select", toSafeArray(arr), 0);
	return true;
}

Step 8 – Wrap-up and Deployment

  1. Save the Project
  2. We need to deploy this project in QTP so that we can use the newly created add-in for jQuery UI Controls
  3. To deploy on a local machine Select Project Deploy Deploy to Quick Test Professional from Main Menu. A confirmation dialog will be displayed with “Toolkit support set was successfully deployed” message
  4. Restart QTP if it’s open.
  5. In Add-In Manager you will see a new “jQueryUI” Add-In under Web Add-In. Select the Web and jQueryUI Add-In
  6. Open Internet Explorer and navigate to http://jqueryui.com/demos/tabs/default.html
  7. In QTP open the Object Spy and spy on the Tab control. Object Spy will identify this control as jQueryUITab Test Object. It will display properties and operations we defined earlier.
  8. Start recording in QTP and click on one of Tabs, QTP should record the Select operation as follows
Browser("jQuery UI Tabs - Default").Page("jQuery UI Tabs - Default").jQueryUITab("jQueryUITab").Select "Proin dolor"

Here is a sample QTP test

Set objTabWidget =  Browser("jQuery UI Tabs - Default").Page("jQuery UI Tabs - Default").jQueryUITab("tabs")

Print objTabWidget.GetItemCount()
Print objTabWidget.GetItems()

'Select by Index
objTabWidget.Select "#1"
Print objTabWidget.GetSelectedItem()

'Select by Title
objTabWidget.Select "Aenean lacinia"
Print objTabWidget.GetSelectedItem()

Using above steps you can add support for other jQuery UI Widgets such as Accordion, Date Picker, Track Bar etc. For more details visit http://jqueryui.com/demos/

Completed Web Extensbility Project is available at my GitHub Page https://github.com/upgundecha/testomatic/tree/master/Qtp/Web%20Extensibility