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 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:

  1. Einlesen der Komponentenobjekte aus dem GUI.
  2. Datenextraktion für die Einzelkomponente: z.B. Komponentenklasse, Id, Koordinaten, Komponententext.
  3. Beziehungsanalyse der Komponenten zueinander: z.B. Strukturinformationen (Index), Bestimmung einer der Komponente zugehörigen Beschriftung qfs:label*-Varianten.
  4. 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 48.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.

Implementierung

Bei der Implementierung eines Resolvers sind folgende beide Schritte nötig:

  1. Implementierung des Resolver-Interfaces.
  2. 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")
Beispiel 53.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 53.1.7 bis Abschnitt 53.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 53.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.

 
 
void addResolver(String resolverName, Method method, Object target=None, ...)
Registriert den über die übergebene Methode festgelegten Resolver für die angegebenen Ziele. Falls bereits ein Resolver unter dem angegebenen Resolver-Namen registriert war, wird dieser zunächst deregistriert.
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, ...)
Registriert den oder die über die Methoden des Objekts festgelegten Resolver für die angegebenen Ziele. Falls bereits ein Resolver unter dem angegebenen Resolver-Namen registriert war, wird dieser zunächst deregistriert.
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 entsprechende 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.
 
 

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.

 
 
void removeAll()
Deregistriert alle über das resolvers Modul registrierten Resolver von allen Zielen, für die sie registriert waren.
 
void removeResolver(String name)
Deregistriert einen Resolver von allen Zielen, für die er registriert war.
Parameter
nameDer 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 53.2:  SUT-Skript zur Deregistrierung eines Resolvers

listNames

Gibt eine Liste der Namen der Resolver zurück, die über das resolvers Modul registriert wurden.

 
 
List<String> listNames()
Listet die Namen der registrierten Resolver auf.
 
 

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 53.3:  Groovy SUT-Skript zur Abfrage registrierter Resolver über das 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.

 
 
String getBestLabel(Component node)
Gibt die Beste Beschriftung zurück.
Parameter
node Die Komponente, für die die Beschriftung ermittelt werden soll.
Rückgabewert Die beste Beschriftung der Komponente.
 
 

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")
Beispiel 53.4:  NameResolver für die beste Beschriftung

3.1+53.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:

 
 
String getName(Object element, String name)
Legt den Namen einer Komponente fest.
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 53.5:  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 53.6:  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 53.7:  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 53.8:  Registrieren eines Resolver für mehrere Klassen

4.0+53.1.8
Das GenericClassNameResolver Interface

Ein GenericClassNameResolver kann die generische Klasse (Kapitel 60) 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:

 
 
String getGenericClassName(Object element, String name)
Legt den generischen Klassennamen einer Komponente fest.
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.
 
 

3.1+53.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 53.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:

 
 
String getClassName(Object element, String name)
Legt den Klassennamen einer Komponente fest.
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.

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:

 
 
String getFeature(Object element, String feature)
Legt das Merkmal einer Komponente fest.
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 53.9:  Ein 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 48.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 und qfs: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:

 
 
ExtraFeatureSet getExtraFeatures(Object element, ExtraFeatureSet features)
Erzeugt ein ExtraFeatureSet, das weitere Merkmale einer Komponente festlegt.
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 53.10:   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)
Beispiel 53.11:   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)
Beispiel 53.12:   Ein 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)
Beispiel 53.13:   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.

 
 
ExtraFeature ExtraFeature(String name, String value)
Erzeugt ein neues ExtraFeature mit Status STATE_IGNORE.
Parameter
nameDer Name des ExtraFeatures.
valueDer Wert des ExtraFeatures.
 
ExtraFeature ExtraFeature(int state, String name, String value)
Erzeugt ein neues ExtraFeature mit gegebenem Status.
Parameter
state Der Status des ExtraFeatures. Mögliche Werte sind resolvers.STATE_IGNORE, resolvers.STATE_SHOULD_MATCH, resolvers.STATE_MUST_MATCH.
nameDer Name des ExtraFeatures.
valueDer Wert des ExtraFeatures.
 
String getName()
Liefert den Namen des ExtraFeatures.
RückgabewertDes Name des ExtraFeatures.
 
boolean getNegate()
Liefert den negate Status des ExtraFeatures.
Rückgabewertder negate Status des ExtraFeatures.
 
boolean getRegexp()
Liefert den regexp Status des ExtraFeatures.
Rückgabewertder regexp Status des ExtraFeatures.
 
int getState()
Liefert den Status des ExtraFeatures.
RückgabewertDer Status des ExtraFeatures.
 
String getValue()
Liefert den Wert des ExtraFeatures.
RückgabewertDer Wert des ExtraFeatures.
 
void setName(String name)
Setze den Namen des ExtraFeatures.
Parameter
nameDer zu setzende Name.
 
void setNegate(boolean negate)
Setzt den negate Status des ExtraFeatures.
Parameter
negateDer zu setzende negate Status.
 
void setRegexp(boolean regexp)
Setzt den regexp Status des ExtraFeatures.
Parameter
regexpDer zu setzende regexp Status.
 
void setState(int state)
Setzt den Status des ExtraFeatures.
Parameter
stateDer zu setzende Status.
 
void setValue(String value)
Setzt den Wert des ExtraFeatures.
Parameter
valueDer zu setzende Wert.
 
 

Die Klasse de.qfs.apps.qftest.shared.data.ExtraFeatureSet bündelt ExtraFeatures in einen Satz:

 
 
ExtraFeatureSet ExtraFeatureSet()
Erzeugt ein neues, leeres ExtraFeatureSet.
 
void add(ExtraFeature extraFeature)
Fügt dem Satz ein ExtraFeature hinzu. Existiert schon ein ExtraFeature mit demselben Namen, wird dieses ersetzt.
Parameter
extraFeatureDas hinzuzufügende ExtraFeature.
 
void add(String name, String value)
Fügt dem Satz ein neues ExtraFeature mit Status STATE_IGNORE hinzu. Existiert schon ein ExtraFeature mit demselben Namen, wird dieses ersetzt.
Parameter
nameDer Name des ExtraFeatures.
valueDer Wert des ExtraFeatures.
 
void add(int state, String name, String value)
Fügt dem Satz ein neues ExtraFeature mit gegebenem Status hinzu. Existiert schon ein ExtraFeature mit demselben Namen, wird dieses ersetzt.
Parameter
state Der Status des ExtraFeatures. Mögliche Werte sind resolvers.STATE_IGNORE, resolvers.STATE_SHOULD_MATCH, resolvers.STATE_MUST_MATCH.
nameDer Name des ExtraFeatures.
valueDer Wert des ExtraFeatures.
 
ExtraFeature get(String name)
Liefert ein ExtraFeature des Satzes.
Parameter
nameDer Name des abzufragenden ExtraFeatures.
Rückgabewert Das ExtraFeature oder null, falls kein ExtraFeature unter diesem Namen im Satz gespeichert ist.
 
ExtraFeature getShouldMatchLabel()
Liefert die qfs:label* ExtraFeature Variante mit dem Status "Sollte übereinstimmen". Falls mehr als eine Variante diesen Status hat, wird die erste davon zurückgeliefert.
Rückgabewert Das ExtraFeature oder null, falls es keine qfs:label* ExtraFeature Variante mit dem Status "Sollte übereinstimmen" gibt.
 
ExtraFeature remove(String name)
Entfernt ein ExtraFeature aus dem Satz.
Parameter
nameDer Name des zu entfernenden ExtraFeatures.
Rückgabewert Das entfernt ExtraFeature oder null, falls kein ExtraFeature unter diesem Namen im Satz gespeichert war.
 
ExtraFeature[] toArray()
Liefert alle ExtraFeatures des Satzes.
RückgabewertEin Array mit allen ExtraFeatures, nach Namen sortiert.
 
 

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:

 
 
String getItemName(Object element, Object item, String name)
Legt den Namen zur textuellen Repräsentation des Index eines Unterelements einer komplexen Komponente fest.
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 53.4.5 beschrieben.
name Der ursprüngliche Name, den QF-Test ohne Resolver verwenden würde.
RückgabewertDer 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(tableHeader, item, name):
    id = tableHeader.getColumnModel().getColumn(item).getIdentifier()
    if id:
        return str(id)
resolvers.addResolver("tableColumnId", getItemName,
                      "javax.swing.table.JTableHeader")
Beispiel 53.14:  Ein ItemNameResolver für JTableHeader

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:

 
 
String getItemValue(Object element, Object item, String value)
Legt den Wert der eines Unterelements einer komplexen Komponente fest, wie er für 'Check Text' oder 'Text auslesen' Knoten verwendet wird.
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 53.4.5 beschrieben.
value Der ursprüngliche Wert, den QF-Test ohne Resolver verwenden würde.
RückgabewertDer Wert oder null, falls der Resolver dieses Element oder Unterelement nicht behandelt.
 
 

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:

 
 
JTree getTree(JTable table)
Ermittelt die JTree Komponente mit deren Hilfe eine TreeTable implementiert ist.
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)
Ermittelt den Index der Spalte des Baums in einer TreeTable. Die meisten Implementierungen haben den Baum in der ersten Spalte. In diesem Fall muss 0 zurückgegeben werden.
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 53.15:  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 53.16:  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 53.17:  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 53.18:  Einfacher 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:

 
 
Boolean isInterestingParent(Object parent, boolean interesting)
Liefert zurück, ob eine Komponente interessant bzw. uninteressant ist.
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.
 
 

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:

 
 
String getTooltip(Object element, String tooltip)
Legt den Tooltip der Komponente fest.
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.
 
 

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 48.3), 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 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:

 
 
String getId(DomNode node, String id)
Legt die ID eines DomNode Knotens fest. Die so bestimmte ID wird gespeichert und kann später mittels node.getId() ausgelesen werden. Dagegen ermittelt node.getAttribute("id") immer das ursprüngliche, unmodifizierte Attribut '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.
 
 

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 EnabledResolvers ausgewertet werden.

Technologien: JavaFX, Web, Windows, Android, iOS

Ein EnabledResolver muss folgende Methode implementieren:

 
 
Boolean isEnabled(Object element, boolean enabled)
Legt fest, ob eine Komponente als aktiv interpretiert wird.
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, \
                      "DOM_NODE")
Beispiel 53.19:  Ein EnabledResolver

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:

 
 
Boolean isVisible(Object element, boolean visible)
Bestimmt, ob eine Komponente als sichtbar angesehen wird.
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 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)
Beispiel 53.20:  Ein VisibilityResolver

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:

 
 
String getMainText(Object element, String text)
Ermittelt den "Haupttext" einer Komponente
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 dem "Haupttext" aller Komponenten den String TO-DO.

def getMainText(element,text):
    if text:
        return text.replace("TO-DO","")

resolvers.addResolver("removeMarkFromText", getMainText)
Beispiel 53.21:  Ein MainTextResolver

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:

 
 
String getWholeText(Object element, String text)
Ermittelt den "Gesamttext" einer Komponente
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 "Gesamttext" 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 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")
Beispiel 53.22:  Ein WholeTextResolver

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:

 
 
Boolean isBusy(Object element)
Bestimmt, ob eine Komonente aktuell von einer BusyPane oder vergleichbaren Komponente verdeckt wird.
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 53.23:  Ein BusyPaneResolver

Der GlassPaneResolver Interfaces

Wenn Komponenten von anderen (evtl. transparenten) Komponenten verdeckt werden, so kann man QF-Test mit Hilfe eines GlassPaneResolvers 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)
Legt die Verbindung zwischen einer überlagernden Komponente und der eigentlichen Zielkomponente fest.
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 53.24:  Ein GlassPaneResolver

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:

 
 
Integer getTreeIndentation(DomNode tree, DomNode treeNode)
Ermittelt die Einrückung einer TreeNode-Komponente innerhalb eines Trees oder TreeTable.
Parameter
tree Der Tree oder TreeTable der den Baumknoten enthält dessen Einrückung ermittelt werden soll.
treeNode Der Baumknoten dessen Einrückung ermittelt werden soll.
Rückgabewert Die Einrückung in Pixeln oder null, falls der Resolver das Element nicht behandelt.
 
 

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")
Beispiel 53.25:  Ein TreeIndentationResolver

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:

 
 
void sync(Object context)
Synchronisiert mit dem Event Dispatch Thread des SUT.
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 53.26:  Ein EventSynchronizer

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:

 
 
Boolean applicationIsBusy
Ermittelt, ob die Anwendung aktuell "beschäftigt" ist.
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 53.27:  Ein 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.

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:

 
 
Boolean matchExtraFeature(Object element, String name, String value, boolean regexp, boolean negate)
Prüft ein ExtraFeature gegen eine Komponente
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 (vgl. Abschnitt 48.3).
negate True, wenn die Prüfung negiert werden 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 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)
Beispiel 53.28:  Ein 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")
Beispiel 53.29:  Nutzung der Methode 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.