Sunday, 17 January 2010

Debugging Flex/QTP Issues

Once you have setup a working QTP build, debugging problems can be really tricky. This is mainly because the errors that are reported (if any) aren’t particularly helpful and the documentation is pretty slim. It can also be pretty hard to know how to tackle QTP performance issues. This post will explain some of the techniques I’ve found useful in debugging and solving these issues.

When the Flex/QTP integration encounters an error it traces the problem rather than logging it. This means that to debug problems you will need to connect your QTP enabled application to a debugger in order to see the trace statements. This can make everything run very slowly, so I suggest enabling the logging of trace to a log file which you can examine when you encounter a problem. This can only be done on a debug player, full details can be found here. The second place you can look is QTP’s logs, although I have generally not found them to be very helpful. An explanation of how to enable these logs can be found here.

The most common problem I've encountered while recording or replaying QTP scripts is the object you are trying to identify simply shows up as a “WinObject” or “ActiveX Object”. This happens because QTP cannot detect the hierarchy of object within Flash Player so it simply identifies it an the ActiveX object, i.e. the ActiveX Flash Player. When you see this happen there should be trace of the error message that has occurred for you to check. Most frequently the error message will be “2 matches for criteria {...}”, this generally means that QTP found 2 or more objects that appear to be the same. By default QTP will only use the type of the object, the automationName and the object's location to uniquely identify it, so if multiple objects exist that are the same type and have the same or no automation name then this error will occur. Remember that QTP does not necessarily see the same hierarchy of objects that actually exist in your view. Components that are under different parents may appear to be under the same parent to QTP if the common parent has its showInAutomationHierarchy set to false. This problem can easily be solved by picking unique automation names and including extra containers in the automation hierarchy to ensure uniqueness.

When built with automation support, mouse clicks only propagate through one level of the view hierarchy. This means that if you have a container on top of another component, say a button, you will not be able to press the button, and hence record clicks on it.  Details of this "bug" can be found here. One way around this is to rearrange your view so the component you wish to interact with is on top of the container, alternatively you can set mouseEnabled to false on the container so the event propagates through.

The performance of recording and replaying QTP scripts should generally be good on a small application but it can become a problem on larger applications. QTP descends the view hierarchy looking for the components to interact with, this can become slow in certain situations. It seems that the search works much better if your tree of components, that QTP sees, is deeper rather than flat. This can easily be achieved by setting showInAutomationHierarchy to true on key containers. This partitions the search space and makes it faster to search, and hence replay scripts. The downside of this is that the scripts recorded will have longer chains of components which couples the scripts even further to the layout of your application. This coupling is a problem when you change the layout of view components in your application because your QTP scripts will need updating to reflect the new layout. Flex 4 claims to improve this performance issue relating to automation.

Sunday, 3 January 2010

Flex/QTP Automation Delegates

In the previous posts I alluded to the fact that you may sometimes have to write custom delegates. Delegates are objects that sit along side every instance of a visual component (when automation is enabled) and interact with the automation software. Delegates exist so that you do not have to place automation related code in you standard components, after all, most people don’t want to run their applications with automation support. Delegates are provided for all the framework classes and will generally work out of the box for any framework class you extend. The need to write custom delegates arrises if you create a custom component that directly extends UIComponent or if you have complex requirements for a component which already has a framework provided delegate.

Delegates register themselves with a given type, for example UIComponentAutomationImpl registers itself as the delegate for UIComponent. When determining which delegate to use for a component the inheritance hierarchy is ascended until a class with a delegate registered is found. If you extend UIComponent then you will, by default, get the delegate for UIComponent. The implementation of UIComponentAutomationImpl provides most of the functionality that you will need for a custom delegate by simply subclassing it.

Automation delegates have two main roles. The first is to record and replay the events generated by user interaction. The second is to provide the automation framework with a collection of automation enabled children. The implementation of UIComponentAutomationImpl does neither of these things by default. In most circumstances, simply recording click events and returning all children is enough to automation enable your component. I have provided a sample implementation below.
[Mixin]
public class ExampleComponentAutomationImpl extends UIComponentAutomationImpl
{
 public function ExampleComponentAutomationImpl( obj : ExampleComponent )
 {
  super( obj );
  recordClick = true;
 }
  
 public static function init( root : DisplayObject ) : void
 {
         Automation.registerDelegateClass( ExampleComponent, ExampleComponentAutomationImpl);
 }
  
 override public function get numAutomationChildren() : int
 {
  return ExampleComponent( uiComponent ).numChildren;
 }
  
 override public function getAutomationChildAt( index : int ) : IAutomationObject
 {
  return ExampleComponent( uiComponent ).getChildAt( index );
 }
}
This delegate enables the recording of clicks by setting a property in the super class. If you wish to listen for other events then you should add listeners in the constructor, you can follow the example of UIComponentAutomationImpl to do this. This implementation also overrides the two functions (get numAutomationChildren and getAutomationChildAt) needed to enable a component’s children to be found by the automation framework. You could put more complex logic in these functions to exclude certain children. The only code here that may strike you as slightly strange is the use of the [Mixin] metadata. This is an almost undocumented feature that ensure that the init function of a class will be called at class loading (generally application startup). We need this “bootstrap” to initially register our delegate before any instances of the related components are created.

Delegates are designed to be non-intrusive objects that can be loaded if necessary when using automation software. This means that your components should never reference a delegate, the result of this is that they will not be compiled into your resulting application. To force this inclusion you need to use the “-includes” compiler argument, which given a list of space separated fully qualified class names ensures that the classes are compiled into the resulting application whether or not they are referenced. The easiest way to maintain this list may be to create a custom flex config file which would take the following format, loading these is described here

<flex-config>
   <includes>
      <symbol>name.space.ExampleComponentAutomationImpl</symbol>
      <symbol>name.space2.ExampleComponentAutomationImpl2</symbol>
   </includes>
</flex-config>

The final step in supporting custom flex components is updating the TEAFlexCustom.xml file which lives in the “QTP_Plugin_Install_Location\Flex 3 Plug-in for Mercury QuickTest Pro” directory. This file is used by QTP to find out the events, properties and function that can be accessed on the components. This file must be maintained to match the components and delegates. A good guide to editing this file can be found here. One point to clarify is the forVerification and forDescription attributes of properties. forDescription specifies if the property can be used to help uniquely identify the component and forVerification specifies if the property can be verified during a test.