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'.
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
:
-
NameResolver
subsection 54.1.7 -
GenericClassNameResolver
subsection 54.1.8 -
ClassNameResolver
subsection 54.1.9 -
FeatureResolver
subsection 54.1.10 -
ExtraFeatureResolver
subsection 54.1.11 -
ItemNameResolver
subsection 54.1.12 -
ItemValueResolver
subsection 54.1.13 -
TreeTableResolver
subsection 54.1.14 -
InterestingParentResolver
subsection 54.1.15 -
TooltipResolver
subsection 54.1.16 -
IdResolver
subsection 54.1.17 -
EnabledResolver
subsection 54.1.18 -
VisibilityResolver
subsection 54.1.19 -
MainTextResolver
subsection 54.1.20 -
WholeTextResolver
subsection 54.1.21 -
BusyPaneResolver
subsection 54.1.22 -
GlassPaneResolver
subsection 54.1.23 -
TreeIndentationResolver
subsection 54.1.24 -
EventSynchronizer
subsection 54.1.25 -
BusyApplicationDetector
subsection 54.1.26 -
ExtraFeatureMatcher
subsection 54.1.27.1
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.
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.
|
|
||||||||||||||||||||||||||||||
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.
|
|
||||||||||||||||||
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()
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
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 classThe 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
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 andqfs: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:
|
|
||||||||||||||||
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:
|
|
||||||||||||||||||
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
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 likemyAutoId
or 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
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
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.