3/30/2006 09:47:00 AM|W|P|Will|W|P|My company is good to me. They give me two 19 inch LCDs to look at all day. Unfortunately, Mercury Interactive isn't as good to their employees. If they were, their developers would write software that works well on a dual monitor system. I've spent quite some time struggling with a problem in QTP 9 where it won't click on a button in a dialog box. I didn't think to try moving the application to the other monitor until I called support. Fortunately, I happened to mention that the dialog was on my right monitor which tipped the support engineer off that I have multiple monitors, and he was able to tell me what the problem is. He said there is little chance of Mercury fixing the problem anytime soon because there is a workaround -- only use one monitor. I'm not writing this to get you to all sympathize with me, but to tell you that there is a way out of this single-monitor bondage. After finding out this is a monitor related issue, I tried switching monitors. Previously I had QuickTest on the primary display and IE on the secondary. I switched that around so that QuickTest is on the right (secondary) monitor and IE is on the left, and it is working beautifully.|W|P|114373450073791224|W|P|QuickTest on the Right|W|P|paperhat@gmail.com3/30/2006 10:17:00 AM|W|P|Blogger Marcus|W|P|I had an issue like that once before, and was simply told that Mercury doesn't support multiple monitors at all.

What sucks about this particular issue is that it worked just fine in version 8.2.

So, if you're keeping score, they made a feature work in 8.2 that they didn't support, and then when they broke it in version 9 they pulled the "we don't support that" trick.

Nice.

The incident I reported was also with version 9:

- Maximize QTP 9 in the RIGHT-HAND monitor
- Put any script in debug mode (can be a single-line script, where "wait 1" is the only line, and select "debug from here")
- Without stepping through any code, use the Object Spy to identify an object (any object, not necessarily a web object)
- After you click on the object, the QTP 9 window will immediately maximize in the LEFT-HAND monitor, and it will stay there until you move it back manually

They aren't planning to fix that.3/27/2006 11:46:00 AM|W|P|Marcus|W|P|This is just sort of a shotgun list of features and my opinion on them. It will also appear as a wish-list of sorts, as there are some opportunities they've missed in developing the QTP 9 interface. Keep in mind that I'm a big admirer of Spolsky, so what may sound like extreme nitpicking is to me a case of programmer-user disconnect. We who use this product are in the business of criticizing GUIs - we do it for a living... we should hold Mercury to the same standards to which we hold our own programmers.
  • Insert breakpoints by clicking in the gutter - great idea, but now it's very difficult to select a single line of code. In the past, if you clicked in the gutter, it would highlight the line of code you had selected. Now, if you want to select a single line, you have to put your cursor all the way to the left of the editor, but to the right of the gutter, then drag slightly down. Drag too much and you get two lines. Click in the wrong place and you either insert a breakpoint or you miss the first character in the line. I would have preferred this to be a checkbox option in the Editor Options menu.
  • New menu bar options - Now, instead of Test and Step in the menu bar, you have Automation and Resources. I don't know about you, but I got used to typing Alt+T, N for "Run Test" and ALT+S, A for "Action Properties". Now, it's ALT+A, N for "Run Test" (which is okay, because I just changed my habit to hit F5), but to go to "Action Properties", you have to hit ALT+E, I, P. No kidding. Action Properties are now in the Edit menu, under Action, then Action Properties. They've inserted a click in here, and it's a painful one because Action is almost all the way down the menu. That's another reason to go to functions instead of actions for most everything.

The Automation item is analagous to the Test item in 8.2, and they've made one huge change that I consider unnecessary and bothersome. They've moved Test Settings under the File menu. That's probably a more logical place, but here's the problem: in the past, when you couldn't remember where to go to change the Row Iterations, or what URL to use to open the browser, or Function Library references, chances were that if you chose Record and Run Settings and didn't find it, you could be assured that you could just choose "the other one in that menu" and it would be there. Now there are not one, not two, but three different places you have to go, none of them close together, in order to set up Test Run Settings. FileSettings, ToolsOptions, AutomationRecord and Run Settings - each of these menu chunks has the word "Run" in it, and who besides me can't remember which one has what?

One of my fondest hopes for QTP 9 was the ability to map many more functions to hotkeys. Alas, I'll have to wait a little longer. With the new clicks further obfuscating their most basic functionality, I'm find a lot of the seconds I'm saving on an improved feature taken away by another. I want to be able to go to Action Properties with a simple hotkey, but I can't map my own. This makes me sad.

I also can't change the annoying behavior that causes ALT+Backspace to erase an entire line of code instead of just the previous word. I don't know what interface precedent told them that was a good idea, but I'd advise them either to follow the Visual Studio interface (let's face it, the V in VBScript stands for Visual, which means Microsoft), or to give me mappings for every blessed function they provide. It's driving me nuts!

Ok, so I've covered the new menu bar and various niggling annoyances. Next I'll talk a little about the new window pane behavior, including the new right-click menu options and toolbars.

|W|P|114348288925867514|W|P|New Interface Features, Quirks, and What-have-yous in QuickTest 9.0 - Part I|W|P|mmerrell@gmail.com3/28/2006 12:36:00 PM|W|P|Blogger Will|W|P|Selecting a line isn't as hard as you think. You just have to tripple-click on the line.3/27/2006 11:22:00 AM|W|P|Marcus|W|P|This is first in a series of analyses of the new features of QTP 9. Over the next few days we'll be posting more in varying levels of deatil, and within a week or two we hope to have just about all of them covered. We'll try to address where they went right, but we're not going to leave out where they could have gone farther. Please add comments where you agree/disagree with out conclusions. We want to be an accurate source of information, so if we're wrong, TELL US! In my opinion, the Multiple Document Interface is the best feature of version 9. It is going to alter how we address the question of External Actions vs Functions. With the new interface you can have one test script open, and then any number of Function Libraries. Under the FileOpen and FileNew menu items, there's now a separate entry for Function Libraries, and the libraries do appear in the Recent File list. The function libraries then appear as tabs across the top. From these tabs, you can right-click and it will bring up a small menu with "Save" and "Close" for options. There's also a "Save All" button in the toolbar. All in all, it's nice. But more importantly than any of these small victories, there are the larger implications of the feature: you can now view, develop, execute, and debug your functions. In the past, I've written the function inside my test script, made sure it worked properly, then put it in the VBS file. Then, if I find a bug in my function, I've had to copy it back into my test script, then repeat the whole thing ad nauseum. It's even one of the things Mercury advises in their training materials. Allowing me to write and debug the code in situ means I don't need to be afraid to write new functions, or groan when I find a bug. In fact, if I get to a breakpoint where I'm calling an external function, it will open the library and allow me to step into the code without the extra step of opening it myself. With this feature, we plan to shift the way we develop our framework: since we have full freedom to develop code in small chunks, we don't always want to spend the necessary overhead to create External Actions. We'll only create actions in certain situations. I'll discuss this topic in detail under a different heading. For now, I just wanted to post a description of this nice new feature, and compliment Mercury on implementing it very effectively. Next, I'll cover in detail the little interface changes they've made. Some good, some not so good.|W|P|114347994977497722|W|P|New Feature in QuickTest Pro 9: Multiple Document Interface|W|P|mmerrell@gmail.com3/27/2006 07:45:00 AM|W|P|Marcus|W|P|

How do you measure the overall quality of a product? One way is to take the spec, take the final product, and compare the two feature by feature. Well, QTP 9 went GA today, and we've pored over it so thoroughly you'd think we've had it for months. First I'll present the summary, then over the next few days we will dissect the minutae of what they did, how well they did it, and whether or not it's going to make your lives easier. Here's a bullet-by-bullet summary of how well they did against their own press release:

  • Open and edit multiple Object Repositories - yes, the fact is that you can do this, but it's not always easy, and there were a lot of missed opportunities to go from good to great
  • Multiple Object Repositories per test asset - I hate the term test asset. I think in this case they mean Action, because as far as I can tell, there is no other "thing" you can associate an OR with, and in version 9 you can associate multiple ORs with any given action. There are problems with this, but I'll get to those later
  • Easy (Object Repository) conversion to/from XML - Yep, for what it's worth, the conversion is easy. It is very very very slow, and the schema is complicated, but both of those are pretty understandable.
  • Easily copy/move objects between Object Repositories - Yes, you can even drag/drop objects from one repository to another, but maybe more importantly, you can reparent an object... this is super-duper cool, because you can now take all the objects under "Frame_4" and "Frame_37" and consolidate them under "Frame". There are some HUGE problems with this feature that will probably be made easier in future releases, but it's still possible and nice when you just have to do one or two objects
  • Manage functions and keywords centrally - I don't know what this means, outside from the ability to view/edit functions from within the main IDE. If that's the case, they're kinda cheating in making this a separate bullet point from the "Step Into..." point below. If that's not the case, I have no idea what this means and shall declare it an example of "silly marketingspeak"
  • New user interface - sure, OK. It's not like the difference between vi and Visual Studio .NET, and they haven't provided an Eclipse plugin yet (note to self: write Eclipse plugin for QTP), but I guess they've made some changes
  • Missing resources panel - This is très cool, but not nearly as cool as the way they make it sound
  • Comment and uncomment multiple lines in Expert View - One of those little nice things they've done to provide features we're used to in every other programmer IDE on the market
  • Indent and un-indent multiple lines in Expert View - ibid
  • Pass parameters between Actions - I have no idea what this means, since you could do this before. Maybe they didn't broadcast that you could do it before. Whatever
  • Multiple Document Interface for function libraries - I guess this means you can open more than one function lib at a time. That's yet another restatement of the "Manage Centrally" nonsense from the earlier bullet. But I'm guessing they list it three separate times because it's simply the single best feature of version 9. It's changed the way we compose our tests, which is funny, because it essentially means that we use Mercury's scaffolding a lot less because they've made it easier for us to write our own
  • Step Into Function Definition Code While Debugging the Test - Yes, THIS RULES!
  • Enhanced IntelliSense Support - the main thing I notice here is that your user-defined RegisterUserFunc methods now appear in the code. Maybe that was there before, but I don't know. I could never make it work
|W|P|114193321888380726|W|P|Comparison of QuickTest Pro 9.0 vs QTP 8.2|W|P|mmerrell@gmail.com3/24/2006 01:38:00 PM|W|P|Marcus|W|P|We don't use Checkpoints at all in QTP. They're just not flexible to the degree that we need them to be.

Whenever you call a checkpoint, there's a lot of stuff going on, very little of which you have control over. You can put in some things that make it more flexible, like regular expressions and comparisons to other output values, but in the end you have this big black box of stuff that you can't really get at. Plus, in the end, you have a Reporter.ReportEvent that goes to the test results as a Pass or Fail, and you have no way to alter the strings. The problem with that is, as I've said in previous posts, I want different tests to behave differently in different circumstances, but I don't want to rewrite any of the code. When I'm not running deep or strict tests, I want the test results to be sparse, just letting me know when the broad strokes of the tests have been completed. The Test Results Viewer that comes with QTP is difficult enough to use (e.g. it doesn't remember filter settings, etc.), so when I filter the test results based on pass/fail, I want to zero in quickly on the tests that were important to me when I ran the tests. Sometimes we use test actions as a means to an end, not an end in themselves, and in those cases I don't want to see every detail. I have a lot of other problems with the checkpoints as they are, but some of my problems may be a lack of education. My team abandoned checkpoints too early to really explore the ins and outs and what-have-yous (mainly because of the reporting problem), so I'd like to hear from anyone who is still using them and gets what they need out of them. I'll state some of the problems below. I'm not 100% sure that I'm right on these, so pretend that with all of them I've added "This is the experience I've had, and I've seen no evidence to contradict it. If you have some knowledge to impart, by all means let me know." Some of these problems are:

  • The Standard Checkpoints seem to be very, very slow. It's nice that you can look into specific object properties and all that, but the fact that it slows down the script is a real pain when you have a lot of them in there at once
  • For Table Checkpoints, you can't be at all flexible in column or row checking. Our application allows the user to change the order of columns, etc., which means we need to
  • For Text checkpoints, you must specify the nebulous "text before" and "text after", when, in my AUT at least, those things are seldom reliable or consistent
  • Worse yet, when it comes to localization, they'll be in a different language. You could specify values from a data table, etc., but sometimes I would rather be able to say "the text to the right of the object with XYZ properties", or "the text that is in this frame, and I don't care about the words, only that the font color is red and the CSS class reference is std_WarnText"

Like I said, there may be other ways to do this, but I haven't found them. When I have coded around things to shoehorn my checkpoints into working, I've often later found them to be very fragile. I had an occurrence recently of a long, drawn-out test script, that probably contained over a thousand lines of code. The one line of code that broke consistently and with no good error message was the one leftover checkpoint from my earlier days. I finally removed it and replaced it with our new, preferred method.

What is that new, preferred method? I'll talk about that soon. To give you a hint, think about JUnit.

In the meantime I want to talk a little more about Test Level, and the different levels we've defined. There are 6 levels, and we're retaining the option to have more. Each level gives an indication of how strictly it behaves, and how its behavior is recorded into the test results.

  • Test Level 0: Loosest testing possible. Try at all costs to be smart about selecting options, and just get through the code by any means necessary. Also, don't log anything that's not an error (and that's a QTP Run error, not a "test failed" error)
  • Test Level 1: Be as loose as level 0, but report all non-errors as "micDone" entries in the results. This way, we've at least kept track of all the things we've done, but when you filter by Warnings and Errors, you'll only get the stuff that was considered Very Important by the test developer
  • Test Level 2: Be as loose as level 0, but report all "smart" behaviors as warnings. When something doesn't exist in a WebList and you have to guess at it by clicking the first non-empty string, report that fact as a "micWarning". If the specified value was in the list, report it as a "micDone"
  • Test Level 3: Again, be as loose as level 0, but report all "smart" behaviors as "micFail", and all correctly specified values as "micPass"
  • Test Level 4: This time, don't be at all smart about how values are selected. If the developer specified something to be a certain way, report the problem as "micFail", then exit the test iteration (like QuickTest would by default, except that the intent is to give very detailed error information about what we were looking for and what appeared instead)
  • Test Level 5: Same as Level 4, but exit all test iterations immediately

The nice thing is that you can ratchet up or down the Test Level as the test goes on. You can test shallow in order to achieve some sort of setup, like object or scenario creation, then knock it up a level or two when you start the really important part of the test.

Our company has tasked us with asking a bit more out of our automated tests than is normally recommended. We understand that you're not usually successful when you try to do outrageously large regression tests, or to simulate manual scripts. But they want something in between "normal" automation and 100% regression coverage, and techniques like this allow us to move much closer to that goal than ever before.

The ability to take the same scripts that perform the low-level field validation, then employ them as "just a step along the way", has been paramount. It takes a while to develop the initial tests, but the development gets faster and faster as we build our library. Then, with very little effort, we can build a smart suite that can be reused against new builds, against customer-deployed sites, and against new versions of the product.

And we don't use Mercury's checkpoints anywhere.

Next, I'll post an example of another Smart method, the one we use for WebLists. It employs heavy use of Test Level to determine its behavior and logging.

|W|P|114193312526571647|W|P|Test Level and Checkpoints in QuickTest Pro (9 and earlier)|W|P|mmerrell@gmail.com3/23/2006 08:41:00 PM|W|P|Marcus|W|P|In our QTP framework, "strictness" has two dimensions: Test Level and Test Weight. Test Level refers to how loose the test is allowed to be when, say, selecting specific values from a WebList or WebRadioGroup. Test Weight refers to how important a given test is, and whether or not it will be run in the current cycle. When we run tests, we run them at a default Test Level of 0, which is to say, "when you encounter a WebList or other input type, if the value I have specified does not exist, or only exists partially, just select a valid response and move on." The Test Weight is 200 (out of a possible 1000). The lower the Test Weight, the more important the test. When you run a 200-weight suite, you are essentially running a smoke test, where you want to run the least number of tests to validate that the system is up and running. This is what we run every time Cruise Control deploys a new site. A suite level of 800 or 1000 tells it to run every stinkin' test we've written, and it takes much longer. That's what we run at 2 am when nobody is here. Let's say I'm writing a QTP test where I create 15 new user accounts (each with different access features), log in as each, then perform existence checks on objects (buttons, checkboxes, etc) that should be enabled/disabled according to the access features. Given the number of different roles, features, and grants in our system, there can be over 800 permutations. Further, since we have between 4 and 8 hosted customer sites, this number mushrooms. As we host more customers, we want to scale our tests to be able to handle any number of customers, and we don't want to have to change to code to do so. Even further, we want to design the code so that we can re-use all these actions (User creation, logging in, access checking, etc.) in other scripts where we don't want to run any checks beyond the most basic "did it work?" functionality. Test Weight and Test Level are Environment variables, set in XML files, and altered at run-time to fit our needs (via an external utility). Each action has its own internal Test Weight, which is compared at run-time to Environment variable. If this internal Test Weight is greater than the weight of the suite being run, the action will short circuit and return to immediately without reporting anything or executing. If the internal Test Weight is less than or equal to the Environment variable, the test will execute. Test Level is a lot more low-level. It is checked on a function-by-function basis, and the function in question always executes. The difference is, the Test Level will tell the function how to execute, then how to report its progress. An example would be (as above) a WebList, where you specify that "abc" be selected. If Test Level is 0 and "abc" doesn't exist in the list, it will take the first non-empty string in the list and just select it without looking deeper into the matter. The idea is that if I'm testing a site in a different language, or a customer-deployed site that contains completely different information, the test will still work just fine. Having these two dimensions on our tests ensures that we can run exactly the same scripts on multiple databases with varying degrees of thoroughness as the situation dictates. Next, I'll delve a little deeper into the different Test Levels we use, then I'll post the code for this WebList.SmartSelect method, and how it implements the concept.|W|P|114282522385559951|W|P|Strictness in QuickTest (9.0 and before), Part II|W|P|mmerrell@gmail.com3/21/2006 10:12:00 AM|W|P|Marcus|W|P|We've had to solve this problem quite a bit, so Will finally turned it into an object method. Here's the problem: (o) Option A ( ) Option B ( ) Option C You want to be able to say "Select the 'Option A' radio button", but the enterprising and oh-so-efficient programmer has made it so that each option has a value that corresponds to some GUID (e.g. "GFED-0839-02E8A5-0923CDB"), and "Option A" is nowhere to be found anywhere in the object's properties. The method below allows you to send the text you wish to select, and the radio group will know exactly which button to select. Select a Radio Button based on the adjacent descriptive text - The WebRadioGroup to use - The text to look for - optional: The sWhere statement for getAdjacentText. Default is afterEnd - optional: The Index number of the matched button to select. 0 selects first match, 1 selects second. Default is 0

Public Function WebRadioGroupSelectByText ( ByRef oRadGroup, sSelectText, sWhereOpt, sIndexOpt )
 If isNull(sWhereOpt) Then
  sWhereOpt = "afterEnd"
 End If

 If isNull(sIndexOpt) Then
  sIndexOpt = 0
 End If
 
 sMatches = Array()

 Set oParent = oRadGroup.GetTOProperty("parent")
 radName = oRadGroup.GetROProperty("name")
 Set oRadioButtons = oParent.Object.GetElementsByName( radName )

 For each oElement in oRadioButtons
  sName = oElement.getAdjacentText("afterEnd")
  If sSelectText = sName Then
   ArrayPush sMatches, oElement.Value
  End If
 Next

 If Ubound(sMatches) < 0 Then
  ' TODO: report error...item doesn't exist in list.
  Exit Function
 End If

 If Ubound(sMatches) < sIndexOpt Then
  sIndexOpt = Ubound(sMatches)
  ' TODO: report warning...index doesn't exist.  selecting last match
 End If

 sSelectValue = sMatches(sIndexOpt)
    oRadGroup.Select sSelectValue
 
End Function
RegisterUserFunc "WebRadioGroup", "SelectByText", "WebRadioGroupSelectByText"

Now, to select the radio button I want, I just to this:
Browser("B").Page("P").WebRadioGroup("All Options").SelectByText "Option B"
Note that this performs an exact string match. The next step is to create our WebRadioGroup.SmartSelect function, which would be much more flexible, and would allow for partial matches of either adjascent text or the traditional button values. It would also allow for pattern matching via regular expressions. We'll post that one later.|W|P|114295813788482253|W|P|QuickTest Pro (9.0 or 8.2) method: WebRadioGroup.SelectByText|W|P|mmerrell@gmail.com3/19/2006 09:02:00 PM|W|P|Marcus|W|P|In this post I'm just going to lob a high concept for dealing with QTP. In the next few days I'll explain how we've solved it through a fairly simple mechanism. Here's the problem: When you're working with web software, sometimes you want to run A, B, and C in order to compare the results of D and E. A, B, and C are means to an end, and all you want to know is that they worked, you don't want to know every little thing about every little detail. Other times you need to accomplish the same thing, but because it's a new build after, say, a massive database schema change, everything is suspect. You then need to dig deep into the details of A, taking several passes over the various pages and reporting every detail before moving on to B. Here's the thing though: I only want to write A once, and I want to call A with the same exact method call every time, regardless of the "strictness" I require at run time. The reasons for this should be obvious, but I'll spell out a few of them while I'm feeling inspired:
  • Having deep tests can take several hours, where the shallower tests can take several minutes in some cases - there are good reasons to have the flexibility to switch between them without a code change
  • I don't want to have separate scripts for deep testing and shallow testing
  • I don't want to have to remember what the difference is (from a code point of view) between the deep code and the shallow code
  • I want to be able to switch between deep and shallow (or degrees in between) with a single variable, which will then dictate how the methods behave. I do not want my driver scripts to be different except in this one variable

You know what? Those are all slightly different wordings of the same thing. I only want to write the code once. Ever.

How do we do this in QuickTest Pro? Well, that's the thing. Checkpoints and Outputs are not the most flexible things in the world. Neither are External Actions. Neither, really, is the Object Repository. We've come up with a way to accomplish the above goals without having to rely on the dodgy parts of QTP. It means we're leaving huge swaths of Mercury's code untouched, but sometimes that's the only way to get things done, isn't it?

More soon.

|W|P|114282502530542944|W|P|Strictness (or: The Problem of Inflexibility in QuickTest Pro)|W|P|mmerrell@gmail.com3/16/2006 11:38:00 AM|W|P|Will|W|P|
QuickTest was my introduction to VBScript. I think and dream in Perl, but I am coming around to a point where I can enjoy coding in VBScript. One of my biggest VBScript annoyances early on was the inability to define optional arguments for my functions. As I said, I think in Perl, so I set about trying to find a solution similar to how I pass arguments to subs in Perl. In Perl, I like to name my arguments, so I pass an array of arguments that the sub collects into a hash as named values. The syntax looks something like this.
printsentence(
 'Subject'   => 'The quick brown fox',
 'Predicate' => 'jumps over the lazy dog.'
 );

sub printsentence {
 my %args = @_;
 print "$args{'Subject'} $args{'Predicate'}";
}
I found an article on 4guysfromrolla.com about using optional arguments in VBScript. It suggests passing an array of arguments to the function. This is a good first step, but I don't like having to remember which value is which in the array. I still wanted my named arguments. Obviously I couldn't use a hash for this, so I used VBScript's nearest data type: a dictionary object. Once I have my parameters in a dictionary object, I can call them by name using syntax like oOptions("optionname"). The question was how to easily get all my options in a dictionary object. I came up with a function I call DictBuild (insert platypus-related humor here). It takes an array and turns it into a dictionary object. The first value in the array is a dictionary element, the second value is that element's value, third - element, fourth - value...and so on. Basically, all even numbered values are dictionary elements and the odd value after each element name is its value. This allows me to easily build a dictionary object with all my parameters.
printsentence ARRAY( _
 "Subject"      , "The quick brown fox" _
 ,"Predicate"   , "jumps over the lazy dog." _
 )

Public Function printsentence(aOptions)
 Set oOptions = BuildDict(aOptions)
 MsgBox oOptions("Subject") & " " & oOptions("Predicate")
End Function
Now this is finally becoming something I can work with, but what about defaults for these parameters? This is where my next function comes in: GetOpts. GetOpts takes two arrays as arguments and returns a dictionary object. The first array is the default values for the function. The second array is the custom options array that was passed to the function. GetOpts starts by building a dictionary object from the defaults array. Then it overwrites the default values with the custom options array if they exist. I also wrote some discipline for myself into GetOpts. If a option isn't listed in the defaults, it won't make it into the final dictionary. So, now my function looks like this:
printsentence ARRAY( _
  "Subject"     , "The quick brown fox" _
  ,"Predicate"  , "jumps over the lazy dog" _
 )

Public Function printsentence(aOptions)
 Set oOptions = GetOpts( ARRAY( _
   "Subject"      ,   "Coke" _
  ,"Predicate"    ,  "is it" _
  ,"Punctuation"  ,  "!" _
 ), aOptions)

 MsgBox oOptions("Subject") & " " & oOptions("Predicate") & _
        oOptions("Punctuation")
End Function
I think that about does it for my method of using named optional arguments in VBScript functions. I should note that I only use this for optional arguments, I put the variable names directly in the function signature for required values. So one of my real function delcarations looks like
Public Function WebCheckBoxSelectByText ( ByRef oCheckBox, sSelectText, aOptions )
The code for BuildDict and GetOpts is below.
Public Function GetOpts(ByRef aDefaults, ByRef aCustom)
 Set oDefaults = DictBuild(aDefaults)
 Set oCustom = DictBuild(aCustom)
 For Each vKey in oDefaults
  If oCustom.Exists(vKey) Then
   oDefaults.Item(vKey) = oCustom.Item(vKey)
  End If
 Next
 Set GetOpts = oDefaults
End Function
Public Function DictBuild(ByRef aArray)
 bWord = True
 Dim oDict
 Set oDict = CreateObject("Scripting.Dictionary")
 Dim sWord, sDef
 If isArray(aArray) Then
  For Each sString in aArray
   If bWord Then
    sWord = sString
    bWord = False
   Else
    sDef = sString
    oDict.Add sWord, sDef
    bWord = True
    sWord = NULL
    sDef = NULL
   End If
  Next
 End If 
 set DictBuild = oDict
End Function
Technorati Tags: , , ,
|W|P|114253071487731523|W|P|Named Optional Arguments in VBScript|W|P|paperhat@gmail.com3/15/2006 08:37:00 AM|W|P|Marcus|W|P|The feature set for QuickTest 9 Beta is impressive, and some of it speaks to our wish list. Will & I were both a part of the beta program, and we're preparing a comprehensive review of how well the new features stack up (against their list as well as ours), as well as some new issues that came up during testing. The non-disclosure agreement we signed prevents us from saying anything about it right now, but we'll be full of information as soon as it goes GA.|W|P|114243377379183943|W|P|QuickTest 9 Coming Soon!|W|P|mmerrell@gmail.com3/14/2006 08:01:00 PM|W|P|Marcus|W|P|

While we're talking about QTP 9, let's put together a list of nice-to-haves that we currently lack. I've found that most of my time is spent fighting with QuickTest Pro, not composing automated tests. I have to solve the same problems multiple times, and I get stuck doing things that should be extremely easy. In their attempt to make an automation tool that is easy for both basic and advanced users, Mercury has given the advanced users the short shrift.

Here's a list of things that would make my life easier. I want this list to be a lot longer, so please, comment away. We've found the MIC is pretty receptive to suggestions like this (all of these have been submitted as SRs, by the way), so keep them coming!

  • Allow me to open more than one test at a time
  • Allow me to change the order of Action parameters
  • When I’m in the Object Repository, I want to click on a button that will output the hierarchy as code that I can use in my script. If it takes a while for me to find the object in the OR, it’s a pain in the ass to go back to the code and hand-write the object. The button should say “Use in step” or something like that. The Step Generator is good, but not good enough
  • In fact, the Step Generator is case-in-point that this should be easy to do... you're about a biscuit away from having this already. Just make the interface better by building in a hot key, or putting a stubbed-out function call directly in the code from the right-click menu
  • Same as above, but for the Object Spy
  • Same as above, but for the Active Screen (again, the Step Generator, but it takes 2 or 3 more clicks than it should)
  • Same as above, extended to object properties, which generate Programmatic Descriptions – this can be further expanded to say that if you are trying to create a PD for a page, but the line of code you’re working on deals with a child object, it will make a PD based on your OR selection, then PD-ize the remaining objects in the line. This is a lot to ask for, but it would be oh so cool
  • When I type “Shift+space”, I want the editor to treat that as a space, not as nothing
  • When I place the cursor at the beginning of a line, and press Ctrl-V to paste a line of code, I want it to go BEFORE the current line, NOT AFTER (so it will work like EVERY other editor in the world - I realize that this doesn't always happen, but the inconsistency is worse than having it one way or the other
  • When I type "Ctrl+Backspace" I want it to delete the previous word, not the entire line I'm on. At least add "delete previous word" and "delete next word" to the Key Bindings menu
  • I desparately want to be able to step through function library code in the debugger. As it is, I have to copy the code into the editor and step through it there if I want to keep an eye on my variables while the code is running
  • I want a one-key way to add an expression to a watch... this means a highlighted expression from Expert View, a Parameter (or you could just add parameters to the current action/function to the Variables tab in the Debug viewer)

I'll probably have a 2.0 version of this, as well as an update whenever QTP version 9 comes out. Until then, let's make this list very very long.

|W|P|114193324034857291|W|P|Wish List for QuickTest Pro 9.0|W|P|mmerrell@gmail.com3/15/2006 08:43:00 AM|W|P|Blogger Will|W|P|Every time I launch object spy, the first thing I do is click the 'pointing hand button'. It would be nice if I could click that button directly from the toolbar instead of having to launch object spy first.3/15/2006 08:44:00 AM|W|P|Blogger Will|W|P|It would also be nice if the Action1, Action2 folders in the test directory would be renamed when you rename the corresponding action.3/15/2006 10:48:00 AM|W|P|Blogger Marcus|W|P|Good point. That's one of the things that's going to lead us to using Functions instead of Actions for a lot of this stuff.3/13/2006 09:53:00 AM|W|P|Marcus|W|P|This is the first in a series of "Smart" methods we'll be posting. The QuickTest Pro Object Model is an admirable attempt to distill and simplify the manipulation of the different application object you'll run into when working on automated tests, but in many cases I think they just didn't go far enough. Luckily, Mercury has given us "RegisterUserFunc" as a way to "finish" the interface and make our lives easier. They make the code much more flexible, and enable us to determine our own level of "strictness" when evaluating errors at run-time. So far, we've written WebCheckBox.SmartSet, WebList.SmartSelect, and WebRadioGroup.SmartSelect. We're working on several others and will post them as we finish. void WebCheckBox.SmartSet(oCheckBox, Variant) Does it annoy the heck out of anyone else that the only way to set a WebCheckBox object is to send a string - "OFF" or "ON"? I frequently create External Actions that interact with WebCheckBox objects, and I like to specify how they are set by using Parameters. I hate that you have to specify a string value of "OFF" or "ON" as the default, and I always thought it would make more sense to send a boolean. I even had one case where I did some math, and wanted to check the box based on whether the resulting integer evaluated to zero or non-zero. I had to do 3 or 4 extra lines of code to evaluate the result and convert it to "OFF" or "ON". The WebCheckBox.SmartSet method allows you to send a Boolean, an Integer (zero or non-zero), an empty string, or (just about) any other type, and it will set the box according to how it "should" be set, based on some simple logic.
Public Function WebCheckBoxSmartSet (ByRef oCheckBox, Setting)
Start with the function declaration. Note that the first parameter is the CheckBox in question. Whenever you register a user-defined function to one of Mercury's Test Objects, the object itself is the first parameter. The second is a Variant, meaning it will accept any primitive (i.e. non-object) data type.
    'The checkbox will be turned OFF unless it is explicitly set ON
    sFinalSetting = "OFF"
The first thing to do is create a variable called "sFinalSetting", which is set to "OFF" by default. At the end of the method, we will call the native WebCheckBox.Set() method using this variable, and the bulk of the code in the method is bent on scrubbing the Setting parameter, and guaranteeing that the sFinalSetting will either be "ON" or "OFF".
    Select Case TypeName(Setting)
        Case "Integer"
            If Setting <> 0 Then
                sFinalSetting = "ON"
            End If

        Case "Byte"
            If Setting <> 0 Then
                sFinalSetting = "ON"
            End If
            .
            .
            .
        End Select
The only thing that can happen in the Select statement coming up is that sFinalSetting can be changed from "OFF" to "ON". If none of the Case statements are TRUE, it drops off the end and remains set to "OFF".
    oCheckBox.Set sFinalSetting
   
End Function
Finally, we call the native Set method, and end the function. The only thing left to do is register the method to the WebCheckBox object.
RegisterUserFunc "WebCheckBox", "SmartSet", "WebCheckBoxSmartSet", True
Now you can send it just about anything and it'll be able to make a good guess as to how the checkbox will be set. Flexible = good. Stiff as a 2x4 = bad. Here's the full code, showing all data types, which ones we've left out, and why:
Public Function WebCheckBoxSmartSet (ByRef oCheckBox, Setting)

    'The checkbox will be turned OFF unless it is explicitly set ON
    sFinalSetting = "OFF"
    Select Case TypeName(Setting)
        Case "Integer"
            If Setting <> 0 Then
                sFinalSetting = "ON"
            End If

        Case "Byte"
            If Setting <> 0 Then
                sFinalSetting = "ON"
            End If

        Case "Long"
            If Setting <> 0 Then
                sFinalSetting = "ON"
            End If

        Case "Single"
            If Setting <> 0 Then
                sFinalSetting = "ON"
            End If

        Case "Double"
            If Setting <> 0 Then
                sFinalSetting = "ON"
            End If

        Case "Decimal"
            If Setting <> 0 Then
                sFinalSetting = "ON"
            End If

        Case "String"
            If Setting = "ON" Then
                sFinalSetting = "ON"
            End If

        Case "Boolean"
            If Setting Then
                sFinalSetting = "ON"
            End If

        'If it is Empty, Null, Nothing, or Unknown, we'll let it remain "OFF"
        'We're not handling the following types: Date, Object, Unknown, 
        'Currency, or Error, they'll remain "OFF"
        'If it's anything else, it'll remain "OFF"
        'It should be sufficiently scrubbed at this point to be valid.
    End Select

    oCheckBox.Set sFinalSetting
    
End Function
RegisterUserFunc "WebCheckBox", "SmartSet", "WebCheckBoxSmartSet", True
|W|P|114226594199110540|W|P|QuickTest Pro method: WebCheckBox.SmartSet|W|P|mmerrell@gmail.com3/10/2006 11:03:00 AM|W|P|Marcus|W|P|Note: We don't know what they're going to do with this in version 9 yet, so everything here applies to QTP 8.2. I don't know why Mercury chooses not to say much about this. I had to do a lot of hunting and pecking to get to it, and the trainers at my Adv QTP course all but told me there's no way to do it. But it's probably sitting there already, right there on your hard drive. The Object Repository API allows you to do just about anything to any Object Repository you have access to - the OR files are stored in binary, and XML export of the OR won't be available until version 9 (or so we hear). Here are the prerequisites:
  • The first thing you have to do is make sure you've installed the QuickTest Plus Toolkit. If you do a custom install, you'll see that Object Repository API is one of the checkboxes.
  • After installation, you have to register the dll manually. It's located (by default) in "C:\Program Files\Mercury Interactive\QuickTest Plus\Sdk\ObjectRepositoryAPI"

    regsvr32 RepositoryUtil.dll
  • Once that's done, you should be able to see all the objects and methods from within Visual Studio, or if you're a geek like me, you'll see them in the ActiveState COM OLE browser. That's right, I use Perl to automate QuickTest Pro.

In the same directory with the DLL is a short rtf document detailing some things about the API, including sample code. It is by no means comprehensive, but the examples are good enough to get you going.

There is an AddObject method, though it's best used when you're opening one OR, reading an object from it, and putting this object into another OR. You can create objects from scratch, but there is a lot of initialization involved, and I've never had a compelling enough case to go through all that. I'm sure you could do it with some perserverance. If you do, let us know and post some code.

Here's a snippet of VB code that, once the dll is registered, you should be able to use to get an OR object, and to start manipulating and changing it:

Dim ImageObj, PageObj, RepositoryFrom, RepositoryTo

Set RepositoryFrom = CreateObject ("Mercury.ObjectRepositoryUtil")

Set RepositoryTo = CreateObject ("Mercury.ObjectRepositoryUtil")

RepositoryFrom.LoadActionRepository "E:\Temp\Tests\Test1", "Action1"

RepositoryTo.Load "E:\Temp\Tests\Default.tsr"

That gets you access to the object so you can start making method calls.

I haven't written much code in VBS for this stuff, so it's time to move to Perl. The syntax is almost the same in this kind of script, so you shouldn't have any trouble following along. This code doesn't do much, but it should give you a basic idea. In the next few days I'll be able to produce my XML-generation code. I posted it to the Mercury forum once, but I think it got buried (as usual) beneath all the crap.

use Win32::OLE;
my ($ImageObj, $PageObj);
my $Rep = Win32::OLE->new("Mercury.ObjectRepositoryUtil");
$Repository->Load('HC.tsr');

######################################################### # The function enumerates all the Test Objects under a # given root, printing out the keys and values. # This could be used to generate a hash, which can # then be used to generate test script code or an # XML dump. #########################################################

sub enumProps { my $root = shift; my ($TOCollection, $TestObject, $PropertiesCollection, $Property, $Msg); $TOCollection = $Repository->GetAllObjects(); for my $i (0..$TOCollection->Count - 1) { $TestObject = $TOCollection->Item($i); $name = $Repository->GetLogicalName($TestObject) . "\n"; $PropertiesCollection = $TestObject->GetTOProperties; for (0..$PropertiesCollection->Count - 1) { print $PropertiesCollection->Item(n); print $Property->Name . "-" . $Property->Value . "\n"; } } return $or; } my $or = enumProps('Browser'); 1;
|W|P|114201090356556968|W|P|Using the Object Repository API in QuickTest Pro|W|P|mmerrell@gmail.com3/13/2006 06:33:00 AM|W|P|Blogger Theo|W|P|I've written a small, internal use tool that allows me to move some objects around in a shared obj repository. My team lead wanted such a tool to cleanup shared repositories. The code, as you say, is a little bit of pain to traverse. I'd be happy to provide the source VB6 code I have, if the example would be useful. 'Course, I could just post the code on my site (as I have been promising to do) and provide a link.

There is some hope, from what I hear, for QTP 9.0 with the XML repository. *crosses fingers*

Theo3/13/2006 09:44:00 AM|W|P|Blogger Marcus|W|P|We are required by NDA to hold off discussing QTP 9, but my hope is to have a comprehensive review of its new features very soon after its release.3/09/2006 11:24:00 AM|W|P|Will|W|P|
It is pretty common for me to write QTP functions that take a Frame as an argument and only operate within that frame. This works out pretty well until I have to address the browser where the frame resides. I can always set a local oBrowser object to the frame's GetTOProperty("parent"), but what if I am working with a table that resides within a frame within a Browser? What if I don't know how many levels up the tree the browser is. I wrote a small function that will return the browser object from the tree of any object no matter how deeply nested.
Public Function WebObjectGetBrowser (oWebObject)
   Set oWebObject2 = oWebObject
   While Not oWebObject2.GetROProperty("micclass") = "Browser"
       Set oWebObject2 = oWebObject2.GetTOProperty("parent")
   Wend
   Set WebObjectGetBrowser = oWebObject2
End Function
Now, with the WebObjectGetBrowser function if I am operating on a table and need to do a sync before the next operation to keep an exist(0) from failing, I can use code like this.
'...operating on the object oTable...
Set oBrowser = WebObjectGetBrowser(oTable)
oBrowser.Sync
'...continue working with oTable...
This works, but it is still one line too long and creates the oBrowser object that isn't otherwise necessary. I do a lot of syncing, so I wrote another function and registered it to WebTable, Frame and several other classes.
Public Function WebObjectSync(oWebObject)
   Set oBrowser = WebObjectGetBrowser(oWebObject)
   oBrowser.Sync
End Function
RegisterUserFunc "Frame", "Sync", "WebObjectSync"
RegisterUserFunc "Image", "Sync", "WebObjectSync"
'...more register entries
Now my previous two lines can be reduced to one.
oTable.Sync
|W|P|114192509430829228|W|P|QuickTest Pro: Get the Browser associated with any Test Object|W|P|paperhat@gmail.com3/10/2006 06:29:00 AM|W|P|Blogger Theo|W|P|Any chance you guys have an RSS feed to which I might subscribe? Always good to see other points of view and insight.

Thanks,
Theo3/09/2006 09:20:00 AM|W|P|Marcus|W|P|"Desiring with supreme ardor, as pastoral solicitude requires, that the production of software in our days everywhere grow and flourish as much as possible, and that all heretical depravity be put far from the territories of the faithful, we freely declare and anew decree this by which our pious desire may be fulfilled, and, all errors being rooted out by our toil as with the hoe of a wise laborer, zeal and devotion to this faith may take deeper hold on the hearts of the faithful themselves." --Innocent VIII, Dec. 5th, 1484 (emphasis added)|W|P|114191778698362339|W|P|By Papal Decree|W|P|mmerrell@gmail.com