Das resolvers
Modul
Mit diesem Erweiterungs-API können Sie Einfluss darauf nehmen, wie QF-Test Komponenten und Unterelemente erkennt und aufzeichnet. Dies ist ein sehr mächtiges Feature, mit dem Sie volle Kontrolle über das Komponenten-Management von QF-Test erhalten.
Video zum Thema: 'Resolver in
QF-Test'.
Verwendung
Hinweis Beim Registrieren eines Resolvers ist es wichtig, das GUI-Engine Attribut für den SUT-Skript Knoten korrekt anzugeben. Wird die falsche Engine gesetzt, funktioniert der Resolver einfach nicht. Ist gar keine Engine angegeben, wirkt der Resolver bei allen Engines und kann zu Verwirrung führen und die Wiedergabe in einer Engine stören, für die er nicht gedacht war.
Zum besseren Verständnis zur Verwendung von Resolvern hier eine kurze Beschreibung der Komponentenerkennung durch QF-Test. Sie läuft grob in vier Schritten ab:
- Einlesen der Komponentenobjekte aus dem GUI.
- Datenextraktion für die Einzelkomponente: z.B. Komponentenklasse, Id, Koordinaten, Komponententext.
-
Beziehungsanalyse der Komponenten zueinander: z.B. Strukturinformationen
(Index), Bestimmung einer der Komponente zugehörigen Beschriftung
qfs:label*
-Varianten. -
Aufnahme: Generierung eines Komponente Knotens und Speicherung der erhaltenen
Daten in den Details des Knotens.
Wiedergabe: Abgleich der erhaltenen Daten mit den Details des Knotens, auf den die Aktion abgespielt werden soll.
QF-Test verwendet für die in Schritt 2 und 3 zu erledigenden Aufgaben Resolver. Über die API können Methoden dieser Resolver erweitert und so Einfluss auf die Komponentenerkennung genommen werden.
Sollen Werte bei der Aufnahme beeinflusst werden, so ist dies nur über einen Resolver möglich. Bei der Wiedergabe können Komponenteninformationen auch über andere Wege (z.B. ein Skript oder einen regulären Ausdruck (vgl. Abschnitt 49.3) in den Details des Komponente Knotens) erlangt werden.
Web
Für Web-Anwendungen steht eine spezielle Schnittstelle zur Verfügung, in der
die Funktionalität der hier beschriebenen Resolver zusammengefasst
und leichter konfigurierbar ist.
Siehe Verbesserte Komponentenerkennung mittels CustomWebResolver
. Der dort beschriebene
CustomWebResolver installieren Knoten ist für Web-Elemente
optimiert und somit wesentlich performanter als der Einsatz der Resolver dieses
Abschnitts. Nur in Spezialfällen ist für Web-Komponenten
der Einsatz der hier beschriebenen Resolver sinnvoll.
Es folgt eine Aufstellung zur Verfügung stehender resolver
.
-
NameResolver
Abschnitt 54.1.7 -
GenericClassNameResolver
Abschnitt 54.1.8 -
ClassNameResolver
Abschnitt 54.1.9 -
FeatureResolver
Abschnitt 54.1.10 -
ExtraFeatureResolver
Abschnitt 54.1.11 -
ItemNameResolver
Abschnitt 54.1.12 -
ItemValueResolver
Abschnitt 54.1.13 -
TreeTableResolver
Abschnitt 54.1.14 -
InterestingParentResolver
Abschnitt 54.1.15 -
TooltipResolver
Abschnitt 54.1.16 -
IdResolver
Abschnitt 54.1.17 -
EnabledResolver
Abschnitt 54.1.18 -
VisibilityResolver
Abschnitt 54.1.19 -
MainTextResolver
Abschnitt 54.1.20 -
WholeTextResolver
Abschnitt 54.1.21 -
BusyPaneResolver
Abschnitt 54.1.22 -
GlassPaneResolver
Abschnitt 54.1.23 -
TreeIndentationResolver
Abschnitt 54.1.24 -
EventSynchronizer
Abschnitt 54.1.25 -
BusyApplicationDetector
Abschnitt 54.1.26 -
ExtraFeatureMatcher
Abschnitt 54.1.27.1
Implementierung
Bei der Implementierung eines Resolvers sind folgende beide Schritte nötig:
- Implementierung des Resolver-Interfaces.
- Registrierung des Interfaces unter Angabe eines Namens und der Komponentenklasse(n), für die es gilt.
In den meisten Fällen besteht das Interface aus einer einzigen Methode, sodass ein typisches Beispiel wie folgt aussieht (Jython-Skript):
def getName(menuItem, name): if not name: return menuItem.getLabel() resolvers.addResolver("menuItems", getName, "MenuItem")
NameResolver
für MenuItems
Die ersten drei Zeilen definieren die Methode des Resolver Interfaces.
Über den Namen der Methode leitet sich
ab, um welchen Resolvertyp es sich handelt, d.h. welcher Wert eines
Komponente Knotens beeinflusst wird. In unserem Fall heißt die Methode
getName
. Es handelt sich also um einen NameResolver
. Die vierte
Zeile ruft die addResolver
Funktion im resolvers
Modul
und registriert den Resolver.
Die meisten Resolver-Methoden haben nur zwei Parameter: Erstens die
Komponete, für die die Komponentenerkennung in diesem Moment ausgeführt wird.
Zweitens das in dieser Methode behandelte Feld bzw. Objekt.
Bei einem NameResolver
ist dies der vom QF-Test
Standard-NameResolver
ermittelte Name. Bei einem FeatureResolver
das
ermittelte Merkmal usw. Eine ausführliche Beschreibung der einzelnen Resolver Interfaces finden Sie in den
Kapiteln Abschnitt 54.1.7 bis Abschnitt 54.1.26.
Der Name, unter dem ein Resolver registriert wird, muss eindeutig sein.
Er wird benötigt, wenn der Resolver
verändert wurde und die alte Version durch die neue ersetzt werden soll oder wenn der
Resolver explizit mittels resolvers.removeResolver("resolvername")
(vgl. removeResolver
) entfernt werden soll. Die Namen aller registrierten
Resolver können mit Hilfe der Funktion resolvers.listNames()
abgerufen werden
(vgl. listNames
).
Nach der Änderung eines Resolver-Skripts muss dieses erneut registriert werden, um die Änderung zu aktivieren. Eine vorherige Deregistrierung des alten Standes ist nicht notwendig, solange der Name, unter dem der Resolver registriert wurde, unverändert bleibt.
Alle Arten von Resolvern können wahlweise für individuelle Komponenten, für spezifische Klassen oder Generische Klassen registriert werden. Resolver für individuelle Komponenten werden nur aufgerufen, wenn ihre Information für genau diese Komponente benötigt wird. Resolver, die für eine Klasse registriert sind, werden für alle Objekte dieser oder einer davon abgeleiteten Klasse aufgerufen.
Ein Resolver kann für eine oder mehrere individuelle Komponenten und/oder Klassen
registriert werden. Falls der entsprechende Parameter nicht spezifiziert wird,
gilt der Resolver für alle Klassen. Dies ist z.B. möglich für NameResolver
,
FeatureResolver
und TreeTableResolver
. Sie werden dann für
jeden zu ermittelnden Namen bzw. Merkmal oder TreeTable aufgerufen. Dies ist äquivalent
zu - aber effektiver als - eine Registrierung für die Klasse
java.lang.Object
bei Java-Applikationen.
Es können verschiedene Resolver mit unterschiedlichen Aufgaben erstellt und zur Laufzeit registriert werden. Um den Resolver permanent zu installieren, verschieben Sie den SUT-Skript Knoten direkt hinter den Warten auf Client Knoten in Ihrer Sequenz zum Start des SUT.
Sind mehrere Resolver für ein bestimmtes Objekt, eine bestimmte Klasse oder global registriert, wird der zuletzt registrierte Resolver zuerst aufgerufen. Der erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis.
Da Resolver für jede im GUI angezeigte Instanz der Komponente bzw. Klasse aufgerufen werden, für
die sie registriert wurden, empfiehlt es sich, zeitsparende
Algorithmen bei der Implementierung zu verwenden. In einem Jython Skript
ist z.B. die Ausführung von string[0:3] == "abc"
deutlich schneller als
etwa string.startswith("abc")
.
Alle Exceptions, die während der Ausführung eines Resolvers auftreten, werden von der
ResolverRegistry
abgefangen. Es wird allerdings nur eine kurze Meldung
und kein Stacktrace ausgegeben, weil insbesondere globale Resolver sehr oft aufgerufen
werden können. Somit würde ein Resolver, der einen Bug hat, durch die Ausgabe von
Stacktraces für jeden Fehler das Client-Terminal überfluten. Daher sollten Resolver
ihre eigenen Fehlerbehandlungsroutinen enthalten. Dabei können zwar immer noch extrem
viele Ausgaben erzeugt werden, aber diese sind dennoch hilfreicher als Java-Stacktraces.
Das resolvers
Modul ist immer automatisch in allen
SUT-Skript Knoten verfügbar.
Die meisten Beispiele sind in Jython implementiert. Im Kapitel Abschnitt 54.1.7 finden Sie ein Beispiel für einen Groovy SUT-Skript Knoten.
addResolver
Die zentrale Funktion des resolvers
Modul ist die generische Funktion
addResolver
, die anhand des Namens der definierten Methode sowie deren Parameter
das jeweils passende Objekt identifiziert und dessen spezifische Funktion zur
Registrierung des Resolvers aufruft.
|
|
||||||||||||||||||||||||||||||
Historie
Resolver haben in QF-Test bereits eine lange Historie. Bis QF-Test Version 4.1 war es
notwendig, die jeweils spezifische Funktion zur Registrierung der Resolver
Interfaces aufzurufen. Diese können weiterhin verwendet werden, sind hier aber
nicht mehr beschrieben. Die flexible addResolver
-Funktion ersetzt dabei
u.a. diese bisherigen Funktionen des resolvers
Moduls:
-
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
Die über das resolvers
Modul registrierten Resolver können mittels der
Funktion removeResolver
deregistriert werden.
Resolver werden oft direkt nach dem Start der Applikation registriert und bleiben während der gesamten Testausführung aktiv. Es gibt jedoch auch Fälle, in denen ein Resolver nur bei der Arbeit mit einer bestimmten Komponente aktiviert und anschließend wieder entfernt werden soll. Sei es aus Performanzgründen oder weil die Wirkung des Resolvers nur in bestimmten Situationen gewünscht ist.
Es stehen zwei Funktionen zur Verfügung. Die erste, removeResolver
deregistriert einen einzelnen Resolver, die zweite, removeAll
entfernt
alle vom Benutzer registrierten Resolver.
|
|
||||||||||||||||||
Im Beispiel wird zunächst ein unter dem Namen "menuItems" registrierter Resolver entfernt, danach
alle über das resolvers
-Modul registrierten Resolver.
resolvers.removeResolver("menuItems") resolvers.removeAll()
listNames
Gibt eine Liste der Namen der Resolver zurück, die über das resolvers
Modul
registriert wurden.
|
|
||||||||
Im Beispiel wird überprüft, ob ein spezifischer Resolver registriert wurde. Falls nicht wird dem Protokoll eine Fehlermeldung hinzugefügt.
if (! resolvers.listNames().contains("specialNames")) { rc.logError("Special Names Resolver nicht registriert!") }
resolvers
Modul
Zugriff auf die beste Beschriftung
Wenn aus einem Resolver auf die Beste Beschriftung
zugegriffen werden soll, dann die Methode
rc.engine.helper.getBestLabel()
genutzt werden.
|
|
||||||||||||||
Das Beispiel zeigt einen NameResolver, der die beste Beschriftung in das Name Attribut überträgt.
_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
Das NameResolver
Interface
Der NameResolver
beeinflusst den Name Attributwert eines
Komponente Knotens.
Nachdem QF-Test den Namen einer Komponente ermittelt hat, erhalten die
registrierten NameResolver
die Chance, diesen zu überschreiben oder zu
unterdrücken. Der erste Resolver, der einen nicht-null Wert
zurückliefert, bestimmt das Ergebnis. Sind keine Resolver
registriert oder liefern alle Resolver null, wird der ursprüngliche Name benutzt.
Ein NameResolver
kann den Namen einer Komponente, der normalerweise mit
setName()
bei AWT/Swing, setId()
oder das
fx:id
Attribut bei JavaFX, setData()
bei SWT oder dem 'ID'
Attribut eines DOM-Knotens bei einer Web-Anwendung gesetzt wird, verändern (oder
überhaupt erst definieren). Dies kann äußerst nützlich sein, wenn der Source code
nicht geändert werden kann, sei es weil fremder Code eingesetzt wird oder weil auf
Bestandteile von komplexen Komponenten kein Zugriff besteht. Ein Beispiel für
letzteren Fall ist der JFileChooser
Dialog von Swing. Für diesen bringt
QF-Test einen eigenen NameResolver mit, über den Sie im Kapitel "Die
Standardbibliothek" des Tutorials weitere Informationen finden.
In einzelnen Fällen kann es sinnvoll sein, den Namen einer
Komponente zu unterdrücken, z.B. wenn er nicht eindeutig ist, oder
- wesentlich schlimmer - wenn er während der Laufzeit variiert. Um
das zu erreichen muss getName
den leeren
String zurückliefern.
Technologien: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. Für Web-Anwendungen
sollte der in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene CustomWebResolver installieren Knoten
genutzt werden. Er ist für Web-Elemente
optimiert. Aus Performance-Gründen sollte daher der NameResolver
nur verwendet werden, wenn die dort bereitgestellte Funktionalität nicht
ausreicht.
Ein NameResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Das erste Beispiel zeigt einen NameResolver
, der für Komponenten der
generischen Klasse MenuItems
, für die der QF-Test Standard-Resolver keinen Namen
ermitteln konnte, den Text des Menüeintrags als Namen der Komponente definiert.
def getName(menuItem, name): if not name: return menuItem.getLabel() resolvers.addResolver("menuItems", getName, "MenuItem")
NameResolver
für MenuItems
Probieren Sie es aus! Kopieren Sie das obige Beispiel in einen
SUT-Skript Knoten und führen Sie diesen aus. Falls Ihre Anwendung auf
SWT basiert, ersetzen Sie getLabel()
durch getText()
. Nehmen
Sie dann einige Menü Aktionen in eine neue, leere Testsuite auf. Sie werden sehen,
dass alle Komponenten ohne eigenen Namen für Menüeinträge nun einen Namen entsprechend
ihrer Beschriftung erhalten. Falls setName
in Ihrer Anwendung nicht
verwendet wird und die Menü-Bezeichner weitgehend statisch sind, kann das sogar eine
recht nützliche Sache sein.
Das zweite Beispiel zeigt einen NameResolver
, der einem teilweise dynamischen
Namen (z.B. "lfd. Nr: 100478") den festen Wert ("laufende Nummer") zuweist.
Er wird für eine spezifische Java-Swing-Klasse registriert.
def getName(menuItem, name): if name and name[0:7] == "lfd. Nr": return "laufende Nummer" resolvers.addResolver("lfdNr", getName, "javax.swing.JMenuItem")
NameResolver
für eine spezifische Klasse
Der folgende NameResolver
ist ein Groovy-Beispiel, das in einer SWT-Anwendung
den Text des Menüpunktes als Namen einsetzt, sofern nicht bereits ein Name
vom QF-Test Standard-NameResolver
vergeben wurde.
def getName(def menuItem, def name) { if (name == null) { return menuItem.getLabel() } } resolvers.addResolver("menuItems", this.&getName, "MenuItem") // Hier ginge auch verkürzt: // resolvers.addResolver("menuItems", this, "MenuItem") // da jedes Groovy-Skript ein Objekt darstellt und // addResolver(...) auf Objekten alle Methoden des Objekts, // wenn möglich, als Resolver registriert.
Resolver
für MenuItems
Ein Resolver kann gleichzeitig für mehrere Klassen von Elementen registriert werden:
def getName(com, name): return com.getText() resolvers.addResolver("labels", getName, "Label", "Button")
Resolver
für mehrere Klassen
4.0+54.1.8
Das GenericClassNameResolver
Interface
Ein GenericClassNameResolver
kann die generische Klasse
(Kapitel 61) bestimmen, die QF-Test für eine Komponente aufzeichnet.
Er kann dazu genutzt werden aufgezeichnete Komponenten lesbarer zu gestalten
und auch um gezielt weitere Resolver auf die neu erstellten Klassen zu registrieren.
Technologien: alle
Web
Dieser Resolver sollte für Web-Anwendungen nur dann verwendet werden, wenn die Möglichkeiten
des in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebenen CustomWebResolver installieren Knoten nicht ausreichen.
Nachdem QF-Test den generischen Klassennamen einer Komponente ermittelt hat, erhalten die
registrierten GenericClassNameResolver
die Chance, diesen zu überschreiben.
Der erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt
das Ergebnis. Sind keine Resolver registriert oder liefern alle Resolver null, wird
der ursprüngliche generische Klassenname benutzt.
Aus Performanzgründen werden die Klassen zwischengespeichert, daher wird der Resolver für jede Komponente höchstens einmal aufgerufen. Falls Sie den Resolver anpassen und testen wollen, müssen Sie den Bereich mit der Komponente neu laden bzw. schließen und erneut öffnen.
Web Falls ein Element bereits über einen CustomWebResolver eine Generische Klasse zugewiesen bekommen hat, werden für dieses Element keine GenericClassNameResolvers mehr aufgerufen.
Ein GenericClassNameResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
3.1+54.1.9
Das ClassNameResolver
Interface
Ein ClassNameResolver
kann die Klasse bestimmen, die QF-Test
für eine Komponente aufzeichnet. Er kann dazu genutzt werden aufgezeichnete
Komponenten lesbarer zu gestalten und auch um gezielt weitere Resolver
auf die neu erstellten Klassen zu registrieren, wobei wir grundsätzlich
die Verwendung von generischen Klassen empfehlen. Für die Registrierung von
Generische Klassen sollten Sie den GenericClassNameResolver
(Abschnitt 54.1.8) verwenden.
Technologien: alle
Web
Dieser Resolver sollte für Web-Anwendungen nur dann verwendet werden, wenn die Möglichkeiten
des in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebenen CustomWebResolver installieren Knoten nicht ausreichen.
Ein ClassNameResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Nachdem QF-Test den Klassennamen einer Komponente ermittelt hat, erhalten die
registrierten ClassNameResolver
die Chance, diesen zu überschreiben.
Der erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt
das Ergebnis. Sind keine Resolver registriert oder liefern alle Resolver null, wird
der ursprüngliche Klassenname benutzt. Ein solcher Resolver kann
jeden beliebigen Klassennamen zurückliefern. Sämtliche QF-Test internen
Methoden werden diese Klassen auch wie normale Klassen behandeln.
Aus Performanzgründen werden die Klassen zwischengespeichert, daher wird der Resolver für jede Komponente höchstens einmal aufgerufen. Falls Sie den Resolver anpassen und testen wollen, müssen Sie den Bereich mit der Komponente neu laden bzw. schließen und erneut öffnen.
3.1+54.1.10
Das FeatureResolver
Interface
Der FeatureResolver
beeinflusst das Merkmal Attribut einer Komponente.
Nachdem QF-Test das Merkmal einer Komponente ermittelt hat, erhalten die
registrierten FeatureResolver
die Chance, dieses zu überschreiben oder zu
unterdrücken. Der erste Resolver, der einen nicht-null Wert
zurückliefert, bestimmt das Ergebnis. Sind keine Resolver
registriert oder liefern alle Resolver null, wird das ursprüngliche Merkmal benutzt.
Um das Merkmal einer Komponente zu unterdrücken, muss
getFeature
den leeren String zurückliefern.
Technologien: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. Für Web-Anwendungen
sollte der in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene CustomWebResolver installieren Knoten
genutzt werden. Er ist für Web-Elemente
optimiert. Aus Performance-Gründen sollte daher der FeatureResolver
nur verwendet werden, wenn die dort bereitgestellte Funktionalität nicht
ausreicht.
Ein FeatureResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Das folgende Beispiel bezieht sich auf ein Java/Swing Panel, das eine Beschriftung in der Umrandung enthält. Diese Beschriftung soll als Merkmal gesetzt werden.
def getFeature(com, feature): try: title = com.getBorder().getInsideBorder().getTitle() if title != None: return title except: pass resolvers.addResolver("paneltitle", getFeature, "Panel")
FeatureResoler
für Java/Swing Panels
Das ExtraFeatureResolver
Interface
Der ExtraFeatureResolver
kann ein Weiteres Merkmal in der
Weitere Merkmale Tabelle einer Komponente ändern, hinzufügen oder löschen.
Hierzu stellt das Interface eine Reihe von Methoden zur Verfügung.
Ein Objekt der Klasse de.qfs.apps.qftest.shared.data.ExtraFeature
repräsentiert ein weiteres Merkmal eines GUI Elements, bestehend aus Name und Wert,
sowie Informationen darüber, ob für das Feature ein Treffer erwartet wird, ob der Wert
ein regulärer Ausdruck (vgl. Abschnitt 49.3) ist und
ob die Aussage negiert werden soll. Für mögliche Zustände
definiert die Klasse die Konstanten STATE_IGNORE, STATE_SHOULD_MATCH und
STATE_MUST_MATCH.
Nachdem QF-Test die weiteren Merkmale eines GUI Elements ermittelt hat, erhalten die
registrierten ExtraFeatureResolver
die Chance, diese zu modifizieren. Im
Gegensatz zu anderen Resolvern bricht QF-Test nicht ab, sobald ein
ExtraFeatureResolver
einen nicht-null Wert zurückliefert, sondern
übernimmt diesen Wert als Parameter für den nächsten Resolver. Hierdurch können
mehrere ExtraFeatureResolver
registriert werden, die jeweils ein
spezielles Merkmal behandeln. Sind keine Resolver registriert oder liefern alle
Resolver null, macht QF-Test mit dem ursprünglich ermittelten Satz von Merkmalen weiter.
Um die getExtraFeatures Methode implementieren zu können, müssen Sie natürlich die API
der beteiligten Klassen ExtraFeature
und ExtraFeatureSet
kennen. Diese werden nach den Beispielen zum ExtraFeatureResolver
beschrieben.
Technologien: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. Für Web-Anwendungen
sollte der in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene CustomWebResolver installieren Knoten
genutzt werden. Er ist für Web-Elemente
optimiert. Aus Performance-Gründen sollte daher der ExtraFeatureResolver
nur verwendet werden, wenn die dort bereitgestellte Funktionalität nicht
ausreicht.
Um die qfs:label*
-Varianten der weiteren Merkmale bei der
Aufnahme und der Wiedergabe konsistent zu halten und sie
auf SmartIDs abbilden zu können, ist es notwendig, bestimmte
Regeln einzuhalten. Diese gelten nicht für das qfs:label
Weiteres Merkmal, das für sich alleine steht.
Bei der Behandlung von qfs:label*
-Varianten in einem
ExtraFeatureResolver
muss darauf geachtet werden,
dass der komplette Variantensatz in sich konsistent bleibt. Das heißt:
-
Es darf höchstens eine
qfs:label*
-Variante mit dem Status "Sollte übereinstimmen" geben. Der Rest sollte auf "Ignorieren" stehen. -
qfs:labelBest
sollte entweder die "Sollte übereinstimmen"-Variante sein oder den gleichen Wert wie die "Sollte übereinstimmen"-Variante haben.
Bei der Ermittlung der zugehörigen Beschriftungen erstellt QF-Test
die qfs:label*
-Varianten gemäß dieser Regeln.
Die ExtraFeatureSet
Klasse berücksichtigt die Regeln
ebenfalls, wenn von einem ExtraFeatureResolver
aus darauf
zugegriffen wird. Dadurch wird die Einhaltung der Regeln
bei der Arbeit mit einem ExtraFeatureResolver
erleichtert.
Dies bedeutet:
-
Wenn der Wert von
qfs:labelBest
geändert wird und es den Status "Ignorieren" hat, wird der Wert der "Sollte übereinstimmen"-Variante automatisch ebenfalls geändert. -
Wenn der Wert der "Sollte übereinstimmen"-Variante geändert wird,
wird der Wert von
qfs:labelBest
automatisch ebenfalls geändert. -
Wenn eine
qfs:label*
-Variante auf "Sollte übereinstimmen" gesetzt wird, werden alle anderen auf "Ignorieren" gesetzt undqfs:labelBest
entsprechend aktualisiert.
Hinweis
Wenn qfs:label*
-Varianten mit dem Status "Muss übereinstimmen"
oder mit einem regulären Ausdruck existieren, wird die Regelbehandlung
deaktiviert.
Ein ExtraFeatureResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Das erste Beispiel zeigt einen ExtraFeatureResolver
, der den Titel
eines Java/Swing Dialogs als Weiteres Merkmal mit dem Status "muss übereinstimmen"
(STATE_MUST_MATCH) hinzufügt. Dies ist sehr nützlich, wenn es bei der
Komponentenerkennung eines Dialogs auf den richtigen Titel ankommt.
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
der ein weiteres Feature für Java/Swing Dialoge erstellt
Das folgende Beispiel zeigt, wie man ein vorhandenes Weiteres Merkmal ändert.
Da es sich um qfs:labelBest
handelt, sorgt QF-Test wie oben beschrieben
dafür, dass die qfs:label*
-Varianten in sich konsistent bleiben.
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
, der ein Weiteres Merkmal ändert
Das nächste Beispiel zeigt, wie man den Status
der qfs:label*
-Variante mit dem Status
"Sollte übereinstimmen" auf "Ignorieren" gesetzt:
def getExtraFeatures(node, features) { def labelFeature = features.getShouldMatchLabel() if (labelFeature) { labelFeature.setState(resolvers.STATE_IGNORE) return features } } resolvers.addResolver("get label example", this)
ExtraFeatureResolver
(in Groovy), welcher den Status einer qfs:label*
-Variante ändert
Dank der oben beschriebenen Regeln für den Satz der qfs:label*
-Varianten,
kann ein einfacher ExtraFeatureResolver
,
mit dem folgenden Inhalt
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 Weiteres Merkmal
einfach in Beispiel
ExtraFeatureResolver
, der ein Weiteres Merkmal ändert
umgewandelt werden.
Im Folgenden finden Sie die Beschreibung der APIs
der Klassen ExtraFeature
und ExtraFeatureSet
.
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Die Klasse de.qfs.apps.qftest.shared.data.ExtraFeatureSet
bündelt
ExtraFeatures
in einen Satz:
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3.1+54.1.12
Das ItemNameResolver
Interface
Ein ItemNameResolver
kann die Textdarstellung des
Index zur Adressierung eines Unterelements einer komplexen Komponente verändern
(oder überhaupt erst definieren).
Nachdem QF-Test einen Namen für den Index eines Unterelements ermittelt hat, erhalten die
registrierten ItemNameResolver
die Chance, diesen zu überschreiben. Der
erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis. Sind
keine Resolver registriert oder liefern alle Resolver null, wird der ursprüngliche
Name benutzt.
Technologien: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. Für Web-Anwendungen
sollte der in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene CustomWebResolver installieren Knoten
genutzt werden. Er ist für Web-Elemente
optimiert. Aus Performance-Gründen sollte daher der ItemNameResolver
nur verwendet werden, wenn die dort bereitgestellte Funktionalität nicht
ausreicht.
Ein ItemNameResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||||
Es folgt ein Beispiel für einen
ItemNameResolver
, der die ID einer JTable
Spalte für den
Index zugänglich macht:
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
für JTableHeader
3.1+54.1.13
Das ItemValueResolver
Interface
Der ItemValueResolver
wird verwendet, um die Prüfung des Textes von
Elementen zu optimieren.
Ein ItemValueResolver
kann die Textdarstellung des
Wertes eines Unterelements einer komplexen Komponente verändern
(oder überhaupt erst definieren), der für einen Check Text oder
Text auslesen Knoten verwendet wird.
Nachdem QF-Test einen Wert für ein Unterelement ermittelt hat, erhalten die
registrierten ItemValueResolver
die Chance, diesen zu überschreiben. Der
erste Resolver, der einen nicht-null Wert zurückliefert, bestimmt das Ergebnis. Sind
keine Resolver registriert oder liefern alle Resolver null, wird der ursprüngliche
Wert benutzt.
Technologien: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. Für Web-Anwendungen
sollte der in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene CustomWebResolver installieren Knoten
genutzt werden. Er ist für Web-Elemente
optimiert. Aus Performance-Gründen sollte daher der ItemValueResolver
nur verwendet werden, wenn die dort bereitgestellte Funktionalität nicht
ausreicht.
Ein ItemValueResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||||
Das TreeTableResolver
Interface
Ein TreeTableResolver
hilft QF-Test TreeTable Komponenten als solche zu
erkennen. Eine TreeTable ist eine Mischung aus einer Tabelle und einem Baum. Sie ist
keine Standard-Swing-Komponente, allerdings werden die meisten TreeTables ähnlich
implementiert, indem ein Baum als Renderer für eine Spalte der Tabelle verwendet wird.
Wenn QF-Test eine TreeTable identifiziert, behandelt es die Zeilenindizes aller Zellen
der Tabelle wie Baumindizes, was in diesem Zusammenhang wesentlich bessere Ergebnisse
liefert. Außerdem werden Geometrie Informationen für Zellen in der Spalte des Baums
basierend auf Baumknoten statt auf Tabellenzellen ermittelt.
Technologien: AWT/Swing
Hinweis Dieses Interface ist nur für AWT/Swing relevant. Mehrspaltige Bäume in SWT und JavaFX werden von QF-Test automatisch unterstützt. Für Web-Frameworks ist die TreeTable im entsprechenden (Custom-)Web-Resolver definiert.
Ein TreeTableResolver
muss die beiden folgenden Methoden implementieren:
|
|
||||||||||||||||||||||||||
Die meisten TreeTableResolver
sind trivial zu implementieren.
Das folgende Beispiel in Jython genügt bereits für die
org.openide.explorer.view.TreeTable
Komponente der populären netBeans
IDE, vorausgesetzt, dass der Resolver für die TreeTable Klasse registriert wird.
def getTreeMethod(table): return table.getCellRenderer(0,0) def getColumn(table): return 0 resolvers.addResolver("treetableResolver", getTreeMethod, \ getColumn, "org.openide.explorer.view.TreeTable")
TreeTableResolver
für die netBeans IDE
Das folgende Beispiel zeigt einen typischen TreeTableResolver
.
def getTree(table): return table.getTree() def getColumn(table): return 0 resolvers.addResolver("treeTable", getTree, getColumn, "my.package.TreeTable")
TreeTableResolver
für Swing TreeTable mit optionaler
getColumn
Methode
Da praktisch
alle TreeTables den Baum in der ersten Spalte der Tabelle darstellen, ist die
getColumn
Methode optional. Wird keine übergeben, wird automatisch eine
default Implementierung für die erste Spalte erstellt:
def getTree(table): return table.getTree() resolvers.addResolver("treeTable", getTree, None, "my.package.TreeTable")
TreeTableResolver
Falls keine dedizierte getTree
Methode vorhanden ist, hilft meist der CellRenderer
der Spalte, die den Baum enthält (typischerweise 0), da dieser oft von JTree
abgeleitet ist.
def getTree(table): return table.getCellRenderer(0,0) resolvers.addResolver("treeTable", getTree, "my.package.TreeTable")
TreeTableResolver
, der die getCellRenderer
Methode nutzt
Das InterestingParentResolver
Interface
Ein InterestingParentResolver
kann beeinflussen, ob eine Komponente
als für die Komponentenerkennung interessant bzw. uninteressant betrachtet wird.
Dies wiederum legt fest, ob für die Komponente ein Komponente Knoten
angelegt wird.
Technologien: AWT/Swing, JavaFX, SWT, Windows, Android, iOS. Für Web-Anwendungen
sollte der in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene CustomWebResolver installieren Knoten
genutzt werden. Er ist für Web-Elemente
optimiert. Aus Performance-Gründen sollte daher der InterestingParentResolver
nur verwendet werden, wenn die dort bereitgestellte Funktionalität nicht
ausreicht.
Ein InterestingParentResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
4.1+54.1.16
Das TooltipResolver
Interface
Ein ToolTipResolver
kann den Tooltip einer Komponente beeinflussen.
Dieser Tooltip wird bei Prüfungen und im weiteren Merkmal 'qfs:labelTooltip' verwendet.
Technologien: AWT/Swing, JavaFX, SWT. Für Web-Anwendungen
sollte der in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene CustomWebResolver installieren Knoten
genutzt werden. Er ist für Web-Elemente
optimiert. Aus Performance-Gründen sollte daher der TooltipResolver
nur verwendet werden, wenn die dort bereitgestellte Funktionalität nicht
ausreicht.
Ein TooltipResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Web54.1.17
Das IdResolver
Interface
Ein IdResolver
kann das Attribut 'ID' eines DOM-Knoten modifizieren bzw. unterdrücken.
Wenn QF-Test die Knoten einer Webseite registriert, wird das Attribut 'ID' dieser Knoten
gespeichert. Abhängig von der Option ID-Attribut als Name verwenden wird der Wert dieses Attributes
auch als Komponentenname für die Erkennung herangezogen.
Da viele Webseiten bzw. Frameworks automatisch generierte IDs verwenden, ist oft eine
Modifikation dieser IDs nötig, um eine stabile bzw. eindeutige Erkennung zu erhalten.
Es gibt drei Möglichkeiten mit solchen automatisch generierten IDs umzugehen:
- Die einfachste Variante solche automatischen IDs zu ignorieren, ist es,
beim generierten CustomWebResolver installieren Knoten die
Kategorie
autoIdPatterns
zu setzen. Dort können Sie konkrete Werte, z.B.meineAutoId
oder auch reguläre Ausdrücke (vgl. Abschnitt 49.3), z.B.auto.*
spezifizieren, um alle IDs, die mitauto
beginnen, zu ignorieren. - Falls Sie ein eigenes Attribut eingeführt haben, welches anstatt des originalen Attributs 'ID'
verwendet werden soll, dann rufen Sie den CustomWebResolver installieren Knoten auf.
Dort können Sie eigene Attribute in der Kategorie
customIdAttributes
spezifizieren. Diese Attribute werden nun anstatt des Attributs 'ID' verwendet werden. - Sie können mit der Option Alle Ziffern aus 'ID'-Attributen eliminieren konfigurieren, dass nur Ziffern aus den IDs gelöscht werden sollen.
- Falls Sie eine komplexere Logik implementieren wollen, brauchen Sie einen eigenen
IdResolver
.
Die oben genannten Methoden schließen einander nicht aus und können auch miteinander kombiniert werden.
Falls Sie sich für eine eigene Logik per Resolver entscheiden, sollten Sie allerdings immer
einen IdResolver
verwenden, weil die ID eines Knotens an verschiedenen Stellen wieder auftauchen kann.
Vor allem im Attribut Name des Knotens (abhängig von der Option
ID-Attribut als Name verwenden), im Attribut Merkmal und im
Attribut Weitere Merkmale. Daher ist es viel effizienter, einmalig
die ID mittels der oben genannten Möglichkeiten zu verändern, als getrennte
Name-
, Feature-
und ExtraFeatureResolvers
zu
implementieren. Noch wichtiger ist der Umstand, dass die Veränderung der ID eines
Knotens großen Einfluss auf die Eindeutigkeit dieser ID haben kann. Der Mechanismus
zum Ermitteln von Namen auf Basis der ID nimmt darauf Rücksicht, so dass ein
IdResolver
auch nicht eindeutige IDs liefern darf. Ein
NameResolver
muss dagegen eindeutige Namen liefern.
Technologien: Web
Ein IdResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
4.1+54.1.18
Das EnabledResolver
Interface
Ein EnabledResolver
beeinflusst, wann eine Komponente als aktiv oder inaktiv angesehen wird.
Bei AWT/Swing Komponenten kann dies direkt per Attribut erfolgen, Web und JavaFX benötigen dafür spezielle
Stylesheet-Klassen, die dann mit Hilfe des EnabledResolver
s ausgewertet werden.
Technologien: JavaFX, Web, Windows, Android, iOS
Ein EnabledResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Das folgende Beispiel bestimmt den Enabled-Zustand eines Webknotens anhand der
CSS-Klasse 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
Das VisibilityResolver
Interface
Ein VisibilityResolver
beeinflusst, wann ein Web-Element als sichtbar angesehen wird.
Technologien: Web, Windows, Android, iOS
Ein VisibilityResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Das folgende Beispiel setzt die Sichtbarkeit eines Web-Elementes zusätzlich auf false, wenn es durchsichtig ist.
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
Das MainTextResolver
Interface
Ein MainTextResolver
ermittelt den "Haupttext" einer Komponente,
standardmäßig die erste Zeile, der zum Beispiel für das Merkmal oder die qfs:label*
-Varianten
verwendet werden soll.
Technologien: AWT/Swing, JavaFX, SWT, Web, Windows, Android, iOS
Ein MainTextResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Das folgende Beispiel entfernt aus dem "Haupttext" aller
Komponenten den String TO-DO
.
def getMainText(element,text): if text: return text.replace("TO-DO","") resolvers.addResolver("removeMarkFromText", getMainText)
MainTextResolver
4.1+54.1.21
Das WholeTextResolver
Interface
Ein WholeTextResolver
ermittelt den Text einer Komponente,
der für Checks und ähnliches verwendet werden soll.
Technologien: AWT/Swing, JavaFX, SWT, Web, Windows, Android, iOS
Ein WholeTextResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Das folgende Beispiel entfernt für Textfelder aus allen für Checks etc. verwendeten Texten den String TO-DO
.
def getWholeText(element,text): if text: return text.replace("TO-DO","") resolvers.addResolver("removeMarkFromText", getWholeText, "TextField", "TextArea")
WholeTextResolver
4.1+54.1.22
Der BusyPaneResolver
Interfaces
QF-Test wartet bei der Testausführung, bis verdeckende BusyPanes verschwinden, um dann
in einem determinierten Zustand fortzufahren. Mit einem BusyPaneResolver
kann man beeinflussen, ob eine Komponenten QF-Test als verdeckt angesehen wird.
Technologie: AWT/Swing, JavaFX
Ein BusyPaneResolver
muss
folgende Methode implementieren:
|
|
||||||||||||||
Das folgende Beispiel deaktiviert effektiv die Erkennung von BusyPanes für Komponenten des Typs "my.special.Component".
def isBusy(): return false resolvers.addResolver("neverBusyResolver",isBusy,"my.special.Component")
BusyPaneResolver
4.1+54.1.23
Der GlassPaneResolver
Interfaces
Wenn Komponenten von anderen (evtl. transparenten) Komponenten verdeckt werden, so kann
man QF-Test mit Hilfe eines GlassPaneResolver
s diese Verbindung mitteilen und
Events so zur korrekten Komponente umleiten.
Technologie: AWT/Swing
Ein GlassPaneResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Das folgende Beispiel deaktiviert effektiv die Weiterleitung der Events durch GlassPanes:
def isGlassPaneFor(element): return element resolvers.addResolver("noGlassPaneResolver", isGlassPaneFor)
GlassPaneResolver
8.0+54.1.24
Das TreeIndentationResolver
Interface
Ein TreeIndentationResolver
ermittelt die Einrückung
von Baumknoten-Elementen in Bäumen.
Verwenden Sie diesen Resolver, wenn QF-Test die Einrückung einzelner Knoten
in einer Tree- oder TreeTable-Komponente nicht korrekt erkennt
und die Möglichkeiten des Parameters "treeIndentationMode"
der CustomWebResolver-Kategorie treeResolver
nicht ausreichen.
Beachten Sie, dass der Rückgabewert des Resolvers wie eine Pixel-Angabe ausgewertet wird. Insbesondere bedeutet dies, dass unterschiedliche Einrückungsebenen sich standardmäßig um mindestens 2 Pixel unterscheiden müssen.
Technologien: Web
Ein TreeIndentationResolver
muss folgende Methode implementieren:
|
|
||||||||||||||||
Das folgende Groovy-Beispiel liest für die Einrückung aller TreeNodes
das Attribut aria-level
aus.
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
Das EventSynchronizer
Interface
Wenn QF-Test Events auf dem SUT wiedergibt, bzw. nachdem dies geschehen ist, wartet QF-Test
auf die Synchronisation mit dem jeweiligen Event Dispatch Thread. Mit einem
EventSynchronizer
kann man QF-Test mitteilen, wann das SUT wieder Events entgegen
nehmen kann. Dies sollte verwendet werden, wenn im SUT eine eigene Synchronisierung implementiert wurde.
Technologien: AWT/Swing, JavaFX, SWT, Web
Ein EventSynchronizer
muss folgende Methode implementieren:
|
|
||||||||||||
Das folgende akademische Beispiel hält die Ausführung auf dem Dispatch Thread bis zur nächsten vollen Sekunde an:
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
Das BusyApplicationDetector
Interface
Mit einem BusyApplicationDetector
kann QF-Test erkennen, dass eine Anwendung aktuell
"beschäftigt" ist und keine Events entgegen nehmen kann.
Technologien: AWT/Swing, JavaFX, SWT, Web
Ein BusyApplicationDetector
muss folgende Methode implementieren:
|
|
||||||||||
Das folgende Beispiel verwendet eine SUT-spezifische Methode, um QF-Test mitzuteilen, dass es beschäftigt ist:
def applicationIsBusy(): return my.app.App.instance().isDoingDbSynchronization() resolvers.addResolver("dbAccessDetector",applicationIsBusy)
BusyApplicationDetector
Matcher
Matcher
sind keine Resolver
im eigentlichen Sinn,
da sie nur bei der Wiedergabe greifen. Dennoch werden sie über das resolvers
Modul registriert.
Matcher
kann man speziell bei der Arbeit mit generischen Komponenten oder
bei schlüsselwortgetriebenen Testen einsetzen, wenn keine Aufzeichnungen gemacht werden sollen.
4.1+54.1.27.1
Das ExtraFeatureMatcher
Interface
Mit einem ExtraFeatureMatcher
kann beeinflusst werden, wann ein Weiteres Merkmal,
welches für eine Komponente registriert wurde, als "passend" angesehen wird.
Technologien: AWT/Swing, JavaFX, SWT, Web, Windows, Android, iOS
Ein ExtraFeatureMatcher
muss folgende Methode implementieren:
|
|
||||||||||||||||||||||
Das folgende Beispiel prüft den Wert des Weiteres Merkmal my:label
gegen das my-label
Attribut des HTML-Elementes.
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
Mit Hilfe der speziellen resolvers-Methode addSpecificExtraFeatureMatcher
kann man den Matcher-Aufruf
auch auf einen einzelnen Feature-Namen einschränken:
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
Externe Implementierung
Möchte man seine Resolver nicht direkt in einem SUT-Skript implementieren, sondern zum Beispiel in eine JAR-Datei im Plugin-Verzeichnis zur Verfügung stellen, so ist es hilfreich, wenn die Resolver-Klassen direkt die oben aufgeführten Resolver-Interfaces implementieren (Prinzipiell kann ein Resolver auch allein anhand des Namens der implementierten Methode erkannt werden).
Dazu ist bei der Entwicklung die Datei qfsut.jar
in den Classpath einzufügen.
Die aufgeführten Interfaces befinden sich mehrheitlich im Paket de.qfs.apps.qftest.extensions
,
bei den Interfaces die zwei Methodenparameter erwarten ist an den eigentlichen Interfacenamen eine "2"
anzufügen. Die Interfaces, welche mit Item...
bezeichnet sind, finden sich im Paket
de.qfs.apps.qftest.extensions.items
. Im SUT-Skript, welches den resolvers.addResolver
-Aufruf
durchführt, ist dann eine Instanz der selbstentwickelten Resolver-Klasse als Argument mitzugeben.