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


Using Google Charts API for Test Results Dashboard

[This post was originally written in May 2008]

Image Charts

I am using Google Charts API to generate charts to visualize test results in Dashboard. Image charts are generated on the fly based on dynamic data and then embedded in to test results generated in HTML format. This feature can be used with any tool/framework where test results are generated in HTML format.  This does not require any software/dependency installed on user’s desktop and comes free from Google.

There are different types of charts you can generate with variety of options. Just hit http://code.google.com/apis/chart/ for API documentation.

Here is an example of Pie and Google-O-Meter types:

<a href="https://unmesh.files.wordpress.com/2012/05/google-charts2.png"><img class="aligncenter size-medium wp-image-333" title="Google Charts" src="https://unmesh.files.wordpress.com/2012/05/google-charts2.png?w=300" alt="" width="300" height="119" /></a>

You can try Pie chart example by simply copying following HTTP request in your Browser’s address bar. A chart will appear in your Browser window once you hit Enter key.

<a href="http://chart.apis.google.com/chart?chtt=Test+Results&amp;chts=000000,12&amp;chs=450x150&amp;chf=bg,s,ffffff&amp;cht=p3&amp;chd=t:66.66,33.33&amp;chl=Tests+Passed(2)|Tests+Failed(1)&amp;chco=006600,990000">http://chart.apis.google.com/chart?chtt=Test+Results&amp;chts=000000,12&amp;chs=450x150&amp;chf=bg,s,ffffff&amp;cht=p3&amp;chd=t:66.66,33.33&amp;chl=Tests+Passed(2)|Tests+Failed(1)&amp;chco=006600,990000</a>

Now, how do we implement this in QTP/VBScript or any other tool of your choice? Here is an example of VBScript function which accepts Number of Test Executed, Passed, and Failed and then generates a nicely formatted HTML report:

Public Function GenerateTestSummaryReport(ByVal intTestsExecuted, ByVal intTestsPassed, ByVal intTestsFailed)

	Set objFSO = CreateObject("Scripting.FileSystemObject")
	Set objFile = objFSO.CreateTextFile("C:\TestSummary.html",True)

	objFile.WriteLine "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
	objFile.WriteLine "<html xmlns='http://www.w3.org/1999/xhtml' >"
	objFile.WriteLine "<head><title>Test Execution Summary Report</title></head>"
	objFile.WriteLine "<body style='font-weight: bold; font-size: 10pt; color: black; font-family: Tahoma'>"
	objFile.WriteLine "<br />Test Execution Summary"
	objFile.WriteLine "<br /><br />Test Suite Summary<table><tr><td style='width: 200px; height: 56px; background-color: #669918'>Total Tests Executed</td><td style='height: 56px; background-color: #669918'>Passed</td><td style='height: 56px; background-color: #669918'>Failed</td></tr>"
	objFile.WriteLine "<tr><td style='width: 200px; background-color: #D0F0A2'>" & intTestsExecuted & "</td><td style='background-color: #D0F0A2'>" & intTestsPassed & "</td><td style='background-color: #D0F0A2'>" & intTestsFailed & "</td></tr>"
	objFile.WriteLine "<tr><td><img src='http://chart.apis.google.com/chart?chtt=Test+Results&amp;chts=000000,12&amp;chs=450x150&amp;chf=bg,s,ffffff&amp;cht=p3&amp;chd=t:" & intTestsPassed & "," & intTestsFailed & "&amp;chl=Tests+Passed(" & intTestsPassed &")|Tests+Failed(" & intTestsFailed & ")&amp;chco=006600,990000' alt='Test Results Chart'/></td></tr>"
	objFile.WriteLine"</table>"

	Set objFile = Nothing
	Set objFSO = Nothing

End Function

'Call Function
Call GenerateTestSummaryReport(5,3,2)

In above code, highlighted IMG tag will make a request to Google Chart API and will show a nicely built chart on the fly when user opens this file in a Web Browser or email client.

If you find API documentation not working for you, there is a very interesting service available at http://dexautomation.com/googlechartgenerator.php

This will enable you to design Charts using guided steps and then it will generate IMG tag which you can embed in your code. Using Google Charting API you could build a nice dashboard for you test suite result reporting.

However Image charts have some limitation on amount of data we can pass. Other alternative is Interactive Chart feature.

Interactive Charts

Unlike image charts which are static, Interactive are charts more advanced. Interactive charts support user interaction and event handling. These are built using JavaScript.

Google Visualization API provides different types of charts for data visualization. Please see following page for sample charts.

http://code.google.com/apis/visualization/documentation/gallery.html

Here is an example in VBScript on how to use Gauge charts for showing test results/build status:

Public Function GenerateTestSummaryReport(ByVal intTestsPassed, ByVal intTestsFailed)
	Set objFSO = CreateObject("Scripting.FileSystemObject")
	Set objFile = objFSO.CreateTextFile("C:\TestSummary.html",True)
	objFile.WriteLine "<html><head><title> Test Execution Summary Report </title>"
	objFile.WriteLine "<script type='text/javascript' src='http://www.google.com/jsapi'></script>"
	objFile.WriteLine "<script type='text/javascript'>"
	objFile.WriteLine "google.load('visualization', '1', {packages:['gauge']});"
	objFile.WriteLine "google.setOnLoadCallback(drawChart);"
	objFile.WriteLine "function drawChart() { var passedData = new google.visualization.DataTable();"
	objFile.WriteLine "passedData.addColumn('string', 'Label'); passedData.addColumn('number', 'Value');"
	objFile.WriteLine "passedData.addRows(1); passedData.setValue(0, 0, 'Tests Passed');"
	objFile.WriteLine "passedData.setValue(0, 1, " & intTestsPassed & ");"
	objFile.WriteLine "var passedChart = new google.visualization.Gauge(document.getElementById('chart_passed'));"
	objFile.WriteLine "var passedOptions = {width: 400, height: 120, redFrom: 0, redTo: 30, yellowFrom: 31, yellowTo: 60, greenFrom: 61, greenTo: 100, minorTicks: 5};"
	objFile.WriteLine "passedChart.draw(passedData, passedOptions);"
	objFile.WriteLine "var failedData = new google.visualization.DataTable();"
	objFile.WriteLine "failedData.addColumn('string', 'Label');"
	objFile.WriteLine "failedData.addColumn('number', 'Value');"
	objFile.WriteLine "failedData.addRows(1);"
	objFile.WriteLine "failedData.setValue(0, 0, 'Tests Failed');"
	objFile.WriteLine "failedData.setValue(0, 1, " & intTestsFailed & ");"
	objFile.WriteLine "var failedChart = new google.visualization.Gauge(document.getElementById('chart_failed'));"
	objFile.WriteLine "var failedOptions = {width: 400, height: 120, redFrom: 31, redTo: 100, yellowFrom: 11, yellowTo: 30, greenFrom: 0, greenTo: 10, minorTicks: 5};"
	objFile.WriteLine "failedChart.draw(failedData, failedOptions);}"
	objFile.WriteLine "</script></head>"
	objFile.WriteLine "<body style='font-weight: bold; font-size: 10pt; color: black; font-family: Tahoma'><br />Test Execution Summary"
	objFile.WriteLine "<br /><br />Test Suite Summary<table><tr><td style='width: 200px; height: 56px; background-color: #669918'>Total Tests Executed</td><td style='height: 56px; background-color: #669918'>Passed</td><td style='height: 56px; background-color: #669918'>Failed</td></tr>"
	objFile.WriteLine "<tr><td style='width: 200px; background-color: #D0F0A2'>100</td><td style='background-color: #D0F0A2'>85</td><td style='background-color: #D0F0A2'>15</td></tr>"
	objFile.WriteLine "<tr><td><div id='chart_passed'></div></td><td><div id='chart_failed'></div></td></tr></table></body></html>"
	Set objFile = Nothing
	Set objFSO = Nothing
End Function

'Call Function
Call GenerateTestSummaryReport(15,85)

Here is interactive Chart Generated:

You can add more metrics to your reports and build more meaningful Charts.


Using DotNetFactory Utility Object in QTP

[This post was originally written in March 2007]

Using DotNetFactory object you can access .NET base classes into your QTP scripts. This also supports using DLLs compiled from any .NET language. This features works on .NET Interop which means you can call .NET components in COM environment and vice versa.

Using ArrayList Class

Here is an example where we have a test scenario which requires sorting and searching of an array of values. We will use ArrayList class from System.Collections namespace. Collections namespace has variety of classes like Array, HashTable, Stack, LinkedList etc.

ArrayList class is enriched with various ready to use methods for Sorting, Reversing, and Searching items.


'//Create an instance of System.Collections.ArrayList using DotnetFactory.CreateInstance method

Set myList = DotnetFactory.CreateInstance("System.Collections.ArrayList")

'//Add items to the List Array
myList.Add("American")
myList.Add("Ukrainian")
myList.Add("bElarusian")
myList.Add("Indian")
myList.Add("123")
myList.Add("$@%^%@")

'//ArrayList.Capacity/ArrayList.Count shows the number of elements it have

Print "Capacity of ArrayList is: " & myList.Capacity
Print "Count of ArrayList is: " & myList.Count

'//ArrayList adds empty memory locations when new items are added to the list
'//using TrimToSize you can remove these empty locations from the list
myList.TrimToSize

'//SORTING
'//You can sort the contents of an ArrayList using Sort method, for Ascending sort:

myList.Sort

'//For displaying array contents in a String
strMsg = ""
For intCnt = 0 To myList.Count - 1
 strMsg = strMsg & myList.Item(CInt(intCnt)) & vbCrLf
Next

Print "Sorted Array List: Asecending" & vbCrLf & strMsg

'//You can Sort an array Descending with Reverse method. But first you need to sort the list
'//using Sort method

myList.Reverse

'//For displaying array contents in a String
strMsg = ""
For intCnt = 0 To myList.Count - 1
 strMsg = strMsg & myList.Item(CInt(intCnt)) & vbCrLf
Next

Print "Sorted Array List: Descending" & vbCrLf & strMsg

'//SEARCHING
'//You can search an item using IndexOf and BinarySearch method. These method return zero based index
'//of an item for its first occurrence

Print "Indian item found at: " & myList.IndexOf("Indian") & vbCrLf & strMsg,,"IndexOf Search"
Print "indian item found at: " & myList.IndexOf("indian") & vbCrLf & strMsg,,"IndexOf Search"
'//For BinarySearch ArrayList should be sorted in Ascending Order
myList.Sort

'//For displaying array contents in a String
strMsg = ""
For intCnt = 0 To myList.Count - 1
 strMsg = strMsg & myList.Item(CInt(intCnt)) & vbCrLf
Next

Print ,"Binary Search: " & vbCrLf & "bElarusian item found at: " & myList.BinarySearch("bElarusian") & vbCrLf & strMsg
Print ,"Binary Search: " & vbCrLf & "Belarusian item found at: " & myList.BinarySearch("Belarusian") & vbCrLf & strMsg

'//ArrayList.Contains method searches for given item and returns True/False
Print myList.Contains("Indian")

Set myList = Nothing

This approach is much faster and reliable than writing code our own code.

Using Clipboard Object

You can access Clipboard object using System.Windows.Forms.Clipboard class. Here is an example showing how to Clear, Set and Get contents from Clipboard.


'//This is not supported in 8x versions

Set oClip = DotNetFactory.CreateInstance("System.Windows.Forms.Clipboard")

'//Clears the Clipboard
oClip.Clear

'//Sets sample text on Clipboard
oClip.SetText "DotNetFactory Rocks!!"

'//Retrieves text from Clipboard
strContents = oClip.GetText

Print strContents

File Input/Output

Using System.IO.File class you can create, read and write to files. Here is an example on writing to and reading from a file.


'//This is not supported in 8x versions

Set objFile = DotNetFactory.CreateInstance("System.IO.File")

'//Creates a new file with given text
objFile.WriteAllText "C:\Test.txt","DotNetFactory Rocks!!"

'//Retrieves text from given file
strContents = objFile.ReadAllText("C:\Test.txt")

Print strContents

This way you can use 100s of classes available in .NET Framework in your QTP Scripts.


Using DOM, XPath & CSS for Object Identification in QTP – Part 3

We saw how to use HTML DOM and XPath for Web Object Identification in Part 1 & Part 2. In this final Part we will explore using CSS for Object Identification in QTP 11.

Introduction to CSS (Cascading Style Sheets)

CSS (Cascading Style Sheets) is a language used for formatting and rendering of HTML and XML documents.

CSS uses Selectors for binding style properties to elements in the document. These Selectors can be used by QTP as another object identification strategy.

Finding elements with XPath can be costly in terms of performance. CSS Selectors can be more efficient as they are forward only. However they do not provide backword search flexibility as XPaths.

Using CSS for Web Object Indetification

Along with XPath, QTP 11 has added CSS Object Identification Property to all Test Objects in Web Add-In. You can specify CSS Selectors by using css Property.

You can enable and use css Property in Object Repository or in Descriptive Programming. In following example CSS class attribute is used identify Sign In Button on Gmail Home Page:


Browser("title:= Gmail: Email from Google").Page("title:= Gmail: Email from Google").WebButton("css:=input.g-button").Click

Using CSS class attribute is most common and simplest method to indetify the elements. You can identify the elements using class attribute in following way:


element.class_attribute

In above example we identified Sign In Button on Gmail Home Page. Here is the HTML Syntax for Sign In Button. You can see developers have assigned class attribute as g-button:

<input id="signIn" class="g-button" type="submit" value="Sign in" name="signIn" style="background-color: rgb(77, 144, 254);">

Identifying Elements using ID

As we saw in XPath, we can identify elements using their IDs, CSS also has similar way to identify elements using ID. Following is the syntax for identifying elements using ID:

element#id

We need to specify a # between element type and its ID. In following example we will identify User Name Text Box on Gmail Home Page using its ID:


Browser("title:= Gmail: Email from Google").Page("title:= Gmail: Email from Google").WebEdit("css:=input#Email").Set "myname"

Identifying Elements using Attributes

Similar to XPath, we can also use element attributes to identify objects. Following is the syntax for identifying elements using their attributes:

element[attribute=value]

In following example, we will identify and check Stay signed in Checkbox on Gmail Home Page using type attribute:


Browser("title:= Gmail: Email from Google").Page("title:= Gmail: Email from Google").WebCheckBox("css:=input[type='checkbox']").Set "ON"

We can also perform partial match on attribute values using following operators:

Operator Description
^=attribute_value Finds the element attribute starting with the value passed. This is similar to starts-with() function in XPath
*= attribute_value Finds the element attribute which contains the value passed. This is similar to contains() function in XPath
$= attribute_value Finds the element attribute ending with the value passed. This is similar to ends-with() function in XPath

In following example, developers have assigned dynamic attribute values for all the input elements in following way:

<div id='login_area'>
<input type='text' name='text_1'>
</div>

We can use either ^= or *= function to identify this object in following way:


Browser("title:= Test App").Page("title:= test App").WebEdit("css:=div#login_area input[name^='text_']").Set "somevalue"

Browser("title:= Test App").Page("title:= test App").WebEdit("css:=div#login_area input[name*='text_']").Set "somevalue"

Identifying Elements using Text Contents

Locating elements by the text they contain can be quite useful. To identify elements, we need to use CSS contains pseudo class. This will match the entire contents of the element.


element:contains('text')

In following example, we will use contains pseudo class to identify the Create an account link on Gmail Home Page:


Browser("title:= Gmail: Email from Google").Page("title:= Gmail: Email from Google").Link("css:=a:contains('Create an account')","index:=0").Click

Identifying Elements using Relationships

Similar to XPath Axes, we can identify elements using relationships in CSS. Following are some exmaples  for identifying elements by their relationships:

Child/Descendent Elements nth-child orE1 >E2
Element <E1> following some sibling <E2> E2 ~ E1
Element <E1> immediately following sibling <E2> E2 + E1
Element <E1> following sibling <E2> with one intermediary E2 + * + E1

For more combinations please refer here.

We will use the Shopping Cart example from Part 2 to understand identifying elements using relationships in CSS. In following example we select an item by entering the quantity and click the Shopping Cart image to add the item in Cart:


Browser("title:= ShopX").Page("title:= ShopX ").WebEdit("xpath:=td:contains('0002')+td+td+td>div>form>div>input[name='qty']").Set "10"
Browser("title:= ShopX").Page("title:= ShopX ").Image("xpath:=td:contains('0002')+td+td+td>div>form>div>input[name='add']").Click

Here is another example I created for a custom Dropdown control. This control is created with combination of a Text Box, a Button and a DIV containing multiple LI elements. QTP identify this control in following way:

Custom Dropdown Control

When clicked on the Dropdown Button which is identified as WebButton, a dynamic DIV is displayed with values available for selection in HTML LI elements. This is automated using CSS in following way:

Browser(title:=MyApp").Page("title:=MyApp).WebButton("id:=item_Dropdown").Click
Browser(title:=MyApp").Page("title:=MyApp).WebElement("css:=div#item_Dropdown>li:contains('Value2')").Click

There are endless possibilities to identify objects using CSS and XPath. However these are some of the example to understand the basics.

I will close this series with a caution that DOM, XPath, CSS type of identifiers are dependent on locations and page structure and may not work always where developers change the UI frequently.


Using DOM, XPath & CSS for Object Identification in QTP – Part 2

XPath is another important Web Object Identifier introduced in QTP 11. It is one of the widely used identification strategy in open source tools. In this tutorial we will understand using XPath for locating Web objects in your application with QTP.

Introduction to XPath

XPath is used for locating nodes in an XML document and it can also be used for locating HTML elements in XHTML. XPath opens up all sorts of new possibilities for locating complex & dynamically rendered elements.

Awkwardly, developers not always follow best practices or testability guidelines while building the applications. XPath comes to your help when you don’t have a suitable id or name attribute for the element you wish to identify. You can use XPath to either identify the element in absolute terms or relative to an element that does have an id or name attribute. XPath locators can also be used to specify elements via attributes other than id and name.

Using XPath for Web Object Identification

QTP 11 has added XPath Object Identification Property to all Test Objects in Web Add-In. QTP offers two ways to use XPath, you can either instruct QTP to record Automatic XPath by settings Options in Tools menu or you can specify User Defined XPath in xpath Property.

You can also enable and use xpath Property in Object Repository or in Descriptive Programming. In following example a direct XPath query is used to identify Search Text Box on Google:


Browser("title:=Google").Page("title:=Google").WebEdit("xpath:=//input").Set "What is XPATH"

In above example we used //input to identify the Search Text Box. Using // in XPath query is called greedy query as it parses the entire HTML DOM until it finds the desired element. This way is useful when objects are dynamically positioned; however it takes certain amount of time to find the element.

However if you are certain about position of the desired element, you can use a direct XPath query by using single /, however please make you sure that the HTML is first node in your query. Following example shows direct XPath query for User Name Text Box on Gmail Home Page:

Browser("title:= Gmail: Email from Google").Page("title:= Gmail: Email from Google").WebEdit("xpath:=/html/body/div/div[2]/div/div/form/label/input").Set "myname"

Direct XPath query will find the element quicker, however if application GUI is going to change, it may fail if the element is moved into a different area of the Page.

Using Element Attributes in XPath

We can use various element attributes such as ID, Name, Value, Type etc. to identify element in following way:


//element [@attribute='attribute value']

In following example, ID attribute is used to identify User Name Text Box on Gmail Home Page:


Browser("title:= Gmail: Email from Google").Page("title:= Gmail: Email from Google").WebEdit("xpath:=//input[@id='Email']").Set "myname"

We can also use combination of attributes in XPath query so that we can try to make the element more unique for identification:


//element[@attribute='attribute value' and @attribute='attribute value']

In following example, we will identify and check “Stay signed in” Checkbox on Gmail Home Page:


Browser("title:= Gmail: Email from Google").Page("title:= Gmail: Email from Google").WebCheckBox("xpath:=//input[@id='PersistentCookie' and @type='checkbox']").Set "ON"

Identifying Elements using partial match on Attribute values

We can use XPath functions such as contains(), starts-with() & ends-with() to perform partial match on attribute values while locating the elements. This is useful when developers use Dynamic IDs or Name attributes while rendering the elements on a Page. This is commonly used in AJAX applications & frameworks.


//element[starts-with(@attribute,’attribute partial value')]
//element[contains(@attribute,’attribute partial value')]
//element[ends-with(@attribute,’attribute partial value')]

In following example, developers have assigned dynamic ID’s for all the input elements in following way:

<div id='login_area'>
    <input type='text' id='text_1'>
</div>

We can use either starts-with() or contains() function to identify this object in following way:


Browser("title:= Test App").Page("title:= test App").WebEdit("xpath:=//div[@id='login_area']/input[starts-with(@id, 'text_')]").Set "somevalue"

 

Browser("title:= Test App").Page("title:= test App").WebEdit("xpath:=//div[@id='login_area']/input[contains(@id, 'text_')]").Set "somevalue"

Identifying Elements using Text Contents

Locating elements by the text they contain can be quite useful. To identify elements, we need to use text() function in XPath query. This will match the entire contents of the element.


//element[text()='inner text']

In following example, we will use text() function to identify the Create an account link on Gmail Home Page:


Browser("title:= Gmail: Email from Google").Page("title:= Gmail: Email from Google").Link("xpath:=//a[text()='Create an account']").Click

We can also use XPath functions contains(), starts-with() or ends-with() for matching partial text.


//element[contains(text(),'text value')]

Exmaple:


strLotsOfStorageMessage = Browser("title:= Gmail: Email from Google").Page("title:= Gmail: Email from Google").WebElement("xpath:=//p[contains(text(),'Over')]").GetROProperty("innerText")

Identifying Elements with XPath Axes

In simple terms XPath Axes helps to identify elements based on element’s relationship with other elements in a document. For more information, there is a nice tutorial available on W3Schools on XPath & XPath Axes.

For example in an e-commerce web application, we want to select and add items to shopping cart. However the items are listed in a table dynamically with complex HTML structure. Following figure shows the structure of the table:

ShoppingCart

Shopping Cart Page

The textbox to enter the Quantity and image to add item was buried under layer of div elements inside a td element in a table. The ids for these elements were generated dynamically and it is difficult to add an item dynamically from the test script. Here is html code for Quantity textbox:

For Product 1


<input type="text" id="count_670756" value="" maxlength="3" size="3" name="qty">

For Product 2


<input type="text" id="count_670759" value="" maxlength="3" size="3" name="qty">

As you can see in the above code, we cannot rely on id attribute as it changes every time page is refreshed and secondly name of textbox is not unique.

We need to find a unique way to identify a Product in the table. There are two columns which contain unique values namely Product & Article column. However Article column contains primary key from the database so it is highly recommended to use this value as basis to identify the product. We can also use Product column otherwise. Following XPath Query will identify the cell containing the specified Article using XPath functions contains() & text():


xpath:=//td[contains(text(),'0002')]

Now we need to find the cell which contains the elements. Here we will use following-sibling axis and find the third cell from the current cell. Following query will return the cell containing Quantity & Add to cart image:


xpath:=//td[contains(text(),'0002')]/following-sibling::td[3]

In next step we need to get the actual elements which are located inside the layer of div elements. Here we need to use descendent axis to find the child elements in the cell:


xpath:=//td[contains(text(),'0002')]/following-sibling::td[3]/descendant::div[2]/input[@name='qty']

xpath:=//td[contains(text(),'0002')]/following-sibling::td[3]/descendant::div[3]/input[@name='add']

We can remove the nth element, or element Index to make this XPath query more flexible in following way:


xpath:=//td[contains(text(),'0002')]/following-sibling::td/descendant::div/input[@name='qty']
xpath:=//td[contains(text(),'0002')]/following-sibling::td/descendant::div/input[@name='add']

VBScript code to enter quantity for an Item and add selected Item to the Shopping Cart in QTP:


Browser("title:= ShopX").Page("title:= ShopX ").WebEdit("xpath:=//td[contains(text(),'0002')]/following-sibling::td/descendant::div/input[@name='qty'").Set "10"
Browser("title:= ShopX").Page("title:= ShopX ").Image("xpath:=//td[contains(text(),'0002')]/following-sibling::td/descendant::div/input[@name='add']").Click

These actions will be used to add multiple items in various test cases. We need to make these actions more generic and remove the hardcoded Article Id. Following example shows user defined function in QTP for above steps:


Public Function fnAddItemToShoppingCart(ByVal strArticleID, ByVal strQTY)

Browser("title:= ShopX").Page("title:= ShopX ").WebEdit("xpath:=//td[contains(text(),'" & strArticleID &  "')]/following-sibling::td/descendant::div/input[@name='qty'").Set strQTY
Browser("title:= ShopX").Page("title:= ShopX ").Image("xpath:=//td[contains(text(),'" & strArticleID & "')]/following-sibling::td/descendant::div/input[@name='add']").Click

End Function

However while using XPath we need to consider the fact that these locators are dependent on structure of the page and the layout of the elements. Any changes to the structure or layout will affect the locators and tests will result into failures.


Using DOM, XPath & CSS for Object Identification in QTP – Part 1

QTP 11 introduced new features to identify Web objects using HTML DOM, XPath & CSS. These object identification strategies are widely used in open source tools like Selenium, WATIR etc.

In this series, I will explain how to use HTML DOM, XPath and CSS for identifying objects in your Web application. In Part 1, I will use HTML DOM for identifying the Web objects.

Document Object Model

The W3C Document Object Model (DOM) is a platform and language-neutral interface that allows programs and scripts to dynamically access and update the content, structure, and style of a HTML document.

The HTML DOM defines the objects and properties of all HTML elements, and the methods (interface) to access them. JavaScript uses DOM to add interactivity to HTML pages.

We can use the combination of JavaScript and HTML DOM interfaces to identify objects within Web Pages and Frames. This strategy can be useful for testing AJAX applications when object are dynamically added to the Web Page or Frame. While QTP’s Object Repository & AJAX testing features should suffice your need, this strategy can be used as a workaround for identifying the objects.

Document Object Model Methods (Interfaces)

Following HTML DOM document object methods (interfaces) can be used in QTP for identifying objects:

Method Description
getElementById Returns the first element with the specified id
getElementsByName Returns all elements with a specified name in an JavaScript array
getElementsByTagName Returns all elements with a specified tagname in a JavaScript array

Calling JavaScript Code in QTP

QTP 11 introduced RunScript method to the Page and Frame Test Objects in Web Add-In. We can use this method to inject and execute JavaScript code in Page or Frame Test Objects. This method has number of uses for example testing JavaScript code within your page, invoking JavaScript events etc. This feature comes handy while testing complex AJAX Web applications.

Following example will display a JavaScript popup with Hello There!! message. This example uses Descriptive Programming to identify Browser and Page objects:

Browser("title:=Google").Page("title:=Google").RunScript("alert('Hello There!!');")

Using HTML DOM Methods & Properties in QTP

To access the HTML DOM methods we need to use the document object. We can use getElementById method to access the element using its HTML ID. This method will return the first element it finds in HTML DOM with specified ID. For example on Gmail Login Page, we can access the User Name text box by following way.

HTML Syntax for User Name Text Box:

<input id="Email" type="text" name="Email" />

QTP VBScript Syntax to access User Name Text Box:

Set objUserNameTextBox = Browser("title:=Gmail: Email from Google").Page("title:=Gmail: Email from Google").RunScript("document.getElementById('Email');")
objUserNameTextBox.value = "myname" 'Uses value property to set the contents of Text Box

This is most recommended method to identify the objects using HTML DOM. However this method might not work for elements where HTML IDs are not defined. We can use getElementsByName method to identify the elements by specifying the name attribute value. In following example we will use same Gmail Login User Name Text Box, however this time we will identify this element using the name attribute value:


Set objUserNameTextBox = Browser("title:=Gmail: Email from Google").Page("title:=Gmail: Email from Google").RunScript("document.getElementsByName('Email')(0);")
objUserNameTextBox.value = "myname" 'Set  User Name Text Box value

The getElementsByName returns a JavaScript array for all the elements whose name matches with the specified value. In above example we use array index to access the 0th element, which is the User Name Text Box.

However there is another method which comes handy when you don’t have either IDs or Names specified for HTML elements. Using getElementsByTagName you can identify the object by Tag name. This is least preferred way to access the elements.


Set objUserNameTextBox = Browser("title:=Gmail: Email from Google").Page("title:=Gmail: Email from Google").RunScript("document.getElementsByTagName('INPUT')(12);")
objUserNameTextBox.value = "myname" 'Set  User Name Text Box value

The getElementsByTagName returns a JavaScript array for all the elements whose HTML Tag name matches with the specified value. In above example we use array index to access the 12th element which is the User Name Text Box. This method needs efforts to find out exact index of the intended element. If the index changes, your script will fail.

We can also use combination of HTML DOM methods to identify the objects. In following example a Text Box is embedded in DIV element, however this Text Box does not have enough identification properties. We will use getElementById to identify the parent DIV element and getElementsByTabName to identify the Text Box:

HTML Syntax:

</pre>
<div id="<span class=&quot;hiddenSpellError&quot; pre=&quot;&quot;>mainArea</span>"> </div>
<pre>
	<p>Enter Amount: <input type=text></input></p>
</div>

QTP VBScript Syntax to access Text Box:


Set objAmountTextBox = Browser("title:=MyApp").Page("title:=MyApp").RunScript("document.getElementById('mainArea').getElementsByTagName('INPUT')(0);")
objAmountTextBox.value = "100" 'Set the Text Box value

We can also identify elements using the relationships. Following table explains using various relationship identifiers:

document.getElementById(‘mainArea’).firstChild Returns the first child element of element returned from getElementById
document.getElementById(‘mainArea’).lastChild Returns the last child element of element returned from getElementById
document.getElementById(‘mainArea’).parentNode Returns the parent element of element returned from getElementById
document.getElementById(‘mainArea’). nextSibling Returns the next sibling element of element returned from getElementById
document.getElementById(‘mainArea’). previousSibling Returns the previous sibling element of element returned from getElementById

In the next instalment we will see how to use XPath for locating the objects in QTP.