This document will attempt to explain the Java-based implementation for SAFS engines. Specifically, how existing classes would be used or subclassed in the creation of a new Java-based engine. Remember that SAFS engines use STAF for cross-process communication. It is essential the engine developer become proficient in the use and understanding of what STAF offers.
Goto: Key Definitions, Using and Subclassing, Standardizing Component Recognition.
A driver generally has a minimal local version of the TRD it maintains as it calls various engines to complete tests. The driver copies required elements of this local TRD into the shared SAFS/HOOK/TRD in SAFSVARS so that all SAFS engines have access to the information needed to accomplish the tests when dispatched.
Each SAFS engine will retrieve the information passed via SAFSVARS and populate its own local TestRecordHelper(TRH) subclass. The engine, being based on a specific GUI testing tool, will have additional information stored in its TRH subclass that is specific to how that tool locates and identifies GUI components, and any other information the GUI tool needs to accomplish the task.
It is important to note that the design of LogUtilities does not mean you choose between using SAFSLOGS, or using your tool's logging instead. The design is intended to allow you to log to all possible and enabled log outputs with a single function call. SAFSLOGS outputs text logs, console logs, and XML logs and informs the subclassed tool-specific LogUtilities which log modes are active and enabled.
Perhaps the quickest way for an engine developer to see how the above items would be used is to review the Javadoc, review the base classes, then review the subclasses (top of each Javadoc) that have already been done in support of existing engines. Examples of subclassing exist for the SAFS/RobotJ engine and the SAFS/DriverCommands engine.
On a historical note, the SAFS/RobotJ engine (RJ) was our first Java-based engine and its "hook" mechanism using a "TestScript" and the "RobotJHook" was the model for the general-purpose "JavaHook".
Jump to subclassing JavaHook, TestRecordHelper, LogUtilities, DDGUIUtilities, ProcessRequest/Processors.
An engine-specific subclass of JavaHook is rather simple. The main task of the subclass is to make sure that the appropriate subclasses of LogUtilities, DDGUIUtilities, TestRecordHelper are instantiated and cross-pollinated with references to each other. (Normally, a subclass of the ProcessRequest class is not necessary.) Override *all* the superclass constructors and implement all the abstract GET methods of the JavaHook superclass. Make sure these GET methods instance and return the appropriate subclass of objects.
If the JavaHook subclass is intended to be run standalone--that is, a runnable Java app in its own JVM--then the subclass will need an appropriate "static void main" entry point as required by all Java applications. An example of such an entry point is shown below. This one is from the SAFS/DriverCommands DCJavaHook subclass of JavaHook.
public static void main (String[] args) {
// SAMPLE STANDARD HOOK INITIALIZATION
// DCJavaHook hook = new DCJavaHook(SAFS_DRIVER_COMMANDS, new LogUtilities());
// SAMPLE ADVANCED HOOK INITIALIZATION
TestRecordHelper datahelper = new TestRecordHelper();
LogUtilities logs = new LogUtilities();
ProcessRequest requester = new ProcessRequest(
datahelper, // TestRecordHelper
logs, // LogUtilities
new DriverCommandProcessor(), // use standard DriverCommandProcessor
null, // disable standard TestStepProcessor
null, // no custom driver command support
null); // no custom test step support
DDGUIUtilities gui_utils = new DCGUIUtilities();
DCJavaHook hook = new DCJavaHook(
SAFS_DRIVER_COMMANDS, // STAF process name for hook instance
STAFHelper.SAFS_HOOK_TRD, // (default) SAFSVARS TestRecordData
logs, // LogUtilities
datahelper, // TestRecordHelper
gui_utils, // DDGUIUtilities
requester); // ProcessRequest
// this should now be properly handled by the superclass...
datahelper.setSTAFHelper(hook.getHelper());
// HOOK INITIALIZATION COMPLETE
if (args.length > 0 && args[0].equalsIgnoreCase("log")) {
Log.setHelper(hook.getHelper());
logs.setCopyLogClass(true);
}
hook.start();
}
The careful observer will notice that this particular engine is for Driver Commands only and is entirely independent of any GUI testing tool. Therefore, there are no special subclasses for LogUtilities, TestRecordHelper, or the ProcessRequest class. There is a "does nothing" generic DCGUIUtilities subclass used for DDDGUIUtilities since the supported Driver Commands in this engine will not be dealing with GUI components.
You may also notice that after all hook initialization is complete, there is additional code dealing with another separate Log class. This class is primarily used for debugging purposes, and this code won't actually do much of anything if a separate debugging Log window has not been launched.
An engine-specific subclass of TestRecordHelper is also fairly straightforward. Primarily, a subclass will override getCompInstancePath() to provide an engine-specific package name prefix for Processors that may be dynamically sought at runtime to handle test records. For example, this method for the RobotJ subclass returns "org.safs.rational" as the package name prefix.
Additionally, the subclass will add any storage and methods needed by the specific engine to handle tool-specific information needed to identify, locate, and even store GUI components referenced by the test record. For example, the RTestRecordData subclass of TestRecordHelper stores Rational RobotJ-specific information and objects needed at runtime to locate application objects.
Again, an easy one to subclass. LogUtilities actually handles everything that is difficult for our shared logging design. RobotJ's RLogUtilities shows how all that is really needed are the wrapper functions "toolLog" to write to your tool-specific logging mechanism and "consoleLog" to write to your tool-specific console (if applicable).
Everything else is handled by LogUtilities. Your engine code simply uses the appropriate logMessage() functions already provided by the LogUtilities superclass. LogUtilities will take care of when and if your tool-specific logging needs to be called.
Things start to get a little more "interesting" with a subclass of DDGUIUtilities. This is largely because we are beginning to identify some key tool-specific functionality that must be satisfied. For example, locating a text item in a List when the reference to the List may be a tool-specific class or object. Unfortunately, at the same time we start to lose important details in our embedded javadoc. Sorry.
DCGUIUtilities represents the minimum abstract functions that must be provided. These are for the SAFS/DriverCommands engine which actually processes no GUI objects, thus needs no real implementation for these methods.
RDDGUIUtilities for RobotJ, on the other hand, provides real, necessary implementations for these. It has also been given many additional fields and methods in support of locating and working with GUI components in the RobotJ toolset.
Until such time as the doc in the superclass can be fleshed out better, you will have to review the source of the DDGUIUtilities superclass and existing subclasses--like the source for DCGUIUtilities and source for RDDGUIUtilities) to get an idea of how best to implement these functions for your specific tool.
Ordinarily, the ProcessRequest class is instanced or initialized with access to all the tool-specific class information it needs to do its job. Thus, it is rare that this class would ever need to be subclassed. It is the Processors instanced by the ProcessRequest class that will likely need a tool-specific subclass.
Processor has already been subclassed to provide partial implementations for handling Driver Commands and Component Functions. Generally, tool-specific subclasses will extend one or both of these two classes to provide the necessary "process" method to handle the incoming request in a tool-specific manner.
Keep in mind, the STAF client that is acting as the DRIVER for these engines will normally have processed all Cycle and Suite level test records; and most Driver Commands too. A new engine based on a specific GUI testing tool normally must only deal with implementing one or more Processors of component functions, and any Driver Commands that might reference or act on GUI objects.
Note, the Javadoc for the DriverCommand and ComponentFunction classes linked above show several existing subclasses for these. Some of these are default implementations, some are default custom classes, and others are implementations specific to the RobotJ engine. The source for these classes provide excellent examples and guidance for implementing your own tool-specific "process()" functions.
Standard Component Recognition:
One of the issues involved when attempting to use multiple testing tools; or when trying to migrate from one tool to another; is that each tool has devised a unique way of specifying or locating application components. Some tools, like IBM Rational Robot and Mercury's WinRunner, allow you to specify a simple string that contains information about the target component.
Other tools, like IBM Rational RobotJ/XDE Tester, require that you provide a valid instance of a proxy "TestObject" or a similar component object--or the component itself--in order to perform an action on the component.
The SAFS framework is evolving a standardized mechanism for specifying, locating, and then retrieving these component references for each tool-specific technology.
Consult the SAFS Component Recognition document for details of implementing support for this in your tool-specific engine.