Creating applications for testing

You can create applications and components that can be tested with automated testing tools such as HP QuickTest Professional™ (QTP). The information in this topic is intended for Adobe® Flex™ developers who write applications that are tested by Quality Control (QC) professionals who use these testing tools. For information on installing and running the Flex plug-in with QTP, QC professionals should see Testing Adobe Flex Applications with HP QuickTest Professional.

Full support for the Flex automation features is included in Adobe® Flash® Builder™ Premium. Adobe Flash Builder Standard allows only limited use of this feature.

About automating applications with Flex

The automation feature provides developers with the ability to create applications that use the automation APIs. You can use these APIs to create automation agents or to ensure that your applications are ready for testing. In addition, the automation feature includes support for the QTP automation tool.

When working with the automation APIs, you should understand the following terms:

  • automation agent (or, simply, agent) — An agent facilitates communication between an application and an automation tool. The Flex Automation Package includes a plugin that acts as an agent between your applications and the QTP testing tool.

  • automation tool — Automation tools are applications that use the data that is derived through the agent. These tools include QTP, Omniture, and Segue.

  • delegates — Flex framework components are instrumented by attaching a delegate class to each component at run time. The delegate class defines the methods and properties required to perform instrumentation.

The following illustration shows the relationship between an application, an agent, and an automation tool.

The relationship between a Flex application, an agent, and an automation tool.

As this illustration shows, the automation tool uses an agent to communicate with the application built with Flex. The agent can be an ActiveX control or other type of utility that fascilitates the interaction between the tool and the application.

The Flex automation feature includes the following:

  • Automation libraries — The SWC files in the libs/automation directory are the implementations of the automation API for the Flex framework components. The following table describes these SWC files.

    SWC file

    Description

    automation.swc

    Provides the delegates for the core Flex classes. This includes the Halo component set.

    automation_agent.swc

    Provides the classes for creating a custom agent.

    automation_dmv.swc

    Provides the delegates for the Flex charting and AdvancedDataGrid classes.

    automation_air.swc

    Provides the delegates for AIR.

    automation_airspark.swc

    Provides the delegates for the Spark components that are used in AIR.

    automation_spark.swc

    Provides the delegates for the Spark component set.

    automation_flashflexkit.swc

    Provides the delegates for the FlashFlexKit.

    qtp.swc

    Provides the classes that allow QTP to communicate with an application built with Flex.

    qtp_air.swc

    Provides the classes that allow QTP to communicate with AIR applications.

  • QTP files — The QTP files let QTP and applications built with Flex communicate directly. You can only use these files if you also have QTP. These files include the QTP plug-in, a QTP demonstration video, a QTP-specific environment XML file, and the QTP-specific libraries, qtp.swc and qtp_air.swc. For more information, see Testing Adobe Flex Applications with HP QuickTest Professional.

Tasks and techniques for testable applications overview

Flex developers should review the information about tasks and techniques for creating testable applications, and then update their applications accordingly. QC testing professionals who use QTP should use the documentation provided in the separate book, Testing Adobe Flex Applications with HP QuickTest Professional. That document is available for download with the Flex plug-in for QTP.

Use the following general steps to create a testable application:

  1. Review the guidelines for creating testable applications. For more information, see Creating test-friendly applications.

  2. Prepare the application to load the automation classes at run time or compile time.

    • To create an application that loads the automation classes at run time, you compile it as normal. At run time, you load your application into a wrapper SWF file that has the automation libraries built in. This wrapper SWF file uses the SWFLoader to load your application SWF file that you plan to test only at run time. For more information, see Using run-time loading.

    • To compile an application that includes the automation classes, you include the needed automation libraries at compile time. Compile the application with the automation SWC files by using the compiler’s include-libraries option. For information on the compilation process, see Using compile-time loading.

  3. Prepare customized components for testing. If you have custom components that extend UIComponent, make them testable. For more information, see Instrumenting custom components.

  4. Create an HTML wrapper that follows proper application naming practices. For more information, see Writing the wrapper.

  5. Deploy the application’s assets to a web server. Assets can include the SWF file; HTML wrapper and related files; external assets such as theme files, graphics, and video files; module SWF files; resource modules; CSS SWF files; and run-time shared libraries (RSLs). For information about what files to deploy with your application, see Deployment checklist.

Using compile-time loading

When you embed functional testing classes in your application SWF file at compile time, you increase the size of the SWF file. If the size of the application SWF file is not important, you can use the same SWF file for functional testing and deployment. If the size of the SWF file is important, you typically generate two SWF files: one with functional testing classes embedded and one without.

To compile a testable application, you must reference the necessary automation SWC files with the include-libraries compiler option. Typically, this includes the automation.swc and automation_spark.swc files. If your application uses charts or the AdvancedDataGrid classes, you must also add the automation_dmv.swc file. You might also be required to add automation tool-specific SWC files; for example, for QTP, you must also add the qtp.swc file to your application’s library path. For a complete list of automation SWC files, see About automating applications with Flex.

By default, Flash Builder includes the automation SWC files in its library path, but you must add these libraries by using the include-libraries compiler option.

Including automation SWC files

To include the SWC file in your application, you can add them to the compiler’s configuration file or as a command-line option. For the SDK, the configuration file is located at sdk_install_dir/frameworks/flex-config.xml. To add the automation.swc and automation_spark.swc libraries, add the following lines to the configuration file:

<include-libraries> 
    <library>/libs/automation.swc</library> 
    <library>/libs/automation_spark.swc</library> 
</include-libraries>

You must uncomment the include-libraries code block. By default it is commented out in the configuration file.

You can also specify the location of the SWC files when you use the command-line compiler with the include-libraries compiler option. The following example adds the automation.swc and automation_spark.swc files to the application:

mxmlc -include-libraries+=../frameworks/libs/automation.swc; 
    ../frameworks/libs/automation_spark.swc MyApp.mxml

Explicitly setting the include-libraries option on the command line overwrites, rather than appends, any existing libraries that you include in the configuration file. As a result, if you add the automation.swc and automation_spark.swc files by using the include-libraries option on the command line, ensure that you use the += operator. This does not overwrite the existing libraries that might be included.

To add automated testing support to a Flash Builder project, you also add the SWC files to the include-libraries compiler option.

Add SWC files to Flash Builder projects

  1. In Flash Builder, select your Flex project in the Navigator.

  2. Select Project > Properties. The Properties dialog box appears.

  3. Select Flex Compiler in the tree to the left. The Flex Compiler properties panel appears.

  4. In the “Additional compiler arguments” field, enter the following command:

    -include-libraries+="sdks\4.0.0\frameworks\libs\automation.swc","sdks\4.0.0\frameworks\libs\automation_spark.swc"

    In Flash Builder, the entries in the include-libraries compiler option are relative to the Flash Builder installation directory; the default location of this directory on Windows is C:\Program Files\Adobe\Flash Builder 4.

  5. Click OK to save your changes.

Deploying the application

When you create the final release version of your application, you recompile the application without the references to the automation SWC files.

If you do not deploy your application to a server, but instead request it by using the file protocol or run it from within Adobe Flash Builder, you must put the SWF file into the local-trusted sandbox. This requires configuration information that is separate from the SWF file and the wrapper. For more information that is specific to QTP, see Testing Adobe Flex Applications with HP QuickTest Professional.

Using run-time loading

You can use the run-time testing files rather than compiling the automation libraries into your applications. This lets you test SWF files that have already been compiled without automated testing support. It also helps keep the SWF file size small. To do this, you use a SWF file that does include the automated testing libraries. In that SWF file, the SWFLoader class loads your application’s SWF file which does not include the testing libraries. The result is that you can test the target SWF file in a testing tool such as QTP, even though the application SWF file was not compiled with automated testing support.

Flash Builder includes the following files necessary for run-time loading in the flash_builder_install_dir/sdks/4.0.0/templates/automation-runtimeloading-files directory:

  • RunTimeLoading.html — The HTML wrapper that loads the run-time loader SWF file. This template includes code that converts the automationswfurl query string parameter to a flashVars variable that it passes to the application. You use this query string parameter to specify the name of the application you want to load and test.

  • runtimeloading.mxml — The source code for the runtimeloading.swf file that you compile. The SWF file acts as a wrapper for your application. This SWF file includes the testing libraries so that you do not have to compile them into your application SWF file.

To use run-time loading:

  1. Compile the runtimeloading.swf application from the runtimeloading.mxml file. You can use the batch file in the flash_builder_install_dir/sdks/4.0.0/templates/automation-runtimeloading-files directory. Execute this batch file from the sdks/4.0.0/frameworks directory. This batch file ensures that your runtimeloading.swf file includes the automation.swc, automation_agent.swc, automation_dmv.swc, and qtp.swc libraries. You might need to add or remove SWC files to this file, depending on what features your application uses.

  2. Deploy the runtimeloading.swf, RunTimeLoading.html, and your application’s SWF file to a web server.

  3. Request the RunTimeLoading.html file and pass the name of your SWF file as the value to the automationswfurl query string parameter. For example:
    http://localhost/RunTimeLoading.html?automationswfurl=MyApp.swf

You can also create a custom HTML wrapper to use with the run-time loading feature, but it must use proper object naming. If you are using Flash Builder, you can generate a wrapper automatically. If you are using the SDK, you can use the wrapper template in the flex_sdk/templates directory to create a wrapper for your application. When using a wrapper, the value of the id attributes can not contain any periods or hyphens.

If you want to recompile the runtimeloading.swf file without the batch file, be sure to include automated testing support by adding the appropriate automation SWC files with the include-libraries compiler option.

The batch file for compiling the runtimeloading.swf file is Windows only. To compile the SWF file on Mac OS or Linux, you can use the command line compiler or write your own batch file.

Creating test-friendly applications

As a Flex developer, there are some techniques that you can employ to make applications as “test friendly” as possible. One of the most important tasks that you can perform is to make sure that objects are identifiable in the testing tool’s scripts. This means that you should explicitly set the value of the id property or whatever property the testing tool uses to identify the object. Also be sure to use a meaningful string for that property so that the testing scripts are more readable. Finally, use unique IDs for each control so that the tool does not encounter ambiguous references.

Providing meaningful identification of objects

When working with testing tools such as QTP, a QC professional only sees the visual representation of objects in your application. A QC professional generally does not have access to the underlying code. When a QC professional records a script, it’s very helpful to see IDs that help the tester identify the object clearly. You should take some time to understand how testing tools interpret applications and determine what names to use for the test objects in the test scripts.

In most cases, testing tools use a visual cue, such as the label of a Button control, to identify the control in the script. Sometimes, however, testing tools use the Flex id property of an MXML tag to identify an object in the test script; if there is no value for the id property, testing tools use other properties, such as the childIndex property. In addition, the automation APIs provide an automationName property that you can use to explicitly set the ID of an object as it appears in the tool’s scripts.

You should give all testable MXML components an ID to ensure that the test script has a unique identifier to use when referring to that Flex control. You should also try to make these identifiers as human-readable as possible to make it easier for a QC professional to identify that object in the testing script. For example, set the id property of a Panel container inside a TabNavigator to submit_panel rather than myPanel or p1.

In some cases, agents do not use the id property, but it is a good practice to include it to avoid naming collisions or confusion. For more information about how QTP identifies Flex objects, see Testing Adobe Flex Applications with HP QuickTest Professional.

You should also set the value of the automationName property for all objects that are part of the application’s test. The value of this property appears in the testing scripts. Providing a meaningful name makes it easier for QC professionals to identify that object. For more information about using the automationName property, see Setting the automationName property.

Avoiding renaming objects

Automation agents rely on the fact that some object’s properties should not be changed during run time. If you change the application property that is used by the agent as the object name at run time, unexpected results can occur.

For example, if you create a Button control without an automationName property, and you do not set the value of its label property during initialization, and then later set the value of the label property, the agent might get confused. This is because agents often use the value of the label property of a Button control to identify it in its object repository if the automationName property is not set. If you later set the value of the label property, or change the value of an existing label while the QC professional is recording a test, an automation tool such as QTP will create a new object in its repository instead of using the exising reference.

As a result, you should try to understand what properties are used to identify objects in the agent, and try to avoid changing those properties at run time. You should set unique, human-readable id or automationName properties for all objects that are included in the recorded script.

Coding containers

Containers are different from other kinds of controls because they are used both to record user interactions (such as when a user moves to the next pane in an Accordion container) and to provide layout logic.

Adding and removing containers from the automation hierarchy

In general, the automated testing tools reduce the amount of detail about nested containers in their scripts. They remove containers that have no impact on the results of the test or on the identification of the controls from the script. This applies to containers that are used exclusively for layout, such as the HGroup, VGroup and Canvas containers.

The following image shows the layout for a simple form-based application. The components with gray backgrounds appear in the automation tool’s scripts because they can be interacted with by the user. Components with white backgrounds do not appear in the tool’s scripts because they only provide layout logic.

View full size graphic
Automation flowchart

In some cases, containers that are being used in multiple-view navigator containers, such as the ViewStack, TabNavigator or Accordion containers, do appear in the automation hierarchy. In these cases, they are added to the automation hierarchy to provide navigation.

Many composite components use containers, such as VGroup or HGroup, to organize their children. These containers do not have any visible impact on the application. So, these containers are usually excluded from the test scripts because there is no user interaction and no visual need for their operations to be recordable. By excluding a container from being tested, the test scripts are shorter and more readable.

To exclude a container from being recorded (but not exclude its children), set the container’s showInAutomationHierarchy property to false. This property is defined by the UIComponent class, so all containers that subclass UIComponent have this property. Children of containers that are not visible in the hierarchy appear as children of the next highest visible parent. You can also use this property to exclude controls that you want to omit from testing, such as a Help button or decorative object.

The default value of the showInAutomationHierarchy property depends on the type of container. For containers that have visual elements or support user interaction, such as lists, Panel, Accordion, Application, DividedBox, and Form, the default value is true. For containers used exclusively for layout, such as Group, Canvas, Box, and FormItem, the default value is false.

The following example forces the VGroup containers to be included in the test script’s hierarchy:

<?xml version="1.0"?>
<!-- agent/NestedButton.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">
    <s:Panel title="ComboBox Control Example">
        <s:layout>
            <s:VerticalLayout/>
        </s:layout>
        <s:HGroup id="hg">
            <s:VGroup id="vg1" showInAutomationHierarchy="true">
                <mx:Canvas id="c1">
                    <s:Button id="b1" 
                        automationName="Nested Button 1" 
                        label="Click Me"/>
                </mx:Canvas>
            </s:VGroup>         
            <s:VGroup id="vg2" showInAutomationHierarchy="true">
                <mx:Canvas id="c2">
                    <s:Button id="b2" 
                        automationName="Nested Button 2" 
                        label="Click Me 2"/>
                </mx:Canvas>
            </s:VGroup>         
        </s:HGroup>
    </s:Panel>    
</s:Application>       

For additional information, see More about showInAutomationHierarchy.

Working with multiview containers

You should avoid using the same label on multiple tabs in multiview containers, such as TabNavigator and Accordion containers. Although the compiler allows you to use the same labels for each view, this is generally not an acceptable UI design practice and can cause problems with control identification in your testing environment. QTP, for example, uses the label properties of multiview containers to identify those views to testers. When two labels are the same, QTP uses different strategies to uniquely identify the tabs, which can result in a confusing name list.

Also, dynamically adding children to multiview containers can cause delays that might confuse the testing tool. You should try to avoid this.

Testing sub-applications

When testing applications that load sub-applications, you should be aware of the different types of applications that a main application can load. These include applications that:

  • Were compiled with the same version of the compiler as the main application (single-versioned applications).

  • Were compiled with different versions of the compiler in different application domains (multi-versioned applications).

  • Are loaded into different security domains (sandboxed applications). These applications can be multi-versioned.

When testing applications that load other applications, there is a single point of communication between the automation tool and the applications built with Flex. This point is the main application. It acts as a gateway between the sub-applications and the automation tool. All events dispatched by the sub-applications are routed through the main application to the tool. And conversely, all calls from the tool are sent to the main application. The main application is responsible for routing those messages to the sub-applications.

When creating applications that will be tested, compile each sub-application and the main application with its own tool library, automation manager, and delegates. In other words, compile the application against the automation libraries.

When recording an application that loads sub-applications, the tool typically uses the IAutomationManager2’s getUniqueApplicationId() method. This method returns a unique ID for each application. The resulting scripts contain longer strings to identify objects, but you can see the heirarchy of the applications in them. For example, a button in a sub-application might be referenced as follows:
FlexApplication("loader1").FlexApplication("local2.swf").SparkButton("Submit Form");

A script that was written for the main application can be reused within a multi-application environment. You might be required to provide the application ID to the operations so that the target application can be uniquely identified.

Support for sub-application testing is built into your applications by default. As a developer, you do not need to add custom code to ensure that multi-application tests work. However, if you write a custom agent for a multi-application environment, keep the following in mind:
  • Multi-versioned applications should use the sandbox root application to dispatch events. This is required for events that are communicated across application boundaries. You can use the following code to get a reference to the sandbox root:
    private  var sandboxRoot:IEventDispatcher; 
    var sm:ISystemManager = Application.application.systemManager; 
    sandboxRoot = sm.getSandboxRoot();
  • Untrusted (or sandboxed) applications use the SWFBridge to communicate across security domain boundaries. Each application has a SWFBridge. The SWFBridge has a child that corresponds to the sub-application that was loaded with a SWFLoader in that application.

Writing the wrapper

In most cases, the testing tool requests a file from a web server that embeds the application. This file, known as the wrapper, is often written in HTML, but can also be a JSP, ASP, or other file that browsers interpret. You can request the SWF file directly in the testing tool by using the file protocol, but then you must ensure that the SWF file is trusted.

If you are using Adobe LiveCycle Data Services ES or Flash Builder, you can generate a wrapper automatically. If you are using the Flex Software Development Kit (SDK), you can use the wrapper templates in the flex_sdk/templates directory to create a wrapper for your application.

When you careate a custom wrapper, your wrapper’s <object> tag must have an id attribute, and the value of the id attribute can not contain any periods or hyphens. The convention is to set the id to match the name of the root MXML file in the application.

When you use the SWFObject 2 wrapper as a template, you must set the id of the application on the attributes.id property.

When you use Flash Builder to generate a wrapper, the value of the id attribute is the name of the root application file. You do not have to make any changes to this attribute.

When you generate a wrapper with the LiveCycle Data Services ES server, the object tag’s id attribute is valid. The following example shows the default object tag for a file named MainApp.swf:

<object id='MainApp' classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' codebase='http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab' height='600' width='600'>
You are not required to change the value of the name in the <embed> tag because <embed> is used by Netscape-based browsers that do not support the testing feature. The <object> tag is used by Microsoft Internet Explorer.

Ensure that the object tag’s id attribute is the same in the <script> and the <noscript> blocks of the wrapper.

Understanding the automation framework

The automation interfaces and the flow of the automation framework change as you initialize, record, and play back an automatable event.

About the automation interfaces

The Flex class hierarchy includes the following interfaces in the mx.automation.* package that enable automation:

Interface

Description

IAutomationClass and IAutomationClass2

Defines the interface for a component class descriptor.

IAutomationEnvironment

Provides information about the objects and properties of automatable components needed for communicating with agents.

IAutomationEventDescriptor

Defines the interface for an event descriptor.

IAutomationManager and IAutomationManager2

Defines the interface expected from an AutomationManager by the automation module.

IAutomationMethodDescriptor

Defines the interface for a method descriptor.

IAutomationMouseSimulator

Describes an object that simulates mouse movement so that components capturing the mouse use the simulated versions of the mouse cursor instead of the live Flash Player version.

IAutomationObject

Defines the interface for a delegate object implementing automation for a component.

IAutomationObjectHelper

Provides helper methods for the IAutomationObject interface.

IAutomationPropertyDescriptor

Describes a property of a test object as well as properties of an event object.

IAutomationTabularData

Defines the interface for components that provide their content information in a tabular form.

For more information about each of these interfaces, see the class’s description in the ActionScript 3.0 Reference for the Adobe Flash Platform.

About the IAutomationObjectHelper interface

The IAutomationObjectHelper interface helps the components accomplish the following tasks:

  • Replay mouse and keyboard events; the helper generates proper sequence of player level mouse and key events.

  • Generate AutomationIDPart for a child: AutomationIDPart would be requested by the Automation for representing a component instance to agents.

  • Find a child matching a AutomationIDPart: Automation would request the component to locate a child matching the AutomationIDPart supplied by an agent to it.

  • Avoid synchronization issues: Agents invoke methods on Automation requesting operations on components in a sequence. Components may not be ready all the time to perform operations.

For example, an agent can invoke comboBox.Open, comboBox.select "Item1" operations in a sequence. Because it takes time for the drop-down list to open and initialize, it is not possible to run the select operation immediately. You can place a wait request during the open operation execution. The wait request should provide a function for automation, which can be invoked to check the ComboBox control’s readiness before invoking the next operation.

Automated testing workflow with the QTP automation tool

Before you automate custom components, you might find it helpful to see the order of events during which Flex’s automation framework initializes, records, and plays back events with a tool such as QTP. You should keep in mind that the QTP adapter class’ implementation is only one way to use the automation API for automated testing.

Automated testing initialization

  1. The user launches the application. Automation initialization code associates component delegate classes with component classes. Component delegate classes implement the IAutomationObject interface.

  2. AutomationManager is a mixin. Its instance is created in the mixin init() method.

  3. The SystemManager initializes the application. Component instances and their corresponding delegate instances are created. Delegate instances add event listeners for events of interest.

  4. QTPAgent class is a mixin. In its init() method, it registers itself for the FlexEvent.APPLICATION_COMPLETE event which is dispatched from the SystemManager. On receiving the event, it creates a QTPAdapter object.

  5. QTPAdapter sets up the ExternalInterface function map. QTPAdapter loads the QTP Plugin DLLs by creating the ActiveX object to communicate with QTP.

  6. The QTPAdapter requests the XML environment information from the plugin and passes it to the AutomationManager.

  7. The XML information is stored in a chain of AutomationClass, AutomationMethodDescriptor, and AutomationPropertyDescriptor objects.

Automated testing recording

  1. The user clicks the Record button in QTP.

  2. QTP calls the QTPAdapter.beginRecording() method. QTPAdapter adds a listener for AutomationRecordEvent.RECORD from the AutomationManager.

  3. The QTPAdapter notifies AutomationManager about this by calling the beginRecording() method. The AutomationManager adds a listener for the AutomationRecordEvent.RECORD event from the SystemManager.

  4. The user interacts with the application. In this example, suppose the user clicks a Button control.

  5. The ButtonDelegate.clickEventHandler() method dispatches an AutomationRecordEvent event with the click event and Button instance as properties.

  6. The AutomationManager record event handler determines which properties of the click event to store, based on the XML environment information. It converts the values into proper type or format. It dispatches the record event.

  7. The QTPAdapter event handler receives the event. It calls the AutomationManager.createID() method to create the AutomationID object of the button. This object provides a structure for object identification.

    The AutomationID structure is an array of AutomationIDParts. An AutomationIDPart is created by using IAutomationObject. (The UIComponent.id, automationName, automationValue, childIndex, and label properties of the Button control are read and stored in the object. The label property is used because the XML information specifies that this property can be used for identification for the Button.)

  8. The QTPAdapter uses the AutomationManager.getParent() method to get the logical parent of the Button control. The AutomationIDPart objects of parent controls are collected at each level up to the application level.

  9. All these AutomationIDParts are made part of an AutomationID object.

  10. The QTPAdapter sends the information in a call to QTP.

  11. At this point, QTP might call the AutomationManager.getProperties() method to get the property values of the Button control. The property type information and codec that should be used to modify the value format are gotten from the AutomationPropertyDescriptor.

  12. User stops recording. This is propagated by a call to the QTPAdapter.endRecording() method.

Automated testing playback

  1. The user clicks the Playback button in QTP.

  2. The QTPAdapter.findObject() method is called to determine whether the object on which the event has to be played back can be found. The AutomationID object is built from the XML data received. The AutomationManager.resolveIDToSingleObject() method is invoked to see if QTP can find one unique object matching the AutomationID. The AutomationManager.getChildren() method is invoked from application level to find the child object. The IAutomationObject.numAutomationChildren property and the IAutomationObject.getAutomationChildAt() method are used to navigate the application.

  3. The AutomationManager.isSynchronized() and AutomationManager.isVisible() methods ensure that the object is fully initialized and is visible so that it can receive the event.

  4. QTP invokes the QTPAdpater.run() method to play back the event. The AutomationManager.replayAutomatableEvent() method is called to replay the event.

  5. The AutomationMethodDescriptor for the click event on the Button is used to copy the property values (if any).

  6. The AutomationManager.replayAutomatableEvent() method invokes the IAutomationObject.replayAutomatableEvent() method on the delegate class. The delegate uses the IAutomationObjectHelper.replayMouseEvent() method (or one of the other replay methods, such as replayKeyboardEvent()) to play back the event.

  7. If there are check points recorded in QTP, the AutomationManager.getProperties() method is invoked to verify the values.

Instrumenting events

When you extend components that are already instrumented, you do not have to change anything to ensure that those components’ events can be recorded by a testing tool. For example, if you extend a Button class, the class still dispatches the automation events when the Button is clicked, unless you override the Button control’s default event dispatching behavior.

Automation events (sometimes known in automation tools such as QTP as operations) are not the same as Flex events. Flex must dispatch an automation event as a separate action. Flex dispatches them at the same time as Flex events, and uses the same event classes, but you must decide whether to make a Flex event visible to the automation tool.

Not all events on a control are instrumented. You can instrument additional events by using the instructions in Instrumenting existing events.

If you change the instrumentation of a component, you might also be required to edit that component’s entry in the class definitions file. This is described in Using the class definitions file.

Instrumenting existing events

Events have different levels of relevance for the QC professional. For example, a QC professional is generally interested in recording and playing back a click event on a Button control. The QC professional is not generally interested in recording all the events that occur when a user clicks the Button, such as the mouseOver, mouseDown, mouseUp, and mouseOut events. For this reason, when a tester clicks on a Button control with the mouse, testing tools only record and play back the click event for the Button control and not the other, lower-level events.

There are some circumstances where you would want to record events that are normally ignored by the testing tool. But the testing tool’s object model only records events that represent the end-user’s gesture (such as a click or a drag and drop). This makes a script more readable and it also makes the script robust enough so that it does not fail if you change the application slightly. So, you should carefully consider whether to add a new event to be tested or rely on events in the existing object model.

You can see a list of events that the QTP automation tool can record for each component in the QTP Object Type Information document. The Button control, for example, supports the following operations, based on its entry in the TEAFlex.xml file:

  • ChangeFocus

  • Click

  • MouseMove

  • SetFocus

  • Type

All of these operations except for MouseMove are automatically recorded by QTP by default. The QC professional must explicitly add the MouseMove operation to their QTP script for QTP to record play back the related event.

However, you can alter the behavior of your application so that this event is recorded by the testing tool. To add a new event to be tested, you override the replayAutomatableEvent() method of the IAutomationObject interface. Because the UIComponent class implements this interface, all subclasses of UIComponent (which include all visible Flex controls) can override this method. To override the replayAutomatableEvent() method, you create a custom class, and override the method in that class.

The replayAutomatableEvent() method has the following signature:

public function replayAutomatableEvent(event:Event):Boolean 

The event argument is the Event object that is being dispatched. In general, you pass the Event object that triggered the event. Where possible, you pass the specific event, such as a MouseEvent, rather than the generic Event object.

The following example shows a custom Button control that overrides the replayAutomatableEvent() method. This method checks for the mouseMove event and calls the replayMouseEvent() method if it finds that event. Otherwise, it calls its superclass’ replayAutomatableEvent() method.

<?xml version="1.0" encoding="utf-8"?>
<!-- agent/CustomButton.mxml -->
<s:Button xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">
        <fx:Script>
        <![CDATA[
            import flash.events.Event;
            import flash.events.MouseEvent;
            import mx.automation.Automation;
            import mx.automation.IAutomationObjectHelper;

            override public function 
                replayAutomatableEvent(event:Event):Boolean {

                trace('in replayAutomatableEvent()');

                var help:IAutomationObjectHelper = Automation.automationObjectHelper;
        
                if (event is MouseEvent && event.type == MouseEvent.MOUSE_MOVE) {
                    return help.replayMouseEvent(this, MouseEvent(event));
                } else {
                    return super.replayAutomatableEvent(event);                
                }
            }
        ]]>
    </fx:Script>
</s:Button>

In the application, you call the AutomationManager’s recordAutomatableEvent() method when the user moves the mouse over the button. The following application uses this custom class:

<?xml version="1.0" encoding="utf-8"?>
<!-- agent/CustomButtonApp.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx"
    xmlns:ns1="*" initialize="doInit()">
    <fx:Script>
        <![CDATA[
            import mx.automation.*;

            public function doInit():void {             
                b1.addEventListener(MouseEvent.MOUSE_MOVE, dispatchLowLevelEvent);
            }

            public function dispatchLowLevelEvent(e:MouseEvent):void {
                var help:IAutomationManager = Automation.automationManager;
                help.recordAutomatableEvent(b1,e,false);
            }
        ]]>
    </fx:Script>

    <ns1:CustomButton id="b1" 
        toolTip="Mouse moved over" 
        label="CustomButton"/>
    
</s:Application>

If the event is not one that is currently recordable, you also must define the new event for the agent. Typically, you do this by adding a new entry to a class definitions file. For a tool such as QTP, the class definitions are in the TEAFlex.xml file. Automation tools can use files like this to define the events, properties, and arguments for each class of test object. For more information, see Instrumenting events. If you wanted to add support for the mouseOver event in QTP, for example, you add the following to the SparkButton’s entry in the TEAFlex.xml file:

<Operation Name="MouseOver" PropertyType="Method" ExposureLevel="CommonUsed"> 
    <Implementation Class="flash.events::MouseEvent" Type="mouseOver"/> 
        <Argument Name="keyModifier" IsMandatory="false" DefaultValue="0"> 
            <Type VariantType="Enumeration" 
                ListOfValuesName="FlexKeyModifierValues" Codec="keyModifier"/> 
            <Description>Occurs when the user moves mouse over the component</Description> 
        </Argument> 
</Operation>

In the preceding example, however, the mouseMove event is already in the SparkButton control’s entry in that file, so no editing is necessary. The difference now is that the QC professional does not have to explicitly add the event to their script. After you compile this application and deploy the new TEAFlex.xml file to the QTP testing environment, QTP records the mouseMove event for all of the CustomButton objects.

For more information about the class definitions file, see Using the class definitions file.

Instrumenting custom events

When you extend components, you often add events that are triggered by new functionality of that component. You can instrument custom events by adding a call to the Automation.automationManager2 class’s recordCustomAutomationEvent() method. You usually do this in the same place that you call the dispatchEvent() method. You do not replace the call to the dispatchEvent() method.

The following example from a custom event class builds a new AutomationRecordEvent and calls the recordCustomAutomationEvent() method to ensure that it is recorded:
this.addEventListener(MyComponentEvent.myEvent, myHandler); 
private function myHandler(event:MyComponentEvent) { 
    var eventToRecord:AutomationRecordEvent = 
        new AutomationRecordEvent(AutomationRecordEvent.CUSTOM_RECORD); 
    eventToRecord.automationObject = this; 
     // Provide the name of the event: 
    eventToRecord.name = "MyCustomEventName"; 
    // Provide the details to be recorded. This should be of basic data types. 
    eventToRecord.args = [MySelectionDetails]; 
    Automation.automationManager2.recordCustomAutomationEvent(eventToRecord); 
}

For a tool such as QTP to recognize the event as a recordable operation, you must also add the event to the control’s entry in the class definitions file (TEAFlex.xml).

To replay the custom event, you listen for the AutomationCustomReplayEvent, get the details about it, and replay the event, as the following example shows:
Automation.automationManager2.addEventListener(AutomationCustomReplayEvent.Replay, 
    handleReplay,false, EventPriority.DEFAULT+1); 
private function handleReplay(event:AutomationCustomReplayEvent):void { 
    if (event.automationObject  == this) { 
        // take the name and args: 
        var name:String = event.name; 
        var args:Array = event.args; 
        // Use the above do the required replay: 
        event.preventDefault(); // prevent the default replay 
    } 
}

Blocking and overriding events

In some cases, you might want to block or override the default events that are recorded for a component. You might even want to replace them with a different event. This is often the case with events dispatched from custom components.

For example, suppose you have an application that uses a custom component that consists of a List and a Button control. When the user selects an item from the list, the application records a change event. However, if you change the List control to a RadioButtonGroup in your custom component, you no longer want to record the change event, but rather, you want to record the itemClick event.

The result is that automation scripts can break due to changes in the custom component. To get around this, you can block the events and dispatch a higher-level event that corresponds to the user’s gesture rather than the specific event.

To block the recording of the default events, create an event listener that listens for all AutomationRecordEvent.RECORD events. Set its priority to be higher than other listeners. In the handler, call the event’s preventDefault() method, as the following example shows:
Automation.automationManager2.addEventListener(AutomationRecordEvent.RECORD, 
    blockEvents,false, EventPriority.DEFAULT+1); 
private function blockEvent(event:AutomationRecordEvent):void { 
    // Block all events on list1 and button1: 
    if ((event.automationObject == list1) || (if (event.automationObject == button1)) { 
        event.preventDefault(); 
    } 
}

You can then dispatch the more generic event that resembles the user’s gesture as shown in Instrumenting custom events.

Instrumenting custom components

The process of creating a custom component that supports automated testing is called instrumentation. Flex framework components are instrumented by attaching a delegate class to each component at run time. The delegate class defines the methods and properties required to perform instrumentation.

If you extend an existing component that is instrumented, such as a Button control, you inherit its parent’s instrumentation, and are not required to do anything else to make that component testable. If you create a component that inherits from UIComponent, you must instrument that class in one of the following ways:

  • Create a delegate class that implements the required interfaces.

  • Add testing-related code to the component.

You usually instrument components by creating delegate classes. You can also instrument components by adding automation code inside the components, but this is not a recommended practice. It creates tighter coupling between automated testing code and component code, and it forces the automated testing code to be included in a production SWF file.

In both methods of instrumenting a component, you must specify any new events to the agent. With QTP, for example, you must add your new component’s information to the class definitions file so that QTP recognizes that component. For more information about this file, see Using the class definitions file.

Consider the following additional factors when you instrument custom components:

  • Composition. When instrumenting components, you must consider whether the component is a simple component or a composite component. Composite components are components made up of several other components. For example, a TitleWindow that contains form elements is a composite component.

  • Container hierarchy. You should understand how containers are viewed in the automation hierarchy so that the QC professional can easily test the components. Also, you should be aware that you can manipulate the hierarchy to better suit your application by setting some automation-related properties.

  • Automation names. Custom components sometimes have ambiguous or unclear default automation names. The ambiguous names make it more difficult in automation tools to determine what component a script is referring to. Component authors can manually set the value of the automationName property for all components except item renderers. For item renderers, use the automationValue.

Creating a delegate class

To instrument custom components with a delegate, you must do the following:

  • Create a delegate class that implements the required interfaces. In most cases, you extend the UIComponentAutomationImpl class. You can instrument any component that implements IUIComponent.

  • Register the delegate class with the AutomationManager.

  • Define the component in a class definitions XML file.

The delegate class is a separate class that is not embedded in the component code. This helps to reduce the component class size and also keeps automated testing code out of the final production SWF file.

All visual Flex controls have their own delegate classes. These classes are in the spark.automation.delegates.components.* package for Spark controls, and the mx.automation.delegates.* package for MX components. The class names follow a pattern of Class_nameAutomationImpl. For example, the delegate class for the Spark Button control is spark.automation.delegates.components.SparkButtonAutomationImpl. The delegate class for the MX Button control is mx.automation.delegates.controls.ButtonAutomationImpl.

The delegate class defines the following:
  • The enableAutomation() method

  • The replayAutomatableEvent() method

  • Event listeners

All subclasses of UIComponent store a reference to their delegate in the automationDelegate property. This behavior is defined in the UIComponent initializer, which calls the delegate’s enableAutomation() method. If you create a custom component that does not subclass UIComponent, then you must manually call the delegate’s enableAutomation() method in your custom component’s initilization code.

You map the delegate’s unique name and value properties to the automationName and automationValue properties.

Instrument with a delegate class

  1. Create a delegate class.

  2. Mark the delegate class as a mixin by using the [Mixin] metadata keyword.

  3. Register the delegate with the AutomationManager by calling the Automation.registerDelegateClass() method in the init() method. The following code is a simple example:

    [Mixin] 
    public class MyCompDelegate { 
        public static init(root:DisplayObject):void { 
            // Pass the component and delegate class information. 
            Automation.registerDelegateClass(MyComp, MyCompDelegate); 
        } 
    }

    You pass the custom class and the delegate class to the registerDelegateClass() method.

  4. Add the following code to your delegate class:

    1. Override the getter for the automationName property and define its value. This is the name of the object as it usually appears in automation tools such as QTP. If you are defining an item renderer, use the automationValue property instead.

    2. In the constructor, add event listeners for events that the automation tool records.

    3. Override the replayAutomatableEvent() method. The AutomationManager calls this method for replaying events. In this method, return whether the replay was successful. You can use methods of the helper classes to replay common events.

      For examples of delegates, see the source code for the Flex controls in the spark.automation.delegates.components.* package.

  5. Link the delegate class with the application SWF file in one of these ways:

    • Add the following includes compiler option to link in the delegate class:

      mxmlc -includes MyCompDelegate -- FlexApp.mxml
    • Build a SWC file for the delegate class by using the compc component compiler:

      compc -source-path+=. -include-classes MyCompDelegate -output MyComp.swc

      Then include this SWC file with your application by using the following include-libraries compiler option:

      mxmlc -include-libraries MyComp.SWC -- FlexApp.mxml

      This approach is useful if you have many components and delegate classes and want to include them as a single library so that you can share them with other developers.

  6. After you compile your application with the new delegate class, you must define the new interaction for the agent and the automation tool. For QTP, you must add the new component to QTP’s custom class definition XML file. For more information, see Using the class definitions file.

Using the class definitions file

The class definitions file contains information about all instrumented components. This file provides information about the components to the automation agent, including what events can be recorded and played back, the name of the component, and the properties that can be tested.

An example of a class definitions file is the TEAFlex.xml file, which is specific to the QTP automation tool. This file is included in the Flex Automation Package.

The TEAFlex.xml file is located in the “QTP_plugin_install\Adobe Flex 4 Plug-in for HP QuickTest Pro” directory. QTP recognizes any file in that directory that matches the pattern TEAFlex*.xml, where * can be any string. This directory also contains a TEAFlexCustom.xml file that you can use as a starting point for adding custom component definitions.

The TEAFlex.xml and FlexEnv.xml class definitions files describe instrumented components with the following basic structure:

<TypeInformation> 
    <ClassInfo> 
        <Description/> 
        <Implementation/> 
        <TypeInfo> 
            <Operation/> 
            ... 
        </TypeInfo> 
        <Properties> 
            <Property/> 
            ... 
        </Properties> 
    </ClassInfo> 
</TypeInformation>

The top level tag is <TypeInformation>. You define a new class that uses the <ClassInfo> tag, which is a child tag of the <TypeInformation> tag. The <ClassInfo> tag has child tags that further define the instrumented classes. The following table describes these tags:

Tag

Description

ClassInfo

Defines the class that is instrumented, for example, SparkButton. This is the name that the automation tools use for the Spark Button control.

Attributes of this tag include Name, GenericTypeID, Extends, and SupportsTabularData.

Description

Defines the text that appears in the automation tool to define the component. This is not implemented in the AutoQuick example, but is implemented for QTP.

Implementation

Defines the class name, as it is known by the Flex compiler, for example, Button or MyComponent.

There is an optional property of the <Implementation> element, version. You use the version property to differentiate controls that are in same package and have the same name, but have differences in their API across the versions. For example:
 <Implementation Class="my.custom.controls::BigButton" version="1.5"/>

TypeInfo

Defines events for this class. Each event is defined in an <Operation> child tag, which has two child tags:

  • The <Implementation> child tag associates the operation with the actual event.

  • Each operation can also define properties of the event object by using an <Argument> child tag.

Properties

Defines properties of the class. Each property is defined in a <Property> child tag. Inside this tag, you define the property’s type, name, and description.

For each Property, if the ForDescription attribute is true, the property is used to uniquely identify a component instance in the automation tool; for example, the label property of a Button control. QTP lists this property as part of the object in QTP object repository.

If the ForVerfication attribute is true, the property is visible in the properties dialog box in QTP.

If the ForDefaultVerification tag is true, the property appears selected by default in the dialog box in QTP. This results in verification of the property value in the checkpoint.

The following example adds a new component, MyComponent, to the class definition file. This component has one instrumented event, click:

<TypeInformation xsi:noNamespaceSchemaLocation="ClassesDefintions.xsd" Priority="0" PackageName="TEA" Load="true" id="Flex" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <ClassInfo Name="MyComponent" GenericTypeID="mycomponent" 
        Extends="FlexObject" SupportsTabularData="false"> 
        <Description>FlexMyComponent</Description> 
        <Implementation Class="MyComponent"/> 
        <TypeInfo> 
            <Operation Name="Select" PropertyType="Method" 
                ExposureLevel="CommonUsed"> 
                <Implementation Class="myComponentClasses::MyComponentEvent" Type="click"/> 
            </Operation> 
        </TypeInfo> 
        <Properties> 
            <Property Name="automationClassName" ForDescription="true"> 
                <Type VariantType="String"/> 
                <Description>This is MyComponent.</Description> 
            </Property> 
            <Property Name="automationName" ForDescription="true"> 
                <Type VariantType="String"/> 
                <Description>The name used by tools to id an object.</Description> 
            </Property> 
            <Property Name="className" ForDescription="true"> 
                <Type VariantType="String"/> 
                <Description>To be written.</Description> 
            </Property> 
            <Property Name="id" ForDescription="true" ForVerification="true"> 
                <Type VariantType="String"/> 
                <Description>Developer-assigned ID.</Description> 
            </Property> 
            <Property Name="index" ForDescription="true"> 
                <Type VariantType="String"/> 
                <Description>The index relative to its parent.</Description> 
            </Property> 
        </Properties> 
    </ClassInfo> 
    ... 
</TypeInformation>

You can edit the class definitions file to add a new recordable event to an existing component. To do this, you insert a new <Operation> in the control’s <TypeInfo> block. This includes the implementation class of the event, and any arguments that the event might take.

The following example adds a new event, MouseOver, with several arguments to the Button control:

<TypeInfo> 
    <Operation ExposureLevel="CommonUsed" Name="MouseOver" PropertyType="Method"> 
        <Implementation Class="flash.events::MouseEvent" Type="mouseOver"/> 
        <Argument Name="inputType" IsMandatory="false" 
            DefaultValue="mouse"> 
            <Type VariantType="String"/> 
        </Argument> 
        <Argument Name="shiftKey" IsMandatory="false" DefaultValue="false"> 
            <Type VariantType="Boolean"/> 
        </Argument> 
        <Argument Name="ctrlKey" IsMandatory="false" DefaultValue="false"> 
            <Type VariantType="Boolean"/> 
        </Argument> 
        <Argument Name="altKey" IsMandatory="false" DefaultValue="false"> 
            <Type VariantType="Boolean"/> 
        </Argument> 
    </Operation> 
</TypeInfo>

When you finish editing the class definitions file, you must distribute the new file to the automation tool users. For QTP, users must copy this file manually to the “QTP_plugin_install\Adobe Flex 4 Plug-in for HP QuickTest Pro” directory. When you replace the class definitions file in the QTP environment, you must restart QTP.

Setting the automationName property

The automationName property defines the name of a component as it appears in testing scripts. The default value of this property varies depending on the type of component. For example, a Button control’s automationName is the label of the Button control. Sometimes, the automationName is the same as the control’s id property, but this is not always the case.

For some components, Flex sets the value of the automationName property to a recognizable attribute of that component. This helps QC professionals recognize that component in their scripts. For example, a Spark Button labeled “Process Form Now” appears in the testing scripts as SparkButton("Process Form Now").

If you implement a new component, or subclass an existing component, you might want to override the default value of the automationName property. For example, UIComponent sets the value of the automationName to the component’s id property by default, but some components use their own methods of setting its value.

The following example sets the automationName property of the ComboBox control to “Credit Card List”; rather than using the id property, the testing tool typically uses “Credit Card List” to identify the ComboBox in its scripts:

<?xml version="1.0"?>
<!-- agent/SimpleComboBox.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">
    <fx:Script>
        <![CDATA[
            [Bindable]
            public var cards: Array = [ 
                {label:"Visa", data:1}, 
                {label:"MasterCard", data:2}, 
                {label:"American Express", data:3} 
            ];
        
            [Bindable]
            public var selectedItem:Object;        
        ]]>
    </fx:Script>
    <s:Panel title="ComboBox Control Example">
        <s:layout>
            <s:VerticalLayout/>
        </s:layout>
        
        <mx:ComboBox id="cb1" dataProvider="{cards}" width="150" 
            close="selectedItem=ComboBox(event.target).selectedItem" 
            automationName="Credit Card List"/>

        <s:VGroup width="250">
            <s:Label width="200" color="blue" 
                text="Select a type of credit card."/>
            <s:Label text="You selected: {selectedItem.label}"/>
            <s:Label text="Data: {selectedItem.data}"/>
        </s:VGroup>         
    </s:Panel>    
</s:Application>       

If you do not set the value of the automationName property, the name of an object that appears in a testing tool is sometimes a property that can change while the application runs. If you set the value of the automationName property, testing scripts use that value rather than the default value. For example, by default, QTP uses a Button control’s label property as the name of the Button in the script. If the label changes, the script can break. You can prevent this from happening by explicitly setting the value of the automationName property.

Buttons that have no label, but have an icon, are recorded by their index number. In this case, you should ensure that you set the automationName property to something meaningful so that the QC professional can recognize the Button in the script. This might not be necessary if you set the toolTip property of the Button because some tools such as QTP use that value if there is no label.

After the value of the automationName property is set, you should not change the value during the component’s life cycle.

For item renderers, use the automationValue property rather than the automationName property. You do this by overriding the createAutomationIDPart() method and returning a new value that you assign to the automationName property, as the following example shows:

<mx:List xmlns:mx="http://www.adobe.com/2006/mxml"> 
    <fx:Script> 
        <![CDATA[ 
            import mx.automation.IAutomationObject; 
            override public function 
                createAutomationIDPart(item:IAutomationObject):Object { 
                    var id:Object = super.createAutomationIDPart(item); 
                    id["automationName"] = id["automationIndex"]; 
                    return id; 
            } 
        ]]> 
    </fx:Script> 
</mx:List>

This technique works for any container or list-like control to add index values to their children. There is no method for a child to specify an index for itself.

Instrumenting composite components

Composite components are custom components made up of two or more components. A common composite component is a form that contains several text fields, labels, and buttons. Composite components can be MXML files or ActionScript classes.

By default, you can record operations on all instrumented child controls of a container. If you have a Button control inside a custom TitleWindow container, the QA professional can record actions on that Button control just like on any Button control. You can, however, create a composite component in which some of the child controls are instrumented and some are not. To prevent the operations of a child component from being recorded, you override the following methods:

  • numAutomationChildren getter

  • getAutomationChildAt()

The numAutomationChildren property is a read-only property that stores the number of automatable children that a container has. This property is available on all containers that have delegate implementation classes. To exclude some children from being automated, you return a number that is less than the total number of children.

The getAutomatedChildAt() method returns the child at the specified index. When you override this method, you return null for the unwanted child at the specified index, but return the other children as you normally would.

The following custom composite component is written in ActionScript. It consists of a VGroup container with three buttons (OK, Cancel, and Help). You cannot record the operations of the Help button. You can record the operations of the other Button controls, OK and Cancel. The following example sets the values of the OK and Cancel buttons’ automationName properties. This makes those button controls easier to recognize in the automated testing tool’s scripts.
// agent/MyVGroup.as
package { // Empty package
    import mx.core.UIComponent;
    import spark.components.VGroup;
    import spark.components.Button;
    import mx.automation.IAutomationObject;
    import spark.automation.delegates.components.SparkGroupAutomationImpl;

    public class MyVGroup extends VGroup {
        public var btnOk : Button;
        public var btnHelp : Button;
        public var btnCancel : Button;

        public function MyVGroup():void { // Constructor
        } 
        
        override protected function createChildren():void {
            super.createChildren();
                
            btnOk = new Button();
            btnOk.label = "OK";
            btnOk.automationName = "OK_custom_form";
            addElement(btnOk);
    
            btnCancel = new Button();
            btnCancel.label = "Cancel";
            btnCancel.automationName = "Cancel_custom_form";
            addElement(btnCancel);
    
            btnHelp = new Button();
            btnHelp.label = "Help";
            btnHelp.showInAutomationHierarchy = false;
            addElement(btnHelp);      
        } 
        
        override public function get numAutomationChildren():int {
            return 2; //instead of 3
        }

        override public function 
            getAutomationChildAt(index:int):IAutomationObject {
            switch(index) {
                case 0:
                    return btnOk;
                case 1:
                    return btnCancel;
            }
            return null;
        }                
    } // Class
} // Package
The following application uses the MyVGroup custom component:
<?xml version="1.0"?>
<!-- agent/NestedButton.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx"
    xmlns:comps="*">
    <s:Panel title="Composite VGroup with Custom Automation Settings">
        <s:layout>
            <s:VerticalLayout/>
        </s:layout>

        <comps:MyVGroup/>

    </s:Panel>    
</s:Application>       

To make this solution more portable, you could create a custom Button control and add a property that determines whether a Button should be testable. You could then set the value of this property based on the Button instance (for example, btnHelp.useInAutomation = false), and check against it in the overridden getAutomationChildAt() method, before returning null or the button instance.

For better performance, you can use the getAutomationChildren() method rather than the getAutomationChildAt() method; for example:
var childList:Array = getAutomationChildren(); 
var n:int = childList ? childList.length : 0; 
for (var i:int = 0; i < n; i++) { 
    var child:IAutomationObject = childList[i]; 
    ... 
}

Custom agents

The Automation Framework defines a single API that has two parts:

  • Component API — Components must implement this API to support automation features. Developers can choose to put the code either in the main component itself or in a mixin class. Mixin classes implement this API for Flex components.

  • Agent API — Agents use this API to communicate with the component API.

Component developers implement the component API for their component once and then the component is ready to converse with any agent. Agent developers implement the agent API for their specific feature or tool and it is able to work with any application built with Flex. For more information, see About the automation APIs.

Uses for agents

You can use agents to gather metrics information, to use automated testing, and to run applications at different locations at the same time.

Metrics

You might want to analyze how your online applications are being used. By gathering metrics information, you can answer the following questions:

  • What product views are most popular?

  • When do users abandon the checkout process?

  • What is a typical path through my application?

  • How many product views does a user look at during a session?

Automated testing

Maintaining the quality of a large software application is difficult. Verifying lots of functionality in any individual build can take a QA engineer many hours or even days, and much of the work between builds is repetitive. To alleviate this difficulty, automated testing tools have been created that can use applications and verify behavior without human intervention. Major application environments such as Java and .NET have testing tool support from vendors such as QTP and Segue.

By using the Flex automation API, you can:

  • Record and replay events in a separate tool

  • Manage communication between components and agents

Co-browsing

You might want to run the same application at different locations and view the application at the same time. By using the automation API, you can ensure that the applications are synchronized as users navigate through the them. User interaction at any location can be played at other locations and other users can see the action in real time.

About the automation APIs

There are four main parts that enable the automation framework in Flex:

  • Core Flex API

  • Automation APIs

  • Agent class (also known as an adapter)

  • Automation tools

The following illustration shows the relationship between these parts:

About the SystemManager class

The SystemManager class is one of the highest level classes in an application built with Flex. Every application built with Flex has a SystemManager class. It is the parent of all displayable objects in the application, such as the main spark.components.Application instance and all pop-up windows, ToolTip instances, cursors, and so on.

SystemManager calls the init() method on all mixins. The SystemManager class is responsible for creating the AutomationManager class as well as the Agent and the delegate classes. The SystemManager class is also responsible for adding the Application object to Adobe® Flash® Player or Adobe® AIR™ stage (root).

About the AutomationManager class

The AutomationManager class is a Singleton that extends the EventDispatcher class. It implements the IAutomationManager, IAutomationObjectHelper, and IAutomationMouseSimulator interfaces.

AutomationManager is a mixin, so its init() method is called by the SystemManager class when the application is initialized. In the init() method, the AutomationManager class adds an event listener for the Event.ADDED event. This event is dispatched by Flash Player or AIR whenever a display object is added to the display list. When that happens, Flash Player or AIR calls the childAddedHandler() method:

root.addEventListener(Event.ADDED, childAddedHandler, false, 0, true);

In the childAddedHandler() method, the AutomationManager class:

  • Creates a new instance of a delegate for each display object.

  • Adds the display object to a delegate class map. This maps the display object to a delegate instance; for example:

    Automation.delegateDictionary[componentClass] = Automation.delegateDictionary[className];

    The delegate class map is a property of the Automation class.

  • Ensures that all children of the new display object are added to the class map as well.

In the recordAutomatableEvent() method, the AutomationManager:

  • Creates an AutomationRecordEvent.RECORD event.

  • Dispatches the RECORD events. The agent listens for these events.

The recordAutomatableEvent() method is called by the delegates.

About the Automation class

During application initialization, an instance of the Automation class is created. This is a static class that maintains a map of its delegate class to its component class.

This object does the following for recording events:

  • Provides access to the AutomationManager class

  • Creates a delegateDictionary as a static property

For example, there is a MyButton class that extends the Button class, but it does not have its own delegate class (MyButton might not add any new functionality to be recorded or played back). When the AutomationManager encounters an instance of the MyButton class, it checks with the Automation class for a corresponding delegate class. When it fails to find one, it uses the getSuperClassName() method to get the super class of the MyButton class, which is the Button class.

The AutomationManager then tries to find the delegate for this Button class. At that point, the AutomationManager adds a new entry into the delegate-component class for the MyButton class, associating it with the ButtonDelegateAutomationImpl class, so that next time the AutomationManager can find this mapping without searching the inheritance hierarchy.

About the delegate classes

The delegate classes provide automation hooks to the Flex components. The delegate classes are in the spark.automation.delegates.* and mx.automation.delegates.* packages. They extend the UIComponentAutomationImpl class. The delegates for the Spark components are named SparkControlNameAutomationImpl. The delegates for the MX components are named ControlNameAutomationImpl. For example, the Spark Button control’s delegate class is SparkButtonAutomationImpl.

The delegate classes register themselves with their associated Automation class by providing the component class and their own class as input. The AutomationManager class uses the Automation class-to-delegate class map to create a delegate instance that corresponds to a component instance in the childAddedHandler() method.

The delegate classes are mixins, so their init() method is called by the SystemManager class. The init() method of the delegate classes:

  1. Calls the registerDelegateClass() method of the Automation class. This method maps the class to an automation component class; for example:

    var className:String = getQualifiedClassName(compClass); 
    delegateDictionary[className] = delegateClass;
  2. Adds event listeners for the mouse and keyboard events; for example:

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

These event handlers call the AutomationManager class’s recordAutomatableEvent() method, which in turn dispatches the AutomationRecordEvent.RECORD events that the automation agent listens for.

All core framework and charting classes have delegate classes already created. You are not required to create any delegate classes unless you have custom components that dispatch events that you want to automate. In this case, you must create a custom delegate class for inclusion in your application.

About the agent

The agent facilitates communication between the application built with Flex and automation tools such as QTP and Segue.

When recording, the agent class is typically responsible for implementing a persistence mechanism in the automation process. It gets information about events, user sessions, and application properties and typically writes them out to a database, log file, LocalConnection, or some other persistent storage method.

When you create an agent, you compile it and its supporting classes into a SWC file. You then add that SWC file to your application by using the include-libraries command-line compiler option. This compiles the agent into the application, regardless of whether you instantiate that agent in the application at compile time.

A custom agent class must be a mixin, which means that its init() method is called by the SystemManager class. The init() method of the agent:

  • Defines a handler for the RECORD events.

  • Defines the environment. The environment indicates what components and their methods, properties, and events can be recorded with the automation API.

A typical custom agent class uses an XML file that contains Flex component API information. The agent typically loads this information with a call to the URLRequest() constructor, as the following example shows:

var myXMLURL:URLRequest = new URLRequest("AutomationGenericEnv.xml"); 
myLoader = new URLLoader(myXMLURL); 
automationManager.automationEnvironment = new CustomEnvironment(new XML(source));

In this example, the source is an XML file that defines the Flex metadata (or environment information). This metadata includes the events and properties of the Flex components.

Note that representing events as XML is agent specific. The general-purpose automation API does not require it, but the XML file makes it easy to adjust the granularity of the events that are recorded.

You are not required to create an instance of your adapter in the init() method. You can also create this instance in the APPLICATION_COMPLETE event handler if your agent requires that the application must be initialized before it is instantiated.

Most agents require a set of classes that handle the way in which the agent persists automation information and defines the environment. For more information, see Creating a recording agent.

Understanding the automation flow

When the application is initialized, the AutomationManager object is created. In its init() method, it adds a listener for Event.ADDED events.

The following image shows the order of events when the application is initialized and the AutomationManager class constructs the delegate map.

  1. The SystemManager class creates the display list, a tree of visible objects that make up your application.

  2. Each time a new component is added, either at the root of the display list or as a child of another member of the display list, SystemManager dispatches an Event.ADDED event.

  3. The AutomationManager listens for the ADDED event. In its ADDED event handler, it calls methods on the Automation class. It then instantiates the delegate for that class.

  4. The Automation class maps each component in the display list to its full class name.

  5. When it is created, the delegate class adds a reference to its instance in the delegate class map. The delegate class then handles events during record and play-back sequences.

    The delegate is now considered registered with the component. It adds event listeners for the component’s events and calls the AutomationManager when the component triggers those events.

After the components in the display list are instantiated and mapped to instances of their delegate classes, the AutomationManager is ready to listen for events and forward them to the agent for processing.

The following image shows the flow of operation when a user performs an action that is a recordable event. In this case, the user clicks a Button control in the application.

  1. The user clicks the Button control in the application. The SystemManager dispatches a MouseEvent.CLICK event.

  2. The SparkButtonAutomationImpl class, the Button control’s automation delegate, listens for click events. In the delegate’s click event handler, the delegate calls the AutomationManager recordAutomationEvent() method. (It is likely that the button also defines a click event handler to respond to the user action, but that is not shown.)

  3. The AutomationManager’s recordAutomationEvent() method dispatches an AutomationRecordEvent.RECORD event. In that event, the replayableEvent property points to the original click event.

  4. The custom agent class listens for RECORD events. When it receives the RECORD event, it uses the replayableEvent property to access the properties of the original event.

  5. The agent records the event properties in a database, logs the event properties, or gets information about the user before recording them.

Creating agents

You create an agent as a SWC file and link it into the application by using the include-libraries compiler option. You can link multiple agents in any number of SWC files to the same application. However, to use multiple agents at the same time, you must use the same environment configuration files for all agents.

The general process for creating a custom agent is:

  • Mark the agent class as a mixin; this triggers a call to a static init() method from the SystemManager class on application start up.

  • Get a reference to the AutomationManager class.

  • Add event listeners for the APPLICATION_COMPLETE and RECORD events.

  • Load the environment information (Flex metadata that describes the objects and operations of the application). Environment information can be an XML file or it can be in some other data format.

  • Define a static init() method that creates an instance of the agent.

  • Define a method that handles RECORD events. In that method, you can access:

    • Automation details such as the automation name

    • User information such as the FlexSession object (through a RemoteObject)

    • The event that triggered the RECORD event and its target

The RECORD event handler in the agent gets an AutomationRecordEvent whose target is the object on which recording happened. The automationManager.createID() method converts the object to a string that can be recorded on the screen. Some tools may require the entire automation hierarchy that needs to be generated in this method.

You also use the custom agent class to enable and disable recording of automation events.