Ein Selenium basiertes Open Source Framework zur Testautomatisierung

UI-Tests mit testIT WebTester

Pascal Moll

©Shutterstock/Michal Ludwiczak

In den meisten Software-Projekten wird Testautomatisierung bereits in der einen oder anderen Form eingesetzt. Eines der am häufigsten verwendeten Werkzeuge zum Testen der Weboberfläche ist dabei der Selenium WebDriver. Selenium ist zum einen generisch handhabbar und gilt zum anderen auch laut W3C als der Standard zur Browserfernsteuerung. Doch gerade diese generische Eigenschaft bringt nicht immer nur Vorteile mit sich und führt oft zu schwer verständlichem Testcode.

Basierend auf unserer Projekterfahrung haben wir uns dazu entschlossen, ein eigenes Framework zu entwickeln und der Community als Open-Source-Lösung zur Verfügung zu stellen. Der in diesem Artikel vorgestellte testIT WebTester ist eine WebDriver-Abstraktion und ergänzt Selenium um weitere Features.

Automatisierte UI-Tests sind häufig wartungsintensiv. Oberflächen ändern sich, weil z.B. neue Elemente hinzukommen oder im schlimmsten Fall alte verschwinden. Die bekannte Folge: Gui-Tests müssen angepasst werden. Diese Anpassung kann für zahlreiche Stellen im Code notwendig werden. Eine Möglichkeit, dem entgegen zu wirken ist die Einbindung verschiedener Pattern, wie z.B. dem Page Object Pattern oder dem Workflow Pattern. Doch auch dieses Vorgehen kann sich in Standard-Selenium-Code als mühsam erweisen und muss stets von Neuem erfolgen.

Des Weiteren besteht noch die Problematik mit den Selenium WebElementen: Jedes Interaktionselement ist standardmäßig vom Typ WebElement. Diese Eigenschaft macht zwar das Arbeiten als solches bequem, verleitet jedoch zu Fehlern. Angenommen, bei dem verwendeten WebElement handelt es sich um einen HTML Button, dann könnte der Entwickler hier problemlos die sendKeys-Methode auf dem Button aufrufen. Die Methode verändert das Element nicht. Da es sich um kein Eingabefeld handelt, gibt es aber auch keinen Fehler aus. Somit fällt im Test nicht auf, dass nichts passiert. Dies kann unter Umständen zu unbemerktem Fehlverhalten im Test führen und damit das Ergebnis negativ beeinflussen.

Dies sind nur zwei Szenarien, die der WebTester mit seinen Funktionen verbessert. Im Folgenden wird eine Auswahl an Features vorgestellt.

Merkmale und Features von WebTester

Der WebTester basiert auf Selenium und setzt darauf auf. Das macht es jederzeit möglich, nativen Selenium-Code auch direkt aufzurufen. Im Folgenden werden ausgewählte Features und dazugehörige Beispiele vorgestellt.

Types of Elements

Wie bereits erwähnt, ist bei Selenium jedes Element vom Typ WebElement. Bei WebTester hingegen wurde eine Hierarchie hineingezogen. Jedes Element leitet sich von einer gemeinsamen Basisklasse ab und repräsentiert einen eigenen Typ.

Button login;

Dem Entwickler wird sofort ersichtlich, mit was für einer Art Objekt er arbeitet, außerdem sind auch nur sinnvoll auf dieses Element anwendbare Funktionen möglich. Das Verwenden unlogischer Funktionen, wie z.B. sendKeys auf einen Button, wird somit direkt vermieden.

Außerdem wird vor Verwendung geprüft, ob das erwartete Element auch tatsächlich den Rahmenbedingungen eines Buttons entspricht (<input type=’button‘> oder <input type=’submit‘> usw…). Im Negativfall folgt eine Fehlermeldung. Das nimmt dem Entwickler einen Teil der Fehlerprüfung ab und trägt zur Übersichtlichkeit des Codes bei. Sollte der Button auf der zu testenden Seite in ein Textfeld umgeändert werden, so würde dies mit nativem Selenium nicht auffallen, mit WebTester dagegen wäre eine Exception die Folge.

@IdentifyUsing

Um Elemente in der zu testenden Webseite ausfindig zu machen, nutzt Selenium seine FindBy-Annotation. WebTester liefert zusätzlich eine eigene, erweiterte Annotation. Diese Annotation ermöglicht es, einen Menschen-lesbaren Namen für die Elemente zur Verfügung zu stellen. Selbstverständlich unterstützt WebTester aber auch die Standard-FindBy-Annotation.

 

@IdentifyUsing (method=Method.ID, value="maxLength", elementname="maximale Länge eines Elements")
TextField maxLength;

@IdentifyUsing ("empty")
TextField empty;

Dieser Name kann beispielsweise für Aktionsprotokolle verwendet werden, um in Reports für mehr Klarheit zu sorgen.

Die Annotation unterstützt als Default ohne weitere Angabe den Zugriff über das ID-Tag eines Elements. Ansonsten ist der Zugriff über Xpath, Css, Name, Classname und weitere Zugriffsmethoden möglich, wie bei Selenium auch.

PageObjects erstellen mit Selenium und Webtester im Vergleich

Um das einfache Erstellen von PageObjects zu demonstrieren, dient das nachfolgende Beispiel eines Log-in-Seiten-PageObjects.

Im direkten Vergleich wird bereits deutlich, wie sich der Code von Selenium unterscheidet.

 

public class LoginPage {

    @FindBy(id = "input_un")
    private WebElement usernameField;
    @FindBy(id = "input_p")
    private WebElement passwordField;
    @FindBy(id = "submit")

    private WebElement loginButton;

    private WebDriver driver;

    public LoginPage(WebDriver driver) {
        this.driver = driver;
        assertThat(driver.getTitle(), is("TestApp: Login"));
    }

    /* workflows */
    public WelcomePage login(String user, String pw) {
        return setUsername(user).setPassword(pw).clickLogin();
    }

    /* actions */
    public LoginPage setUsername(String username) {
        usernameField.sendKeys(username);
        return this;
    }

    public LoginPage setPassword(String password) {
        passwordField.sendKeys(password);
        return this;
    }

    public WelcomePage clickLogin() {
        loginButton.click();
        return PageFactory.initElements(driver, WelcomePage.class);
    }
}
public class LoginPage extends PageObject {

    @IdentifyUsing("input_un")
    private TextField usernameField;
    @IdentifyUsing("input_p")
    private PasswordField passwordField;
    @IdentifyUsing("submit")
    private Button loginButton;



    @PostConstruct
    public void assertThatCorrectPageIsDisplayed() {
        assertThat(getBrowser().getPageTitle(),
                   is("TestApp: Login"));
    }

    /* workflows */
    public WelcomePage login(String user, String pw) {
        return setUsername(user).setPassword(pw).clickLogin();
    }

    /* actions */
    public LoginPage setUsername(String username) {
        usernameField.setText(username);
        return this;
    }

    public LoginPage setPassword(String password) {
        passwordField.setText(password);
        return this;
    }

    public WelcomePage clickLogin() {
        loginButton.click();
        return create(WelcomePage.class);
    }
}

Der erste Unterschied besteht in der Vererbungsstruktur. Mit WebTester lassen sich für den Entwickler unwichtige Informationen ausblenden, wie z.B. die WebDriver-Referenz.

Ein weiterer wesentlicher Unterschied ist das eigentliche Initialisieren eines neuen PageObjects: Mit WebTester wird der Generierungsmechanismus ebenfalls in den Hintergrund verlagert und irrelevante Details verborgen.

Mit Hilfe der @PostConstruct-Annotation wird bei WebTester geprüft, ob es sich beim Aufruf des PageObjects auch um die erwartete Seite handelt. Die mit @PostConstruct annotierte Methode wird aufgerufen, sobald die Seite geladen und verwendbar ist.

Nested PageObjects

WebTester ermöglicht das Verwenden von sogenannten Nested PageObjects. Dabei handelt es sich um ineinander verschachtelte PageObjects.

Um dies zu illustrieren, sei nachfolgendes Codebeispiel gegeben:

public class NestedPageObject extends PageObject {
 
    @IdentifyUsing(„textfield“)
    private TextField textfield;
 
    public TextField getTextField() {
        return textfield;
    }
}

Das NestedPageObject wird im nachfolgenden Code als eigener Elementtyp referenziert und beinhaltet damit alle Eigenschaften.

public class PageObjectUnderTest extends PageObject { 

    @IdentifyUsing(„div“) 
    private NestedPageObject nestedPageObject;

    public NestedPageObject getNestedPageObject() { 
        return nestedPageObject; 
    } 
}

EventListener und EventSystem

Durch das integrierte Eventsystem feuert jede Aktion sein eigenes Ereignis. EventListener können sowohl registriert als auch entfernt werden. In einem Event befinden sich sämtliche notwendigen Informationen um das Ereignis zu verstehen und darauf zu reagieren. Dadurch ist es beispielsweise möglich, beim Auftreten von bestimmten Exceptions ein Screenshot oder Video des aktuellen Bildschirms aufzunehmen.

Nachfolgend ein Beispiel das auf TimeoutExceptions reagiert und von der aktuell aufgerufenen Seite einen Screenshot erstellt.

@Override
public void eventOccured (Event event) {
     
    if ( ! ( event instanceof ExceptionEvent )) {
        return;
    }
     
    Throwable exception  = ( ( ExceptionEvent ) event ).getException();
    if (exception instanceof TimeoutException) {
        Browser browser  = BrowserRegistry.lookupBrowser(event.getBrowseridentification());
        browser.takeScreenshot(screenshotPath);
    }
}


Diese Art von Listener sollte vor dem Test registriert und nach dem Test wieder entfernt werden.

Debug Markings

Eine bekannte Herausforderung ist die Nachvollziehbarkeit der durchgeführten Schritte während eines laufenden Tests. Der Test-Code läuft zu schnell für das menschliche Auge ab. Oft ist nicht klar, welches Element bereits verwendet wurde und welches noch aussteht.

Aus diesem Grund führt WebTester optionale Debug Markings ein, die bereits besuchte Elemente farblich hervorheben. Diese Markings lassen sich in den Optionen deaktivieren, sowie farblich anpassen.

Abb1

Während eines laufenden Tests stellen sich die aktivierten Markings wie folgt dar:

Abb.2

Das Eingabefeld für den Benutzernamen wurde bereits verwendet und farblich hervorgehoben.

Hilfsmodule und Weiteres

Neben den bereits angesprochenen Features beinhaltet das WebTester Framework noch zahlreiche weitere Funktionen. Unter anderem sind dies eine eigene Wait API, die allgemeine Funktionalitäten zum Warten für bestimmte Situationen bereitstellt.

Ebenfalls enthalten ist eine Mouse– Utility-Klasse, die Mausbewegungen simuliert. Besonders für das Verwenden von Dropdown- Menüführungen ist das von Vorteil.

Support-Module für Spring, JUnit, AssertJ und Hamcrest sind ebenfalls Bestandteile des WebTester Frameworks.

Einbindung von WebTester

Da WebTester im Maven Central Repository verfügbar ist, fällt die Integration über Maven oder Gradle leicht.

 Maven

Dependency:

    info.novatec.testit
    webtester-core
    1.0.1
    test

Zusätzlich existieren noch weitere Support-Module für die Browser Firefox, Chrome und IE.

    info.novatec.testit
    webtester-support-firefox
    1.0.1
    test

Gradle

Dependency:

    
dependencies {
    testCompile group: 'info.novatec.testit', name: 'webtester-core', version: '1.0.1'
c.testit', name: 'webtester-support-chrome', version: '1.0.1'
    testCompile group: 'info.novatec.testit', name: 'webtester-support-firefox', version: '1.0.1'  
}

Eine vollständige Liste der Dependencies zu allen bisher verfügbaren Modulen ist auf GitHub zu finden.

Fazit und Ausblick

WebTester erweitert Selenium mit zahlreichen Funktionalitäten, welche den Entwickler bei der Testautomatisierung unterstützen. Darüber hinaus erleichtert ein verschlankter Test-Code die Fehlersuche. Dies wird beispielweise durch die Einführung der Feature NestedPageObjects, dem EventSystem sowie vielen weiteren Erweiterungen erreicht. In der Zukunft sind nicht nur weitere Neuerungen, sondern auch der Aufbau einer Community geplant. Der WebTester befindet sich bereits bei einigen unserer Kunden im Einsatz und profitiert dadurch von unseren Erfahrungen in der täglichen Arbeit mit WebTester.

Der WebTester steht auf GitHub zum Download bereit.

Aufmacherbild: Glass in a chemical laboratory filled with colored liquid during the reaction von Shutterstock / Urheberrecht: Michal Ludwiczak

Geschrieben von
Pascal Moll
Pascal Moll
Senior Consultant bei NovaTec Consulting GmbH in Frankfurt am Main, befasst sich seit mehr als zehn Jahren mit Anwendungsentwicklung. Die Beratungsschwerpunkte von Herrn Moll liegen in der Java-Entwicklung, Testautomatisierung und Webentwicklung.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: