3.1+52.4
Implementing custom item types with the ItemResolver interface

As described in section 5.9, QF-Test is able to dig below the given structure of GUI elements and work with sub-items that are not GUI components themselves, like the cells of a table, nodes in a tree or drawings on a canvas. Such items are implemented via the ItemResolver mechanism which can be used to implement your own custom items.

The ItemResolver interface is more complex than the simple NameResolver2 or FeatureResolver2 described in the previous section and cannot be implemented without solid programming skills and a thorough understanding of the underlying concepts. Also, ItemResolvers and Checkers, described in the next section, are closely related and need to be implemented together if you want to be able to perform checks for your items.

On the upside the whole mechanism is very powerful and once implemented and registered, your ItemResolvers will integrate smoothly with QF-Test so that there is no distinction between standard and custom items, so don't let yourself be deterred.

You can find demo implementations in the directory qftest-7.1.2/Jython/Lib under QF-Test's root directory. The respective demo files are ktable.py and gef.py. Both resolvers are for SWT specific tables, but the concept is the same for all engines.

ItemResolver concepts

Before you start implementing an ItemResolver you need to determine the kinds of items that your GUI element might hold. There might be more than one kind, though the decision is arbitrary. For example, we implemented items for all standard tables of Swing, SWT and JavaFX so that an item can either be a table cell or a complete column. The latter is useful because it makes it possible to implement a check that checks a whole table column at once.

First you need to decide how to represent your items internally. You can use any kind of Object because QF-Test doesn't ever examine your internal representation itself, it just passes it to the methods of your ItemResolver. What works best depends on the API of the GUI element. If it already comes with its own concept for sub-items it may be best to reuse those classes.

The most important decision to make is how to represent the item to the user of QF-Test. As described in section 5.9, the user can address an item via a numerical index or a textual one which can also be matched by a regular expression. You need to be able to provide a two-way mapping between an item and its index(es), i.e. you need to be able to answer the following two questions:

  • Given an item, what is its numerical and its textual index?
  • Given a numerical or textual index, which item matches that index best?

The issues involved in naming sub-items are the same as those for setting component names. Please take a thorough look at subsection 5.4.2.1 and subsection 5.4.2.2 before continuing.

An single numerical or textual index is represented by a SubItemIndex object. The current item concept supports addressing an item like a table cell via a primary and a secondary index, but in the future we hope to support indexes to any depth, so instead of using a path for a tree node it could be addressed with a mixed-type index in a form like "tree@Root&1%Name:.*". Therefore the complete index is represented as an array of SubItemIndex objects, though currently limited to single or two-element arrays.

Most items have a geometry, i.e. a location and a size. The coordinates for an item are always calculated relative to the true upper left origin of the element, regardless of whether it is scrolled, so they are independent of the current scroll position of the element. For items where geometry is not applicable or cannot be determined, coordinates can be ignored and the methods getItemLocation and getItemSize should simply return [0,0].

The ItemResolver interface

The methods of the interface de.qfs.apps.qftest.extensions.items.ItemResolver fall into three categories: Retrieving an item, mapping between an item and its index and retrieving miscellaneous information for - or performing actions on - an item.

 
 
Object getItem(Object element, int x, int y)
Get a sub-item for a GUI element at a given location. For items where geometry is not applicable or cannot be determined, the coordinates can be ignored and the item determined based on some other means like selection.
Parameters
elementThe GUI element to get the item for.
xThe X coordinate relative to element.
yThe Y coordinate relative to element.
Returns An arbitrary object representing an item or null if there is no item at the given location.
 
int getItemCount(Object element, Object item)
Get the number of items in a GUI element or at the next item level. Though this method is currently unused it should be implemented if possible. In the future it may be used for a 'Fetch count' node similar to 'Fetch index' or 'Fetch text'.
Parameters
elementThe GUI element for which to get the item count.
item Null to get the number items at the top level of the element. An item to get the number of its sub-items.
Returns The number of items or -1 if there is no further sub-item level.
 
Object getItemForIndex(Object element, SubItemIndex[] idx)
Get an item for a given sub-item index.
4.0+ At the end of this procedure a call of setIndexesResolved might be required in case more than one index is resolved.
Parameters
elementThe GUI element to get the item for.
idxThe sub-item index(es) for the item.
ReturnsThe item that best matches the given index.
Throws
IndexNotFoundException If no item matches the given index. Use the constructor de.qfs.apps.qftest.shared.exceptions.IndexNotFoundException(SubItemIndex) for this case.
 
SubItemIndex[] getItemIndex(Object element, Object item, int type)
Get the SubItemIndex(es) for a sub-item of a GUI element.
Parameters
elementThe GUI element to which the item belongs.
itemThe item to get the index for.
type The type of index to get. Possible values are INTELLIGENT, AS_STRING and AS_NUMBER, all defined in the SubItemIndex class. Unless only one kind of index is supported, a textual index should be returned for AS_STRING and a numerical index for AS_NUMBER. If the type is INTELLIGENT you are free to return whatever best represents the given item, even a mixed index like a column title and a row index for a table cell.
Returns An array of SutItemIndex objects. Currently only single or two-element arrays are allowed.
 
int[] getItemLocation(Object element, Object item)
Get the location of a sub-item relative to its parent element.
Parameters
elementThe GUI element to which the item belongs.
itemThe item whose location to get.
Returns The item's location as a two-element int array [x,y]. The location returned must always be relative to the upper left corner of the whole element, even if that corner is not currently visible, for example because it has been scrolled outside the visible area. For items without geometry simply return [0,0].
 
int[] getItemSize(Object element, Object item)
Get the size of a sub-item relative to its parent element.
Parameters
elementThe GUI element to which the item belongs.
itemThe item whose size to get.
Returns The item's size as a two-element int array [width,height]. For items without geometry simply return [0,0].
 
String getItemValue(Object element, Object item)
Get the value of a sub-item to be used for a text check.
Parameters
elementThe GUI element to which the item belongs.
itemThe item whose value to get.
Returns A string representing the value of the item, i.e. its contents, label, whatever.
 
Boolean repositionMouseEvent(Object element, Object item, int[] pos)
Change the coordinates for a mouse event on a sub-item of a GUI element to a default location - typically the center - if the coordinates may be safely ignored during replay. This method is called only if the option Record MouseEvents without coordinates where possible is active. Whether or not it is safe to override the event coordinates may depend on the original coordinates. For example, QF-Test repositions events on a tree node with positive coordinates pointing inside the node to its center whereas negative coordinates indicate a click on the expansion toggle and are left unchanged.
Parameters
elementThe GUI element to which the item belongs.
itemThe target item for the event.
pos A two-element int array of the form [x,y] with the coordinates of the event relative to the item. Its values can be modified in place. You can either set them to a specific coordinate or to [Integer.MAX_VALUE,Integer.MAX_VALUE] to ignore coordinates for this event so that it will later be replayed on the center of the target.
Returns Boolean.TRUE if the position was modified, Boolean.FALSE if it was left unchanged. Null to signal that this resolver does not handle the element.
Throws
BadItemException If element and item types don't match up (should never happen).
 
Boolean scrollItemVisible(Object element, Object item, int x, int y)
Scroll a GUI element so that an item becomes visible. If possible, the item should be made fully visible. In case the item doesn't fit into the visible region of the element, at least the given position should be shown. In most cases you can simple return null to let QF-Test handle scrolling. Sometimes however, scrolling cannot be implemented generically based on the item's geometry, because some items need special compensation, for example an SWT Table with a visible header. For GUI elements that cannot be scrolled, this method should simply return Boolean.FALSE. If Boolean.TRUE is returned, QF-Test will call the method again after a short interval because SWT sometimes interferes with scrolling. In the ideal case the second call should return FALSE because the position is already OK. After three attempts that return TRUE QF-Test gives up and signals an error.
Parameters
elementThe GUI element to which the item belongs.
itemThe item that must be made visible.
x The X-coordinate of the position relative to the item that must always become visible.
y The Y-coordinate of the position relative to the item that must always become visible.
Returns Boolean.TRUE if the scroll position of the element change, Boolean.FALSE if the position is unchanged scrolled or if the element cannot be scrolled. Null to signal that QF-Test should use its default mechanism and try to scroll the item itself.
 
void setIndexesResolved(int num)
Notify the registry about how many item indexes were resolved during item resolution in getItemForIndex. If this method is not called at the end of getItemForIndex, the registry assumes one index.
Parameters
num The number of indexes resolved.
 
 

The class SubItemIndex

As explained in the previous section, a de.qfs.apps.qftest.shared.data.SubItemIndex represents a (partial) index for a sub-item of a complex GUI element. This class defines some constants with the following meanings:

STRING
This is a textual index
NUMBER
This is a numerical index
REGEXP
This is a regular expression to match a textual index
INTELLIGENT
When retrieving an index, use whichever type best suits the item
AS_STRING
Retrieve a textual index
AS_NUMBER
Retrieve a numerical index

It also provides the following methods:

 
 
SubItemIndex SubItemIndex(String index)
Create a new SubItemIndex of type STRING.
Parameters
indexThe textual index.
 
SubItemIndex SubItemIndex(int index)
Create a new SubItemIndex of type NUMBER.
Parameters
indexThe numerical index.
 
int asNumber()
Get the index as a number.
ReturnsThe numerical index.
Throws
IndexFormatException If the index is not of type NUMBER or cannot be parsed as an integer.
 
String getIndex()
Get the index as a String.
ReturnsThe index converted to a String.
 
String getType()
Get the type of the index.
Returns The type of the index, one of STRING, NUMBER or REGEXP.
 
boolean matches(String name)
Test whether the index matches a given item name.
Parameters
nameThe name to match.
Returns True if the index is not numerical and matches the given name.
Throws
IndexFormatException If the index contains a malformed regular expression.
 
 

The ItemRegistry

Once implemented and instantiated, your ItemResolver must be registered with the ItemRegistry. The class de.qfs.apps.qftest.extensions.items.ItemRegistry has the following interface:

 
 
static ItemRegistry instance()
There can only ever be one ItemRegistry object and this is the method to get hold of this singleton instance.
ReturnsThe singleton ItemRegistry instance.
 
void registerItemNameResolver2(Object element, ItemNameResolver2 resolver)
Register an ItemNameResolver2 for an individual GUI element.
Parameters
element The GUI element to register for. The resolver does not prevent garbage collection and will be removed automatically when the element becomes unreachable.
resolverThe ItemNameResolver2 to register.
 
void registerItemNameResolver2(String clazz, ItemNameResolver2 resolver)
Register an ItemNameResolver2 for a class of GUI elements.
Parameters
clazzThe class of GUI element to register for.
resolverThe ItemNameResolver2 to register.
 
void registerItemResolver(Object element, ItemResolver resolver)
Register an ItemResolver for an individual GUI element.
Parameters
element The GUI element to register for. The resolver does not prevent garbage collection and will be removed automatically when the element becomes unreachable.
resolverThe ItemResolver to register.
 
void registerItemResolver(String clazz, ItemResolver resolver)
Register an ItemResolver for a class of GUI elements.
Parameters
clazzThe class of GUI element to register for.
resolverThe ItemResolver to register.
 
void registerItemValueResolver2(Object element, ItemValueResolver2 resolver)
Register an ItemValueResolver2 for an individual GUI element.
Parameters
element The GUI element to register for. The resolver does not prevent garbage collection and will be removed automatically when the element becomes unreachable.
resolverThe ItemValueResolver2 to register.
 
void registerItemValueResolver2(String clazz, ItemValueResolver2 resolver)
Register an ItemValueResolver2 for a class of GUI elements.
Parameters
clazzThe class of GUI element to register for.
resolverThe ItemValueResolver2 to register.
 
void unregisterItemNameResolver2(Object element, ItemNameResolver2 resolver)
Unregister an ItemNameResolver2 for an individual GUI element.
Parameters
element The GUI element to unregister for.
resolverThe ItemNameResolver2 to unregister.
 
void unregisterItemNameResolver2(String clazz, ItemNameResolver2 resolver)
Unregister an ItemNameResolver2 for a class of GUI elements.
Parameters
clazzThe class of GUI element to unregister for.
resolverThe ItemNameResolver2 to unregister.
 
void unregisterItemResolver(Object element, ItemResolver resolver)
Unregister an ItemResolver for an individual GUI element.
Parameters
element The GUI element to unregister for.
resolverThe ItemResolver to unregister.
 
void unregisterItemResolver(String clazz, ItemResolver resolver)
Unregister an ItemResolver for a class of GUI elements.
Parameters
clazzThe class of GUI element to unregister for.
resolverThe ItemResolver to unregister.
 
void unregisterItemValueResolver2(Object element, ItemValueResolver2 resolver)
Unregister an ItemValueResolver2 for an individual GUI element.
Parameters
element The GUI element to unregister for.
resolverThe ItemValueResolver2 to unregister.
 
void unregisterItemValueResolver2(String clazz, ItemValueResolver2 resolver)
Unregister an ItemValueResolver2 for a class of GUI elements.
Parameters
clazzThe class of GUI element to unregister for.
resolverThe ItemValueResolver2 to unregister.
 
 

Default item representations

For the implementation of the ItemNameResolver2, ItemValueResolver2 and Checker interfaces it is important to know which kind of Object is used for the internal representation of an item. This internal representation will be passed to the methods getItemName, getItemValue, getCheckData and getCheckDataAndItem.

JavaFX The following table lists the complex GUI elements and the default internal item representation used by QF-Test standard ItemResolvers for JavaFX.

GUI element classItem type
AccordionInteger index
ChoiceBoxInteger index
ComboBoxInteger index
ListViewInteger index
TabPaneInteger index
TableViewint array [column,row] with row < 0 to represent a whole column
TableHeaderRowInteger column index
TextAreaInteger line
TreeViewTreeItem object
Table 52.1:  Internal item representations for JavaFX GUI elements

Swing The following table lists the complex GUI elements and the default internal item representation used by QF-Test standard ItemResolvers for Swing.

GUI element classItem type
JComboBoxInteger index
JListInteger index
JTabbedPaneInteger index
JTableint array [column,row] with row < 0 to represent a whole column
JTableHeaderInteger column index
JTextAreaInteger line
JTreeTreePath path
Table 52.2:  Internal item representations for Swing GUI elements

SWT The following table lists the complex GUI elements and the default internal item representation used by QF-Test standard ItemResolvers for SWT.

GUI element classItem type
CComboInteger index
ComboInteger index
CTabFolderInteger index
ListInteger index
StyledTextInteger line
TabFolderInteger index
Tableint array [column,row] or just Integer column to represent a whole column
TextInteger line
TreeObject array [Integer column,TreeItem row] or just Integer column to represent a whole column
Table 52.3:  Internal item representations for SWT GUI elements

Web The following table lists the complex GUI elements and the default internal item representation used by QF-Test standard ItemResolvers for Web.

GUI element classItem type
SELECT nodeOPTION node
TEXTAREA nodeInteger line
Table 52.4:  Internal item representations for DOM nodes