How FlexMonkey Works

This overview is intended for people attempting to debug or enhance FlexMonkey. Most FlexMonkey users can remain blissfully unaware of the gory details described here.

Automation Delegates

Every Flex UI component class in the Flex SDK has 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. You add recording and playback capabilities (and overhead) through the use of the -include-libraries compiler flag:

-include-libraries ${flexlib}/libs/automation/automation.swc ${flexlib}/libs/automation_monkey.swc ...

By omitting the above flag, you build a production version of a SWF that cannot be automated, but that is smaller and faster since it excludes automation delegate code and associated processing.

Note:  The source code for all automation delegates can be found with your flex sdk in the frameworks/projects/automation*/src folders.

When you start your SWF, the automation delegate classes are loaded and static initialization occurs (via the Flex Mixin metadata tag mechanism) during which each delegate class registers with the Automation framework, and tells it which UI class (or classes) the delegate is responsible for handling.

For example, mx.automation.delegates.controls.ButtonAutomationImpl registers as the delegate for mx.controls.Button components:

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

Selecting Events for Recording

Behind the scenes, the AutomationManager adds a listener to the SystemManager to be notified of components being added to the stage. Whenever a component is added to the stage, the AutomationManager finds the delegate class registered to handle the component's class, and creates a delegate instance, passing it a reference to the UI component instance for which the delegate instance will be handling recording and playback.

When the delegate is created, it subscribes to events it is interested in recording. For example:

        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);
        }

Event Recording

When the delegate receives an event, it decides if and how it wants to record it. Since, for example, buttons can be activated by clicking on them or by pressing the spacebar key, the ButtonAutomationImpl delegate records spacebar and ignores all other keyboard events, and since the Button control sends out a click when you hit the spacebar, the delegate ignores click events that it receives immediately following a spacebar event.

 

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

        private function btnKeyUpHandler(event:KeyboardEvent):void 
        {
            if (!btn.enabled)
                return;

            if (event.keyCode == Keyboard.SPACE)

            {
                // we need to ignore recording a click being dispatched here
                ignoreReplayableClick = true;
                recordAutomatableEvent(event);
            }
        }

The recordAutomatableEvent method results in an AutomationRecordEvent being sent to FlexMonkey.

Composite Components

Many Flex components are composites of several primitive components. For example, ComboBox contains a Button for opening or closing the ComboBox, as well as a TextInput and various other components used for presenting the dropdown and entering values. The ComboBox delegate subscribes to ComboBox open events, and when it receives one, it records an "Open" AutomationRecordEvent. The actual underlying click event generated by the ComboBox's internal button is suppressed by the automation framework so that you only record the "Open ComboBox" event, and not the underlying "Click ComboBoxButton" event.

Composite components specify which of their components to "hide" and which to expose to the automation manager by implementing three methods from the IAutomationObject interface:

  • numAutomationChildren:int
  • getAutomationChildAt(index:int)
  • getAutomationChildren():Array

Recording problems are often caused by incorrect implementation of these methods by delegates. If a component does not include a particular child in its children (via these 3 methods), no recording will occur for that child. Additionally, FlexMonkey can only interactively create Verify commands for components identified as automation children by their parents. If you are trying to interactively create a verify, and the component you want to verify does not highlight for selection when you mouse over it, the component is probably not being included in its parent's automation children. (To help diagnose automation children problems, Select Project > Application Tree from the FlexMonkey console menu to view the "automation hierarchy" within your applicaion, and see if components that should be automated are not being included as "automation children" by their parents.)

FlexMonkeyEnv.xml

The FlexMonkeyEnv.xml file specifies how events are recorded for each component class. FlexMonkey uses the file to specify the IAutomationEnvironment to the AutomationManager. When the AutomationManager receives an event to record for a component, it finds he ClassInfo associated with the component class (or nearest superclass if no ClassInfo has been specified in FlexMonkeyEnv.xml for the class itself) and then searches the found ClassInfo for an Event definition corresponding to the received event's class. If none is found, the AutomationManager searches up the automation class hierarchy (as specified by the Extends attributes in FlexMonkeyEnv.xml) for the first parent ClassInfo entry with the required Event definition. 

The Event definition specifies the name of the command to be recorded (eg, "Close"), and also specifies "codecs" to be used for serializing and deserializing the event's arguments.

Here is an example of a class definition from FlexMonkeyEnv.xml:

<ClassInfo Name="SparkDropDownListBase" Extends="SparkList">

<Implementation Class="spark.components.supportClasses::DropDownListBase"/>

<Events>

<Event Name="Close">

<Implementation Class="spark.events::DropDownEvent" Type="close"/>

<Property Name="triggerEvent" DefaultValue="1">

<PropertyType Type="Enumeration" ListOfValuesName="FlexTriggerEventValues"

Codec="event"/>

</Property>

</Event>

<Event Name="Open">

<Implementation Class="spark.events::DropDownEvent" Type="open"/>

<Property Name="triggerEvent" DefaultValue="1">

<PropertyType Type="Enumeration" ListOfValuesName="FlexTriggerEventValues"

Codec="event"/>

</Property>

</Event>

</Events>

    </ClassInfo>

This definition specifies how to record DropDownEvents received from Spark drop down lists. Events with a type of "open" will be recorded as "Open" commands. The triggerEvent property of the DropDownEvent will be serialized by translating to a string as specified in the FlexTriggerEventsValues enumeration (specified elsewhere in FlexMonkeyEnv.xml).

In addition to the Property specifications for Event definitions, many ClassInfo definitions include Property definitions for the class itself. FlexMonkey does not currently use any of this information. If you're creating a new ClassInfo definition, you must specify Property entries for each event argument to be recorded, but there is no need to specify any class Property definitions.

Event Playback

At playback time, the delegate receives the recorded event and does whatever is necessary to play it back. In the code below, the button delegate replays clicks and key presses by calling helper methods provided by the automation framework.

        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);
        }

 

Additonal Resources