This document attempts to provide rudimentary information for a Java developer wishing to add custom support and functionality to the existing SAFS code without modifying the existing code. This is useful when support for custom controls needs to be added, or when other custom features are desired. This requires a Java developer familiar with their own Java development environment having the ability to implement a development project referencing SAFS JARS.
Because this type of customization is intended for experienced Java developers, very little general information for preparing the development environment is provided.
We must note that extending SAFS inside the core source is the most desirable of endeavors for generically useful functionality. That is, if your intended extensions are useful for others, then they should be presented to the community as a proposed enhancement or implemented in the core SAFS code.
If, however, the extension is something very specific to your site or your applications, then this custom extensions feature is what you need to exploit.
There are several different ways to inject customization:
Existing Java-based engines like IBM Rational Functional Tester(RFT) and Selenium 2.0 WebDriver typically implement a subclass of TestRecordHelper allowing them to provide engine-specific processors for things like Driver Commands and Component Functions.
The actual order of Processor instantiation attempted is:
Thus, using the predefined Package naming convention for each engine we can see that Driver Commands and Component Functions would be instanced in this order:
In general, a custom class should be a subclass of the standard processor it is trying to extend. The custom class has the opportunity to enhance, replace, and provide all new functionality while still having access to all existing functionality by invoking superclass methods.
For every custom command you wish to handle, you generally need to define a custom keyword to represent that command, and then the function in the code to handle it.
Below is a sample SAFS Driver Command--record type "C"--that will ultimately get handled by the custom Driver Command processor:
C, MyCustomCommand, AParam, ^MyVar, "Anything At All"Note in the example above how you can pass any number of parameters to the custom code. Consult your specific superclass JavaDoc or sourcecode to find how they are retrieved in your subclass implementation.
A sample SeleniumPlus TestCase invocation of the same custom command:
Runner.command("MyCustomCommand", AParam, "^MyVar", "Anything At All");
Below is a sample custom Selenium WebDriver Driver Command Processor intended to handle the MyCustomCommand Driver Command.
We need our default custom subclass Constructor to invoke the superclass Constructor.
The Selenium WebDriver engine implementation suggests we override the superclass localProcess() method, as shown below:
package org.safs.selenium.webdriver.custom;
import org.safs.Log; // The Debug Log
import org.safs.SAFSException;
import org.safs.StatusCodes;
public class DCDriverCommand extends org.safs.selenium.webdriver.DCDriverCommand{
public static final String MY_CUSTOM_COMMAND = "MyCustomCommand";
/**************************
Default Constructor
Must initialize superclass
**************************/
public DCDriverCommand() { super(); }
/******************************************************
localProcess() function
automatically invoked by superclass chain of Processors
*******************************************************/
@Override
protected void localProcess() {
// recommended debug message prefix for this method
String dbg = getClass().getName()+".localProcess ";
// handle the incoming command in much the same way the superclass does
try {
// command and params are already prepared for us
if( MY_CUSTOM_COMMAND.equalsIgnoreCase( command )){
// call our custom function implementation
doMyCustomCommand();
}
// if we didn't recognize the command at all
// our superclass chain will automatically give it a try
// assuming it is NOT a custom command that we have implemented
// gracefully handle Exceptions during our processing
}catch (SAFSException ex) {
Log.error(dbg+"error processing command "+ command, ex); // The Debug Log
issueErrorPerformingAction(command, ex.getMessage());
}
}
/*********************************************
doMyCustomCommand() called from localProcess()
**********************************************/
protected void doMyCustomCommand(){
// recommended debug message prefix for this method
String dbg = getClass().getName()+".doMyCustomCommand ";
Log.info(dbg+"processing "+ command); // The Debug Log
String anArg = null;
// retrieve any parameters if the code expects them
iterator = params.iterator();
if( iterator.hasNext() ){
// assign and validate required and optional parameter(s)
anArg = iterator.next();
}
// do it
boolean success = myWorkOutput();
// set appropriate status code and pass/fail/debug logging
if( success ){
Log.info(dbg+"successful executing "+ command +" using "+ anArg); // The Debug Log
issueGenericSuccessUsing(anArg, null);
}else{
Log.error(dbg+"failed when executing "+ command +" using "+ anArg); // The Debug Log
issueErrorPerformingActionUsing(anArg, "Something terrible happened!");
}
}
}
Extending SAFS in this way can be trivial or exceeding complex. It all depends on what is trying to be done. Some extensions are easier than others. The developer needs to have a good understanding of the SAFS Processor API they are trying to extend, and the volume of utility libraries that come along with each SAFS engine. That generally comes with experience and exposure to the framework details.
Your best source of code samples are the superclass, or any of the other Processor classes provided for the target engine.
Ensuring the new custom classes are visible to SAFS and\or the automation tool that may be executing them is critical. For example, it may be possible the custom classes can reside in their own JAR and simply reside in the CLASSPATH. Some tools--notably IBM Rational Functional Tester using Eclipse--have implemented custom ClassLoaders that may or may not allow the custom extensions to exist in separate JAR files.
Essentially, you have to find what is required (or what works) for your specific tools and environment.
SAFS and SeleniumPlus support the automatic dependency inclusion of:
You should be able to JAR up the custom classes into safscust.jar. As long as this JAR file is placed in the same directory as the SAFS and/or SeleniumPlus runtime JAR files it should be found along with the custom classes within it.
Read about using the SAFS Debug Log.
A (silent) video tutorial on the SAFS Debug Log is also available.
The debug log will show when (and if) a custom processor was successfully invoked. Below is a sample debug log snippet showing the attempts at custom support invocation for the IBM Rational Functional Tester engine--SAFS/RobotJ:
[INFO 17:41:49.322:SAFS/RobotJ: PROC.IPAP2:Trying processor : org.safs.custom.DCDriverCommand@3e103e10 ]
[INFO 17:41:49.322:SAFS/RobotJ: Custom driver commands, FOR NOW< THERE ARE NONE > ]
[INFO 17:41:49.322:SAFS/RobotJ: DCP.IAPDC:Trying Custom Processors for WaitForGui ]
[DEBUG 17:41:49.338:SAFS/RobotJ: org.safs.rational.RTestRecordData.getCompInstancePath() return package name org.safs.rational. ]
[DEBUG 17:41:49.338:SAFS/RobotJ: PROC.VPCN:trying processor:org.safs.rational.custom ]
[INFO 17:41:49.369:SAFS/RobotJ: PROC.VPCN:org.safs.rational.custom:java.lang.ClassNotFoundException ]
[DEBUG 17:41:49.369:SAFS/RobotJ: org.safs.rational.RTestRecordData.getCompInstancePath() return package name org.safs.rational. ]
[DEBUG 17:41:49.369:SAFS/RobotJ: PROC.VPCN:trying processor:org.safs.rational.custom.DCDriverCommand ]
[INFO 17:41:49.400:SAFS/RobotJ: PROC.VPCN:org.safs.rational.custom.DCDriverCommand:java.lang.ClassNotFoundException ]
[DEBUG 17:41:49.400:SAFS/RobotJ: PROC.VPCN:trying processor:org.safs.rational.custom.custom.DCDriverCommand ]
[INFO 17:41:49.431:SAFS/RobotJ: PROC.VPCN:org.safs.rational.custom.custom.DCDriverCommand:java.lang.ClassNotFoundException ]
[INFO 17:41:49.431:SAFS/RobotJ: DCP.IAPDC:Trying SubClass Processors for WaitForGui ]
[DEBUG 17:41:49.431:SAFS/RobotJ: org.safs.rational.RTestRecordData.getCompInstancePath() return package name org.safs.rational. ]
[DEBUG 17:41:49.431:SAFS/RobotJ: PROC.VPCN:trying processor:org.safs.rational. ]
[INFO 17:41:49.478:SAFS/RobotJ: PROC.VPCN:org.safs.rational.:java.lang.ClassNotFoundException ]
[DEBUG 17:41:49.478:SAFS/RobotJ: org.safs.rational.RTestRecordData.getCompInstancePath() return package name org.safs.rational. ]
[DEBUG 17:41:49.478:SAFS/RobotJ: PROC.VPCN:trying processor:org.safs.rational.DCDriverCommand ]
[DEBUG 17:41:49.478:SAFS/RobotJ: PROC.VPCN:processorClass: org.safs.rational.DCDriverCommand ]
[INFO 17:41:49.478:SAFS/RobotJ: PROC.IPAP2:Trying processor : org.safs.rational.DCDriverCommand@24d024d0 ]
Note that every ClassNotFoundException above is an indication of a custom class that COULD exist, but does not.
You might also note that org.safs.custom.DCDriverCommand did NOT issue a ClassNotFoundException. That is because the class actually exists! It is a do-nothing sample implementation which simply displays the single debug log message shown.