In this article by Meir Bar-Tal and Jonathon Lee Wright, authors of Advanced UFT 12 for Test Engineers Cookbook, we will cover the following recipes:
(For more resources related to this topic, see here.)
This article describes how to use classes in VBScript, along with some very useful and illustrative implementation examples. Classes are a fundamental feature of object-oriented programming languages such as C++, C#, and Java. Classes enable us to encapsulate data fields with the methods and properties that process them, in contrast to global variables and functions scattered in function libraries. UFT already uses classes, such as with reserved objects, and Test Objects are also instances of classes. Although elementary object-oriented features such as inheritance and polymorphism are not supported by VBScript, using classes can be an excellent choice to make your code more structured, better organized, and more efficient and reusable.
In this recipe, you will learn the following:
From the File menu, navigate to New | Function Library…, or use the Alt + Shift + N shortcut. Name the new function library cls.MyFirstClass.vbs and associate it with your test.
We will build our MyFirstClass class from the ground up. There are several steps one must follow to implement a class; they are follows:
Class MyFirstClass
Class MyFirstClass Private m_sMyPrivateString Private m_oMyPrivateObject Public m_iMyPublicInteger End Class
It is a matter of convention to use the prefix m_ for class member fields; and str for string, int for integer, obj for Object, flt for Float, bln for Boolean, chr for Character, lng for Long, and dbl for Double, to distinguish between fields of different data types. For examples of other prefixes to represent additional data types, please refer to sites such as https://en.wikipedia.org/wiki/Hungarian_notation.
Hence, the private fields' m_sMyPrivateString and m_oMyPrivateObject will be accessible only from within the class methods, properties, and subroutines. The public field m_iMyPublicInteger will be accessible from any part of the code that will have a reference to an instance of the MyFirstClass class; and it can also allow partial or full access to private fields, by implementing public properties.
By default, within a script file, VBScript treats as public identifiers such as function and subroutines and any constant or variable defined with Const and Dim respectively,, even if not explicitly defined. When associating function libraries to UFT, one can limit access to specific globally defined identifiers, by preceding them with the keyword Private.
The same applies to members of a class, function, sub, and property. Class fields must be preceded either by Public or Private; the public scope is not assumed by VBScript, and failing to precede a field identifier with its access scope will result in a syntax error. Remember that, by default, VBScript creates a new variable if the explicit option is used at the script level to force explicit declaration of all variables in that script level.
A property is a special case in VBScript; it is the only code structure that allows for a duplicate identifier. That is, one can have a Property Get and a Property Let procedure (or Property Set, to be used when the member field actually is meant to store a reference to an instance of another class) with the same identifier. Note that Property Let and Property Set accept a mandatory argument. For example:
Class MyFirstClass Private m_sMyPrivateString Private m_oMyPrivateObject Public m_iMyPublicInteger Property Get MyPrivateString() MyPrivateString = m_sMyPrivateString End Property Property Let MyPrivateString(ByVal str) m_sMyPrivateString = str End Property Property Get MyPrivateObject() Set MyPrivateObject = m_oMyPrivateObject End Property Private Property Set MyPrivateObject(ByRef obj) Set m_oMyPrivateObject = obj End Property End Class
The public field m_iMyPublicInteger can be accessed from any code block, so defining a getter and setter (as properties are often referred to) for such a field is optional. However, it is a good practice to define fields as private and explicitly provide access through public properties. For fields that are for exclusive use of the class members, one can define the properties as private. In such a case, usually, the setter (Property Let or Property Set) would be defined as private, while the getter (Property Get) would be defined as public. This way, one can prevent other code components from making changes to the internal fields of the class to ensure data integrity and validity.
Class MyFirstClass '… Continued Private Function MyPrivateFunction(ByVal str) MsgBox TypeName(me) & " – Private Func: " & str MyPrivateFunction = 0 End Function Function MyPublicFunction(ByVal str) MsgBox TypeName(me) & " – Public Func: " & str MyPublicFunction = 0 End Function Sub MyPublicSub(ByVal str) MsgBox TypeName(me) & " – Public Sub: " & str End Sub End Class
Keep in mind that subroutines do not return a value. Functions by design should not return a value, but they can be implemented as a subroutine. A better way is to, in any case, have a function return a value that tells the caller if it executed properly or not (usually zero (0) for no errors and one (1) for any fault). Recall that a function that is not explicitly assigned a value function and is not explicitly assigned a value, will return empty, which may cause problems if the caller attempts to evaluate the returned value.
Set obj = New MyFirstClass
The Initialize event takes place at the time the object is created. It is possible to add code that we wish to execute every time an object is created. So, now define the standard private subroutine Class_Initialize, sometimes referred to (albeit only by analogy) as the constructor of the class. If implemented, the code will automatically be executed during the Initialize event. For example, if we add the following code to our class:
Private Sub Class_Initialize MsgBox TypeName(me) & " started" End Sub
Now, every time the Set obj = New MyFirstClass statement is executed, the following message will be displayed:
Set obj = Nothing
The Finalize event takes place at the time when the object is removed from memory. It is possible to add code that we wish to execute, every time an object is disposed of. If so, then define the standard private subroutine Class_Terminate, sometimes referred to (albeit only by analogy) as the destructor of the class. If implemented, the code will automatically be executed during the Finalize event. For example, if we add the following code to our class:
Private Sub Class_Terminate MsgBox TypeName(me) & " ended" End Sub
Now, every time the Set obj = Nothing statement is executed, the following message will be displayed:
'Declare variables Dim obj, var 'Calling MyPublicFunction obj.MyPublicFunction("Hello") 'Retrieving the value of m_sMyPrivateString var = obj.MyPrivateString 'Setting the value of m_sMyPrivateString obj.MyPrivateString = "My String"
Note that the usage of the public members is done by using the syntax obj.<method or property name>, where obj is the variable holding the reference to the object of class. The dot operator (.) after the variable identifier provides access to the public members of the class. Private members can be called only by other members of the class, and this is done like any other regular function call.
Public Default Function MyPublicFunction(ByVal str) MsgBox TypeName(me) & " – Public Func: " & str MyPublicFunction = 0 End Function
Now, the following statements would invoke the MyPublicFunction method implicitly:
Set obj = New MyFirstClass obj("Hello")
This is exactly the same as if we called the MyPublicFunction method explicitly:
Set obj = New MyFirstClass obj.MyPublicFunction("Hello")
Contrary to the usual standard for such functions, a default method or property must be explicitly defined as public.
In general, the reason is linked to the fact that UFT uses a wrapper on top of WSH, which actually executes the VBScript (VBS 5.6) code. Therefore, in order to create instances of such a custom class, we need to use a kind of constructor function that will perform the New operation from the proper memory namespace. Add the following generic constructor to your function library:
Function Constructor(ByVal sClass) Dim obj On Error Resume Next 'Get instance of sClass Execute "Set obj = New [" & sClass & "]" If Err.Number <> 0 Then Set obj = Nothing Reporter.ReportEvent micFail, "Constructor", "Failed
to create an instance of class '" & sClass & "'." End If Set Constructor = obj End Function
We will then instantiate the object from the UFT Action, as follows:
Set obj = Constructor("MyFirstClass")
Consequently, use the object reference in the same fashion as seen in the previous line of code:
obj.MyPublicFunction("Hello")
As mentioned earlier, using the internal public fields, methods, subroutines, and properties is done using a variable followed by the dot operator and the relevant identifier (for example, the function name).
As to the constructor, it accepts a string with the name of a class as an argument, and attempts to create an instance of the given class. By using the Execute command (which performs any string containing valid VBScript syntax), it tries to set the variable obj with a new reference to an instance of sClass. Hence, we can handle any custom class with this function. If the class cannot be instantiated (for instance, because the string passed to the function is faulty, the function library is not associated to the test, or there is a syntax error in the function library), then an error would arise, which is gracefully handled by using the error-handling mechanism, leading to the function returning nothing. Otherwise, the function will return a valid reference to the newly created object.
The following articles at www.advancedqtp.com are part of a wider collection, which also discuss classes and code design in depth:
In this recipe, we will see how to create a class that can be used to execute a search on Google.
From the File menu, navigate to New | Test, and name the new test SimpleSearch. Then create a new function library by navigating to New | Function Library, or use the Alt + Shift + N shortcut. Name the new function library cls.Google.vbs and associate it with your test.
Proceed with the following steps:
Class GoogleSearch Public Function DoSearch(ByVal sQuery) With me.Page_ .WebEdit("name:=q").Set sQuery .WebButton("html id:=gbqfba").Click End With me.Browser_.Sync If me.Results.WaitProperty("visible", 1, 10000) Then DoSearch = GetNumResults() Else DoSearch = 0 Reporter.ReportEvent micFail, TypeName(Me),
"Search did not retrieve results until timeout" End If End Function Public Function GetNumResults() Dim tmpStr tmpStr = me.Results.GetROProperty("innertext") tmpStr = Split(tmpStr, " ") GetNumResults = CLng(tmpStr(1)) 'Assumes the number
is always in the second entry End Function Public Property Get Browser_() Set Browser_ = Browser(me.Title) End Property Public Property Get Page_() Set Page_ = me.Browser_.Page(me.Title) End Property Public Property Get Results() Set Results = me.Page_.WebElement(me.ResultsId) End Property Public Property Get ResultsId() ResultsId = "html id:=resultStats" End Property Public Property Get Title() Title = "title:=.*Google.*" End Property Private Sub Class_Initialize If Not me.Browser_.Exist(0) Then SystemUtil.Run "iexplore.exe",
Environment("OPEN_URL") Reporter.Filter = rfEnableErrorsOnly While Not Browser_.Exist(0) Wait 0, 50 Wend Reporter.Filter = rfEnableAll Reporter.ReportEvent micDone, TypeName(Me),
"Opened browser" Else Reporter.ReportEvent micDone, TypeName(Me),
"Browser was already open" End If End Sub Private Sub Class_Terminate If me.Browser_.Exist(0) Then me.Browser_.Close Reporter.Filter = rfEnableErrorsOnly While me.Browser_.Exist(0) wait 0, 50 Wend Reporter.Filter = rfEnableAll Reporter.ReportEvent micDone, TypeName(Me),
"Closed browser" End If End Sub End Class
Dim oGoogleSearch Dim oListResults Dim oDicSearches Dim iNumResults Dim sMaxResults Dim iMaxResults '--- Create these objects only in the first iteration If Not LCase(TypeName(oListResults)) = "arraylist" Then Set oListResults =
CreateObject("System.Collections.ArrayList") End If If Not LCase(TypeName(oDicSearches)) = "Dictionary" Then Set oDicSearches = CreateObject("Scripting.Dictionary") End If '--- Get a fresh instance of GoogleSearch Set oGoogleSearch = GetGoogleSearch() '--- Get search term from the DataTable for each action
iteration sToSearch = DataTable("Query", dtLocalSheet) iNumResults = oGoogleSearch.DoSearch(sToSearch) '--- Store the results of the current iteration '--- Store the number of results oListResults.Add iNumResults '--- Store the search term attached to the number of
results as key (if not exists) If Not oDicSearches.Exists(iNumResults) Then oDicSearches.Add iNumResults, sToSearch End If 'Last iteration (assuming we always run on all rows), so
perform the comparison between the different searches If CInt(Environment("ActionIteration")) =
DataTable.LocalSheet.GetRowCount Then 'Sort the results ascending oListResults.Sort 'Get the last item which is the largest iMaxResults = oListResults.item(oListResults.Count-1) 'Print to the Output pane for debugging Print iMaxResults 'Get the search text which got the most results sMaxResults = oDicSearches(iMaxResults) 'Report result Reporter.ReportEvent micDone, "Max search", sMaxResults
& " got " & iMaxResults 'Dispose of the objects used Set oListResults = Nothing Set oDicSearches = Nothing Set oGoogleSearch = Nothing End If
The Action takes care to preserve the data collected through the iterations in the array list oListResults and the dictionary oDicSearches. It checks if it reaches the last iteration after each search is done. Upon reaching the last iteration, it analyses the data to decide which term yielded the most results. A more detailed description of the workings of the code can be seen as follows.
First, we create an instance of the GoogleSearch class, and the Class_Initialize subroutine automatically checks if the browser is not already open. If not open, Class_Initialize opens it with the SystemUtil.Run command and waits until it is open at the web address defined in Environment("OPEN_URL").
The Title property always returns the value of the Descriptive Programming (DP) value required to identify the Google browser and page.
The Browser_, Page_, and Results properties always return a reference to the Google browser, page, and WebElement respectively, which hold the text with the search results.
After the browser is open, we retrieve the search term from the local DataTable parameter Query and call the GoogleSearch DoSearch method with the search term string as parameter. The DoSearch method returns a value with the number of results, which are given by the internal method GetNumResults.
In the Action, we store the number itself and add to the dictionary, an entry with this number as the key and the search term as the value.
When the last iteration is reached, an analysis of the results is automatically done by invoking the Sort method of oListResults ArrayList, getting the last item (the greatest), and then retrieving the search term associated with this number from the dictionary; it reports the result.
At last, we dispose off all the objects used, and then the Class_Terminate subroutine automatically checks if the browser is open. If open, then the Class_Terminate subroutine closes the browser.
In this recipe, we will see how to implement a generic Login class. The class captures both, the GUI structure and the processes that are common to all applications with regard to their user access module. It is agnostic to the particular object classes, their technologies, and other identification properties. The class shown here implements the command wrapper design pattern, as it encapsulates a process (Login) with the main default method (Run).
You can use the same function library cls.Google.vbs as in the previous recipe Implementing a simple search class, or create a new one (for instance, cls.Login.vbs) and associate it with your test.
Class Login Private m_wndContainer 'Such as a Browser, Window,
SwfWindow Private m_wndLoginForm 'Such as a Page, Dialog,
SwfWindow Private m_txtUsername 'Such as a WebEdit, WinEdit,
SwfEdit Private m_txtIdField 'Such as a WebEdit, WinEdit,
SwfEdit Private m_txtPassword 'Such as a WebEdit, WinEdit,
SwfEdit Private m_chkRemember 'Such as a WebCheckbox,
WinCheckbox, SwfCheckbox Private m_btnLogin 'Such as a WebEdit, WinEdit,
SwfEdit End Class
These fields define the test objects, which are required for any Login class, and the following fields are used to keep runtime data for the report:
Public Status 'As Integer Public Info 'As String
The Run function is defined as a Default method that accepts a Dictionary as argument. This way, we can pass a set of named arguments, some of which are optional, such as timeout.
Public Default Function Run(ByVal ArgsDic) 'Check if the timeout parameter was passed, if not
assign it 10 seconds If Not ArgsDic.Exists("timeout") Then ArgsDic.Add
"timeout", 10 'Check if the client window exists If Not me.Container.Exist(ArgsDic("timeout")) Then me.Status = micFail me.Info = "Failed to detect login
browser/dialog/window." Exit Function End If 'Set the Username me.Username.Set ArgsDic("Username") 'If the login form has an additional mandatory field If me.IdField.Exist(ArgsDic("timeout")) And
ArgsDic.Exists("IdField") Then me.IdField.Set ArgsDic("IdField") End If 'Set the password me.Password.SetSecure ArgsDic("Password") 'It is a common practice that Login forms have a
checkbox to keep the user logged-in if set ON If me.Remember.Exist(ArgsDic("timeout")) And
ArgsDic.Exists("Remember") Then me.Remember.Set ArgsDic("Remember") End If me.LoginButton.Click End Function
The Run method actually performs the login procedure, setting the username and password, as well as checking or unchecking the Remember Me or Keep me Logged In checkbox according to the argument passed with the ArgsDic dictionary.
The Initialize method accepts Dictionary just like the Run method. However, in this case, we pass the actual TOs with which we wish to perform the login procedure. This way, we can actually utilize the class for any Login form, whatever the technology used to develop it. We can say that the class is technology agnostic. The parent client dialog/browser/window of the objects is retrieved using the GetTOProperty("parent") statement:
Function Initialize(ByVal ArgsDic) Set m_txtUsername = ArgsDic("Username") Set m_txtIdField = ArgsDic("IdField") Set m_txtPassword = ArgsDic("Password") Set m_btnLogin = ArgsDic("LoginButton") Set m_chkRemember = ArgsDic("Remember") 'Get Parents Set m_wndLoginForm =
me.Username.GetTOProperty("parent") Set m_wndContainer =
me.LoginForm.GetTOProperty("parent") End Function
In addition, here you can see the following properties used in the class for better readability:
Property Get Container() Set Container = m_wndContainer End Property Property Get LoginForm() Set LoginForm = m_wndLoginForm End Property Property Get Username() Set Username = m_txtUsername End Property Property Get IdField() Set IdField = m_txtIdField End Property Property Get Password() Set Password = m_txtPassword End Property Property Get Remember() Set Remember = m_chkRemember End Property Property Get LoginButton() Set LoginButton = m_btnLogin End Property Private Sub Class_Initialize() 'TODO: Additional initialization code here End Sub Private Sub Class_Terminate() 'TODO: Additional finalization code here End Sub
We will also add a custom function to override the WinEdit and WinEditor Type methods:
Function WinEditSet(ByRef obj, ByVal str) obj.Type str End Function
This way, no matter which technology the textbox belongs to, the Set method will work seamlessly.
Dim ArgsDic, oLogin 'Register the set method for the WinEdit and WinEditor RegisterUserFunc "WinEdit", "WinEditSet", "Set" RegisterUserFunc "WinEditor", "WinEditSet", "Set" 'Create a Dictionary object Set ArgsDic = CreateObject("Scripting.Dictionary") 'Create a Login object Set oLogin = New Login 'Add the test objects to the Dictionary With ArgsDic .Add "Username",
Browser("Gmail").Page("Gmail").WebEdit("txtUsername") .Add "Password",
Browser("Gmail").Page("Gmail").WebEdit("txtPassword") .Add "Remember",
Browser("Gmail").Page("Gmail")
.WebCheckbox("chkRemember") .Add "LoginButton",
Browser("Gmail").Page("Gmail").WebButton("btnLogin") End With 'Initialize the Login class oLogin.Initialize(ArgsDic) 'Initialize the dictionary to pass the arguments to the
login ArgsDic.RemoveAll With ArgsDic .Add "Username", "myuser" .Add "Password", "myencriptedpassword" .Add "Remember", "OFF" End With 'Login oLogin.Run(ArgsDic) 'or: oLogin(ArgsDic) 'Report result Reporter.ReportEvent oLogin.Status, "Login", "Ended with "
& GetStatusText(oLogin.Status) & "." & vbNewLine &
oStatus.Info 'Dispose of the objects Set oLogin = Nothing Set ArgsDic = Nothing
Here we will not delve into the parts of the code already explained in the Implementing a simple search class recipe. Let's see what we did in this recipe:
What is a function pointer? A function pointer is a variable that stores the memory address of a block of code that is programmed to fulfill a specific function. Function pointers are useful to avoid complex switch case structures. Instead, they support direct access in runtime to previously loaded functions or class methods. This enables the construction of callback functions. A callback is, in essence, an executable code that is passed as an argument to a function. This enables more generic coding, by having lower-level modules calling higher-level functions or subroutines.
This recipe will describe how to implement function pointers in VBScript, a scripting language that does not natively support the usage of pointers.
Create a new function library (for instance, cls.FunctionPointers.vbs) and associate it with your test.
Class WebEditSet Public Default Function Run(ByRef obj, ByVal sText) On Error Resume Next Run = 1 'micFail (pessimistic initialization) Select Case True Case obj.Exist(0) And _ obj.GetROProperty("visible") And _ obj.GetROProperty("enabled") 'Perform the set operation obj.Set(sText) Case Else Reporter.ReportEvent micWarning,
TypeName(me), "Object not available." Exit Function End Select If Err.Number = 0 Then Run = 0 'micPass End If End Function End Class
Dim pFunctiontion Set pFunctiontion = New WebEditSet Reporter.ReportEvent
pFunctiontion(Browser("Google").Page("Google")
.WebEdit("q"), "UFT"), "Set the Google Search WebEdit",
"Done"
The WebEditSet class actually implements the command wrapper design pattern (refer to also the Implementing a generic Login class recipe). This recipe also demonstrates an alternative way of overriding any native UFT TO method without recurring to the RegisterUserFunc method.
First, we create an instance of the WebEditSet class and set the reference to our pFunctiontion variable. Note that the Run method of WebEditSet is declared as a default function, so we can invoke its execution by merely referring to the object reference, as is done with the statement pFunctiontion in the last line of code in the How to do it… section. This way, pFunctiontion actually functions as if it were a function pointer. Let us take a close look at the following line of code, beginning with Reporter.ReportEvent:
Reporter.ReportEvent
pFunc(Browser("Google").Page("Google").WebEdit("q"), "UFT"),
"Set the Google Search WebEdit", "Done"
We call the ReportEvent method of Reporter, and as its first parameter, instead of a status constant such as micPass or micFail, we pass pFunctiontion and the arguments accepted by the Run method (the target TO and its parameter, a string). This way of using the function pointer actually implements a kind of callback. The value returned by the Run method of WebEditSet will determine whether UFT will report a success or failure in regard to the Set operation. It will return through the call invoked by accessing the function pointer.
The following articles are part of a wider collection at www.advancedqtp.com, which also discusses function pointers in depth:
In this article, we learned how to implement a general class; basic concepts and the syntax required by VBScript to implement a class. Then we saw how to implement a simple class that can be used to execute a search on Google and a generic Login class. We also saw how to implement function pointers in VBScript along with various links to the articles that discusses function pointers.
Further resources on this subject: