A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_URI::$config is deprecated

Filename: core/URI.php

Line Number: 101

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Router::$uri is deprecated

Filename: core/Router.php

Line Number: 127

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$benchmark is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$hooks is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$config is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$log is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$utf8 is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$uri is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$exceptions is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$router is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$output is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$security is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$input is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$lang is deprecated

Filename: core/Controller.php

Line Number: 75

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property Pages::$load is deprecated

Filename: core/Controller.php

Line Number: 78

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$benchmark is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$hooks is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$config is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$log is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$utf8 is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$uri is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$exceptions is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$router is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$output is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$security is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$input is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$lang is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

A PHP Error was encountered

Severity: 8192

Message: Creation of dynamic property CI_Loader::$load is deprecated

Filename: core/Loader.php

Line Number: 925

Backtrace:

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 147
Function: view

File: /home/project-web/vbscript-development-kit/htdocs/application/controllers/Pages.php
Line: 142
Function: loadPage

File: /home/project-web/vbscript-development-kit/htdocs/index.php
Line: 315
Function: require_once

VDK - UFT framework

VDK UFT/QTP Framework

October 3, 2017Posted by Jason Ely

 

This tutorial will explain how you can use the VDK in an OO driven UFT/QTP driven automation framework.

Getting Started

To get started and actually see this framework run, you can download the framework here. Once downloaded, unzip the file to the directory that %VDK_HOME% is pointing to. My %VDK_HOME% is pointing to "f:\vdk" so I will unzip it to there. To check that you unzipped correctly, check that the following path exists: %vdk_home%\uftVdkFramework\drive.vbs .

VDK UFT Directory Structure

If the file exists, you can double click on it which will start the run. You will require that UFT 14 and VDK to be installed on your machine. Additionally, you will require an internet connection as the default tests in the framework will be testing the google homepage.

Framework Requirements

Everything that we do in the IT world is driven by a requirement and our sample framework is no different. Lets start out with some simple requirements for our framework.

  • Framework must use HP UFT version 14
  • Must use an object orientated MVC (Model-View-Controller) approach
  • Must use an MS Excel data/keyword driven approach so that non technical resources can design there own tests.
  • Must be able to run on click of a button
  • Built in reporting mechanism that is easy to understand and use
  • Can be integrated into a CI tool like Jenkins, bamboo etc

Framework Basics

Before we get stuck into the nitty-gritty of the framework, lets cover a couple of basics:

  • Our framework will use VBScript as its programming language of choice. This is a necessity because we will be using UFT/QTP as our tool to do the automation heavy lifting. Having said that, you should never be constrained to a tool(or a language for that matter of fact) to do heavy automation lifting. If you really wanted to be clever, you could use this framework and swap out all the UFT/QTP code for AutoIt code or use a low level DLL like user32.dll to do the heavy automation lifting for you.
  • We will use the VDK to satisfy our OO coding approach. MVC is a design pattern and can be implemented in any programming language. When coding our framework with VDK features, we just have to get into the habit of programming in an MVC way.
  • The framework will use a flat directory structure to save resources and dependencies. Many people who use UFT/QTP have HP ALM as their resource repository however not all people are so equally blessed (or cursed if you happen to hold a different opinion). This framework will not use ALM for the simple reason that its a very, very, very expensive repository to save a couple of function libraries and object repositories.

Framework Driver

Our framework starts with a simple driver script. Its job is pretty simple. When a user double clicks on it, it must run. I've added some additional comments to keep in mind. The script is found in location: %vdk_home%\uftVdkFramework\drive.vbs

							'the main purpose of this entry point script is to start the VDK and framework driver.

							'If you wanted to get really clever, what you could do is actually use this script 
							'to do a couple of things:

							'1. Check that VDK is installed. If not, install VDK on this client via command-line MSI installer.
							'Alternatively, save the VDK core files on a remote drive that all your clients can access and then during runtime, 
							'copy those files to the client path and then set VDK_HOME path in system environment variables on this client.
							'2. Check for the existence of the framework. If it doesn't exist, then 
							'download it. If you are developing VDK framework or tests you should really develop
							'it under some form of source control like GIT, Mercurial or SVN. You can then just 
							'do a clone or pull onto this client.
							'3. Check that QTP/UFT is installed on this client. If not, gracefully exit. If yes, continue.
							'4. Once everything is done, you can initialize the VDK.

							'
							'You should also integrate your UFT framework into a continuous integration (CI) tool like Bamboo, Hudson, Jenkins, WebSphere, etc.

							'after this script completes, it will automatically open up a 
							'browser window which will give you a view of all the results 
							'for the run.

							'Start VDK
							ExecuteGlobal CreateObject("wsh.vdk.initialize").initialize

							'import the framework driver core class
							import "uftvdkframework.core.clsVdkUftFrameworkDriver"

							'create an object from the core driver class and let it run
							set objDriver = new clsVdkUftFrameworkDriver
							objDriver.run

							

The driver starts up the VDK, creates an instance of the root controller and lets that controller run. There might be only 4 lines of executable code but that 4 lines is pretty powerful as it leverage's the power of the VDK and OO programming to make for a simple and elegant approach.

Framework Controller

The framework controller is the main entry point into our framework and one of the "Controller" parts of our MVC pattern. The controllers job is is to "control" our framework execution and decide how it does its business.

The framework controllers job is broken down into the following main actions:

  1. Create a temp run environment for our framework.
  2. Configure UFT/QTP for our framework run
  3. Get a listing of all excel files in location '%vdk_home%\uftVdkFramework\Tests\'. For each excel file in our collection, execute as a framework test.
  4. When executing the test, pass the excel test file to a test controller for execution.
  5. When all tests are done executing, generate a result dashboard and display to the user.

There are many other things that are happening in the framework controller but that is beyond the scope of this tutorial. The following code is contained in vbscript file '%vdk_home%\uftVdkFramework\core\clsVdkUftFrameworkDriver.vbs'.

Take note of how the root controller generates code at runtime and then saves the code into our default UFT test .mts file. This is the key to how the Test Controller works which is covered in the next section.

							'The below code is the framework root controller
							'code can be found in file %vdk_home%\uftVdkFramework\core\clsVdkUftFrameworkDriver.vbs

							import "vdk.io.clsFile"
							import "vdk.utils.properties.clsProperties"
							import "vdk.lang.collections.clsVdkQueue"
							import "vdk.lang.clsSimpleDateFormat"
							import "vdk.io.clsFileUtils"
							import "uftvdkframework.core.clsVdkUftFrameworkDriver"
							import "uftvdkframework.core.clsVdkUftFrameworkDashboard"

							Class clsVdkUftFrameworkDriver
								'/**
								'

This is the root driver for UFT tests. It is expected that a user may execute 'create an instance of this class and execute via any WSH host like wscript, cscript, etc.

' '

It is the responsibility of the calling code to ensure that 'all dependencies for this class are met namely:

' '
    '
  • UFT/QTP is installed on the client machine
  • '
  • VDK is installed and configured on the client machine
  • '
' '

All results from this driver is saved to directory "%localappdata%\uftvdkframework\runs". The most 'recent timestamped directory is the current run.

' '@author Jason Ely '@version 1 '@since 1 '/ private objQtApp 'As QuickTest.Application ' Declare the Application object variable private qtTest 'As QuickTest.Test ' Declare a Test object variable private qtResultsOpt 'As QuickTest.RunResultsOptions ' Declare a Run Results Options object variable private strResultDirectory 'as the directory in which all uft results will be saved. private objConfigProperties 'as the properties collection for config file private strBasePath private objTempDirectory private strTimeStamp private objResultsDirectory private hasConfiguredTest Private objFileUtils Private objThisDashboard public Sub class_initialize() '/** '

Default constructor initialises the driver.

' '

During the initialisation process, the original sourcefiles in the framework root 'directory are copied to a temp run directory. The default framework UFT test is opened 'and the test is configured to the specification of the driver framework as well 'as properties as defined in config.properties.

' '@author Jason Ely '@version 1 '@since 1 '/ 'scratch objects and variables to be used throughout our class hasConfiguredTest = false Set objRegEx = New RegExp objRegEx.IgnoreCase = True objRegEx.Global = True set objFileUtils = new clsFileUtils 'we need a unique timestamp for our result output folder so use simple date format to get one set objDatePattern = new clsSimpleDateFormat objDatePattern.setPattern "YYYYMMDD_hhmmss_SSS" strTimeStamp = objDatePattern.format() 'base path for our run. All our file resources are relative to this path strBasePath = vdk.system.getWindowsProperty("%LOCALAPPDATA%") & "\uftvdkframework\runs\" & strTimeStamp set objTempDirectory = new clsFile.init(strBasePath) objTempDirectory.mkdirs 'copy the framework over to temp so that we don't work with the original files set objFrameworkDirectory = new clsFile.init(vdk.getVdkHome() & "\uftvdkframework") 'going to use shell copy instead of vbscript...much faster and safer Set objShell = CreateObject ("WScript.Shell") strCommand = "cmd.exe /C xcopy " & chr(34) & objFrameworkDirectory.getAbsolutePath() & chr(34) & " " & chr(34) & strBasePath & "\" & objFrameworkDirectory.getBaseName & chr(34) & "/e /r /y /i" varResult = objShell.Run(strCommand, 0, true) Set objShell = Nothing 'get the config properties for this run from our framework 'first replace all instances of windows properties like %vdk_home% strConfigFilePath = getBasePath() & "\uftvdkframework\config\config.properties" strText = objFileUtils.GetFileText (strConfigFilePath) strRegExPattern = "(%[\w]+%)" objRegEx.Pattern = strRegExPattern Set objMatches = objRegEx.Execute (strText) For Each objMatch In objMatches strWindowsVal = vdk.system.getWindowsProperty(objMatch.Submatches(0)) strText = Replace(strText, objMatch.Value, strWindowsVal,1 ,-1, vbTextCompare) Next 'write text back to our properties file objFileUtils.WriteToFile strConfigFilePath, strText 'now we open our properties file set objConfigProperties = new clsProperties.init(getBasePath() & "\uftvdkframework\config\config.properties") 'dashboard Set objThisDashboard = New clsVdkUftFrameworkDashboard.init(strBasePath) 'create uft objects Set objQtApp = CreateObject("QuickTest.Application") ' Create the Application object objQtApp.Launch ' Start QuickTest objQtApp.Visible = false ' Make the QuickTest application visible End Sub Public Sub Class_Terminate '/** '

Within the context of QTP, will close the current test and then quit QTP.

' '@author Jason Ely '@version 1 '@since 1 '/ 'close the current test if any to release any file locks objQtApp.Test.Close 'close QTP and release any resources it might have objQtApp.Quit End sub public function getBasePath() '/** '

Returns the path to the directory in which all our framework resources are saved. This 'usually translates to %LOCALAPPDATA%\uftvdkframework\runs\{timestampe}

' '

The directory returned should be treated as a temp directory as it is saved in windows path %LOCALAPPDATA%. 'The directory returned has a timestamped value as its name which is guaranteed to be unique. The name is in timestamp 'format "YYYYMMDD_hhmmss_SSS"

' '

The basepath contains a copy of the original UFT VDK framework. The original framework files should not be used, instead 'use the files in this directory.

'@author Jason Ely '@version 1 '@since 1 '@returnType String '@return absolute path to base path where all resources are saved '/ getBasePath = strBasePath end function public sub run() '/** '

The default method of this class. A call to this method executes all the testcases 'assigned to this driver.

' '

During the run process, a list of test files will be retrieved. Each test file will be executed. 'the results of each test will be saved to a directory as specified by property framework.uft.results.directory.path that is defined 'in the config file. If the property value is not defined, then results are save to directory %vdk_home%\uft\framework\results

' '@author Jason Ely '@since 1 '/ 'configure the framework default UFT test according to our framework specification configureTest() 'get a collection of all excel test datasheets 'data sheets exist in 2 places. '1. In the default testcase folder found in %vdk_home%\uftvdkframework\Tests '2. A directory path specified by property 'add files in default tests directory set objDefaultTestDirectory = new clsFile.init(getBasePath() & "\uftvdkframework\tests") Set objFiles = getFiles(objDefaultTestDirectory, Array("xls")) For intI = 0 To objFiles.getCount() - 1 executeTest objFiles.getItemAt(intI).getAbsolutePath Next 'make and display the results dashboard showDashBoard End sub private function getCodeForTest () '/** '

Generates all the code required for a test. The String 'returned from this method should be written to the .mts file 'in the framework default qtp test which can be found in location '"%vdk_home%\uftvdkframework\bin\defaultTest\Action1\Script.mts".

' '@author Jason Ely '@version 1 '@since 1 '@returnType String '/ dim objString : set objString = new clsString strCoreClassPath = "uftvdkframework.core" objString.concat("ExecuteGlobal CreateObject(").concat(chr(34)).concat("wsh.vdk.initialize").concat(chr(34)).concat(").initialize").concat(vbcrlf).concat vbcrlf objString.concat("import ").concat(chr(34)).concat(strCoreClassPath & ".clsVdkUftFrameworkTestDriver").concat(chr(34)).concat(vbCrlf).concat vbcrlf objString.concat("set obj = new clsVdkUftFrameworkTestDriver").concat vbcrlf objString.concat "obj.executeTest" getCodeForTest = objString.toString end function private Sub ExecuteTest(byval strTestDatatablePath) '/** '

Private helper method which executes an individual test. Method 'will be called from the {@link #run} method.

' '@author Jason Ely '@since 1 '/ 'Open our default UFT test in read only mode objQtApp.Open getBasePath() & "\uftVdkFramework\bin\defaultTest" , true ' Open the test in read only mode mode 'set the test datatable path for the test to run. 'This environment variable will be consumed 'by the test driver in the default UFT test objQtApp.Test.Environment("uftVdkFramework_test_datatable_path") = strTestDatatablePath 'The below code is to generate a run results file path 'As far as possible we seek to retain the original framework directory structure strTestPath = getBasePath() & "\uftvdkframework\tests" 'the run results directory Set objTestFile = new clsFile.init(strTestDatatablePath) strRelativePath = objTestFile.getParentPath() & "\" & objTestFile.getBaseName() & "_results" strRelativePath = replace(strRelativePath, strTestPath, "",1 ,-1, vbTextCompare) strNewPath = getBasePath() & "\results" & strRelativePath call New clsFile.init(strNewPath).mkdirs 'create the run results object so that we can specify where results must be saved. Set qtResultsOpt = CreateObject("QuickTest.RunResultsOptions") ' Create the Run Results Options object qtResultsOpt.ResultsLocation = strNewPath 'run the test objQtApp.Test.Run qtResultsOpt 'This test is done. Close the test objQtApp.Test.Close 'add the result to dashboard result objThisDashboard.addResult strNewPath, StrComp(objQtApp.Test.LastRunResults.Status, "passed", vbTextCompare) End Sub public Sub configureTest '/** '

The framework ships with a default UFT test template. This template is an empty test 'with standard default configuration. When this method is called, then this default test 'is configured according to the configuration specifications as defined by the framework.

' '

For the duration of this run, the default test is configured only once. Any attempt to 'call this method multiple times will result in no action.

' '@author Jason Ely '@since 1 '/ if not hasConfiguredTest then 'if this test hasn't been configured yet 'overwrite the code in the local test for default framework UFT test call objFileUtils.writeToFile(getBasePath() & "\uftVdkFramework\bin\defaultTest\Action1\Script.mts", getCodeForTest()) 'open the test in UFT 'This will Open a script 'Open the test in write mode mode as we need to save our config changes objQtApp.Open getBasePath() & "\uftVdkFramework\bin\defaultTest" , false 'congfigure the test configureOptions configureRunOptions configureFunctionLibraries configureObjectRepositories configureRecoveryScenario 'save the test objQtApp.test.Save 'close the test objQtApp.Test.Close end if End Sub private sub configureOptions() '/** '

Configures the general options for UFT according to the framework specification. Navigate to UFT->Tools->Options to see 'possible configurations.

' '@author Jason Ely '@since 1 '/ 'configure folders where qtp searches for files 'some of the resources in the VDK uses relative paths so this is important 'We will add VDK_HOMEe because all our framework files as well as recoveries, function libraries, object 'repositories are relative to VDK_HOME objQtApp.Folders.RemoveAll objQtApp.Folders.Add vdk.getVdkHome() 'Set QuickTest run options 'objQtApp.Options.Run.ImageCaptureForTestResults = "OnError" objQtApp.Options.Run.ImageCaptureForTestResults = "Always" objQtApp.Options.Run.RunMode = "Fast" objQtApp.Options.Run.ViewResults = false end sub private sub configureRunOptions() '/** '

Configures the run options for all our tests. The run options can be found on the run tab 'of the test settings in UFT.

' '@author Jason Ely '@since 1 '/ 'Instruct QuickTest to perform next step when error occurs. 'The recovery will kick in so we don't have to worry that the test will run indefinitely0. objQtApp.Test.Settings.Run.OnError = "NextStep" 'run one iteration. The test driver will iterate all the rows on the test_data sheet objQtApp.Test.Settings.Run.IterationMode = "OneIteration" end sub private sub configureFunctionLibraries() '/** '

A call to this procedure removes existing function libraries and then adds function 'libraries contained within directory %vdk_home%\uftvdkframework\FunctionLibraries

' '

A function library is any file that ends with file extension "vbs" or "qlf"

' '@author Jason Ely '/ 'attach all our function libraries set objLib = objQtApp.Test.Settings.Resources.Libraries objLib.RemoveAll 'remove all current libraries if any. 'add files in default tests directory set objDefaultLibraryDirectory = new clsFile.init(getBasePath() & "\uftVdkFramework\FunctionLibraries") set objFiles = getFiles(objDefaultLibraryDirectory, array("vbs", "qlf")) For intI = 0 To objFiles.getCount() - 1 'load the library If objQtApp.Test.Settings.Resources.Libraries.Find(objFiles.getItemAt(intI).getAbsolutePath()) = -1 Then ' If library is not already added objQtApp.Test.Settings.Resources.Libraries.add objFiles.getItemAt(intI).getAbsolutePath() end if next end sub private sub configureObjectRepositories() '/** '

A call to this procedure configures all the object repositories for this 'run. All object respositories with file extension "tsr" in directory '"%vdk_home%\uftvdkframework\ObjectRepository" are added to the current test.

'

' '@author Jason Ely '@since 1 '/ Dim qtpRepositories, objDefaultRepositoryDirectory, objFiles, intI 'remove all repositories from the collection Set qtpRepositories = objQtApp.Test.Actions(1).ObjectRepositories qtpRepositories.removeAll 'get all tsr files in %vdk_home%\uftvdkframework\ObjectRepository set objDefaultRepositoryDirectory = new clsFile.init(getBasePath() & "\uftVdkFramework\ObjectRepository") set objFiles = getFiles(objDefaultRepositoryDirectory, array("tsr")) For intI = 0 To objFiles.getCount() - 1 qtpRepositories.Add objFiles.getItemAt(intI).getAbsolutePath() next end sub private sub configureRecoveryScenario() '/** '

A call to this procedure configures all the recovery scenarios for this 'run. All recovery scenarios with file extension "qrs" in directory '"%vdk_home%\uftvdkframework\recovery" are attached.

'

' '@author Jason Ely '@since 1 '/ Dim blnHasRecovery, qtTestRecovery, objDefaultRecoveryDirectory, objFiles, intIndex, intI blnHasRecovery = false 'set recovery for this test. First remove all existing recoveries Set qtpTestRecovery = objQtApp.Test.Settings.Recovery qtpTestRecovery.RemoveAll ' Remove them 'add files in default tests directory set objDefaultRecoveryDirectory = new clsFile.init(getBasePath() & "\uftVdkFramework\Recovery") 'We'll use a queue to recursively traverse set objFiles = getFiles(objDefaultRecoveryDirectory, array("qrs")) For intI = 0 To objFiles.getCount() - 1 strFile = objFiles.getItemAt(intI).getAbsolutePath qtpTestRecovery.Add objFiles.getItemAt(intI).getAbsolutePath, "Recovery Scenario " & CStr(intI) blnHasRecovery = true next if not blnHasRecovery then 'load the default recovery qtpTestRecovery.Add getBasePath() & "\uftVdkFramework\bin\defaultRecovery.qrs", "VdkFrameworkDefaultRecoveryScenario" end if 'enable all recovery For intIndex = 1 To qtpTestRecovery.Count 'Enable all scenarios and Iterate the scenarios qtpTestRecovery.Item(intIndex).Enabled = True ' Enable each Recovery Scenario (Note: the 'Item' property is default and can be omitted) Next end sub private sub showDashBoard() '/** '

Private helper method that constructs an html dashboard 'with information on the current run. A call to this method should only 'made after the run has executed. Any call to this method before the 'driver has run will be ignored.

' '@author Jason Ely '@since 1 '/ objThisDashboard.display end Sub Private function getFiles(objFileDirectory, arrFileExtensionToGet) '/** '

Private helper methood to recurse a directory and retrieve all the 'files that have the pre-requisite file extension.

' '@author Jason Ely '@since 1 '@param objFileDirectory an object of type {@link vdk.io.clsFile} that points to a directory. '@param strFileExtensionToGet the extension to filter files on. Don't add the dot, just the file extension e.g., vbs, txt, docx '@returnType vdk.lang.collections.clsArrayList '@return each item in the list will be of type {@link vdk.io.clsFile} with each file item matches the file extension supplied. 'In the case where the directory supplied is invalid or no file matches the criteria, an empty list is returned. '/ Set getFiles = New clsArrayList 'add file extensions to a arraylist because then we can use the contains method instead of looping the array all the time Set objFileExtensionArray = New clsArrayList For Each strItem In arrFileExtensionToGet objFileExtensionArray.add LCase(strItem) Next 'We'll use a queue to recursively traverse set objQueue = new clsVdkQueue if objFileDirectory.exists and objFileDirectory.isDirectory then objQueue.push objFileDirectory end if 'recurse all files while objQueue.getCount() > 0 dim objFile, intI, objListFiles set objFile = objQueue.pop() if objFile.isDirectory() then set objListFiles = objFile.listFiles for intI = 0 to objListFiles.getCount() - 1 objQueue.push objListFiles.getItemAt(intI) next elseif objFile.isFile() Then if objFileExtensionArray.contains(LCase(objFile.getExtension)) Then getFiles.Add objFile end if end if wend End function End Class

Test Controller

The test controller lives within a UFT/QTP test. Its the code that actually does the automating within the context of UFT. When the main framework controller generates a test, it generates 4 lines of code, saves that code into an empty UFT test and once successfully saved, executes the test.

							'the code below is the UFT entry point intro a test.
							'This is what UFT runs
							'The below code is generated by the UFTFrameworkDriver and then saved in a default UFT test

							'start vdk
							ExecuteGlobal CreateObject("wsh.vdk.initialize").initialize

							'import test controller class
							import "uftvdkframework.core.clsVdkUftFrameworkTestDriver"

							'create test controller object and execute test
							set obj = new clsVdkUftFrameworkTestDriver
							obj.executeTest
							

Once again, the 4 lines of code are driven by the VDK. The entry point for code execution is the class clsVdkUftFrameworkTestDriver which lives in location '%vdk_home%\uftVdkFramework\core\clsVdkUftFrameworkTestDriver.vbs'

The test controller knows which test to execute because the root framework controller has assigned an environment variable which contains the absolute path to the excel test which is to be executed. The controller within UFT will consume this environment variable and so execute a test. Before the test is executed, the excel file is imported as a datatable.

								'The below code is the actual test driver that lives in UFT.
								'This controllers job is to generate a runtime data model.
								'Based on the data model, this class will generate runtime 
								'code for each step and sub step and then pump it 
								'into UFT.
								'
								import "vdk.lang.clsString"
								import "vdk.lang.collections.clsArrayList"
								import "vdk.io.clsFolderUtils"
								import "vdk.io.clsFile"
								import "uftvdkframework.core.clsVdkUftFrameworkTestDataModel"

								Class clsVdkUftFrameworkTestDriver
									'/**
									'

This is the root driver/controller for a test. A test is defined as any excel workbook 'that conforms to the framework specification. To see a sample framework excel 'template, see file "sample_uft_datatable.xls" in bin folder.

' '

This controller acts as an enforcer for our UFT framework business logic. If any 'incoming test satisfies the framework requirements, then this 'controller' will 'execute every step in sequence in a excel/datatable test.

' '

It is assumed that this class will only be run in the UFT/QTP IDE. If you run this 'in any WSH host other than UFT, this will fail.

' '@author Jason Ely '@version 1 '@since 1 '/ private objTestDataModel private blnHasExecuted private objRegEx private strTestDataTablePathEnvironmentVariableName 'as the name of the environment variable that will be set by the parent driver public Sub class_initialize() '/** '

Default construction initialises the test driver.

' '@author Jason Ely '@version 1 '@since 1 '/ 'load the datatable strTestDataTablePathEnvironmentVariableName = "uftVdkFramework_test_datatable_path" datatable.Import Environment(strTestDataTablePathEnvironmentVariableName) set objTestDataModel = new clsVdkUftFrameworkTestDataModel blnHasExecuted = false Set objRegEx = New RegExp objRegEx.IgnoreCase = True objRegEx.Global = True End Sub public function executeTest() '/** '

A call to this method executes the test. The test will execute 'under the following conditions:

' '
    '
  1. The test has not executed already. Any attempt to call this method multiple 'times will result in no action being taken.
  2. '
  3. The datamodel passed to this test is valid. If the data model is invalid, then the 'data model errors will be written to the UFT results and the test will exit.
  4. '
' '@author Jason Ely '@version 1 '@since 1 '@returnType boolean '@return true if the test executed, false otherwise. '/ executeTest = false if not hasExecuted() then dim intI if objTestDataModel.isDataModelValid then for intI = 0 to objTestDataModel.getStepCount() - 1 executeStep intI + 1, objTestDataModel.getSteps().getItemAt(intI) next executeTest = true else for intI = 0 to objTestDataModel.getTestDataErrors().getCount() - 1 reporter.ReportEvent micFail, "Test Data Error", objTestDataModel.getTestDataErrors().getItemAt(intI) next 'exit the uft test. Datasheet is not valid so can't execute ExitTest end if blnHasExecuted = true end if end function private sub executeStep (byval intStepNumber, objStep) '/** '

Private helper method that executes a data model step.

' '@author Jason Ely '@version 1 '@since 1 '@param intStepNumber the step number for this step. This step number will 'be written to the UFT results sheet and every sub step under this step will be a child of this step number. '@param objStep an object of type {@link uftvdkframework.core.clsVdkUftFrameworkTestStepModel} '/ 'report the step reporter.ReportEvent micDone, "Step " & intStepNumber & ": " & objStep.getStepDescription, objStep.getStepExpectedResult 'this is the pattern for global environment variables in the datasheets 'we'll need to replace this pattern with vbscript UFT compliant code when executing. objRegEx.Pattern = "\$\{\s*([\w]+)\s*\}" Dim intI, objSubStep, objCodeString for intI = 0 to objStep.getSubStepCount() - 1 set objSubStep = objStep.getSubStepAtIndex(intI) set objCodeString = new clsString 'replace any instances of environment variable in object text strObject = objRegEx.Replace (objSubStep.getObject, "Environment(" & Chr(34) & "$1" & Chr(34) & ")") select case lcase(objSubStep.getAction) case "object" objCodeString.concat strObject case "new_object" objCodeString.concat("new ").concat strObject case "procedure" end select 'method if lcase(objSubStep.getAction) = lcase("procedure") then objCodeString.concat (objSubStep.getMethod()) ElseIf new clsString.init(objSubStep.getMethod()).supertrim().getLength() > 0 Then objCodeString.concat (".").concat objSubStep.getMethod() end if 'a space just in case objCodeString.concat " " 'arguments 'replace any environment variables if needed strArgs = objRegEx.Replace (objSubStep.getArguments, "Environment(" & Chr(34) & "$1" & Chr(34) & ")") arrArgs = split(strArgs, ",") set objArgString = new clsString for each arg in arrArgs if isnumeric(arg) or vartype(arg) = 11 or new clsString.init(arg).startsWithIgnoreCase("Environment(") then objArgString.concat arg else 'its a literal string so slap some double quotes around it objArgString.concat (chr(34)).concat(arg).concat chr(34) end if objArgString.concat " ," next if ubound (arrArgs) >= 0 then if objArgString.endsWith(",") then set objArgString = objArgString.subString(0, objArgString.getLength() - 2) end if End if 'output 'depending on whether we have output or not we might need to append brackets to the arguments if new clsString.init(objSubStep.getOutput()).superTrim().getLength() <> 0 then if new clsString.init(objSubStep.getMethod()).supertrim().getLength() > 0 then objArgString.preconcat ("(").concat ")" End if objCodeString.concat objArgString.toString objCodeString.preconcat("Environment(" & chr(34) & objSubStep.getOutput() & chr(34) & ") = ") else objCodeString.concat(" ").concat objArgString.toString end if 'execute 'if there is an error in the code string, we'll catch it via recovery scenario. execute objCodeString.toString next end sub public function hasExecuted() '/** '

Tests whether this driver has been previously executed already.

' '@author Jason Ely '@version 1 '@since 1 '@returnType boolean '@param true if driver has executed, false otherwise '/ hasExecuted = blnHasExecuted end function End Class

When the test controller runs, the first thing that it attempts to do is build a data model for our excel test (the M part of our MVC pattern) from the imported excel datatable. However, before any attempt to build a model is made, the excel test file is interrogated for compliance via a unit test type assertion matrix driven by VDK reflexive programming. The class that does the testing is '%vdk_home%\uftvdkframework\core\clsVdkUftFrameworkDatasheetChecker.vbs'. If the datasheet(s) are valid according to the tests, the data model is loaded. If not, the data model is discarded.

							'below code is the datamodel class
							'When building OO programs, you enforce contractual compliance via interfaces
							'Vbscript does not support interfaces so we have to be a little bit clever about how we can build a substitute.
							'The datamodel checker will be instantiated from the test controller. The datamodel class will then reflexively 
							'execute every method in datachecker class that starts with text 'test_'. After all methods are executed, it goes 
							'and fetches an error collection. If the error collection is empty, tests passed.
							'See method 'CheckDataModel' to see how we reflexively check our model

							import "vdk.lang.collections.clsArrayList"
							import "uftvdkframework.core.clsVdkUftFrameworkTestSubStepModel"
							import "uftvdkframework.core.clsVdkUftFrameworkTestStepModel"
							import "vdk.lang.reflect.clsVdkReflection"
							import "uftvdkframework.core.clsVdkUftFrameworkDatasheetChecker"



							Class clsVdkUftFrameworkTestDataModel
								'/**
								'

Class wraps the test data supplied within in a UFT datasheet and then creates a 'data model for usage in our MVC pattern.

' '

Before the data model is loaded, it will be checked using the 'datamodel checker class {@link uftvdkframework.core.clsVdkUftFrameworkDatasheetChecker}. 'Should the supplied data model not pass teh requirements as laid out by the 'data checker, then the model is not loaded. A call to method {@link #isDataModelValid} 'should always be called to check data integrity.

' '

The calling class should always make the decision on what to do with a corrupt 'data model.

' '

A note on usage of Excel Interfaces

' '

While it is possible to extract the data using an Excel object, keep in 'mind that you are not guaranteed to actually have microsoft excel installed 'on your client machine. This class makes the assumption that only UFT and 'VDK is installed on the client machine hence we use UFT to interrogate the 'excel data.

' '@author Jason Ely '@version 1 '@since 1 '/ 'collections to hold step and test data errors private objStepCollection private objDataChecker 'column label names for QTP data sheet Private strStepNumberColumnLabel Private strSubStepNumberColumnLabel private strStepDescriptionColumnLabel private strExpectedResultColumnLabel private strActionColumnLabel private strMethodColumnLabel private strArgumentsColumnLabel private strOutputColumnLabel Private strObjectColumnLabel Private intStartRow 'as the datatable start row from which to start reading test data 'the sheet names private strThisTestDataSheetName private strThisGlobalDataSheetName public sub class_initialize '/** '

Default constructor initialises the test data model.

' '

A call to this method will check the integrity of the uft data sheet. Should the datafile not contain 'the necessary data elements or have an error, the data model will not be parsed.

' '@author Jason Ely '@version 1 '@since 1 '/ set objStepCollection = new clsArrayList 'the sheet names strThisTestDataSheetName = "test_data" strThisGlobalDataSheetName = "global" 'indexes of the columns in our data sheet intStepNumberColumn = 1 intSubStepNumberColumn = 2 intStepDescriptionColumn = 3 intStepExpectedResultColumn = 4 intActionColumn = 5 intMethodColumn = 6 intValueColumn = 7 intOutputColumn = 8 intStartRow = 1 'column label names strStepNumberColumnLabel = "Step_number" strStepDescriptionColumnLabel = "Description" strExpectedResultColumnLabel = "Expected_Result" strActionColumnLabel = "Action" strMethodColumnLabel = "Method" strArgumentsColumnLabel = "Arguments" strOutputColumnLabel = "Output" strObjectColumnLabel = "Object" loadDataModelFromUft End sub private function checkDataModel() '/** '

Method will interrogate our datatable to make sure it complies with our framework 'standard.

' '@author Jason Ely '@since 1 '@returnType boolean '@return true if model is valid, false otherwise '@see uftvdkframework.core.clsVdkUftFrameworkDatasheetChecker '/ checkDataModel = false dim objDataChecker, reflect, objProcs, intI, strExecute set reflect = new clsVdkReflection.init("uftvdkframework.core.clsVdkUftFrameworkDatasheetChecker") 'the below objName is linked to the execute statement below. DOn't change this set objDataChecker = reflect.getClasses().getItemAt(0).newInstance set objProcs = reflect.getClasses().getItemAt(0).getProcedures() for intI = 0 to objProcs.getCount() - 1 set objProc = objProcs.getItemAt(intI) if objProc.isPublic and objProc.isSub and objProc.getArgumentCount() = 0 and new clsString.init(objProc.getName).startsWithIgnoreCase("test_") then 'fire the method strExecute = "objDataChecker." & objProc.getName execute strExecute end if next if objDataChecker.getErrors().getCount() = 0 then checkDataModel = true end if end function private sub loadDataModelFromUft() '/** '

Parses the UFT datasheet and then extracts the data into a Framework data model.

' '

Before the parsing of the UFT datasheet, a call to method {@link #checkDataModel} will be made. If the 'datasheet IS NOT VALID, then this method exits immediately and the data is not parsed.

' '@author Jason Ely '/ if not checkDataModel then 'check the data in the sheet and record errors (if any) exit sub end if dim intI, objCurrentStep, isSTep set objCurrentStep = nothing For intI = intStartRow To DataTable.GetSheet(strThisTestDataSheetName).GetRowCount() Datatable.SetCurrentRow intI 'check if its a step. If yes, then report step isSTep = isRowAStep(intI) If isStep Then set objCurrentStep = new clsVdkUftFrameworkTestStepModel.init(Datatable(strStepDescriptionColumnLabel, dtLocalSheet), Datatable(strExpectedResultColumnLabel, dtLocalSheet)) objStepCollection.add objCurrentStep else 'this is a sub step. Create a sub step and add to the step data model Set objSubStep = new clsVdkUftFrameworkTestSubStepModel.init(Datatable(strStepDescriptionColumnLabel, dtLocalSheet), _ Datatable(strExpectedResultColumnLabel, dtLocalSheet), _ Datatable(strActionColumnLabel, dtLocalSheet), _ Datatable(strObjectColumnLabel, dtLocalSheet), _ Datatable(strMethodColumnLabel, dtLocalSheet), _ Datatable(strArgumentsColumnLabel, dtLocalSheet), _ Datatable(strOutputColumnLabel, dtLocalSheet) _ ) objCurrentStep.addSubStep objSubStep End If Next end sub public function getSteps() '/** '

Returns all the data steps in this data model.

' '@author Jason Ely '@returnType vdk.lang.collections.clsArrayList '@return a collection of objects where each element in the list is of type {@link uftVdkFramework.core.clsVdkUftFrameworkTestStepModel} '/ Set getSteps = objStepCollection end function public function getStepCount() '/** '

Returns the step count in the step collection.

' '@author Jason Ely '@returnType int '/ getStepCount = objStepCollection.getCount() end function public function getTestDataErrors() '/** '

If there are data errors in the datasheet, then each error will be recorded as 'a string value which describes the error. Should there be no errors, 'then this collection is empty.

' '@author Jason Ely '@returnType vdk.lang.collections.clsArraylist '@return a collection where each element is of type String '/ Set getTestDataErrors = objDataChecker.getErrors() end function public function isDataModelValid() '/** '

Tests whether the current UFT data sheet is a valid data model.

' '@author Jason Ely '@returnType boolean '/ isDataModelValid = (objDataChecker.getErrors().getCount = 0) end function Private Function isRowAStep( byval intIndex) '/** '

Private helper method to determine if a datasheet row is a step or sub step

' '@author Jason Ely '@returnType boolean '/ dim intCurrentRow intCurrentRow = datatable.GetSheet(strThisTestDataSheetName).GetCurrentRow datatable.setCurrentRow intIndex If len(trim(Datatable(strStepNumberColumnLabel, dtLocalSheet))) > 0 Then isRowAStep = true else isRowAStep = false End If 'set it back to what it was datatable.setCurrentRow intCurrentRow End Function End Class

Once the data model is loaded or discarded, the test controller then makes a decision to run the test based on whether the data model is valid.

								'below code is the datamodel checker class
								'Notice all the 'subs' that start with 'test_'
								'If you wanted to, you could add more test subs.
								'if the method name starts with 'test_' it will be executed as part of the datatable check process
								
								
								import "vdk.lang.collections.clsArrayList"

							Class clsVdkUftFrameworkDatasheetChecker
								'/**
								'

Class acts as a pseudo unit test by using an assertion matrix 'to test the integrity of a given UFT datatable.

' '

This class will contain numerous methods which can be used to test for various 'datatable integrity issues. Every public sub that starts with text 'test_' is a unit test 'which tests a specific element of the datatable. Should an error be encountered, the given 'method will add string is added to the the errors collection which defines the issue.

' '

The ideal way to execute this class and the tests in them is to reflexively 'load the class and then fire each method in the class that starts with text 'test_'. 'When all methods have been fired, you can then check the test errors collection to see 'if any issues exist.

' '

Additional unit tests can be added to this class without disturbing the calling 'code and in so doing, produce unnecessary code overhead and maintenance. To add 'an additional test, create a new function and make sure that the function name starts 'with "test_".

' '@author Jason Ely '@version 1 '@since 1 '/ private objTestDataSheetErrors 'column label names for QTP data sheet Private strStepNumberColumnLabel Private strSubStepNumberColumnLabel private strStepDescriptionColumnLabel private strExpectedResultColumnLabel private strActionColumnLabel private strMethodColumnLabel private strArgumentsColumnLabel private strOutputColumnLabel private strObjectColumnLabel Private intStartRow 'as the datatable start row from which to start reading test data private objMandatoryColumnNamesInTestDataSheet private strThisTestDataSheetName private strThisGlobalDataSheetName public sub class_initialize '/** '

Default constructor initialises the test data model data checker.

' '@author Jason Ely '@version 1 '@since 1 '/ Set objTestDataSheetErrors = new clsArrayList 'the sheet names that must be present strThisTestDataSheetName = "test_data" strThisGlobalDataSheetName = "global" 'indexes of the columns in our data sheet intStepNumberColumn = 1 intStepDescriptionColumn = 2 intStepExpectedResultColumn = 3 intActionColumn = 4 intMethodColumn = 5 intValueColumn = 6 intOutputColumn = 7 intStartRow = 2 'column label names that must be present strStepNumberColumnLabel = "Step_number" strStepDescriptionColumnLabel = "Description" strExpectedResultColumnLabel = "Expected_Result" strActionColumnLabel = "Action" strMethodColumnLabel = "Method" strArgumentsColumnLabel = "Arguments" strOutputColumnLabel = "Output" strObjectColumnLabel = "Object" 'add all the mandatory column names to an arraylist so we can iterate them set objMandatoryColumnNamesInTestDataSheet = new clsArrayList objMandatoryColumnNamesInTestDataSheet.add strStepNumberColumnLabel objMandatoryColumnNamesInTestDataSheet.add strStepDescriptionColumnLabel objMandatoryColumnNamesInTestDataSheet.add strExpectedResultColumnLabel objMandatoryColumnNamesInTestDataSheet.add strActionColumnLabel objMandatoryColumnNamesInTestDataSheet.add strMethodColumnLabel objMandatoryColumnNamesInTestDataSheet.add strArgumentsColumnLabel objMandatoryColumnNamesInTestDataSheet.add strOutputColumnLabel objMandatoryColumnNamesInTestDataSheet.add strObjectColumnLabel End sub public sub test_sheets() '/** '

Method checks that the required sheets exist in the datatable. 'Should any of the mandatory sheets not exist, an error is written to the error collection.

' '@author Jason Ely '/ if not dataSheetExists(strThisGlobalDataSheetName) then objTestDataSheetErrors.add "Datasheet '" & strThisGlobalDataSheetName & "' does not exist." end if if not dataSheetExists(strThisTestDataSheetName) then objTestDataSheetErrors.add "Datasheet '" & strThisTestDataSheetName & "' does not exist." end if end sub public sub test_data_columns() '/** '

Method checks that the necessary columns exist in the test data sheet. 'Should any of the mandatory solumns not exist, an error is written to the error collection.

' '@author Jason Ely '/ if not dataSheetExists(strThisTestDataSheetName) then exit sub end if for i = 0 to objMandatoryColumnNamesInTestDataSheet.getCount() - 1 if not sheetHasParameter(strThisTestDataSheetName, objMandatoryColumnNamesInTestDataSheet.getItemAt(i)) then objTestDataSheetErrors.add "Column '" & objMandatoryColumnNamesInTestDataSheet.getItemAt(i) & "' does not exist in sheet: '" & strThisTestDataSheetName & "." end if next end sub public sub test_steps() '/** '

Method checks that there is at least 1 step in the datatable. If there is no tep, 'an error is written to the error collection.

' '@author Jason Ely '/ if not dataSheetExists(strThisTestDataSheetName) then exit sub end if datatable.SetCurrentRow 1 if len(trim(Datatable(strStepNumberColumnLabel, dtLocalSheet))) = 0 then objTestDataSheetErrors.add "The first row on sheet '" & strThisTestDataSheetName & "' must be a step." end if end sub public function getErrors() '/** '

Returns an arraylist collection where each item is a string value that represents 'an error message detailing whats wrong with a fiven datatable. All errors represented in this 'collection must be resolved be a test will execute.

' '

If there are no errors in the datasheet, then this method returns an empty list.

' '@author Jason Ely '@returnType vdk.lang.collections.arraylist '@return will return a list where each item is of type string. Each string value represents a error message '/ set getErrors = objTestDataSheetErrors end function private function dataSheetExists(byval strSheetname) '/** '

Private helper method to check if a datasheet exists.

' '@author Jason Ely '@returnType vdk.lang.collections.arraylist '@param strSheetname the name of the sheet that you want to check. Sheet name is not case sensitive '@return true if it ecists, false otherwise '/ dataSheetExists = false 'Disable Error Reporting in QTP On Error Resume Next err.clear 'Check for the existence of a sheet inside QTP DataTable.GetSheet(strSheetname) If Err.Number = 0 Then dataSheetExists = true End If 'Enable Error Reporting in QTP On Error Goto 0 end function private Function sheetHasParameter(byval strSheetName, byval strParameterName) '/** '

Private helper method to check if a datasheet has a particular paramater.

' '@author Jason Ely '@returnType vdk.lang.collections.arraylist '@param strSheetname the name of the sheet that you want to check. Sheet name is not case sensitive '@param strParameterName the name of the parameter that you want to check. '@return true if it ecists, false otherwise '/ sheetHasParameter = false if not dataSheetExists (strSheetName) then exit function end if For i = 1 to datatable.GetSheet(strSheetName).GetParameterCount if strcomp(strParameterName, datatable.GetSheet(strSheetName).GetParameter(i).Name, vbtextcompare) = 0 then sheetHasParameter = true Exit function end if Next End Function End Class

Within the context of the test controller (see method 'executeStep' in class clsVdkUftFrameworkTestDriver), the controller generates runtime code from the data model. For every step and sub step in the model, that runtime code is then executed via the vbscript 'Execute' keyword. Should any runtime generated code fail to execute, then the framework assumes (and rightly so) that the default recovery scenario will kick in and the test will terminate with a fail written to the result report.

The probability of runtime code generated code errors are extremely high because no code can anticipate a users actions or intents. However, this is where the datatable checker class becomes so important. You as a programmer know how your input data must look. You can write additional data check methods to interogate the data coming in to check that it is valid. For instance, a popular data mistake is leading or trailing spaces. You could write a procedure that checks each cell in the excel document for spaces and then raise an error if there is.

Test Excel Files

Tests in the framework are driven by an excel workbooks that mimics a UFT datatable. To see samples of these excel documents, see all test files in directory '%vdk_home%\uftVdkFramework\tests\google'.

Sample Excel Test Workbook

Sheet Name :- Global

Sheet Name :- test_data
Step_number Description Expected_Result Action Object Method Arguments Output
1 Navigate to google home Google home opened in browser          
      object SystemUtil CloseProcessByName iexplore.exe  
      object SystemUtil run iexplore.exe  
      object browser("Browser") sync    
      object browser("Browser") Navigate www.google.com  
      object browser("Browser").Page("googleHome").WebEdit("Search") waitproperty disabled, 0, 20000  
2 Check google home page Homepage checked          
      object browser("Browser").Page("googleHome").WebEdit("Search") checkproperty disabled, 0  
      object browser("Browser").Page("googleHome").WebEdit("Search") checkproperty max length, 2048  
      object browser("Browser").Page("googleHome").WebButton("GoogleSearch") checkproperty disabled, 0  
      object browser("Browser").Page("googleHome").WebButton("GoogleSearch") checkproperty acc_name,Google Search  
3 Close browser            
      object SystemUtil CloseProcessByName iexplore.exe  



The rules for our workbooks are:
  • The workbook must have 2 sheets namely 'global' and 'test_data'
  • The 'test_data' sheet must have data columns "Step_number", "Description", "Expected_Result", "Action", "Object", "Method", "Arguments", "Output"
  • The 'test_data' sheet must have at least 1 step

If you look at the datachecker class, you'll actually notice that these requirement are enforced via code.

Document Test Steps

Test steps are documented using a standard ALM test as a template. In an ALM test, you'll usually have columns step number, description, expected result. Our framework uses that same model as a base allowing a user to export an existing ALM test to Excel and then building on that test. Any given test will consist of a step followed by 1 to many sub steps (see sample tests)

Explanations for the excel columns are as follows:

  • Step_number: A number indicating the step number in the test sequence
  • Description: A description for the row to be executed.
  • Expected_Result: Text outlining the expected result for the step
  • Action: a value indicating what action should be taken with our current sub_step. There are currently 3 actions namely: procedure, object, new_object. The value in this step tells the framework what to do when generating runtime code.
  • Object: The object that the action will be performed on. The object is any valid object within the context of the current UFT space and can be a UFT object or a VDK object. However the object must exist and be instantiated. For instance, you could define a UFT object like 'SystemUtil' or a vdk object like 'VDK'
  • Method: a user will define the method name to be executed in this data cell. If an object was defined in the previous step, then you define a procedure name that can be defined on on the object e.g., 'wait' for object 'vdk' or 'CloseProcessByName' for object 'SystemUtil'.
  • Arguments: Specify a comma seperated list of arguments to the method e.g. 'iexplore.exe' for method 'CloseProcessByName' for object 'SystemUtil'
  • Output: Define an environment variable where the value from the object/procedure call will be outputted to. The environment variable output value can be used later in the test.

The test steps column were designed with a very programming-centric point of view to allow a user to write or document steps in a very UFT programming way. If you look at the sample tests in the directory '%vdk_home%\uftVdkFramework\Tests', each sub step looks like UFT code. This approach is intentional for the following reasons:

  • Build tests using a low level code approach
  • Build tests using a Function LIbrary approach (Keyword Driven approach)
  • Build tests using a VDK object approach which allows you to leverage VDK classes

The point of automation frameworks is that you expend alot of effort upfront so that you spend more time building tests later instead of writing code and this data approach does exactly that. See the sample tests to get a feel for how to build tests.

MVC - the 'View' part

The last part of our MVC puzzle is the 'view'. There are 2 views in our framework namely framework result view and test result view. The test result view is the results that UFT generates at the end of a run. In our framework, this is managed by our test controller when we specify a RunResultsOption object. Since UFT takes responsibility for generating this view, we don't have to worry about it in our framework. We do however give the user the ability to access that report from our framework results view.

The framework results view is where we give a summary of the entire framework test run. Here the user can see a high level report as well as drill down to individual low level test reports. The framework Results View is initiated by the Framework Controller but managed by class 'clsVdkUftFrameworkDashboard' found in location '%vdk_home%\vdkUftFramework\core\clsVdkUftFrameworkDashboard.vbs'

The class uses a html templating approach to manage a result data model as well as a view.

								import "vdk.lang.collections.clsVdkMap"
								import "vdk.io.clsFile"
								import "vdk.lang.clsString"
								import "vdk.io.clsFileUtils"

								class clsVdkUftFrameworkDashboard
									'/**
									'

Class manages the result dashboard of the VDK UFT framework. 'From this class you can get a handle on the result dashboard as well as display it.

' '@author Jason Ely '@since 1 '@version 1 '/ private objThisMap private strPathToHtmlReportTemplate private strThisBasePath private strDashboardPath private objFileUtils public sub Class_Initialize '/** '

Constructor initializes class level variables.

' '@author Jason Ely '@since 1 '/ set objThisMap = new clsVdkMap strPathToHtmlReportTemplate = vdk.getVdkHome() & "\uftVdkFramework\bin\HtmlResultsReport\ResultReportTemplate.html" set objFileUtils = new clsFileUtils end sub public function init(byval strBasePath) '/** '

Sets the basepath. The basePath should be passed on from the main framework driver.

' '@author Jason Ely '@since 1 '@param strBasePath a file path pointing to a directory that is the run basepath '/ strThisBasePath = strBasePath strDashboardPath = strThisBasePath & "\dashboard.html" set init = me end function public sub addResult(byval strRunResultsPath, byval blnPassed) '/** '

Adds a run result to the dashboard.

' '@author Jason Ely '@since 1 '@param strRunResultsPath a file path pointing to a directory that is directory for the UFT results assigned to a test '@param blnPassed a boolean value indicating whether the test passed or not '/ objThisMap.put strRunResultsPath, blnPassed end sub sub display() '/** '

Will display the dashboard file. The dashboard file will be opened in the default browser 'on the client.

' '@author Jason Ely '@since 1 '/ makeResults Set objDashboardFile = New clsFile.init(strDashboardPath) If objDashboardFile.exists() Then CreateObject("WScript.Shell").Run objDashboardFile.toUri().toUrl().toString End if end sub private sub makeResults() '/** '

Private helper method to make the dashboard html file

' '@author Jason Ely '@since 1 '/ set objHtmlString = new clsString.init(objFileUtils.getFileText(strPathToHtmlReportTemplate)) set objIndividualResults = new clsString strRunStatus = "passed" intTotalTests= objThisMap.getCount() intTotalPassed= 0 intTotalfailed= 0 set objKeySet = objThisMap.getKeySet for intI = 0 to objKeySet.getCount() - 1 'the individual test results that will be added as rows to the report strkey = objKeySet.getItemAt(intI) blnValue = objThisMap.getKeyValue(objKeySet.getItemAt(intI)) 'run status if blnValue then intTotalPassed = intTotalPassed + 1 else strRunStatus = "Failed" intTotalfailed = intTotalfailed + 1 end if 'this result set objString = new clsString objString.concat("").concat vbCrLf 'test number objString.concat("").concat vbCrLf objString.concat CStr(intI + 1) objString.concat("").concat vbCrLf 'test status objString.concat("").concat vbCrLf if blnValue then objString.concat "Passed" else objString.concat "Failed" objString.concat("").concat vbCrLf set objFile = new clsFile.init(strkey) 'test name objString.concat("").concat vbCrLf strName = split(objFile.getBaseName(), "_")(0) objString.concat strName objString.concat("").concat vbCrLf 'test results objString.concat("").concat vbCrLf strPath = split(objFile.getBaseName(), "_")(0) strResultFile = strKey & "\Report\run_results.html" objString.concat("").concat(strResultFile).concat "" objString.concat("").concat vbCrLf objString.concat("").concat vbCrLf objIndividualResults.concat objString.toString() next 'start replacing all the place holders in the file objHtmlString.replaceText "${run_status}", strRunStatus objHtmlString.replaceText "${total_tests}", intTotalTests objHtmlString.replaceText "${total_passed}", intTotalPassed objHtmlString.replaceText "${total_failed}", intTotalFailed objHtmlString.replaceText "${test_result_rows}", objIndividualResults.toString set objDashBoardFile = new clsFile.init(strDashboardPath) objDashBoardFile.createNewFile objFileUtils.writeToFile strDashboardPath, objHtmlString.toString end sub end class

Conclusion

The downloaded code has numerous comments and readme.txt files that outline my approach and how I went about building this framework. All code that drives this framework is contained in directory "%vdk_home%\uftVdkFramework\core". If the code looks a little complicated, then first go read up on other tutorials that explain VDK OO programming principles.

This tutorial outlines a conceptual approach on how to integrate the VDK and UFT. The framework that I have built is by no means complete. It could do with a lot of panel beating, checks and thorough testing but it suffices to explain a conceptual approach.

If you are interested in using this framework, drop me an email and I will give you some tips on how to make it production ready and stable.