Posts Tagged ‘VBScript’

I sure could use an “implements interface” in VBScript right about now…

Wednesday, June 4th, 2008

In the last post, I showed a basic code snippet and described a little about how we implement our test scripts for automation. This post will go more into detail about how the underlying objects are implemented.

(more…)

QTP 9.5 feature review - Web Extensibility Add-ins

Tuesday, January 29th, 2008

I’ve now had a few days with Quicktest Pro 9.5, and most of that time has been spent with the new Web Extensibility feature. This feature has huge potential. I look forward to seeing how it is accepted by QTP programmers.

The basic idea is that you can create your own QTP add-in. When your add-in is loaded, Quicktest will recognize objects you defined in your add-in and make your custom methods available for them.

After I went through the tutorial in the QuickTest 9.5 documentation, I jumped into creating a Web add-in for a simple DIV in the product I am testing. I called the extension auLogin and the object auLoginForm. The first method I added was SubmitLogin. All it does is click the Login button. This seemed to be an unambitious task, but it proved more difficult than I counted on because I didn’t learn as much as I thought through the tutorial. It is also something much easier done with the single line Browser().Link(”html id:=Submit1_a”).click, but this exercise is about learning, not being efficient.
(more…)

Test Design Studio

Wednesday, September 5th, 2007

I have spent most of the last few weeks working on QTP libraries, but very little of that time was spent wrestling with Mercury’s beast of an IDE. Aside from actually running my tests, I use QuickTest for very little these days. Instead, I am using a new editor called Test Design Studio. Everybody who uses QuickTest Pro’s expert view should try Test Design Studio. Once you do, you won’t go back to QTP.

Before we get too far into this, you need to know that Test Design Studio (TDS) isn’t a complete QTP replacement. TDS replaces QuickTest’s editor and library management, but it doesn’t actually run the tests. You use TDS to write tests and QTP to run them.

I could go on for pages about TDS’s features, but I won’t. You can see all of that at Patterson-consulting.net. Instead I will give you my first-ever top 10 list.

Top Ten Test Design Studio features that QTP is missing

10. Edit two tests at once – QTP is already on version 9.2 and they still haven’t figured out that users programmers might want to copy text from one test to another.

9. Excellent technical support – This isn’t technically a product feature, but it makes a big difference in using the product. I have submitted my share of help requests and bugs to Mercury, and I don’t think I have ever had an issue resolved within a day…rarely within a week. Patterson Consulting is another story. They will bend over backward to help you. Send an email to support or post in their help forum, and you have a good chance of getting a resolution within the hour. I haven’t called their support number, but I think I’m safe in assuming that if I do I won’t have to navigate through multiple levels of tech support before getting a resolution. The same can’t be said for Mercury.

8. Documentation Wizard – Automatically generate a help file from your code comments. If you maintain a large library, you know what a pain the documentation can be. TDS’s Documentation Wizard takes 90% of the work out of it.

7. IntelliSense for every class and function in your library – Don’t confuse this with QTP’s IntelliSense. TDS goes far beyond what QTP offers. With QTP, if you type Browser(oDescription), you will get IntelliSense for the various methods registered to the Browser type, but what if you have a function that returns a Browser object? You are out of luck with QTP, but TDS is able to tell that the function will return a browser. For instance, I often call the function BrowserDefault, which returns my default browser. With TDS, as soon as I type the period after the function name, I see all of Browser’s registered methods.
TDS Intellisense
I can’t do justice to TDS’s IntelliSense here. You’ll have to see it for yourself.

6. Customizable menus – You can put almost anything you can think of on any toolbar or menu. I recently worked on customizing the xslt file that defines the layout of TDS’s Object Browser. Instead of restarting TDS to try out changes, I added the “Reload ObjectBrowser.xslt” command to my tools menu. For most applications, that would be an internal-only call, but it looks like Patterson Consulting has made a point to make everything possible available to their users.

5. Hooks for external tools – There are still a few things that Test Design Studio doesn’t do. For that, there are external tools. For instance, I have an external tool set up that grabs all the vbs files in my project and adds them as resources on the active test. Another external tool is a vbs script that will open and run the current test in QTP.

4. Customizable Hot Keys – One of the few things I don’t care for in TDS are the default hot-keys assignments, but thanks to the customizable hot keys, I don’t have to remember that Ctrl+E,C is the command to comment out a block of text. It took about five minutes to make TDS match the Eclipse hot keys that I am used to.
Also, by combining this with the external tools mentioned above, I just have to press F5 to run the current test in QTP.

3. Code snippets – If you use Visual Studio, you already know what these are. I had not used code snippets before TDS, but I have come to find them very useful. I’m not sure that I can adequately explain code snippets in this post, so I’ll let Patterson explain it.

2. Macros – If you like to do the same thing over and over and over and over, this isn’t for you.

1. Search and replace throughout the project – Search and replace throughout the project is huge. Shortly after I started evaluating TDS, I changed the signature on a function. It literally took less than a minute to find and update hundreds of calls to that function spread over dozens of files.

There you have my first-ever top ten list. I hope you enjoyed it. I probably shouldn’t have numbered them because I just wrote them in the order that I thought of them. I also ran out of numbers before I ran out of features. If the list were a bit longer I would have included Object Browser, collapsible code outlining and xml-comments.

Introducing SIFL

Thursday, August 23rd, 2007

The Inquisitors are proud to introduce a bit of code we have been working on. We are calling it the Software Inquisition Foundation Library for QTP, but you can call it SIFL (pronounced like "sniffle", but without the "n").

SIFL is a collection of QTP functions that we have been using for a while as a base framework for our QTP tests. Having eschewed checkpoints, the object repository, and the idea of record-and-playback testing, we needed a uniform way to write tests. SIFL is what we came up with.

Before I get into the details, let me show you a sample test to give an idea of what we are looking at. This is a quick example that uses the all-too-familiar Mercury Tours web site. We will open the site, navigate to the cruises page and make sure the departure time for the Skagway cruise is 5 pm.

Visual Basic:
  1. BrowserDefault.Navigate "http://newtours.mercuryinteractive.com"
  2. BrowserDefault.Link("innerText:=Cruises").Click
  3. Set oTable = BrowserDefault.WebTable("innerText:=Cruise Itinerary.*")
  4. sDeparture = oTable.GetXWhereYisString("Departure", "Port of Call", "Skagway, Alaska")
  5. assertStringEquals sDeparture, "5 pm"

If you are used to seeing QTP code, that should look somewhat – but not entirely – familiar. I’ll go line-by-line to show you what is happening.

BrowserDefault.Navigate "http://newtours.mercuryinteractive.com"

The “BrowserDefault” function returns the current default browser. Because we have not already defined a default browser, it will open a new instance of IE for us and make it the default for future calls to BrowserDefault. The “Navigate” portion of that line is just the browser object’s standard navigate method. This causes the newly opened instance of IE to navigate to the Mercury Tours site.

BrowserDefault.Link("innerText:=Cruises").Click

Here we are calling the default browser again. This time we are clicking a link in the default browser.

Set oTable = BrowserDefault.WebTable("innerText:=Cruise Itinerary.*")

Once again, this is pretty standard. We are just assigning this web table instance to oTable. This step isn’t really necessary, but I wanted to keep the next line short

sDeparture = oTable.GetXWhereYisString("Departure", "Port of Call", "Skagway, Alaska")

Here we are using the SIFL method “GetXWhereYisString”. To put it simply, this gets the data from the “Departure” column in the row where the “Port of Call” column is “Skagway, Alaska”. If everything is kosher on the site, sDeparture should be a string with the value “5 pm”.

assertStringEquals sDeparture, "5 pm"

Here we assert that the string sDeparture equals “5 pm” and note the results in the log. If the assertion is correct, it passes, if not, it fails. Note the fine control we have over the types involved in this homemade "checkpoint". This is what allows us to execute tests across continents without a complicated source tree.

I am writing this on a plane that is about to land, so that's it for now. Expect to see more on SIFL in the next few days along with a link to download it for yourself.

More on Named Optional Arguments in VBScript

Saturday, December 9th, 2006

I have been extensively using the method for vbscript optional arguments that I described in March. I want to expand on this a little with some recent changes we have made to how we use this.

Functions that use this method have an aOptions parameter in their signatures. That means, that even if we are not going to specify any optional arguments, we have to pass something, so we pass a Null. The problem is that I end up passing Null most of the time, and I don't like having to do that - especially for our smoke tests where are code is visible to manual testers and developers.

For example, I will use a simple function called WebEditFillByID that simply finds a WebEdit based on it's html id and fills it with the text supplied. There is also an aOptions parameter in its signature, but I don't usually set any options. So, when I am filling out a form it looks something like this.

Visual Basic:
  1. WebEditFillByID "firstName", "Bob", NULL
  2. WebEditFillByID "lastName", "Saget", NULL
  3. WebEditFillByID "ssn", "208-49-8168", NULL

That doesn't look too bad, but it would be much more legible without the Nulls in the way. So, to get aOptions out of WebEditFillByID's signature I create a new function called WebEditFillByIDAdv that is identical to WebEditFillByID. Directly beneath WebEditFillByIDAdv, I put a second function named WebEditFillByID with the same signature minus the aOptions. All WebEditFillByID does is call WebEditFillByID with the same arguments it was passed and adds a Null for the aOptions in WebEditFillByID's signature.

Now I can fill the same form without the Nulls.

Visual Basic:
  1. WebEditFillByID "firstName"  ,       "Bob"
  2. WebEditFillByID "lastName"   ,       "Saget"
  3. WebEditFillByID "ssn"        ,       "208-49-8168"

In my opinion, this is much more pleasant to deal with. For reference, here are the new functions.

Visual Basic:
  1. Public Function WebEditFillByIDAdv ( sID, sValue, aOptions )
  2.     Set oOptions = GetOpts( _
  3.         ARRAY( _
  4.             "oBrowser", BrowserDefault(NULL) ), _
  5.         aOptions)
  6.     Set oBrowser = oOptions("oBrowser")
  7.     oBrowser.WebEdit("html id:=" & escapeHTMLid(sID)).Set sValue
  8. End Function
  9.  
  10. Public Function WebEditFillByID ( sID, sValue )
  11.     WebEditFillByIDAdv sID, sValue, NULL
  12. End Function

To read more about named optional arguments in vbscript, see my previous post about it.

Classes, Objects, and QuickTest Pro 9

Wednesday, September 20th, 2006

VBScript is considered to be "object-based", meaning you can use classes, and those classes can have methods and members, but you can't get nearly the kind of re-use and extensibility you could out of a real OOP language. You don't get inheritance, you don't get polymorphism. In short, you get encapsulation, and that's about it. To me that's pretty important, the abilty to wrap up a bunch of common functions and attributes into a single entity called "an object" and throw it around kinda like you would in Java or Ruby. It allows me to create (instantiate) an object in line 10, then count on it being persisted three levels deep and 200 lines later, and never need to look inside the box to find out how it works.

I'll give some code first.

I'm using VBScript classes right now to model our AUT and make the code prettier. For example, if I want to create a user, it looks like this:

Visual Basic:
  1. LoginProfile "ADMIN"
  2. Set user = NewUser()
  3. user.create(“marcus”, “password”)
  4. user.addRole(“Administrator”)

This snippet opens a browser, logs in as an administrator, creates a new User object (basically a variable that holds attributes about a user), then uses that data (if I had supplied any) to actually create user from within the GUI. After the user is created and declared "valid", I call the "addRole()" method off of the same user. The User object knows how to fill out all the form fields. It knows how to add a role through the security interface. How? That's the beauty: I don't have to know. Someone else can develop the library of objects and maintain it, and all I have to know how to do is invoke it.

I know, I know, it's the same with functions. Yes, it is. But here they're all wrapped up together. Consider this:

Visual Basic:
  1. Set userGuest = NewUser()
  2. userGuest.create(“guest”, “password”)
  3. userGuest.addRole("Guest")

Later, if I want to know the name of that user, I can just ask the object:

Visual Basic:
  1. sName = userGuest.FirstName

Rather than have to code up a function to go and look it up somewhere. It's the persistance of data between states that I'm most interested in. The lack of inheritance and polymorphism is bad, but the most important problems are solved.

Here's the problem: in QTP, you don't just have the limitations of VBScript, you also have some severe limitations built in to (or rather "excluded from") QTP.

The class declaration (in a file called User.vbs) looks like this:

Visual Basic:
  1. Class User
  2.     Private sUserID
  3.     Public Function Init ( aOptions )
  4.         'do some proprietary stuff
  5.     End Function
  6.     Public Function create ( sUserName, sPassword )
  7.         'do some proprietary stuff
  8.     End Function
  9. End Class
  10.  
  11. Public Function NewTigerUser ( aOptions )
  12.     Set NewTigerUser = new User
  13.     NewTigerUser.Init ( aOptions )
  14. End Function

The first limitation:
That last function is there because QTP doesn't have the ability to use classes from external vbs files within the function libraries. You must give the test script access to the above function, which has access to the class, in order for your test script to be able to "see" the class.

The second limitation:
There's a bug in QTP where you can't, no matter what you do, use the debugger to step through class functions. That was almost a showstopper for me when doing this, before I learned just how few of me teammates use the debugger. When I found out I was the only one I decided that for the greater good, OO was a good direction to go in, if for no other reason than to make the code seem more familiar to us non-VBScript types.

So, in short, it's possible for us to get what we need to when we confront OO concepts, but we're severely limited by VBScript as well as Mercury. I'll submit defects on both of these issues, but I'm not sure whether I'd be optimistic about my chances. We seem to be the only ones trying to do this. Let me know if you know different!

QuickTest Pro (9.0 or 8.2) method: WebRadioGroup.SelectByText

Tuesday, March 21st, 2006

We've had to solve this problem quite a bit, so Will finally turned it into an object method.

Here's the problem:

Milk

Butter

Cheese

You want to be able to say "Select the 'Butter' 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. "AFED-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

Visual Basic:
  1. Public Function WebRadioGroupSelectByText ( ByRef oRadGroup, sSelectText, _
  2.                                                               sWhereOpt, sIndexOpt )
  3.     If isNull(sWhereOpt) Then
  4.         sWhereOpt = "afterEnd"
  5.     End If
  6.  
  7.     If isNull(sIndexOpt) Then
  8.         sIndexOpt = 0
  9.     End If
  10.  
  11.     sMatches = Array()
  12.  
  13.     Set oParent = oRadGroup.GetTOProperty("parent")
  14.     radName = oRadGroup.GetROProperty("name")
  15.     Set oRadioButtons = oParent.Object.GetElementsByName( radName )
  16.  
  17.     For each oElement in oRadioButtons
  18.         sName = oElement.getAdjacentText("afterEnd")
  19.         If sSelectText = sName Then
  20.             ArrayPush sMatches, oElement.Value
  21.         End If
  22.     Next
  23.  
  24.     If Ubound(sMatches) <0 Then
  25.         ' TODO: report error...item doesn't exist in list.
  26.         Exit Function
  27.     End If
  28.  
  29.     If Ubound(sMatches) <sIndexOpt Then
  30.         sIndexOpt = Ubound(sMatches)
  31.         ' TODO: report warning...index doesn't exist.  selecting last match
  32.     End If
  33.  
  34.     sSelectValue = sMatches(sIndexOpt)
  35.     oRadGroup.Select sSelectValue
  36.    
  37. End Function
  38. RegisterUserFunc "WebRadioGroup", "SelectByText", "WebRadioGroupSelectByText"

Now, to select the radio button I want, I just to this:

Visual Basic:
  1. 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.

Named Optional Arguments in VBScript

Thursday, March 16th, 2006
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.

PERL:
  1. printsentence(
  2.     'Subject'   => 'The quick brown fox',
  3.     'Predicate' => 'jumps over the lazy dog.'
  4. );
  5.  
  6. sub printsentence {
  7.     my %args = @_;
  8.     print "$args{'Subject'} $args{'Predicate'}";
  9. }

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.

Visual Basic:
  1. printsentence ARRAY( _
  2.     "Subject",     "The quick brown fox", _
  3.     "Predicate",   "jumps over the lazy dog." _
  4. )
  5.  
  6. Public Function printsentence(aOptions)
  7.     Set oOptions = BuildDict(aOptions)
  8.     MsgBox oOptions("Subject") & " " & oOptions("Predicate")
  9. 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:

Visual Basic:
  1. printsentence ARRAY( _
  2.     "Subject",    "The quick brown fox", _
  3.     "Predicate""jumps over the lazy dog" _
  4. )
  5.  
  6. Public Function printsentence(aOptions)
  7.     Set oOptions = GetOpts( ARRAY( _
  8.         "Subject",     "Coke", _
  9.         "Predicate",   "is it", _
  10.         "Punctuation", "!" _
  11.     ), aOptions)
  12.  
  13.     MsgBox oOptions("Subject") & " " & oOptions("Predicate") & _
  14.         oOptions("Punctuation")
  15. 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

Visual Basic:
  1. Public Function WebCheckBoxSelectByText ( ByRef oCheckBox, sSelectText, aOptions )

The code for BuildDict and GetOpts is here:

Visual Basic:
  1. Public Function GetOpts(ByRef aDefaults, ByRef aCustom)
  2.     Set oDefaults = DictBuild(aDefaults)
  3.     Set oCustom = DictBuild(aCustom)
  4.     For Each vKey in oDefaults
  5.         If oCustom.Exists(vKey) Then
  6.             oDefaults.Item(vKey) = oCustom.Item(vKey)
  7.         End If
  8.     Next
  9.  
  10.     Set GetOpts = oDefaults
  11. End Function

Visual Basic:
  1. Public Function DictBuild(ByRef aArray)
  2.     bWord = True
  3.     Dim oDict
  4.     Set oDict = CreateObject("Scripting.Dictionary")
  5.     Dim sWord, sDef
  6.     If isArray(aArray) Then
  7.         For Each sString in aArray
  8.             If bWord Then
  9.                 sWord = sString
  10.                 bWord = False
  11.             Else
  12.                 sDef = sString
  13.                 oDict.Add sWord, sDef
  14.                 bWord = True
  15.                 sWord = NULL
  16.                 sDef = NULL
  17.             End If
  18.         Next
  19.     End If
  20.     set DictBuild = oDict
  21. End Function

Technorati Tags: , , ,