Automating Custom Components

FlexMonkey sits on top of the Adobe Automation framework. The automation framework is a Flex / AIR component level API provided by Adobe that assists tools like FlexMonkey in providing record and playback functionality with visual components (i.e. classes that extend from sub-components, as all the Flex SDK visual components do). To get a better understanding of how FlexMonkey and the Automation Framework work, we suggest you read our How FlexMonkey Works document before jumping into automating your own custom components.

When creating FlexMonkey tests, the automation framework filters the events that are seen, generally targeting events that represent user gestures for record and playback. This eliminates the noise and makes tests less brittle, as tests do not rely on internal application events, but events that are less likely to change (e.g. mouse click events). Thus, to prepare custom components for record and playback by FlexMonkey, the main task is enabling the automation framework to see the appropriate events on your components by working with delegate classes and the FlexMonkey environment file. Along with, making sure that the automation framework sees the component tree correctly through the automation children methods.

Automation Children Methods

Flex provides three primary methods for telling the automation framework about the component tree on any UIComponent: getAutomationChildern(), getAutomationChildAt(index:int), and numAutomationChildren().  The SDK has implementations of these methods for each of the standard Flex components.  When automating custom components, you often need to override these methods to expose the proper tree of components to the automation framework.  Examples:

   override public function getAutomationChildren():Array {
   	return [comp.closeButton]; 
   } 		 

   override public function getAutomationChildAt(index:int):IAutomationObject { 	
        return getAutomationChildren()[index]; 
   } 		 

   override public function get numAutomationChildren():int { 	
        return 1; 
   } 

There are two options for where the automation children code can live.  The methods can be added directly to the component class, or they can be implemented on an automation delegate class (discussed later).  The code logically fits best on the delegate, but sometimes it is easiest to just add it directly to the UIComponent class when it is the only change that is necessary. The delegate class is typically the best place for this code when you cannot or do not want to modify the component class source code.

Once you are aware that these methods are there and informing the automation framework, they are very straightforward to work with and override for your needs. Under "Project > View Application Tree", FlexMonkey allows you to view what the automation framework sees, this makes it very easy to tweak and review what the automation framework is seeing in your application.

Delegate Classes

Flex UI component classes in the Flex SDK have a corresponding "automation delegate" class which provides the component with recording and playback functionality.  By segregating recording and playback code in this way, the Flex SDK gives developers the option of "mixing in" recording and playback functionality.  

To automate your custom components, you typically need to create your own delegate class by extending one of the existing delegate classes.  Luckily, Adobe makes this task easier by providing source code for the delegates they include.  You can find the delegate source code in your 4.x SDK home directory at: <SDK_HOME>/frameworks/projects/automation.

Let's look at what makes up a typical delegate class, by breaking down the mx.automation.delegates.controls.ButtonAutomationImpl delegate class (used for automating a spark Button).  

[mixin]: Delegates use the Flex mixin metadata to cause the init method to be called by the system manager when the application is loaded.    

  [Mixin]
  public class ButtonAutomationImpl extends UIComponentAutomationImpl

init method: The static init method registers the delegate with the proper UIComponent class in the automation manager.   

  public static function init(root:DisplayObject):void {
    Automation.registerDelegateClass(Button, ButtonAutomationImpl);
  }

constructor: The constructor gets an instance of the component - a spark Button in this case.  This is where delegates add listeners for the user events that are be recorded.    

  public function ButtonAutomationImpl(obj:Button) {
     super(obj);

     obj.addEventListener(KeyboardEvent.KEY_UP, btnKeyUpHandler, false, EventPriority.DEFAULT+1, true);
     obj.addEventListener(MouseEvent.CLICK, clickHandler, false, EventPriority.DEFAULT+1, true);
  }

example handler: Here we look at the click handler which calls the parent delegate method recordAutomatableEvent.  Only events that are handled and recorded are seen by the automation framework.  

  protected function clickHandler(event:MouseEvent):void {
    if (!ignoreReplayableClick)
       recordAutomatableEvent(event);
    ignoreReplayableClick = false;
  }

replayAutomatableEvent: Next, the delegate needs to support playback and does so by overriding the  replayAutomatableEvent method.

  override public function replayAutomatableEvent(event:Event):Boolean {
     var help:IAutomationObjectHelper = Automation.automationObjectHelper;

     if (event is MouseEvent && event.type == MouseEvent.CLICK)
        return help.replayClick(uiComponent, MouseEvent(event));
     else if (event is KeyboardEvent)
        return help.replayKeyboardEvent(uiComponent, KeyboardEvent(event));
     else
        return super.replayAutomatableEvent(event);
     }

As you can see, the individual parts of a delegate are fairly straightforward.  When dealing with a custom component, you need to implement the custom delegate, listen to the proper events, and then implement the replayAutomatableEvent method to play them back.  This typically, takes a bit of trial and error to get up and running correctly, but is easy to get the hang of once you dig in.

FlexMonkey Environment File

In order for FlexMonkey to record the events handled in the delegates, they must be defined in the FlexMonkey class definition file FlexMonkeyEnv.xml.  This is required to satisfy how the AutomationManager class is configured.  The structure for the FlexMonkeyEnv file is defined below. You will need to have a good understanding of this file before you modify it for events and/or components.

<ClassInfo Name=”” Extends=””>
       
<Implementation Class=””/>
       
<Events>
               
<Event Name=””>
                       
<Implementation Class=””/>
                       
<Property>
                               
<PropertyType Type=”” />
                       
</Property>
               
</Event>
       
</Events>
       
<Properties>
       
</Properties>
</ClassInfo>
Tag Description
ClassInfo Defines the name of the component that is to be recorded, for example FlexButton. Attributes for this tag include Name and Extends.
Implementation Defines the class name as it is known by the compiler, for example mx.controls;;Button. Attributes for this tag include Class, Type. This tag is used for defining both component classes as well as event classes.
Event Defines the event that will be recorded. Attributes for this tag include Name, which is the event name.
Property

Defines a property of either an event. Although properties can also be specified for components and many definitions in the env file include such properties, only property definitions for Events (as shown below) are actually used by FlexMonkey at the present time, so these are the only properties that currently must be specified. Property definitions are used to determine how to convert arguments to string representations (for display in the console, or storage as XML) when recording a command.

Button sample definition.

<ClassInfo Name="FlexButton" Extends="FlexObject">
       
<Implementation Class="mx.controls::Button"/>
       
<Events>
           
<Event Name="Type" >
               
<Implementation Class="flash.events::KeyboardEvent" Type="keyPress"/>
               
<Property Name="keyCode" >
<PropertyType Type="String" Codec="keyCode" DefaultValue="SPACE"/>
</Property>

               
<Property Name="keyModifier"  DefaultValue="0">
                   

<PropertyType
Type="Enumeration"
ListOfValuesName
="FlexKeyModifierValues" Codec="keyModifier"/>
               
</Property>
           
</Event>
       
</Events>

</ClassInfo>

Modifying FlexMonkeyEnv.xml

Download FlexMonkeyEnv.xml from the FlexMonkey downloads page. Make your modifications and save the file to the root of your application source directory. FlexMonkey checks this location before loading the default FlexMonkeyEnv.xml.