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.

1 comment: