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'.
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).
-
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 das 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
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
. Die dort beschriebene Prozedur
qfs.web.ajax.installCustomWebResolver
der Standardbibliothek
qfs.qft
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
.
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,
so dass ein typisches Beispiel wie folgt aussieht (Jython-Skript):
|
def getName(menuItem, name):
if not name:
return menuItem.getLabel()
resolvers.addResolver("menuItems", getName, "MenuItem") |
|
| | Beispiel 49.1: Einfacher Jython 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 49.1.6 bis Abschnitt 49.1.23.
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 49.1.6
finden Sie ein Beispiel für einen Groovy 'SUT Skript' Knoten.
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.
|
|
|
void addResolver(String resolverName, Method method, Object target=None, ...) |
Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
method |
Die Methode, welche die Methode des Resolvers implementiert. Der Name dieser Methode
definiert den Typ des registrierten Resolvers, d.h. bei Groovy ist hier eine MethodClosure
anzugeben.
Zulässige Werte sind z.B.: getName , getClassName ,
getGenericClassName , getFeature , getExtraFeatures ,
getItemName , getItemValue , getItemNameByIndex ,
getTree und getTreeColumn , isInterestingParent ,
getTooltip , getId , isEnabled , isVisible ,
getMainText , matchExtraFeature , isBusy ,
isGlassPaneFor , sync
und applicationIsBusy .
|
target |
Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für
jedes Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
|
void addResolver(String resolverName, Object object, Object target=None, ...) |
Parameter |
name |
Der Name unter dem der Resolver registriert werden soll.
|
object |
Ein Objekt oder eine Klasse, die eine oder mehrere Resolver-Methoden bereitstellt. Anhand der
Methodennamen werden entdprechende Resolver registriert. Die zulässigen Methodennamen sind
identisch mit denen zuvor definierten.
|
target |
Ein oder mehrere optionale Ziele für die der Resolver registriert werden soll. Für
jedes Ziel gibt es folgende Varianten:
- Eine individuelle Komponente
- Der Name einer Klasse
Ist kein Ziel angegeben, wird der Resolver global für alle Komponenten registriert.
|
|
|
|
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,...)
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.
|
|
|
void removeAll() |
|
void removeResolver(String name) |
Parameter |
name | Der Name, unter dem der Resolver registriert war. |
|
|
|
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() |
|
| | Beispiel 49.2: SUT-Skript zur Deregistrierung eines Resolvers | |
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!")
} |
|
| | Beispiel 49.3: Groovy SUT-Skript zur Abfrage registrierter Resolver über das resolvers Modul | |
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 Webanwendung 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. Für Webanwendungen
sollte die in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene 'Prozedur'
qfs.web.ajax.installCustomWebResolver
der Standardbibliothek
qfs.qft
genutzt werden. Sie 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:
|
|
|
String getName(Object element, String name) |
Parameter |
element |
Das GUI Element dessen Name ermittelt werden soll.
|
name |
Der ursprüngliche Name, den QF-Test ohne Resolver verwenden würde.
|
Rückgabewert |
Der zu verwendende Name oder null, falls der Resolver das Element nicht behandelt.
Durch Rückgabe des leeren Strings wird der ursprüngliche Name der Komponente
unterdrückt.
|
|
|
|
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") |
|
| | Beispiel 49.4: Jython 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") |
|
| | Beispiel 49.5: Jython 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. |
|
| | Beispiel 49.6: Einfacher Groovy 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") |
|
| | Beispiel 49.7: Registrieren eines Resolver für mehrere Klassen | |
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.
Für Swing, JavaFX und SWT gibt es wenig
Anwendungsfälle, da es in diesen Technologien spezifische
Klassen pro Komponententyp gibt. Für Webanwendungen
ist dies dagegen vielleicht der wichtigste Resolver überhaupt,
da es in HTML sehr wenige konkrete Typen gibt und diese in
einigen AJAX Toolkits auch sehr flexibel und unnormiert gehandhabt werden.
Ein ClassNameResolver
kann DOM Knoten
beliebige Klassennamen zuweisen kann und somit z.B. tief verschachtelte Strukturen von
<DIV> Knoten, wie sie bei vielen AJAX Toolkits vorkommen,
in etwas brauchbares verwandelt werden können. Bitte lesen Sie diesbezüglich auch
Abschnitt 5.4 über die pseudo Klassenhierarchie von
Webanwendungen. Es
sollte allerdings vorrangig die in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene 'Prozedur'
qfs.web.ajax.installCustomWebResolver
der Standardbibliothek
qfs.qft
genutzt werden. Sie 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.
Web
Für Webanwendungen verwendet QF-Test eine pseudo Klassenhierarchie, die in Abschnitt 5.4 beschrieben ist.
Ein ClassNameResolver
muss folgende Methode implementieren:
|
|
|
String getClassName(Object element, String name) |
Parameter |
element |
Das GUI Element dessen Klassenname ermittelt werden soll.
|
name |
Der ursprüngliche Klassenname, den QF-Test ohne Resolver verwenden würde.
|
Rückgabewert |
Der zu verwendende Klassenname oder null, falls der Resolver das Element nicht
behandelt.
|
|
|
|
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.
Der GenericClassNameResolver
kommt genauso wie der ClassNameResolver
hauptsächlich für Web-Komponenten zum Einsatz. Er sollte aus Performanzgründen ebenfalls
nur dann verwendet werden, wenn die 'Prozedur' qfs.web.ajax.installCustomWebResolver
implementierte IdResolver
nicht ausreicht.
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.
Ein GenericClassNameResolver
muss folgende Methode implementieren:
|
|
|
String getGenericClassName(Object element, String name) |
Parameter |
element |
Das GUI Element dessen generischer Klassenname ermittelt werden soll.
|
name |
Der ursprüngliche generische Klassenname, den QF-Test ohne Resolver verwenden
würde. Kann auch Null sein.
|
Rückgabewert |
Der zu verwendende generische Name oder null, falls der Resolver das Element nicht
behandelt. Durch Rückgabe des leeren Strings wird der ursprünglich ermittelte
generische Name der Komponente unterdrückt.
|
|
|
|
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. Für Webanwendungen
sollte die in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene 'Prozedur'
qfs.web.ajax.installCustomWebResolver
der Standardbibliothek
qfs.qft
genutzt werden. Sie 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:
|
|
|
String getFeature(Object element, String feature) |
Parameter |
element |
Das GUI Element dessen Merkmal ermittelt werden soll.
|
feature |
Das ursprüngliche Merkmal, das QF-Test ohne Resolver verwenden würde.
|
Rückgabewert |
Das zu verwendende Merkmal oder null, falls der Resolver das Element nicht
behandelt. Durch Rückgabe des leeren Strings wird das ursprüngliche Merkmal der
Komponente unterdrückt.
|
|
|
|
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") |
|
| | Beispiel 49.8: Ein FeatureResoler für Java/Swing Panels | |
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 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 das API
der beteiligten Klassen ExtraFeature
und ExtraFeatureSet
kennen. Diese werden nach den Beispielen zum ExtraFeatureResolver
beschrieben.
Technologien: AWT/Swing, JavaFX, SWT. Für Webanwendungen
sollte die in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene 'Prozedur'
qfs.web.ajax.installCustomWebResolver
der Standardbibliothek
qfs.qft
genutzt werden. Sie 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.
Ein ExtraFeatureResolver
muss folgende Methode implementieren:
|
|
|
ExtraFeatureSet getExtraFeatures(Object element, ExtraFeatureSet features) |
Parameter |
element |
Das GUI Element dessen weitere Merkmale ermittelt werden sollen.
|
features |
Ein Satz von weiteren Merkmale, die QF-Test selbst festgelegt hat, ein leerer Satz
falls es keine solchen Merkmale gibt. Dieser Satz kann modifiziert oder ignoriert
und durch einen anderen ersetzt werden.
|
Rückgabewert |
Der modifizierte original Satz von Merkmalen oder ein neuer Satz, falls der
Resolver das GUI Element behandelt, andernfalls null. Die ursprünglichen Merkmale
können durch Rückgabe eines leeren Satzes unterdrückt werden.
|
|
|
|
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") |
|
| | Beispiel 49.9:
ExtraFeatureResolver der ein weiteres Feature für Java/Swing Dialoge erstellt
| |
Das folgende Beispiel zeigt, wie man ein vorhandenes ExtraFeature ändert.
|
def getExtraFeatures(node, features):
if features.get("qfs:label") and \
features.get("qfs:label").getValue() == "bad name":
features.get("qfs:label").setValue("good name")
return features
resolvers.addResolver("get label example", getExtraFeatures,"TextField") |
|
| | Beispiel 49.10:
ExtraFeatureResolver der ein 'Weiteres Merkmal' ändert
| |
Das nächste Beispiel zeigt, wie man den Status eines vorhandenes 'Weiteres Merkmal' ändert - hier wird
das 'Weiteres Merkmal' qfs:label
auf "ignorieren" gesetzt:
|
def getExtraFeatures(node, features) {
def labelFeature = features.get("qfs:label")
if (labelFeature) {
labelFeature.setState(resolvers.STATE_IGNORE)
return features
}
}
resolvers.addResolver("get label example", this) |
|
| | Beispiel 49.11:
Ein ExtraFeatureResolver (in Groovy), welcher den Status eines 'Weiteres Merkmal' ändert
| |
Im Folgenden finden Sie die Beschreibung der APIs
der Klassen ExtraFeature
und ExtraFeatureSet
.
|
|
|
ExtraFeature ExtraFeature(String name, String value) |
Parameter |
name | Der Name des ExtraFeatures. |
value | Der Wert des ExtraFeatures. |
|
ExtraFeature ExtraFeature(int state, String name, String value) |
Parameter |
state |
Der Status des ExtraFeatures. Mögliche Werte sind resolvers.STATE_IGNORE,
resolvers.STATE_SHOULD_MATCH, resolvers.STATE_MUST_MATCH.
|
name | Der Name des ExtraFeatures. |
value | Der Wert des ExtraFeatures. |
|
String getName() |
Rückgabewert | Des Name des ExtraFeatures. |
|
boolean getNegate() |
Rückgabewert | der negate Status des ExtraFeatures. |
|
boolean getRegexp() |
Rückgabewert | der regexp Status des ExtraFeatures. |
|
int getState() |
Rückgabewert | Der Status des ExtraFeatures. |
|
String getValue() |
Rückgabewert | Der Wert des ExtraFeatures. |
|
void setName(String name) |
Parameter |
name | Der zu setzende Name. |
|
void setNegate(boolean negate) |
Parameter |
negate | Der zu setzende negate Status. |
|
void setRegexp(boolean regexp) |
Parameter |
regexp | Der zu setzende regexp Status. |
|
void setState(int state) |
Parameter |
state | Der zu setzende Status. |
|
void setValue(String value) |
Parameter |
value | Der zu setzende Wert. |
|
|
|
Die Klasse de.qfs.apps.qftest.shared.data.ExtraFeatureSet
bündelt
ExtraFeatures
in einen Satz:
|
|
|
ExtraFeatureSet ExtraFeatureSet() |
|
void add(ExtraFeature extraFeature) |
Parameter |
extraFeature | Das hinzuzufügende ExtraFeature. |
|
void add(String name, String value) |
Parameter |
name | Der Name des ExtraFeatures. |
value | Der Wert des ExtraFeatures. |
|
void add(int state, String name, String value) |
Parameter |
state |
Der Status des ExtraFeatures. Mögliche Werte sind resolvers.STATE_IGNORE,
resolvers.STATE_SHOULD_MATCH, resolvers.STATE_MUST_MATCH.
|
name | Der Name des ExtraFeatures. |
value | Der Wert des ExtraFeatures. |
|
ExtraFeature get(String name) |
Parameter |
name | Der Name des abzufragenden ExtraFeatures. |
Rückgabewert |
Das ExtraFeature oder null, falls kein ExtraFeature unter diesem Namen im Satz
gespeichert ist.
|
|
ExtraFeature remove(String name) |
Parameter |
name | Der Name des zu entfernenden ExtraFeatures. |
Rückgabewert |
Das entfernt ExtraFeature oder null, falls kein ExtraFeature unter diesem Namen im
Satz gespeichert war.
|
|
ExtraFeature[] toArray() |
Rückgabewert | Ein Array mit allen ExtraFeatures, nach Namen sortiert. |
|
|
|
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. Für Webanwendungen
sollte die in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene 'Prozedur'
qfs.web.ajax.installCustomWebResolver
der Standardbibliothek
qfs.qft
genutzt werden. Sie 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:
|
|
|
String getItemName(Object element, Object item, String name) |
Parameter |
element |
Das GUI Element zu dem das Unterelement gehört.
|
item |
Das Unterelement, dessen Name ermittelt wird. Sein Typ hängt von der Art des
GUI Elements und der registrierten ItemResolver ab, wie in Abschnitt 49.4.5 beschrieben.
|
name |
Der ursprüngliche Name, den QF-Test ohne Resolver verwenden würde.
|
Rückgabewert | Der Name oder null, falls der Resolver dieses Element oder Unterelement
nicht behandelt.
|
|
|
|
Es folgt ein Beispiel für einen
ItemNameResolver
, der die ID einer JTable
Spalte für den
Index zugänglich macht:
|
def getItemName(table, item, name):
if item[1] < 0: # whole column addressed
id = table.getColumnModel().getColumn(item[0]).getIdentifier()
if id:
return str(id)
resolvers.addResolver("tableColumnId", getItemName,
"javax.swing.table.TableColumn") |
|
| | Beispiel 49.12: Ein ItemNameResolver für JTable
Spalten | |
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 Unterelements 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. Für Webanwendungen
sollte die in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene 'Prozedur'
qfs.web.ajax.installCustomWebResolver
der Standardbibliothek
qfs.qft
genutzt werden. Sie 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:
|
|
|
String getItemValue(Object element, Object item, String value) |
Parameter |
element |
Das GUI Element zu dem das Unterelement gehört.
|
item |
Das Unterelement, dessen Wert ermittelt wird. Sein Typ hängt von der Art des
GUI Elements und der registrierten ItemResolver ab, wie in Abschnitt 49.4.5 beschrieben.
|
value |
Der ursprüngliche Wert, den QF-Test ohne Resolver verwenden würde.
|
Rückgabewert | Der Wert oder null, falls der Resolver dieses Element oder Unterelement
nicht behandelt.
|
|
|
|
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:
|
|
|
JTree getTree(JTable table) |
Parameter |
table |
Die Tabelle für die der Baum ermittelt werden soll.
|
Rückgabewert |
Der Baum oder null, falls es sich um eine normale Tabelle handelt.
|
|
int getTreeColumn(JTable table) |
Parameter |
table |
Die Tabelle für die der Spaltenindex des Baums ermittelt werden soll.
|
Rückgabewert |
Der Spaltenindex oder -1, falls es sich um eine normale Tabelle handelt. Der
Spaltenindex muss immer in Model-Koordinaten geliefert werden, nicht in
View-Koordinaten.
|
|
|
|
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")
|
|
| | Beispiel 49.13: 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") |
|
| | Beispiel 49.14: 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") |
|
| | Beispiel 49.15: Vereinfachter 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") |
|
| | Beispiel 49.16: Einfacher TreeTableResolver , der die getCellRenderer Methode nutzt | |
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. Für Webanwendungen
sollte die in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene 'Prozedur'
qfs.web.ajax.installCustomWebResolver
der Standardbibliothek
qfs.qft
genutzt werden. Sie 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:
|
|
|
Boolean isInterestingParent(Object parent, boolean interesting) |
Parameter |
parent |
Die zu prüfende Komponente.
|
interesting |
Ob QF-Test diese Komponente bis jetzt als interessant behandelt hat.
|
Rückgabewert |
Boolean.TRUE wenn interessant, Boolean.FALSE wenn nicht, null, wenn dieser Resolver das nicht entscheiden soll.
|
|
|
|
Ein ToolTipResolver
kann den Tooltip einer Komponente beeinflussen.
Dieser Tooltip wird bei Prüfungen und im weiteren Merkmal 'qfs:label' verwendet.
Technologien: AWT/Swing, JavaFX, SWT. Für Webanwendungen
sollte die in Verbesserte Komponentenerkennung mittels CustomWebResolver
beschriebene 'Prozedur'
qfs.web.ajax.installCustomWebResolver
der Standardbibliothek
qfs.qft
genutzt werden. Sie 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:
|
|
|
String getTooltip(Object element, String tooltip) |
Parameter |
element |
Das GUI Element dessen Tooltip ermittelt werden soll.
|
tooltip |
Der ursprüngliche Tooltip, den QF-Test ohne Resolver verwenden würde.
|
Rückgabewert |
Der zu verwendende Tooltip oder null, falls der Resolver das Element nicht
behandelt. Durch Rückgabe des leeren Strings wird der ursprüngliche Tooltip der
Komponente unterdrückt.
|
|
|
|
Ein IdResolver
kann das 'ID' Attribut eines DOM Knoten modifizieren bzw. unterdrücken.
Wenn QF-Test die Knoten einer Webseite registriert, wird das 'ID' Attribut dieser Knoten
gespeichert. Abhängig von der Option 'ID' Attribut als Name verwenden falls "eindeutig genug" wird der Wert dieses Attributes
auch als Komponentenname für die Erkennung herangezogen.
Da viele Webseiten bzw. AJAX Toolkits 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 Prozeduraufruf der Prozedur
qfs.web.ajax.installCustomWebResolver
den
Parameter autoIdPatterns
zu setzen. Dort können Sie konkrete Werte, z.B. meineAutoId
oder auch reguläre Ausdrücke, z.B. %auto.*
spezifizieren, um alle IDs,
die mit auto
beginnen, zu ignorieren.
- Falls Sie ein eigenes Attribut eingeführt haben, welches anstatt des originalen 'ID' Attributes
verwendet werden soll, dann rufen Sie die Prozedur
qfs.web.ajax.installCustomWebResolver
aus der Standardbibliothek auf.
Dort können Sie eigene Attribute im Parameter customIdAttributes
spezifizieren.
Diese Attribute werden nun anstatt des 'ID' Attributes 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 falls "eindeutig genug"), 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:
|
|
|
String getId(DomNode node, String id) |
Parameter |
node |
Der DomNode Knoten dessen ID ermittelt werden soll.
|
id |
Die ID, die QF-Test für diesen Knoten ermittelt hat, möglicherweise nach
Entfernen der darin enthaltenen Ziffern, abhängig von der Option Alle Ziffern aus 'ID' Attributen eliminieren. Um den Resolver auf Basis des ursprünglichen 'ID'
Attributs zu implementieren, ermitteln Sie dieses einfach via
node.getAttribute("id") .
|
Rückgabewert |
Die ID oder null, falls keine ID festgelegt werden kann.
Durch Rückgabe des leeren Strings wird der eigentliche ID
der Komponente unterdrückt.
|
|
|
|
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
Ein EnabledResolver
muss folgende Methode implementieren:
|
|
|
Boolean isEnabled(Object element, boolean enabled) |
Parameter |
element |
Das GUI-Element, dessen Zustand bestimmt werden soll.
|
enabled |
Der Zustand, den QF-Test ohne Hilfe des Resolvers ermittelt hätte.
|
Rückgabewert |
True oder false, bzw. null, falls der Resolver das Element nicht
behandelt.
|
|
|
|
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, \
"de.qfs.apps.qftest.client.web.dom.DomNode") |
|
| | Beispiel 49.17: Ein EnabledResolver | |
Ein VisibilityResolver
beeinflusst, wann ein Web-Element als sichtbar angesehen wird.
Technologien: Web
Ein VisibilityResolver
muss folgende Methode implementieren:
|
|
|
Boolean isVisible(Object element, boolean visible) |
Parameter |
element |
Der Web-Knoten, dessen Sichtbarkeit bestimmt werden soll.
|
visible |
Der Zustand, den QF-Test ohne Hilfe des Resolvers ermittelt hätte.
|
Rückgabewert |
True oder false, bzw. null, falls der Resolver das Element nicht
behandelt.
|
|
|
|
Das folgende Beispiel setzt die Sichbarkeit 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) |
|
| | Beispiel 49.18: Ein VisibilityResolver | |
Ein MainTextResolver
ermittelt den Text einer Komponente, der als Label, Feature usw.
verwendet werden soll.
Technologien: AWT/Swing, JavaFX, SWT, Web
Ein MainTextResolver
muss folgende Methode implementieren:
|
|
|
String getMainText(Object element, String text); |
Parameter |
element |
Das GUI-Element, dessen Text ermittelt werden soll.
|
text |
Der Text, den QF-Test ohne den Resolver verwenden würde.
|
Rückgabewert |
Der "Haupttext" oder null, falls der Resolver das Element nicht
behandelt. Durch Rückgabe des leeren Strings wird der Komponente
kein Text zugeordnet.
|
|
|
|
Das folgende Beispiel entfernt aus allen angezeigten Texten den String TO-DO
.
|
def getMainText(element,text):
if text:
return text.replace("TO-DO","")
return text
resolvers.addResolver("removeMarkFromText",getMainText) |
|
| | Beispiel 49.19: Ein MainTextResolver | |
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:
|
|
|
Boolean isBusy(Object element) |
Parameter |
element |
Das GUI-Element, dessen Zustand bestimmt werden soll.
|
Rückgabewert |
True, wenn aktuell wegen einer BusyPane o.Ä. nicht auf das Element zugegriffen werden kann,
false sonst. Null, falls der Resolver das Element nicht
behandelt.
|
|
|
|
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") |
|
| | Beispiel 49.20: Ein BusyPaneResolver | |
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:
|
|
|
Object isGlassPaneFor(Object element, Object target) |
Parameter |
element |
Die GUI-Komponente, auf dem Events empfangen werden
|
target |
Die GUI-Komponente an die QF-Test die Events ohne Resolver weiterleiten würde
|
Rückgabewert |
Die Komponente, an welche die Events gesendet werden sollen oder null, falls der Resolver das Element nicht
behandelt.
|
|
|
|
Das folgende Beispiel deaktiviert effektiv die Weiterleitung der Events durch GlassPanes:
|
def isGlassPaneFor(element):
return element
resolvers.addResolver("noGlassPaneResolver", isGlassPaneFor) |
|
| | Beispiel 49.21: Ein GlassPaneResolver | |
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:
|
|
|
void sync(Object context) |
Parameter |
context |
Der Kontext, der bei der Registrierung des Resolvers angegeben wurde.
|
|
|
|
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) |
|
| | Beispiel 49.22: Ein EventSynchronizer | |
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:
|
|
|
Boolean applicationIsBusy |
Rückgabewert |
True, wenn die Anwendung "beschäftigt" ist, sonst false.
|
|
|
|
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) |
|
| | Beispiel 49.23: Ein BusyApplicationDetector | |
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 Komponten oder
bei schlüsselwortgetriebenen Testen einsetzen, wenn keine Aufzeichnungen gemacht werden sollen.
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
Ein ExtraFeatureMatcher
muss folgende Methode implementieren:
|
|
|
Boolean matchExtraFeature(Object element, String name, String value, boolean regexp, boolean negate) |
Parameter |
element |
Die GUI-Komponente, für die das 'Weiteres Merkmal' geprüft werden soll.
|
name |
Der Name des 'Weiteres Merkmal'.
|
value |
Der Wert des 'Weiteres Merkmal'.
|
regexp |
True, wenn value ein Regulärer Ausdruck ist.
|
negate |
True, wenn die Prüfung negiert weden soll.
|
Rückgabewert |
True, falls das 'Weiteres Merkmal' passt, sonst False. Null, falls der Resolver das Element nicht
behandelt.
|
|
|
|
Das folgende Beispiel prüft den Wert des 'Weiteres Merkmal' my:label
gegen das my-label
Attribut des Web-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) |
|
| | Beispiel 49.24: Ein ExtraFeatureMatcher | |
Mit Hilfer 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") |
|
| | Beispiel 49.25: Nutzung der Methode addSpecificExtraFeatureMatcher | |
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.