The resolvers module

This extension API lets you install hooks that can modify the way QF-Test recognizes and records components and items. This is a very powerful feature that gives you fine-grained control over the QF-Test component management.

Video Video: 'Resolvers in QF-Test'.

Usage

Note When registering resolvers it is important to specify the correct 'GUI engine' attribute in the 'SUT script'. If the wrong engine is specified, the resolver simply will not work. If no engine is specified the resolver applies to all engines which can cause confusion and break replay in engines for which the resolver was not intended.

We will start with a short description of how QF-Test does component recognition to give you an idea where resolvers come into play. It consists roughly of four steps:

  1. Get the component object from the GUI.
  2. Extract the data for each component: e.g. component class, id, coordinates, component text.
  3. Analyze the relationship between components: e.g. structure information (index), find the label belonging to the component (qfs:label).
  4. Recoding: Create a 'Component' node and save the retrieved data in the details of the node.
    Replay: Compare the retrieved data with the details of the node that is the target of the replay action.

QF-Test uses resolvers for steps 2 and 3. Via the API you can overwrite resolver methods and thus manipulate component recognition.

A resolver is the only way to influence component recognition during recording. During replay you also have other options (e.g. a script or a regular expression in the details of the 'Component' node) to get at component data.

Web For web applications QF-Test offers a specialized interface providing most of the functionality of the resolvers described below, which is a lot easier to handle. See Improving component recognition with a CustomWebResolver. The 'Install CustomWebResolver' node has been optimized for web elements thus providing a better performance than resolvers of this section. The use of below resolvers for web components should be limited to very special cases.

A list of available resolvers:

Implementation

The following two steps are required to implement a resolver:

  1. Implementation of the resolver interface.
  2. Registration of the interface for the desired component class(es).

In most cases the interface consists of only one method. A typical example would be (Jython-Skript):

def getName(menuItem, name):
    if not name:
        return menuItem.getLabel()
resolvers.addResolver("menuItems", getName, "MenuItem")
Example 53.1:  Simple NameResolver (Jython) for MenuItems

The first three lines are the method of the resolver interface. The name of the method defines the resolver type. Each resolver type manipulates a certain value in the 'Component' node data. In our case the method is getName thus defining a name resolver. The fourth line calls the function addResolver of the resolvers module and registers the resolver.

Most resolver methods only have two parameters: the first is the component for which component recognition is done at that moment. The second is the value or object to be handled by the method. With a name resolver it is the name determined by the QF-Test standard name resolver. With a feature resolver the feature determined by QF-Test and so on. You will find a detailed description of the resolvers interfaces in chapters subsection 53.1.7 to subsection 53.1.26.

Each resolver needs to be given a name at registration time. The name has to be unique. It will be used when the resolver needs to be updated or uninstalled explicitly via resolvers.removeResolver("resolver name") (see subsection 53.1.4). The names of all registered resolvers can be listed via the function resolvers.listNames() (see subsection 53.1.5).

After changing the contents of a resolver script it needs to be executed again in order to register the updated resolver. As long as the name of the resolver remains unchanged there is no need to first deregister the old version first.

All resolver types can be registered either for single components, specific classes or Generic classes. Resolvers registered for a single component are only called when exactly that component is being handled. Resolvers registered for a certain class are called for all components of this type and derived classes.

A resolver may be registered for one or several components and/or classes. If no parameter is specified the resolver will be called for components of all classes. For example, a NameResolver or a FeatureResolver registered globally will be called for each and every name or feature. This is similar to but more efficient than registering them on the java.lang.Object class in the case of Java applications.

You may set up resolvers for various tasks and register them at run time. In order to install a resolver permanently, put the 'SUT script' node for the resolver directly after the 'Wait for client to connect' node in the start sequence of the SUT.

If multiple resolvers are registered globally or registered on the same object or class, the resolver added last will be called first. The first resolver returning a non-null value determines the outcome.

Since a resolver will be called for each instance of the component, respectively class, displayed in the GUI you should implement time-saving algorithms for the resolvers. For example, in a Jython script the execution of string[0:3] == "abc" is faster than string.startswith("abc").

All exceptions thrown inside a name resolver will be caught and handled by the ResolverRegistry. However, instead of dumping a stack trace, the registry will only print a short message like "Exception inside NameResolver" because some resolvers may be called very often, and a buggy resolver printing a stack trace for every error would flood the net and the client terminal. Therefore name resolvers should include their own error handling. This can still generate a lot of output in some cases, but the output will be more useful than a Java stack trace.

The resolvers module is always automatically available in all 'SUT script' nodes.

Most examples in the manual are implemented as Jython scripts. In subsection 53.1.7 you will find examples for Groovy 'SUT script' nodes.

addResolver

The generic function addResolver has a central role in the resolvers module. Given the name of the defined method and its parameters it identifies the respective object and its specific function for registering the resolver.

 
 
void addResolver(String resolverName, Method method, Object target=None, ...)
Register the resolver determined by the given method for the given target(s). If another resolver was previously registered under the given name, deregister that first.
Parameters
name The name under which to register the resolver.
method The method implementing the resolver method. The name of the method defines the type of the registered resolver, i.e. for Groovy this has to be a MethodClosure. Valid names are e.g.: getName, getClassName, getGenericClassName, getFeature, getExtraFeatures, getItemName, getItemValue, getItemNameByIndex, getTree and getTreeColumn, isInterestingParent, getTooltip, getId, isEnabled, isVisible, getMainText, matchExtraFeature, isBusy, isGlassPaneFor, sync and applicationIsBusy.
target One or more optional targets to register the resolver for. Each can be any of the following:
  • An individual component
  • The fully qualified name of a class
If no target is given a global resolver for all components is registered.
 
void addResolver(String resolverName, Object object, Object target=None, ...)
Register the resolver determined by the given method(s) of the object for the given targets. If another resolver was previously registered under the given name, deregister that first.
Parameters
name The name under which to register the resolver.
object An object or a class providing one or more resolver methods. Depending on the method names the respective resolver is registered. For valid method names see the description of addResolver above.
target One or more optional targets to register the resolver for. Each can be any of the following:
  • An individual component
  • The fully qualified name of a class
If no target is given a global resolver for all components is registered.
 
 

History

Resolvers have quite a history in QF-Test. Up to QF-Test version 4.1 you had to call a function specific to each resolver interface in order to register a certain resolver type. You may continue to use those functions. However, they are no longer described in the manual. The flexible addResolver function replaces the following functions, among others, of the resolvers module:

  • addNameResolver2(String name, Method method, Object target=None, ...)
  • addClassNameResolver(String name, Method method, Object target=None, ...)
  • addGenericClassNameResolver(String name, Method method, Object target=None, ...)
  • addFeatureResolver2(String name, Method method, Object target=None, ...)
  • addExtraFeatureResolver(String name, Method method, Object target=None, ...)
  • addItemNameResolver2(String name, Method method, Object target=None, ...)
  • addItemValueResolver2(String name, Method method, Object target=None, ...)
  • addTreeTableResolver(String name, Method getTable, Method getColumn=None, Object target=None)
  • addTooltipResolver(String name, Method method, Object target=None, ...)
  • addIdResolver(String name, Method method, Object target=None,...)

removeResolver

The function removeResolver may be used to deregister resolvers installed via the resolvers module.

Often, resolvers are registered directly after the start of the application and remain active during the full time of test execution. In some cases, however, resolvers are required only for handling a certain component and then need be to removed, either due to performance issues or because the effect of the resolver is not desirable for other components.

There are two functions for deregistration. The first, removeResolver deregisters a single resolver, the second, removeAll, removes all resolvers registered by the user.

 
 
void removeAll()
Deregister all resolvers registered via the resolvers module from all targets they were registered for.
 
void removeResolver(String name)
Deregister a resolver from all the targets it was registered for.
Parameters
nameThe name the resolver was registered under.
 
 

The example first removes a resolver registered under the name "menuItems", then deregister all resolvers registered via the resolvers module.

resolvers.removeResolver("menuItems")
resolvers.removeAll()
Example 53.2:  SUT script deregistering a resolver

listNames

Return a list of resolver names registered via the resolvers module.

 
 
List<String> listNames()
List the registered resolvers.
 
 

The example checks whether a certain resolver has been registered. If not, an error message is written to the run log.

if (! resolvers.listNames().contains("specialNames")) {
    rc.logError("Special names resolver not registered!")
}
Example 53.3:  Groovy SUT script searching for a certain resolver registered via the resolvers module

Accessing 'Best label'

When you want to access the Best label from within a resolver, you can use the method rc.engine.helper.getBestLabel().

 
 
String getBestLabel(Component node)
Returns the Best label.
Parameters
node The component for with to fetch the best label.
Returns The best label for the component.
 
 

The example shows a name resolver transferring the best label to the 'Name' attribute.

_h = rc.engine.helper
def getName(node, name):
    label = _h.getBestLabel(node)
        return label

resolvers.addResolver("labelAsName", getName, "TextField", "TextArea")
Example 53.4:  NameResolver using the best label

3.1+53.1.7
The NameResolver Interface

The NameResolver works on the 'Name' attribute value of a 'Component' node.

After QF-Test determined the name of a GUI element the registered NameResolvers get a chance to override or suppress this name. The first resolver that returns a non-null value determines the outcome. If no resolvers are registered or all of them return null the original name is used.

A NameResolver can change (or provide) the name of a GUI element as set with setName() for AWT/Swing, setId() or the fx:id attribute for JavaFX, setData(name, ...) for SWT or via the 'ID' attribute of a DOM node for web applications. It can be very useful when setting names in the source code is not an option, like for third-party code or when child components of complex components are not readily accessible. For example, QF-Test provides a name resolver for the Java Swing JFileChooser dialog, which you can read more about in the Tutorial chapter 'The Standard Library'.

In some cases it may be desirable to suppress an element's name, for example for names which are not unique or which - even worse - vary depending on the situation. To do so, getName should return the empty string.

Technologies: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. For web applications please use 'Install CustomWebResolver' node as described in Improving component recognition with a CustomWebResolver. It was optimized for web elements and is more performant. Just in case the functionality provided there is insufficient make use of the NameResolver.

A NameResolver needs to implement the following method:

 
 
String getName(Object element, String name)
Determine the name of a GUI element.
Parameters
element The element to determine the name for.
name The original name QF-Test would use without a resolver.
Returns The name to use or null if the element is not handled by the resolver. Returning an empty string suppresses the original name.
 
 

The first example is a NameResolver returning the text of the menu item as name for components of the generic class MenuItems for which the QF-Test standard resolver could not determine a name.

def getName(menuItem, name):
    if not name:
        return menuItem.getLabel()
resolvers.addResolver("menuItems", getName, "MenuItem")
Example 53.5:  Jython name resolver for MenuItems

Give it a try. Copy the example above into a 'SUT script' node and execute it. If your application is based on SWT instead of Swing, replace getLabel() with getText(). Then record some menu actions into a new, empty test suite. You'll find that all recorded menu item components without name will now have names set according to their labels. If setName is not used in your application and the labels of menu items are more or less static while the structure of the items often changes, this can be a very useful feature.

The second example is a name resolver assigning a defined name ('Serial number') to a component which would otherwise have a partially dynamic name (e.g. 'Serial no: 100347'). It is registered for a specific Java Swing class.

def getName(menuItem, name):
    if name and name[0:10] == "Serial no:":
        return "Serial number"
resolvers.addResolver("lfdNr", getName, "javax.swing.JMenuItem")
Example 53.6:  Jython NameResolver for a specific class

The following Groovy example returns the text of the menu item as the name for a component the QF-Test standard resolver did not find a name for.

def getName(def menuItem, def name) {
    if (name == null) {
        return menuItem.getLabel()
    }
}

resolvers.addResolver("menuItems", this.&getName, "MenuItem")

// You could also code it shorter:
//   resolvers.addResolver("menuItems", this, "MenuItem")
// since every Groovy script represents an object
// and addResolver(...) for objects registers
// all methods of the object as a resolver if possible.
Example 53.7:  Groovy resolver for MenuItems

A resolver can be registered for multiple component classes at once:

def getName(com, name):
    return com.getText()
resolvers.addResolver("labels", getName, "Label", "Button")
Example 53.8:  Register a Resolver for multiple classes

4.0+53.1.8
The GenericClassNameResolver Interface

A GenericClassNameResolver can assign generic classes (chapter 60) to arbitrary components. It can be used to make recorded components more readable and to register additional resolvers for the newly created classes.

Technologies: all

Web You should only use this resolver with a web application if the 'Install CustomWebResolver' node is insufficient.

After QF-Test determined the generic class name of a GUI element the registered GenericClassNameResolvers get a chance to override this generic class name. The first resolver that returns a non-null value determines the outcome. If no resolvers are registered or all of them return null the original generic class name is used.

For performance reasons classes are cached so the resolver will only be called once at the most for each element. If you change your resolver you need to re-load or to close and re-open the area which shows the component.

Web If a generic class name was already assigned to an element via a 'CustomWebResolver', no GenericClassNameResolvers will be called for that element.

A GenericClassNameResolver needs to implement the following method:

 
 
String getGenericClassName(Object element, String name)
Determine the name of the generic class of a GUI element.
Parameters
element The element to determine the generic class name for.
name The original generic class name QF-Test would use without a resolver. May be null.
Returns The generic class name to use or null if the element is not handled by this resolver. An empty string to suppress the generic class determined by QF-Test.
 
 

3.1+53.1.9
The ClassNameResolver Interface

The ClassNameResolver can control the class QF-Test records for a component. It can be used to make recorded components more readable and to register additional resolvers for the newly created classes. However, we generally recommend the use of Generic classes instead. To register generic classes you should use the GenericClassNameResolver (subsection 53.1.8).

Technologies: all

Web You should only use this resolver with a web application if the 'Install CustomWebResolver' node is insufficient.

A ClassNameResolver needs to implement the following method:

 
 
String getClassName(Object element, String name)
Determine the name of the class of a GUI element.
Parameters
element The element to determine the class name for.
name The original class name QF-Test would use without a resolver.
Returns The class name to use or null if the element is not handled by this resolver.
 
 

After QF-Test determined the class name of a GUI element the registered ClassNameResolvers get a chance to override this class name. The first resolver that returns a non-null value determines the outcome. If no resolvers are registered or all of them return null the original class name is used. The resolver is free to return any arbitrary class name. Those names will be treated as normal classes in QF-Test internal methods.

For performance reasons classes are cached so the resolver will only be called once at the most for each element. If you change your resolver you need to re-load or to close and re-open the area which shows the component.

The FeatureResolver Interface

A FeatureResolver can provide a feature for a GUI element.

After QF-Test determined the feature of a GUI element the registered FeatureResolvers get a chance to override or suppress this feature. The first resolver that returns a non-null value determines the outcome. If no resolvers are registered or all of them return null the original feature is used.

To suppress an element's feature getFeature should return the empty string.

Technologies: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. For web applications please use the 'Install CustomWebResolver' node as described in Improving component recognition with a CustomWebResolver. It was optimized for web elements and is more performant. Just in case the functionality provided there is insufficient make use of the FeatureResolver.

A FeatureResolver needs to implement the following method:

 
 
String getFeature(Object element, String feature)
Determine the feature of a GUI element.
Parameters
element The element to determine the feature for.
feature The original feature QF-Test would use without a resolver.
Returns The feature to use or null if the element is not handled by this resolver. Returning an empty string suppresses the original feature.
 
 

The following example implements a feature resolver returning the title of the panel border as feature for Java/Swing panels.

def getFeature(com, feature):
    try:
        title = com.getBorder().getInsideBorder().getTitle()
        if title != None:
            return title
    except:
        pass

resolvers.addResolver("paneltitle", getFeature, "Panel")
Example 53.9:  A FeatureResoler for Java/Swing Panels

The ExtraFeatureResolver Interface

An ExtraFeatureResolver can add, change or delete an 'Extra feature' in the 'Extra features' table for a GUI element. For this purpose the interface provides a number of methods.

Instances of the class de.qfs.apps.qftest.shared.data.ExtraFeature represent one 'Extra feature' for a GUI element, comprising its name and value along with information about whether the feature is expected to match, whether it is a regular expression and whether the match should be negated. For possible states the class defines the constants STATE_IGNORE, STATE_SHOULD_MATCH and STATE_MUST_MATCH.

After QF-Test determined the 'Extra features' for a GUI element, the registered ExtraFeatureResolvers get a chance to override these features. In contrast to other resolvers, QF-Test does not stop when the first resolver returns a non-null value. Instead it passes its result as input to the next resolver which makes it possible to register several ExtraFeatureResolvers that handle different 'Extra features'. If no resolvers are registered or all of them return null, QF-Test will proceed to use the original set.

Of course, in order to be able to implement the getExtraFeatures method properly, you need to know the details for the API of the classes involved, namely ExtraFeature and ExtraFeatureSet described below - after the examples.

Technologies: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. For web applications please use the 'Install CustomWebResolver' described in Improving component recognition with a CustomWebResolver. It was optimized for web elements and is more performant. Only if the functionality provided there is insufficient should you use the ExtraFeatureResolver.

To ensure consistency when capturing and replaying qfs:label* 'Extra feature' variants as well as mapping them to and from SmartID, some constraints should be maintained. They do not apply to the legacy qfs:label 'Extra features', which stands on its own.

When handling qfs:label* variants in an ExtraFeatureResolver you have to make sure that the whole set of the variants remains consistent. This means

  • There should be at most one qfs:label* variant with "Should match", the rest should be "Ignore".
  • qfs:labelBest should either be the "Should match" variant or it should have the same value as the "Should match" variant.

QF-Test itself maintains those constraints when determining associated labels. To make it easier for ExtraFeatureResolvers to do so as well, the ExtraFeatureSet class manages those constraints when called from an ExtraFeatureResolver:

  • If the value of qfs:labelBest is changed and it has the state "Ignore", the value of the "Should match" variant is automatically changed as well.
  • If the value of the "Should match" variant is changed, the value of qfs:labelBest is automatically changed as well.
  • If a qfs:label* variant is set to "Should match" all others are changed to ignore and qfs:labelBest is updated accordingly.

Note If there are qfs:label* variants with the status "Must match" or with a regular expression constraint handling is immediately deactivated.

A ExtraFeatureResolver needs to implement the following method:

 
 
ExtraFeatureSet getExtraFeatures(Object element, ExtraFeatureSet features)
Determine extra features for a GUI element.
Parameters
element The element to determine the extra features for.
features The extra features determined by QF-Test itself, an empty set in case there are none. These can be modified in place or ignored and a different ExtraFeatureSet returned.
ReturnsThe original features modified in place or a different set, null if the element is not handled by this resolver. To suppress all the element's original extra features return an empty ExtraFeatureSet.
 
 

The first example implements an ExtraFeatureResolver adding the title of a Java/Swing dialog as an 'Extra feature' with the status "must match" (STATE_MUST_MATCH). This comes in handy when component recognition depends on the correct title of the dialog.

def getExtraFeatures(node, features):
    try:
        title = node.getTitle()
        features.add(resolvers.STATE_MUST_MATCH,"dialog.title", title)
        return features
    except:
        pass
resolvers.addResolver("dialog title", getExtraFeatures,"Dialog")
Example 53.10:   ExtraFeatureResolver adding an 'Extra feature' for Java/Swing dialogs

The following example shows how to change an existing 'Extra feature'. The example handles qfs:labelBest, which triggers special treatment of the qfs:label* variants as described above: if they adhere to the constraints, QF-Test will update the respective qfs:label* variants so that the whole set will remain consistent.

def getExtraFeatures(node, features):
    label = features.get("qfs:labelBest")
    if label and label.getValue() == "unwanted":
        label.setValue("wanted")
        return features
resolvers.addResolver("change label", getExtraFeatures)
Example 53.11:   ExtraFeatureResolver changing an existing 'Extra feature'

The next example shows how to change the state of the qfs:label* variant with the state 'should match' to 'ignore':

def getExtraFeatures(node, features) {
    def labelFeature = features.getShouldMatchLabel()
    if (labelFeature) {
        labelFeature.setState(resolvers.STATE_IGNORE)
        return features
    }
}
resolvers.addResolver("get label example", this)
Example 53.12:   An ExtraFeatureResolver (Groovy) changing the state of the 'Extra feature'

Thanks to the constraints described above, a simple ExtraFeatureResolver that was formerly written as

def getExtraFeatures(node, features):
    label = features.get("qfs:label")
    if label and label.getValue() == "unwanted":
        label.setValue("wanted")
        return features
resolvers.addResolver("change label", getExtraFeatures)
Example 53.13:   ExtraFeatureResolver changing an existing 'Extra feature'

can simply be updated to example ExtraFeatureResolver changing an existing 'Extra feature' above.

In the following you will find the description of the APIs of the classes ExtraFeature and ExtraFeatureSet.

 
 
ExtraFeature ExtraFeature(String name, String value)
Create a new ExtraFeature with the default state STATE_IGNORE.
Parameters
nameThe name of the ExtraFeature.
valueThe value of the ExtraFeature.
 
ExtraFeature ExtraFeature(int state, String name, String value)
Create a new ExtraFeature with a given state.
Parameters
state The state of the ExtraFeature. Values allowed are STATE_IGNORE, STATE_SHOULD_MATCH, STATE_MUST_MATCH.
nameThe name of the ExtraFeature.
valueThe value of the ExtraFeature.
 
String getName()
Get the name of the ExtraFeature.
ReturnsThe name of the ExtraFeature.
 
boolean getNegate()
Get the negate state of the ExtraFeature.
ReturnsThe negate state of the ExtraFeature.
 
boolean getRegexp()
Get the regexp state of the ExtraFeature.
ReturnsWhether the ExtraFeature's value is a regular expression.
 
int getState()
Get the state of the ExtraFeature.
ReturnsThe state of the ExtraFeature.
 
String getValue()
Get the value of the ExtraFeature.
ReturnsThe value of the ExtraFeature.
 
void setName(String name)
Set the name of the ExtraFeature.
Parameters
nameThe name to set.
 
void setNegate(boolean negate)
Set the negate state of the ExtraFeature.
Parameters
negateThe negate state to set.
 
void setRegexp(boolean regexp)
Set the regexp state of the ExtraFeature.
Parameters
regexpThe regexp state to set.
 
void setState(int state)
Set the state of the ExtraFeature.
Parameters
stateThe state to set.
 
void setValue(String value)
Set the value of the ExtraFeature.
Parameters
valueThe value to set.
 
 

The class de.qfs.apps.qftest.shared.data.ExtraFeatureSet collects ExtraFeatures into set:

 
 
ExtraFeatureSet ExtraFeatureSet()
Create a new, empty ExtraFeatureSet.
 
void add(ExtraFeature extraFeature)
Add an ExtraFeature to the set, potentially replacing a feature with the same name.
Parameters
extraFeatureThe ExtraFeature to add.
 
void add(String name, String value)
Add a new extra feature to the set with the state STATE_IGNORE, potentially replacing a feature with the same name.
Parameters
nameThe name of the ExtraFeature.
valueThe value of the ExtraFeature.
 
void add(int state, String name, String value)
Add a new extra feature with the given state to the set, potentially replacing a feature with the same name.
Parameters
state The state of the ExtraFeature. Values allowed are resolvers.STATE_IGNORE, resolvers.STATE_SHOULD_MATCH, resolvers.STATE_MUST_MATCH.
nameThe name of the ExtraFeature.
valueThe value of the ExtraFeature.
 
ExtraFeature get(String name)
Get an ExtraFeature from the set.
Parameters
nameThe name of the ExtraFeature to get.
Returns The ExtraFeature or null if no feature by that name is stored in the set.
 
ExtraFeature getShouldMatchLabel()
Get the qfs:label* ExtraFeature variant with the state "Should match" from the set. In case there are more than one, the first one will be returned.
Returns The ExtraFeature or null if there is no qfs:label* ExtraFeature variant with the state "Should match".
 
ExtraFeature remove(String name)
Remove an extraFeature from the set.
Parameters
nameThe name of the ExtraFeature to remove.
Returns The ExtraFeature that was removed or null if no feature by that name was stored in the set.
 
ExtraFeature[] toArray()
Get all ExtraFeatures in the set.
ReturnsAn array of the contained ExtraFeatures, sorted by name.
 
 

The ItemNameResolver Interface

An ItemNameResolver can change (or provide) the textual representation of the index for addressing a sub-item of a complex component.

After QF-Test determined the name for an item's index the registered ItemNameResolvers get a chance to override. The first resolver that returns a non-null value determines the outcome. If no resolvers are registered or all of them return null the original name is used.

Technologies: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. For web applications please use the 'Install CustomWebResolver' node described in Improving component recognition with a CustomWebResolver. It was optimized for web elements and is more performant. Only if the functionality provided there is insufficient should you use the ItemNameResolver.

An ItemNameResolver needs to implement the following method:

 
 
String getItemName(Object element, Object item, String name)
Determine the name of an item of a complex GUI element that will be used for the textual representation of the item's index.
Parameters
element The GUI element to which the item belongs.
item The item to get the name for. Its type depends on the GUI element and the registered ItemResolvers as described in subsection 53.4.5.
name The original name that QF-Test would use without a resolver.
Returns The name to use or null if the resolver does not handle this element or item.
 
 

The example implements an ItemNameResolver making the ID of a JTable available as index:

def getItemName(tableHeader, item, name):
    id = tableHeader.getColumnModel().getColumn(item).getIdentifier()
    if id:
        return str(id)
resolvers.addResolver("tableColumnId", getItemName,
                      "javax.swing.table.JTableHeader")
Example 53.14:  An ItemNameResolver for JTableHeader

The ItemValueResolver Interface

The ItemValueResolver is used to improve the textual check of elements.

An ItemValueResolver can change (or provide) the textual representation of the value a sub-item of a complex component as used by a 'Check text' node or retrieved via a 'Fetch text' node.

After QF-Test determined the value for an item's index the registered ItemValueResolvers get a chance to override. The first resolver that returns a non-null value determines the outcome. If no resolvers are registered or all of them return null the original value is used.

Technologies: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. For web applications please use the 'Install CustomWebResolver' as described in Improving component recognition with a CustomWebResolver. It was optimized for web elements and is more performant. Just in case the functionality provided there is insufficient use the ItemValueResolver.

An ItemValueResolver needs to implement the following method:

 
 
String getItemValue(Object element, Object item, String value)
Determine the value of an item of a complex GUI element that will be used for its textual representation in a 'Check text' or a 'Fetch text' node.
Parameters
element The GUI element to which the item belongs.
item The item to get the value for. Its type depends on the GUI element and the registered ItemResolvers as described in subsection 53.4.5.
value The original value QF-Test would use without a resolver.
Returns The value to use or null if the resolver does not handle this element or item.
 
 

The TreeTableResolver Interface

A TreeTableResolver helps QF-Test recognize TreeTable components. A TreeTable is a mixture between a table and a tree. It is not a standard Swing component, but most TreeTables are implemented alike using a tree as the renderer component for one column of the table. Once QF-Test recognizes a TreeTable as such, it treats the row indexes of all table cells as tree indexes, which is a lot more useful in that context than standard table row indexes. In addition, geometry information for cells in the tree column is based on tree nodes instead of table cells.

Technologies: AWT/Swing

Note The interface is only relevant for AWT/Swing. For SWT and JavaFX multi-column trees are support by QF-Test automatically. For web frameworks the TreeTable is defined by the (custom) web resolver (see Improving component recognition with a CustomWebResolver).

A TreeTableResolver needs to implement to following two methods:

 
 
JTree getTree(JTable table)
Determine the tree component used to implement a TreeTable.
Parameters
table The JTable component to determine the tree for.
Returns The tree or null if the JTable is a plain table and not a TreeTable.
 
int getTreeColumn(JTable table)
Determine the column index of the tree component in a TreeTable. Most implementations place the tree in the first column, in which case the index is 0.
Parameters
table The JTable component to determine the tree's column index for.
Returns The column index or -1 if the JTable is a plain table and not a TreeTable. The column index must always be given in the table's model coordinates, not in view coordinates.
 
 

Most TreeTableResolvers are trivial to implement. The following Jython example works well for the org.openide.explorer.view.TreeTable component used in the popular netBeans IDE, provided that the resolver is registered for the TreeTable class:

def getTreeMethod(table):
        return table.getCellRenderer(0,0)
    def getColumn(table):
        return 0

    resolvers.addResolver("treetableResolver", getTreeMethod, \
    getColumn, "org.openide.explorer.view.TreeTable")
Example 53.15:  TreeTableResolver for netBeans IDE

The following example shows a typical TreeTableResolver.

def getTree(table):
    return table.getTree()
def getColumn(table):
    return 0
resolvers.addResolver("treeTable", getTree, getColumn,
                               "my.package.TreeTable")
Example 53.16:  TreeTableResolver for Swing TreeTable with optional getColumn method

As practically all TreeTables implement the tree in the first column of the table the getColumn method is optional. When none is passed QF-Test automatically creates a default implementation for the first column:

def getTree(table):
    return table.getTree()
resolvers.addResolver("treeTable", getTree, None,
                               "my.package.TreeTable")
Example 53.17:  Simplified TreeTableResolver

If no dedicated getTree method is available, the cell renderer of the column containing the tree (typically 0) might work, as it is typically derived from JTree.

def getTree(table):
    return table.getCellRenderer(0,0)
resolvers.addResolver("treeTable", getTree,
                               "my.package.TreeTable")
Example 53.18:  Simplified TreeTableResolver using the method getCellRenderer

The InterestingParentResolver Interface

An InterestingParentResolver influences which components will be treated as interesting or ignorable by QF-Test recording. This, in turn, determines whether a 'Component' node will be created for a component.

Technologies: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. For web applications please use the 'Install CustomWebResolver' node as described in Improving component recognition with a CustomWebResolver. It was optimized for web elements and is more performant. Just in case the functionality provided there is insufficient make use of the InterestingParentResolver.

An InterestingParentResolver needs to implement the following method:

 
 
Boolean isInterestingParent(Object parent, boolean interesting)
Determine whether a parent element is interesting.
Parameters
parent The (direct or indirect) parent element.
interesting Whether QF-Test considers the parent interesting without resolver.
Returns Boolean.TRUE if interesting, Boolean.FALSE if not, null if this resolver cannot tell either way.
 
 

The TooltipResolver Interface

A TooltipResolver can provide a tooltip for a component. A tooltip is one of the texts considered for the 'qfs:label' 'Extra feature'.

Technologies: AWT/Swing, JavaFX, SWT. For web applications please use the 'Install CustomWebResolver' node as described in Improving component recognition with a CustomWebResolver. It was optimized for web elements and is more performant. Just in case the functionality provided there is insufficient make use of the TooltipResolver.

A TooltipResolver needs to implement the following method:

 
 
String getTooltip(Object element, String tooltip)
Determine the tooltip of a GUI element.
Parameters
element The element to determine the tooltip for.
tooltip The original tooltip QF-Test would use without a resolver.
Returns The tooltip to use or null if the element is not handled by this resolver. Returning an empty string suppresses the original tooltip.
 
 

The IdResolver interface

An IdResolver allows modifying or even removing the 'ID' attribute of a DomNode. When QF-Test registers the DOM nodes of a web page it also caches the "id" attribute of those nodes. Depending on the option Use ID attribute as name the value of the "id" attribute will even be taken as name for the component. As many web pages or component libraries generate such IDs automatically it's a very common requirement to modify that ID in order to get stable and reliable component recognition.

There are three possibilities to deal with such automatically generated IDs:

  • The simplest method influencing the IDs can be achieved by using the 'Install CustomWebResolver' node. There you should configure the category autoIdPatterns. This parameter allows to specify dedicated values to ignore like myAutoId or even regular expressions like auto.*, which ignores any ID beginning with auto.
  • In case you have introduced a custom attribute, which should act as id instead of the original 'ID' attribute, you should also use 'Install CustomWebResolver'. There you should configure the category customIdAttributes. It allows to specify custom attributes which will be used for determining the 'ID'.
  • You can activate the option Eliminate all numerals from 'ID' attributes to ignore any numerals from the ID.
  • In case you would like to implement a complex algorithm you need to implement an IdResolver.

The options mentioned above can also be combined and don't exclude each other. In case you decide to implement a custom algorithm you should always use an IdResolver. You should take care that the 'ID' attribute of a node can show up in multiple places. The most notably place is the attribute 'Name' of the node (depending on the option Use ID attribute as name), its 'Feature' and its 'Extra feature'. Because of that many locations you should prefer implementing an IdResolver over implementing individual Name-, Feature- and ExtraFeatureResolvers. More importantly, changing a node's 'ID' attribute can have a major impact on whether the attribute is unique and QF-Test's mechanism for using an ID as a 'Name' takes uniqueness into account, so an IdResolver is allowed to return non-unique IDs whereas a NameResolver2 is not.

Technologies: Web

The de.qfs.apps.qftest.extensions.IdResolver interface consists of a single method:

 
 
String getId(DomNode node, String id)
Determine the ID of a DomNode. The resolved ID will be cached and can later be retrieved via node.getId(), whereas node.getAttribute("id") always returns the original, unmodified 'ID' attribute.
Parameters
node The DomNode to determine the ID for.
id The ID that QF-Test has determined, possibly with suppressed numerals, depending on the setting of the option Eliminate all numerals from 'ID' attributes. To implement the resolver based on the original 'ID' attribute, simply fetch this with node.getAttribute("id").
Returns The ID or null if the element is not handled by this resolver. Returning an empty string will suppress or hide the node's actual ID.
 
 

The EnabledResolver Interface

An EnabledResolver provides information about when to consider a component active or inactive. AWT/Swing Components have a respective attribute. Web and JavaFX, however, have special stylesheet classes that need to be evaluated via the EnabledResolver.

Technologies: JavaFX, Web, Windows, Android, iOS

An EnabledResolver needs to implement the following method:

 
 
Boolean isEnabled(Object element, boolean enabled)
Determine whether a GUI element is regarded as active or inactive.
Parameters
element The element to determine the enabled state for.
enabled The original state QF-Test would use without a resolver.
Returns True or false. Null if the element was not handled by the resolver.
 
 

The example determines the enabled state of a web node via the css class v-disabled.

def isEnabled(element):
    try:
        return not element.hasCSSClass("v-disabled")
    except:
        return True

resolvers.addResolver("vEnabledResolver",isEnabled, \
                      "DOM_NODE")
Example 53.19:  An EnabledResolver

The VisibilityResolver Interface

A VisibilityResolver influences whether to consider a web element to be visible.

Technologies: Web, Windows, Android, iOS

A VisibilityResolver needs to implement the following method:

 
 
Boolean isVisible(Object element, boolean visible)
Determine whether a GUI element is regarded visible.
Parameters
element The web element to determine the visible state for.
visible The original state QF-Test would use without a resolver.
Returns True or false. Null if the element was not handled by the resolver.
 
 

The resolver in the example below returns false for the visibility state of the web element in case it is opaque.

import re

def getOpacity(element):
    style = element.getAttribute("style")
    if not style:
        return 1
    m = re.search("opacity:\s*([\d\.]+)", style)
    if m:
        return float(m.group(1)) == 0.4
    else:
        return 1

def isVisible(element,visible):
    while visible and element:
        visible = getOpacity(element) > 0
        element = element.getParent()
    return visible

resolvers.addResolver("opacityResolver",isVisible)
Example 53.20:  A VisibilityResolver

The MainTextResolver Interface

A MainTextResolver determines the primary line of text of a component, which then may be used for the 'Feature', the qfs:label* variants etc.

Technologies: AWT/Swing, JavaFX, SWT, Web, Windows, Android, iOS

A MainTextResolver needs to implement the following method:

 
 
String getMainText(Object element, String text)
Determine the 'main' text of a component
Parameters
element The GUI element to determine the text for.
text The original text QF-Test would use without a resolver.
Returns The 'main' text. Null if the element was not handled by the resolver. Returning an empty string suppresses the original text.
 
 

The resolver in the example removes the string TO-DO from the 'main' text of all components.

def getMainText(element,text):
    if text:
        return text.replace("TO-DO","")

resolvers.addResolver("removeMarkFromText", getMainText)
Example 53.21:  A MainTextResolver

The WholeTextResolver Interface

A WholeTextResolver determines the 'whole' text of a component, i.e. what should be used for checks, etc.

Technologies: AWT/Swing, JavaFX, SWT, Web, Windows, Android, iOS

A WholeTextResolver needs to implement the following method:

 
 
String getWholeText(Object element, String text)
Determine the 'whole' text of a component
Parameters
element The GUI element to determine the text for.
text The original text QF-Test would use without a resolver.
Returns The 'whole' text. Null if the element was not handled by the resolver. Returning an empty string suppresses the original text.
 
 

The resolver in the example removes the string TO-DO from the texts used for example for checks of TextFields and TextAreas.

def getWholeText(element,text):
    if text:
        return text.replace("TO-DO","")

resolvers.addResolver("removeMarkFromText", getWholeText, "TextField", "TextArea")
Example 53.22:  A WholeTextResolver

The BusyPaneResolver Interfaces

At text execution, QF-Test waits for BusyPanes covering other components to disappear before resuming in a determined state. A BusyPaneResolver influences whether to consider a component as being covered.

Technologies: AWT/Swing, JavaFX

A BusyPaneResolver needs to implement the following method:

 
 
Boolean isBusy(Object element)
Determine whether a component is currently being covered by a BusyPane or comparable component.
Parameters
element The GUI element to determine the text state for.
Returns True if the element cannot currently be accessed because of a BusyPane or similar.
False otherwise.
Null if the element was not handled by the resolver.
 
 

The resolver in the example below deactivates recognition of BusyPanes for components of the type "my.special.Component".

def isBusy():
    return false

resolvers.addResolver("neverBusyResolver",isBusy,"my.special.Component")
Example 53.23:  A BusyPaneResolver

The GlassPaneResolver Interfaces

When components (e.g. transparent ones) hide others components you can use a GlassPaneResolver to inform QF-Test of this relationship and thus redirect events to the correct component.

Technology: AWT/Swing

A GlassPaneResolver needs to implement the following method:

 
 
Object isGlassPaneFor(Object element, Object target)
Determine the relationship between an overlaying component and the actual target component.
Parameters
element The GUI element events are received for.
target The GUI element QF-Test would pass the event on to without a resolver.
Returns The component to which to pass the events on to. Null if the element was not handled by the resolver.
 
 

The resolver in the example below deactivates passing on events through GlassPanes.

def isGlassPaneFor(element):
    return element

resolvers.addResolver("noGlassPaneResolver", isGlassPaneFor)
Example 53.24:  A GlassPaneResolver

The TreeIndentationResolver Interface

A TreeIndentationResolver is used to determine the indentation of a tree node in a tree or tree table. Use this resolver if QF-Test can not automatically determine the right indentation of nodes in a Tree or TreeTable component and the abilities of the parameter "treeIndentationMode" of the CustomWebResolver category treeResolver are not sufficient.

Note that the return value of the resolver is treated like a pixel amount. This means that to distinguish different tree levels, the indentation value must differ by at least 2 by default.

Technologies: Web

A TreeIndentationResolver has to implement the following method:

 
 
Integer getTreeIndentation(DomNode tree, DomNode treeNode)
Determines the indentation of a tree node in a tree.
Parameters
tree The DOM node of the tree the resolver should apply to.
treeNode The DOM node of the tree node to calculate the indentation for.
Returns The indentation for the given tree node, or null to let other resolvers or QF-Test determine the indentation. Note: To distinguish different tree levels, indentation must exceed the TreeResolver nodeTolerance value (default 1).
 
 

The following Groovy example tries to determine the indentation of all TreeNodes through the HTML attribute aria-level.

Integer getTreeIndentation(Object tree, Object treeNode) {
    def ariaLevel = treeNode.getAttribute('aria-level')
    return ariaLevel ? ariaLevel as Integer * 10 : null
}

resolvers.addResolver("TreeIndentationResolver-Tree", this, "Tree")
Example 53.25:  A TreeIndentationResolver

The EventSynchronizer Interface

After replaying an event to the SUT QF-Test waits for synchronization with the respective Event Dispatch Thread. Via an EventSynchronizer you can tell QF-Test when the SUT is ready to accept the next event. It ought to be used when the SUT has a non-standard event synchronization.

Technologies: AWT/Swing, JavaFX, SWT, Web

An EventSynchronizer needs to implement the following method:

 
 
void sync(Object context)
Synchronization with the Event Dispatch Thread of the SUT.
Parameters
context The context specified when registrating the resolver.
 
 

The resolver in the following example stops execution on the Dispatch Thread until the next full second.

import time

def sync():
    t = time.time()
    full = int(t)
    delta = t - full
    time.sleep(delta)

resolvers.addResolver("timeSynchronizer",sync)
Example 53.26:  An EventSynchronizer

The BusyApplicationDetector Interface

Using a BusyApplicationDetector can tell QF-Test when to consider an application to be currently 'busy' and not in grade of accepting events.

Technologies: AWT/Swing, JavaFX, SWT, Web

A BusyApplicationDetector needs to implement the following method:

 
 
Boolean applicationIsBusy
Determine whether an application is currently 'busy'.
Returns True if the application is 'busy', false otherwise.
 
 

The resolver in the example uses a SUT specific method to tell QF-Test it is 'busy':

def applicationIsBusy():
    return my.app.App.instance().isDoingDbSynchronization()

resolvers.addResolver("dbAccessDetector",applicationIsBusy)
Example 53.27:  A BusyApplicationDetector

Matcher

The difference between a matcher and a resolver is that matchers are relevant for replay only. They have no effect on recordings. However, they are registered via the resolvers module as well.

A matcher can become useful when you are working with generic components or for keyword driven testing, if you do not record components.

The ExtraFeatureMatcher Interface

An ExtraFeatureMatcher influences whether to consider an 'Extra feature' QF-Test registered for the component as 'suitable'.

Technologies: AWT/Swing, JavaFX, SWT, Web

An ExtraFeatureMatcher needs to implement the following method:

 
 
Boolean matchExtraFeature(Object element, String name, String value, boolean regexp, boolean negate)
Check the 'Extra feature' of a component.
Parameters
element The GUI element to check the 'Extra feature' for.
name The name of the 'Extra feature'.
value The value of the 'Extra feature'.
regexp True, if value is a regular expression.
negate True, if the check is to be negated.
Returns True, if the ExtraFeature is suitable, false otherwise. Null if the element was not handled by the resolver.
 
 

The matcher in the example below checks the value of the 'Extra feature' my:label against the my-label attribute of the web element.

import re

def matchExtraFeature(element, name, value, regexp, negate):
    if not name == "my:label":
        return None
    label = element.getAttribute("my-label")
    if label:
        if regexp:
            match = re.match(value,label)
        else:
            match = (value == label)
    else:
        match = False
    return (match and not negate) or (not match and negate)

resolvers.addResolver("myLabelResolver", matchExtraFeature)
Example 53.28:  An ExtraFeatureMatcher

The resolver method call can be limited to a specific feature name by means of the special resolvers method addSpecificExtraFeatureMatcher:

import re

def matchExtraFeature(element, name, value, regexp, negate):
    label = element.getAttribute("my-label")
    if label:
        if regexp:
            match = re.match(value,label)
        else:
            match = (value == label)
    else:
        match = False
    return (match and not negate) or (not match and negate)

resolvers.addSpecificExtraFeatureMatcher("myLabelResolver", \
                                  matchExtraFeature, "my:label")
Example 53.29:  Using addSpecificExtraFeatureMatcher

External Implementation

As an alternative to directly implementing a resolver in an SUT-script, it is possible to provide them as Java classes inside a JAR file in the plugin folder. In doing so, it is helpful to implement the aforementioned resolver interfaces (Basically, QF-Test is able to detect resolvers by their implemented method names).

To implement the interfaces provided by QF-Test, the file qfsut.jar has to be added to the development classpath. Most of the interfaces reside in the de.qfs.apps.qftest.extensions package, and the names of the interfaces which have two method parameters are suffixed with a "2". All Interfaces named Item... reside in the package de.qfs.apps.qftest.extensions.items. When calling resolvers.addResolver(...) in an SUT script, provide an instance of the implemented resolver class as argument.