Limitations of RegisterUserFunc

Article Tools

In previous articles (e.g., A Fresh Look on RegisterUserFunc, Assa, 2008) we have discussed one of the most powerful features provided by QuickTest Professional (QTP): “the ability to override methods of [test] object classes with custom functions tailored to serve specific purposes” (Bar-Tal, 2008b). Whilst the advantages of this feature were already reviewed in the articles mentioned and quoted above, some limitations are seemingly undocumented in the QuickTest reference. This brief article will describe two limitations of this mechanism as emerged from an extensive research I conducted recently.

 

Limitation 1: Number of Function Arguments

When defining a function that overrides a method, it must have the same signature. This means that the overriding function cannot have a number of arguments different than the overridden method. The snippet below shows examples of correct and incorrect versions of overriding functions

'Native method signature
Function Exist(ByRef obj, ByVal intSeconds)
 
'Example 1: Overriding (registered) function (correct)
Function ExistEx(ByRef obj, ByVal intSeconds)
 
'Example 2: Overriding (registered) function (correct)
Function ExistEx(ByRef obj, ByVal objDictionary)
 
'Example 3: Overriding (registered) function (incorrect)
Function ExistEx(ByRef obj, ByVal intSeconds, ByVal strRetries)
 
'Example 4: Overriding (registered) function (incorrect)
Function ExistEx(ByRef obj)

Snippet 1. A native method (Exist) and its respective correct/incorrect custom overriding function signatures

 

One possible workaround is to pass as one of the function arguments a Dictionary or an Array (a dictionary is preferable to avoid argument order mistakes). However, this is impractical in the case of a method that accepts only a test object as the only argument (for example, the Browser Sync method), since the syntax of the automation model requires one to use the dot operator (e.g., Browser(“GMail”).Sync) to get access to the test object’s methods, so there is no room for the additional argument or for wrapping the object together with additional arguments with a Dictionary.

One possible reason for this limitation might be that as the RegisterUserFunc is aimed at enabling one to change the basic method functionality offered by QTP, it was intended only to enable internal functional modifications without allowing for interface changes (i.e., the function signature). This limitation can also be viewed as an advantage, because it constrains the changes allowed for infrastructure methods. This may allow for code that is more portable between projects and less complex as the interface remains fixed. However, I do not share this view, since it does not allow for a straightforward implementation of method overloading, which is the way true OO languages enable polymorphism, which is a powerful technique (in a future article I will show how this can be accomplished despite the limitations outlined here).

 

Limitation 2: Registered Functions Interoperability

When a registered function includes a call to another registered function, care must be taken about the correct syntax. While usually one would put a Call obj.[native method]([arg1], [...], [argn]) to call a registered function (such that no changes to existing calls in your code should be done), you might get a VB Script Runtime Error: “Type Mismatch” during your run session. It seems that the mechanism has an inherent limitation (perhaps it is a bug?) as it is unable to trace the call from a registered function through the object class to another registered function. The only solution I found in such a case is to write the code in a “scripting” (rather than OO) fashion, passing the test object as the first argument, as follows: Call [custom method](obj, [arg1], [...], [argn]). See a real life example in the following snippet.

Public Function IsEnabled(ByRef obj, ByVal intTimeoutMSec)
    IsEnabled = obj.WaitProperty("disabled", 0, intTimeoutMSec)
End Function
 
Public Function WaitPropertyEx(ByRef obj, ByVal strPName, ByVal varPValue, ByVal intTimeoutMSec)
    If obj.Exist(0) Then
        WaitPropertyEx = obj.WaitProperty(strPName, varPValue, intTimeoutMSec)
    Else
        WaitPropertyEx = False
    End If
End Function
 
Public Function SetEx(ByRef obj, ByVal strValue)
    If obj.IsEnabled(Environment("default_timeout_msec")) Then
        obj.Set strValue
        SetEx = True
    Else
        Reporter.ReportEvent micWarning, "Set", "Object is disabled"
        SetEx = False
    End If
End Function
 
RegisterUserFunc "JavaEdit", "IsEnabled", "IsEnabled"
RegisterUserFunc "JavaEdit", "WaitProperty", "WaitPropertyEx"
RegisterUserFunc "JavaEdit", "Set", "SetEx"

Snippet 2. Three interdependent registered functions (leading to runtime error)

 

The snippet shows three registered functions. Two of them are overriding versions of the QTP native methods (Set and WaitProperty) and one is an extension of the object’s functionality that checks if the object is enabled with a timeout. The overriding functions help us catch situations that are frequently found during a run session, while the extension function wraps a frequently used call to the WaitProperty function, one that helps the script better synchronize. For example, such synchronization might be crucial in case the Edit control changes its state from disabled to enabled following a GUI operation like marking or unmarking a checkbox.

The sequence of calls between the above functions is as follows. After the registration, when we call the obj.Set method, actually the SetEx function will be called. The SetEx method will then first check if the object is enabled, obviously by calling the IsEnabled extension method, just before actually attempting to change the value of the Edit control. The IsEnabled function, in turn, will call the object’s WaitProperty method and, similar to the case of the Set function, it will actually call the registered WaitPropertyEx function.

This seems pretty logical and one might not suspect that there is some flaw in this design. However, my experience has shown that attempting to implement such a scheme leads to a runtime error (Type Mismatch). Extensive fiddling with such functions led me to the conclusion that QTP is unable to trace correctly the sequence of calls when one registered function calls another.

There is, however, a way to make such functions interoperability actually work. The (awkward) solution is shown below.

Public Function IsEnabled(ByRef obj, ByVal intTimeoutMSec)
    IsEnabled = WaitPropertyEx(obj, "disabled", 0, intTimeoutMSec)
End Function
 
Public Function WaitPropertyEx(ByRef obj, ByVal strPName, ByVal varPValue, ByVal intTimeoutMSec)
    If obj.Exist(0) Then
        WaitPropertyEx = obj.WaitProperty(strPName, varPValue, intTimeoutMSec)
    Else
        WaitPropertyEx = False
    End If
End Function
 
Public Function SetEx(ByRef obj, ByVal strValue)
    If IsEnabled(obj, Environment("default_timeout_msec")) Then
        obj.Set strValue
        SetEx = True
    Else
        Reporter.ReportEvent micWarning, "Set", "Object is disabled"
        SetEx = False
    End If
End Function

Snippet 3. Three interdependent registered functions (working version)

 

As is shown in Snippet 3 above, changing the calls to other registered function such that the overriding functions are explicitly called does the trick. This way we can enjoy of both worlds: enjoy the OO-like coding style by registering the functions as usual, and also enjoy full interoperability among the registered functions.

Though I have no inside knowledge of the way the HP (Mercury) people have implemented the function registration mechanism, I will attempt to give my black-box tentative guesses as to the root cause of this limitation. My analysis suggests that the registration mechanism seems to hold a reference list that indicates which function should be called instead of the native method, but this list can be used only to one level depth. In other words, after reaching the scope of an overriding function, the registration retrieval mechanism either stops checking for such references until exiting the function or has some trouble with the cross-reference. I assume that HP (Mercury) had no intention for, or did not foresee, such complex use in practice. It is my view that originally, the basic functionality of the function registration mechanism only aimed at enabling developers to add custom functionality to the basic methods. For example, this mechanism is useful to enhance the control over the test flow with custom error handling and verifications, as well as with custom reporting. However, it may well be that the behavior of the registration mechanism was not designed (and hence also not tested) with regard to the interoperability issue described above.

 

Summary

This short article reviewed two apparent limitations of QuickTest Professional’s function registration mechanism. It was indicated that an overriding function must have the same signature (number of arguments) as the native method, and that the implementation of overridden functions interoperability requires a special treatment to make it work. In a future article I plan to provide a workaround for the first limitation to enable the implementation of a (true) kind of polymorphism. As to the second limitation, a simple workaround was suggested that just changes the code writing style from OO-like to scripting-like.

 

Comments, questions and suggestions can be posted here below or sent to: meir.bar-tal@advancedqtp.com

Previous postWriting QTP Tests in Visual Studio Next postThe Undocumented DeviceReplay (Translated to Russian)

Related Posts

Post Your Comment

You must be logged in to post a comment.