Thursday, May 24, 2007

We've moved!

Finally we've moved to a different platform.
Check us out at www.AdvancedQTP.com

Interface wrapped objects and VBScript

Today we'll do another round of our (actually – mine) favorite activity - VBScript bashing. Specifically, we're going to talk about VBScript's inability to work with certain Interface objects. This question in SQAForums made me understand that I'm not the only one who deals with these problems, and that maybe it's time to share my workarounds.

Here're some quick facts: While VBScript is quite a backwards programming language (hence the "Script" in VBScript), it can work with sophisticated objects that were written in other languages. So, if you got an object with a COM interface, you can create it via the CreateObject command, and use it with your everyday VBScript commands. Moreover, if you write your own .Net DLL, you can create it within QTP's VBScript context via the DotNetFactory command. This can fool us into believing that life is indeed good, and that there's no object too complicated for VBScript and QTP.

BUT, this turns out to be wrong. There are objects which are too complex for VBScript, and as a result, too complex for QTP. These objects are certain types of Interfaces, which wrap around the application's actual objects and hide them from VBScript. If you have no idea what an interface is, don't worry – it's not very important for our purposes. The bottom line is that very complex applications are prone to work with interfaces, which means that you won't be able to access their internal object structure.

To give an example which bugged me for the better part of a year, if you're trying to automate an ESRI (geographic) based application, and you need the map's coordinates, you're kinda screwed. You can try accessing the properties of the map object all you want; it won't do you any good, because it's an interface, not a "regular" object.

So, how can you know if the object you're trying to access is an interface? You could read the object's documentation, but a much quicker way is trying to access it from QTP. Run a script which can lead you to the object, and pause it. Go to the debug window, and write the path to the run-time object. You'll see that QTP marks it as (Object). But now, try accessing the some internal property of this object (e.g. QTPObject.Object.SuspectROObject.SomeProperty). It doesn't really matter which property, nor if the object even has this property, because you'll see QTP spits out an "Object needed" error. This means that even though QTP knows this is an object, it can't access it. These objects will appear as var-type 13 (vbDataObject).

For now, this problem only surfs with complex applications and controls. However, it's only reasonable that these objects will become more and more dominant in the coming years, so be prepared.

OK, we've got an object which holds much needed information, but we can't work with it in QTP. How can we workaround that? I've found 3 methods for doing that:

Try finding a way to make it work: I don’t KNOW these objects are inoperable in VBScript, I've just given up finding a way to do so. It took me the better part of a year to give up, and I would LOVE to hear of some real solution to the problem.

Write extensibility: QTP .net add-in has a remarkable feature, which allows you to write your own QTP shell for unknown .net objects. The extensibility works with the .net objects within a C# context, which means that it can rise above any VBScript limitation, and specifically, it can work with these problematic objects. You can find out more about writing extensibilities in the QTP help files.

Write a custom DLL to do your dirty work: This is quite similar to the extensibility workaround, though it's much simpler and easy to implement. You can write a .Net function which receives the problematic object, works with it, extract the needed information from it, and returns it in a form which VBScript can work with. This is usually not a problem since at the end of the day, deep down the object hierarchy, you probably only need a string, number, or array value (e.g., the object is an abstract geographic point, but you actually need an X double value, and a Y one, easily structured as an array).

Once you got your function written packed as a .Net DLL, you can create it within QTP (using the DotNetFactory utility object). Then it's very simple to pass the problematic object to your function, get the answer, and continue the script without any problems.

Monday, May 21, 2007

Site updates and upgrades

Hey all,
The site will undergo a major upgrade in the coming week (or two): immigrating to my own server, better platform (Wordpress 2.2), a structued Knowladge Base, better loading time, and much more. For me the most importent upgrade will be an automatic code highlighter, which will allow me to publish articles in 20 sec., instead of an hour.

I hope this will all happen by this Sunday, but there're many factors involved - my work, the students strike, the coming holiday etc.

Anyways, heads up.

Thursday, May 17, 2007

Multiple Exit Gates

It's very easy to control the entry gate to an action or function - there's only one way to enter them. However, the number of exit gates can vary widely from 1 to many according to the inner logic of the action/function. Today I'm going to address managing multiple exit gates in actions and functions.

When everything goes according to plan, an action flow tends to be very simple. There may be inner loops, If or Select switches, but for the most part, the flow just runs straight down to the last line. The problems usually appear when… well, problems appear. If an application error occurs, or even just an unexpected business logic behavior, there might be no escape from immediately exiting the action. There's no point trying to input 20 data fields, if the form they're in didn’t even open, is there?

Thankfully, the nice guys and gals at Mercury have taken this into account, and have provided us with the ExitAction and ExitActionIteration commands. So usually we've got something like the following:


'….Action Code
If CritialCondition = False Then ExitAction
'….Continue Action



But this is uninformative. So we add a reporter command:

If CritialCondition = False Then
   Reporter.ReportEvent MicFail, "Something bad has happened", "Aborting"
   ExitAction
End If


And we probably got some objects to remove from memory:

If CritialCondition = False Then
   Reporter.ReportEvent MicFail, "Something bad has happened", "Aborting"
   oFile.Close
   Set oFile = Nothing
   ExitAction
End If


Ho wait, we've gathered some data we need to report back:

If CritialCondition = False Then
  
Reporter.ReportEvent MicFail, "Something bad has happened",
"Aborting"
  
oFile.Close
   Set oFile = Nothing
   DataTable("out_EntityID", dtlocalsheet) = sEntityID
  
'More here
  
ExitAction
End If




Well, you probably get the picture. Pretty soon we get massive amounts of code in all the exit gates. This means we have duplicate code to maintain. Duplicate code is the digital manifestation of pure evil – and it's never a good idea to have pure evil in you actions. Seriously though, this is exactly the kind of things that produces untraceable bugs, and it must be avoided at all cost.

So, how can we deal with this situation? One way is to create an exit gate function. It's the ONLY function I ever put within an action, and not in an external file. Here's an example of my action template, with the function:


Dim sResult 'I store result data and values to be reported up the action-call chain
Dim sActionReport
'Instead of flooding the log with inner-action messages, I store them, and report all of them at the exit gate.
'Errors are still reported on-the-fly

'Action code goes here

'All the exit gates execute only one command : ActionEnd.
'It receives two parameters: Boolean for Pass/Fail, string for the exit reason
If CriticalCondition = False Then Call ActionEnd(False, "Reason for exiting")

'Rest of action

'Even the normal successful action exit is managed through ActionEnd, so the last line in every action is:

Call ActionEnd(True, "Action successful")

Sub ActionEnd(bStatus, sReason)
  
'Report details
  
Reporter.ReportEvent MicGeneral, "Inner Action Logs", sActionReport

   If bStatus = True Then
      Reporter.ReportEvent MicFail, "An error has occurred", sReason
   Else
      Reporter.ReportEvent MicPass, "Action successful",
"See inner logs for details"
  
End if

   'Plant datatable info for action-call chain
   DataTable("out_Status", dtlocalsheet) = bStatus
   DataTable("out_Result", dtlocalsheet) = sResult
  
'More if needed

   'Close objects and set to nothing here

   'Other needed exit code

   ExitActionIteration
End Sub


With this mechanism, maintaining the exit code becomes very simple, and the logs are much more readable. Ok, so this solves the problem for actions, but what about functions? Well, obviously we can write an inner function within a function, but there is an alternative solution. It's less elegant than the ActionEnd solution by far, and is harder to maintain, so I recommend using it only in a small number of very complex functions. The solution is based on the Execute command, so I recommend reading about it in QTP's help file in case you're not familiar with it. In a nutshell, the Execute command takes a string, and runs its contents as if it were VBScript code. So for instance the command Execute "msgbox(2)" will pop a message box with the number 2. Here's an example for the solution, applied to the ComplexFunc function:


Function ComplexFunc
   Dim sExitCode
   Dim sResult
   Dim oFile
'will be FSO textstream

  
'separate code lines by vbcrlf or ":"
  
sExitCode = "oFile.Close" &          vbcrlf & _
         "Set oFile = Nothing" & vbcrlf & _
         "ComplexFunc = sResult"
     
'More exit code

   'Function code goes here

   'Exit Gate
  
If CriticalCondition =
False Then
     
sResult = "False, No Connection"
      Execute sExitCode
      Exit Function
   End if

  
'More function code

   'Successful exit
  
Execute sExitCode
End Function

Saturday, May 12, 2007

DP from a personal point of view

Now that we've covered some of the basic issues regarding Descriptive Programming, I can elaborate further, on a more personal note. I've picked up some coding habits during my days, and some of them are DP related. Habits are arbitrary by definition, so there may indeed be better ways of doing things out there. However, I see no harm in pointing out a thing or two:

I don't trust regular expressions in DP
When we create a static description object, we can define the identification values by using Regular Expressions:


Dim oDesc
Set oDesc = Description.Create

oDesc("property1").Value = "RegExPattern"
oDesc("property1").RegularExpression = True


For example:


oDesc("text").Value = ".*Middle of text.*"
oDesc("property1").RegularExpression = True


I never do this, mainly because most of the time, the identification criteria is much too complex, requires accessing run-time properties, etc. So, instead of filtering the objects this soon, I use a very basic identification criteria (e.g. the object time beign Combobox / Checkbox / whatever), which results in a large object collection (since many objects comply with the identification criteria). Sometimes I would even leave the description object blank, and get all the objects in the window/application.

Once I have my collection, I can run through it, and easily find any object that I'm interested in – even if my criteria are very complex. For example, say I want to find a checkbox with a given Tooltip:


Dim oDesc, oChildren
Dim i
Set oDesc = Description.Create

oDesc("micclass").Value = "VbCheckbox"
Set oChildren = VbWindow("Main").ChildObjects(oDesc)

For I = 0 to oChildren.Count -1
If oChildren(i).GetROProperty("ToolTipText") = "WantedToolTip" Then
oChildren(i).Set "ON"
End If
Next




Since I write my own identification code (i.e. the IF conditions), I enjoy total freedom and flexibility, and I can write a much more tidy and readable code (I can unite many .ChildObjects commands by doing one that receives all the objects, and separate the different cases by Select Case switch).

I create objects collections even without the .ChildObjects command
As you might have guessed by now, I use the .ChildObjects command a LOT. As a result, many of my functions are built to receive and deal with objects collections, and not straight on, specific objects. This means that in case I occasionally use "regular" objects, I either have to write duplicate code (to deal with objects, as well as collections) , or to find a way to transform objects into collections.

Well, since duplicate code is evil manifested via digital form, I tend to use the second option. Here's how I artificially create a collection, and add an existing object to it:


Dim oObjectCollection
Set oObjectCollection = CreateObject("Scripting.Dictionary")

oObjectCollection.Add 0, VbWindow("Main")



About the last line: what happens is we add the object VBWindow("Main") to the collection, and we set its identifier to 0 (by which it can be "pulled out" of the collection). Identifiers can actually be strings (e.g. name, phone number, internet address etc.), but in order to be compatible with ChildObjects generated collections, I use a zero-based numeric identifier. The syntax for using an object within the collection remains the same as with .ChildObjects collections:


oObjectCollection(0).Click


A quick note regarding Scripting.Dictionary objects – these are extremely useful in a variety of situations. I strongly recommend getting familiar with them as soon as possible (see QTP help – under "Dictionary Object").

I prefer full DP over OR run-time change
Some QTP programmers like to record objects to the native OR, and then change their properties during run-time using the SetTOProperty command (more on that command in the future). In my opinion, this results in scattered object-identification logic, de-encapsulation, it causes confusion, and turns debug into a living hell.

If I'm going to change an object's OR description on a regular basis, I remove it from the OR, and use DP instead.

I ALWAYS use the index property
When adding the index property to a DP statement, we can protect ourselves from situations in which two or more objects match the same description. The down side is, that it might be important to know when these situations actually occur. Having said that, I prefer a stable script over this information. In QTP 8.2, the index property would work ONLY when there was more than one object matching the description, but in later versions (9+), this issue was resolved, so there's really no reason not to use the Index property. For example :


VBWindow("title:=Example", "index:=0").VBCheckBox("visible:=true", _
"index:=0").Set "ON"

Tuesday, May 8, 2007

Site Updates

Due to the gross incompetence of the Israeli Internet Association (ISOC-IL), the Hebrew version of the site is undergoing DNS problems. It seems that in 2007, DNS updates are done manually, via FAX (I shit you not).

Update to the update
Finally, someone got out of his chair and solved the problem. The Hebrew site is now available in www.AdvancedQTP.co.il.

All the best,
Combustible Moo.

Monday, May 7, 2007

Basic Principle – Descriptive Programming

To the hebrew version of this post

Before we can move on to some of the more advanced subjects, it's probably better if we ensure we're all speaking the same language. So now we'll create a proper base-line for some basic QTP techniques (though even they are sometimes referred to as "advanced"). One of the most profound techniques necessary for advanced QTP usage is "Descriptive Programming" (DP).

General Background




When using DP, we're bypassing the native object repository (OR) mechanism, which may have many advantages (easy to generate and read code, good data organization, etc.), but is extremely not flexible. We'll examine situations in which the OR's advantages are outweighed by the DP's flexibility.

In order to fully grasp how DP actually works, it's best to first understand how QTP's native OR works. The native OR is not some complex and deep mechanism, but rather a simple way of keeping groups of properties and values. To clarify: Whenever we add a certain window to the OR, QTP gathers the window's properties (e.g. – Height="400", Title="New Entity", vbName="NewEntityID"), and stores them as a group of property-value pairs. When QTP runs the script, it compares these properties and values to the "physical" objects, and finds (or fails to find) the object which they describe. Nothing mystical about it.

Usage

In DP, we're "manually" specifying the properties and values by which the relevant object will be identified. This way QTP won't search for the properties data in the OR, but will take it from the DP statement. This can be done in a fast, ad-hoc way, in the very line of the command we want to execute:

The syntax is: Class(PROPERTIESSTRINGS).Command, where PROPERTIESSTRINGS specifies the properties and values for the object's identification. For example:


VBWindow("property1:=value1", "property2:=value2").Click


We can even create an object hierarchy (though once we use DP, we must continue to use DP down the hierarchy):


VBWindow("property1:=value1").VBCheckBox("property2:=value2").Click


Of course "VBWindow" class is just an example for DP with VB objects. DP can be done with SWFWindow, Browser, WinComboBox etc.

The only difference between using DP to using the native QTP OR, is in the content within the class's brackets (in this example: VBWindow()). When using the native OR, we'll put the logical name of the object as defined in the OR. When using DP, we'll put strings describing the properties and values the object will be identified by (structured as "property:=value").

This syntax has the benefit of being shot and quick, but may cause problems if we'll use the object again somewhere else. In this case we'll have to rewrite the all the description strings, making the code less readable, and harder to maintain (i.e., if one of the properties were to change, we'll be forced to make several repairs in different places throughout the code). Luckily, DP allows us to define a static descriptive object, and set its properties once. This way, the script simply refers to the descriptive object:


'----Create Object----'
Dim oDesc
Set oDesc = Description.Create

'----Set ID properties & values---'
oDesc("property1").Value = "value1"
oDesc("property2").Value = "value2"

'----Use and reuse the description object---'
VBWindow(oDesc).Type "Something"
'…
'…

VBWindow(oDesc).Close

'----Release description object---'
Set oDesc = Nothing


We can even combine the two methods:


VBWindows(oDesc).VBCheckBox("vbname:=DPIsCool").Set "ON"


Common uses

(Detailed examples can be found in the attached file)

Well, you might ask yourself why we even bother. QTP's native OR does all these things, is tidier, and up to QTP 9, was the only way to enjoy the benefits of code auto-complete. Well, in some sense, you're right – sometimes, the best way to go is to use the OR, straight up. However, in some cases, DP is preferable by far, and sometimes even the only way to go:

Easy-breezy coding: if we're only going to use an object once or twice, there's no need to use the slow, complex OR, when you can just immediately write the ID string as part of the command. Moreover, with DP you can copy code-snippets between scripts, without having to worry about references to undefined object in the new script.

Inherent dealing with double objects: in case the identification properties and values match more than one object, QTP will through an "object's description matches more than one of the objects currently displayed in your application" error. If we're using the native OR, there no easy way to deal with the situation (we could use a complex Recovery Scenario, but it gets very ugly, very soon). However, DP easily deals with double objects by using the index property. We can add "index:=X" to the description strings (when X is a zero-based counter), and QTP will point to object #X.

Object reference in external functions: when using external functions, you can never count on the relevant object being defined in the calling action's OR. And even if the object is defined there, its logical name might be different, so really, DP remains the only option.

Objects that change hierarchies: sometime an object will appear under a different parent each time (e.g. – a pop-up which appears under the initiating sub-window). In some applications, the only way to work with such objects is with DP.

A common usage which deserves a separate section

Working with a collection of objects: other reasons aside, this is the "Killer Feature" of DP. It makes DP a must for every QTP programmer, and it's hard to overstate its importance and inherent possibilities. According to this concept, instead of working with a single object at a time, we can gather all the objects which answer to our identification properties, and work with them as a collection, serially.

This will be clarified via an example: we're dealing with an unknown number of checkboxes. We don’t know (and don’t care) how they're called, or where they are on the screen – we just need to mark all of them as checked. If we were to do this "the old way", we would have to keep track of each checkbox's properties, and write separate commands that identify each of them, and marks them. With the new objects-collection method, we can ask for all the checkboxes in the screen, loop through them, and mark all of them with a single, easily maintainable command.

This method represents a major improvement, since we've severely reduced our dependence in the application. If the number of checkboxes, their locations, or their names were to change, our code will need zero-maintenance to keep on working. Moreover, in some situations, this method is really the only possible way to go.

This method is implemented with the .ChildObjects command. It receives a DP descriptive object, and returns a collection of the objects that answer to the description. The description can have 0 properties, in which case all the objects will be returned. The following example demonstrates the .ChildObjects command syntax, as well as a pretty standard way to utilize the returned collection:


Dim oDesc
Dim oChildren
Dim i

oDesc = Description.Create
oDesc("micclass").Value = "VbCheckBox"
'----We could've left the oDesc object blank, To get all objects----'

Set oChildren = VBWindow("Main").ChildObjects(oDesc)
'----Now oChildren holds the checkboxs' collection----'

'----Run through the collection----'
For I = 0 to oChildren.Count-1
'----Set the specific checkbox to "ON"----'
oChildren(i).Set "ON"
Next


As you may have noticed, the .ChildObjects command must be executed on a top-level window. This means that you can't get a collection of the top-level windows of an app, only a collection of child-objects In a certain top-level window. This was true until QTP 9, which introduced the Desktop utility object. This allows us to execute Desktop.ChildObjects(oDesc), which returns a collection of top-level windows, so all is well.

More examples and uses are available in the attached file.

Further reading

1. QTP help : Using Programmatic Descriptions