February 15, 2012

Using Callbacks on the Regexp.Replace function

On our testprojects, I use regular expressions most of the time for the matching or testing of strings on our Application Under Test. But sometimes I have to use the Replace method of the RegExp object. This day, I discovered an advanced usage of the RegExp.Replace() method. Let me show you with an example:

Dim replacement
Dim regEx : Set regEx = New RegExp ' Create a regular expression.
Dim myString : myString = "Cake, and grief COUNSELING, will be Available at the CoNcLuSiOn of the test."

regEx.Pattern = "\b\w+\b" ' \b = inside word boundary, \w = word character,
                          ' so this pattern matches each single word
regEx.Global = True       ' Set the scope to Global

' normally we replace like this:
replacement = "word"
MsgBox regEx.Replace(mystring, replacement)

' resulting in: "word, word word word, word word word word word word word word word."

This looks familiar I think. But it is not very sophisticated. Wouldn't it be nice if we can replace each match with a custom replacement. With the use of a Function Reference, we can!

 ' We can actually perform a custom action on replace instead of doing a fixed replace
' We do this with making a reference to a function with getref
Set replacement = getRef("Capitelize")

' We need to create a function of course with the same name and three parameters
' 1. singleMatch : the string that matched to the pattern
' 2. position : the position the singlematch string was found
' 3. fullString : the full string (same as 'myString' in this case) without _any_ replacements
Function Capitelize(singleMatch, position, fullString)

    ' Capitelize returns the string singleMatch changed with the first character
    ' in uppercase and all others in lowercase
    Capitelize = ucase(left(singleMatch,1)) & lCase(mid(singleMatch,2))
End Function

MsgBox regEx.Replace(myString, replacement)
' This results in: "Cake, And Grief Counseling, Will Be Available At The Conclusion Of The Test."

Nice huh? Now we can match for a date and replace it on the fly by the correct format. We can replace straight quote characters for curly quotes with the correct orientation with much more ease, or only do a replace if it is within a certain character range in the string. "All new possibilities arise!"

January 27, 2012

Exist or be Created!

Just encountered a collegue having difficulties ensuring if a folder exists. And if a folder does not exists then it has to be created in its full path, not only the lowest level folder. The code was bloated with if/then’s, instr(), instrrev() and left()/right() functions. Even the lowest level folder name was hardcoded.

To help him out, we could go for the fancy recursive solution to show off our 1337 programming skillz, but I decided to just do it with a simple loop. Enjoy.

Public Function EnsureFolder(fPath)
    dim fso, fols, fol, pathBuild
    set fso=CreateObject("Scripting.FileSystemObject")

    fols = split(fpath, "\")

    For each fol in fols
        If fol = "" Then exit for
        pathBuild = pathBuild & fol & "\"
        If Not fso.FolderExists(pathBuild) Then
            Call fso.CreateFolder(pathBuild)
        End If

    EnsureFolder = pathBuild
End Function

December 13, 2011

QTP and Method Chaining disclosed

With method chaining, you can chain object methods to an object in order to change it. It is the default paradigm of JQuery. After using it a while with JQuery, I figured out it is an extremely usefull method to use with QTP.

If you use QTP long enough, you start to create your own function. But because VBScript doesn't allow optional parameters in homebrew functions, existing functions become clumsy with all types of parameters you don't use most of the time.
I remember using functions like findTextInTable(oTbl, txtToFind, startRow, endRow, startColumn, endColumn, byRef foundRow, byRef foundColumn) where each parameter could also be an array or contain signal characters. This results in a function call like this:
findTextInTable(myTable, array("account xyz", "!customer \d+"), 4, "", "", 5, foundRow, "")
So far for self-explanatory code.

When you use object chaining, you use only the parameters you need AND your code will become self explanatory. The code above will become:
xTable.addFindText("account xyz").addRegexFindText("customer \d+").startRow(4).endColumn(5).getFindRow(foundRow)

Of course this need some programming skills to build this correctly into a class, so let's start with an easy example: A string wrapper. When we create the class for the stringwrapper, we use functions for the methods. Functions can return primitives or objects, and with each object call, we return the object itself. You can also use "Get Property", but that will restrict you in the amount of parameters you can pass with your method to be exactly the same as the Let/Set Property.

Class cls_xString

    Private value_

    Public Function SetValue(v)
        value_ = v
        Set SetValue = me
    End Function

    Public Default Function getValue()
        getValue = value_
    End Function

    Public Function ToUpper()
        value_ = uCase(value_)
        Set ToUpper = me
    End Function

    Public Function Append(v)
         value_ = value_ & v
        Set Append = me
    End Function

    Public Function Prepend(v)
        value_ = v & value_
        Set Prepend = me
    End Function

    Public Function Surround(v)
        value_ = v & value_ & v
        Set Surround = me
    End Function

End Class

We use another trick with the getValue; it is the default return value if the string class is called without a Set. To make the class usable globally, we need an initiator function that returns an object of class xString:

Public Function [new xString](initialValue)
    Set [new xString] = new cls_xString
    call [new xString].SetValue(initialValue)
End Function

And now we can create some testcode:
Dim myString
Set myString = [new xString]("W00t, Method Chaining is really Working!")
msgbox myString.ToUpper.Prepend("(").Append(")").Surround("'")

This will result in an editbox with text '(W00T, METHOD CHAINING IS REALLY WORKING!)'
The method are parsed from left to right; first the string is transformed to uppercase, then prepended by (, appended by ) and finally surrounded by '.

Other usage:
- Narrowing down a collection and get a single object
Set linkCollection = [new linkCollection](oPage)    ' Get all links from a page
Set myLink = linkCollection.withRegexText(".*Help.*").Index(3)

Or do directly an action on that object:

- Narrowing down collections and do actions on them:
Set editCollection = [new editboxCollection](oPage)    ' Get all editboxes from a page
' Set all editboxes that are visible, enabled and empty with a value
editCollection.withProperty("visible:=true").withProperty("enabled:=true").withValue("").setValue("Foo Bar!")

- Do a custom action on an object
Private Function DisplayToString(object)
    MsgBox object.ToString()
End Function
Set editCollection = [new editboxCollection](oPage)    ' Get all editboxes from a page
' Show the toString value of all editboxes that are not visible on a page

Method chaining is very versatile, with the trap of being used to much. Actually it does pretty much the same using the 'with' keyword with objects. It can make your code more clear, or just more obfuscated. Use it wisely.

May 11, 2011

Curious QTP behaviour when using on error: Proceed to Next Step

Because we build our own framework, the on error settings in QTP are default on "Proceed to Next Step" for our unattended continuous script. So when a test is scheduled in the middle of the night, the test won't stall on errors like a function with an argument too few or many, an array that is out of bounds or an accidentally undeclared variable (please build an undeclared variable checker HP, the Patterson boys could do it years ago, you can do it too!).

It works fine most of the time, although debugging is done with an "attended run" script and all possible error detection on: Popup Messagebox and Option Explicit everywhere.

The curious behaviour happened in an Select Case. Consider this code:

Option Explicit
Dim myCondition : myCondition = 2
Select Case myCondition
    Case 1 MsgBox "Wooh! Condition 1"
    Case 2 MsgBox "Displaying undeclared variable: " & undeclaredVariable
    Case 3 MsgBox "Meh, Condition 3"
End Select 

When you run this code in attended mode, you'll get a nice error nagging about the undeclared variable.
However, when you run this in unattended mode, you'll get a messagebox with the text: "Meh, condition 3"

QTP is doing what it says: Continue with the next step. But the step is not fulfilling the Select condition and program technical a major sin!
Because I ran this accidentally in unattended mode, it took me some while to get a finger behind the error. In the end, I learned to always debug in attended mode.

May 10, 2011

Stuttering Firefox 4; Solved!

Haha! I solved my stuttering, stalling and staggering FireFox 4. An annoying problem that let FF halt for a second while scrolling, typing and selecting. 
The solution: just rename or delete the sessionstore.js file (don't worry, FF will create a new one for you) and restart FireFox. Firefox works as a sunshine right now.

For OSX users, it is located here: /Users/{username}/Library/Application Support/Firefox/Profiles/{randomkey}.default/sessionstore.js

May 5, 2011

Sneller zoeken op Marktplaats

Het zoeken naar spulletjes op marktplaats kan soms wat tijdrovend zijn. Zo ben ik al een tijdje op zoek naar een bankierslamp die ook wel een notarislamp genoemd wordt. Als eerste zoek je dus op "bankierslamp" en krijg je 10 hits. Na die een beetje doorgelopen te hebben zoek je vervolgens op "notarislamp" met 16 hits. Naast dat er 4 dubbele in de lijst staan, wil je ook nog even terug naar de zoekactie op bankierslamp terwijl je niet zo slim was geweest de notarislamp te zoeken in een aparte tab. Inefficiƫnte ellende alom.

Booleaanse logica
Vandaar dat ik wat ben gaan experimenteren met de marktplaats zoek functie (je kan ze ook deels ontdekken via de geavanceerde search, maar dat zit weer 1 klik verder, dus wie komt daar nu?). En ontdekte het volgende:
Marktplaats zoekt standaard met AND. Dit kan je overrulen door OR. Ik had dus moeten zoeken op "bankierslamp or notarislamp": 22 hits.

Dit kan je ook combineren met een AND criteria, bijvoorbeeld bij het zoeken op skeelers oftewel inline skates. Om deze zoekterm in te voeren zoek ik op "skeelers or inline skates". De AND criteria tussen inline en skates heb ik hier niet ingetikt, want dat marktplaats standaard. Helaas zoekt marktplaats dan op alle termen waar skeelers of inline in voorkomt, en daarnaast in elk geval skates.
Oplossing: Haakjes! Zoeken op "skeelers or (inline skates)" zorgt ervoor dat marktplaats begrijpt wat je wilt.

Een andere mogelijkheid is uitsluiten met NOT. Zo zocht ik een sleutel om een klok mee op te winden. De zoekterm "sleutel klok" levert resultaten op, maar helaas nog heel veel in combinatie met sloten. Om deze uit te sluiten zoek je op "sleutel klok not slot", et voila.

Je kan dit natuurlijk net zo ingewikkeld maken als je wilt, mijn huidige zoekterm voor de gewenste lamp ziet er nu als volgt uit:
"((notaris or bankiers) and lamp) or notarislamp or bankierslamp", waarbij ik dus ook de anglicismen "notaris lamp" en "bankiers lamp" vind: 31 hits.

Sterretje en spelling

Op marktplaats komt lui van alle allooi, ook degene die het niet zo nauw nemen met onze geliefde spelling. Skeelers wil je ook wel eens vinden onder de naam skealers en inline skates als inlijn skeets, skeates, skeats of skaats. Je kan hier een mooie OR term mee bouwen, sneller is het gebruik van het sterretje.
Op marktplaats heb ik een wildcard ontdekt en gelijk een hele krachtige: de spin (*) die matcht op alles behalve de spatie.
Om niets te missen van het buitenspeelgebeuren zoek je op "sk*lers or (inl*n* sk*t*s)".

Gelukkig is marktplaats zelf ook slim. Het heeft een library van veelgebruikte termen. Zoek je dus op skeelers, dan krijg je ook automatisch alle zoektermen voor alle andere vormen van inline skating terug. Helaas zoek je op marktplaats vaak niet altijd op een veelgebruikte term en dan is het wel superhandig als je met een beetje slimme zoekterm snel resultaten kan vinden.

Mocht je nog meer handige zoektips hebben, dan hoor ik ze graag in de comments!

December 1, 2010

Using the Levenshtein Distance to get the best match from a list

A while ago, I created a document in which I explained how to use the Levenshtein Distance to get the best match from a list of options. Well, the article has its own introduction so if you are interested; start reading!