54.1 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: 'Resolvers in
QF-Test'.
54.1.1 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:
- Get the component object from the GUI.
- Extract the data for each component: e.g. component class, id, coordinates, component text.
-
Analyze the relationship between components: e.g. structure information (index),
find the label belonging to the component
qfs:label*variants. -
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:
-
NameResolversubsection 54.1.7 -
GenericClassNameResolversubsection 54.1.8 -
ClassNameResolversubsection 54.1.9 -
FeatureResolversubsection 54.1.10 -
ExtraFeatureResolversubsection 54.1.11 -
ItemNameResolversubsection 54.1.12 -
ItemValueResolversubsection 54.1.13 -
TreeTableResolversubsection 54.1.14 -
InterestingParentResolversubsection 54.1.15 -
TooltipResolversubsection 54.1.16 -
IdResolversubsection 54.1.17 -
EnabledResolversubsection 54.1.18 -
VisibilityResolversubsection 54.1.19 -
MainTextResolversubsection 54.1.20 -
WholeTextResolversubsection 54.1.21 -
BusyPaneResolversubsection 54.1.22 -
GlassPaneResolversubsection 54.1.23 -
TreeIndentationResolversubsection 54.1.24 -
EventSynchronizersubsection 54.1.25 -
BusyApplicationDetectorsubsection 54.1.26 -
ExtraFeatureMatchersubsection 54.1.27.1
54.1.2 Implementation
The following two steps are required to implement a resolver:
- Implementation of the resolver interface.
- 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")
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 NameResolver it is the name
determined by the QF-Test standard NameResolver. 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 54.1.7 to
subsection 54.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 54.1.4). The names of all registered
resolvers can be listed via the function resolvers.listNames()
(see subsection 54.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 54.1.7 you will find examples for Groovy SUT script nodes.
54.1.3 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.
|
|||||||||||||||||||||||||||||||
54.1.3.1 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,...)
54.1.4 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.
|
|||||||||||||||||||
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()
54.1.5 listNames
Return a list of resolver names registered via the resolvers module.
|
|||||||||
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!")
}
resolvers module
54.1.6 Accessing 'Best label'
When you want to access the Best label
from within a resolver, you can use the method
rc.engine.helper.getBestLabel().
|
|||||||||||||||
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")
3.1+54.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:
|
|||||||||||||||||
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")
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")
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.
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")
Resolver for multiple classes
4.0+54.1.8
The GenericClassNameResolver Interface
A GenericClassNameResolver can assign generic classes (chapter 61) 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:
|
|||||||||||||||||
3.1+54.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 54.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:
|
|||||||||||||||||
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.
3.1+54.1.10
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:
|
|||||||||||||||||
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")
FeatureResoler for Java/Swing Panels
54.1.11 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:labelBestshould 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:labelBestis 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:labelBestis automatically changed as well. -
If a
qfs:label*variant is set to "Should match" all others are changed to ignore andqfs:labelBestis 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:
|
|||||||||||||||||
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")
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)
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)
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)
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.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The class de.qfs.apps.qftest.shared.data.ExtraFeatureSet collects
ExtraFeatures into set:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3.1+54.1.12
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:
|
|||||||||||||||||||
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")
ItemNameResolver for JTableHeader
3.1+54.1.13
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:
|
|||||||||||||||||||
54.1.14 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:
|
|||||||||||||||||||||||||||
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")
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")
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")
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")
TreeTableResolver using the method getCellRenderer
54.1.15 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:
|
|||||||||||||||||
4.1+54.1.16
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:
|
|||||||||||||||||
Web54.1.17 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 likemyAutoIdor even regular expressions likeauto.*, which ignores any ID beginning withauto. -
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:
|
|||||||||||||||||
4.1+54.1.18
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:
|
|||||||||||||||||
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")
EnabledResolver
4.1+54.1.19
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:
|
|||||||||||||||||
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)
VisibilityResolver
4.1+54.1.20
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:
|
|||||||||||||||||
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)
MainTextResolver
4.1+54.1.21
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:
|
|||||||||||||||||
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")
WholeTextResolver
4.1+54.1.22
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:
|
|||||||||||||||
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")
BusyPaneResolver
4.1+54.1.23
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:
|
|||||||||||||||||
The resolver in the example below deactivates passing on events through GlassPanes.
def isGlassPaneFor(element):
return element
resolvers.addResolver("noGlassPaneResolver", isGlassPaneFor)
GlassPaneResolver
8.0+54.1.24
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:
|
|||||||||||||||||
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")
TreeIndentationResolver
4.1+54.1.25
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:
|
|||||||||||||
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)
EventSynchronizer
4.1+54.1.26
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:
|
|||||||||||
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)
BusyApplicationDetector
54.1.27 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.
4.1+54.1.27.1
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:
|
|||||||||||||||||||||||
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)
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")
addSpecificExtraFeatureMatcher
54.1.28 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.