Empfohlenes Webinar: KI-gestütztes API-Testing: Ein No-Code-Ansatz zum Testen | Zum Video

Top-Tipps für Selen-Experten

Kopfbild von Tony, leitender Softwareentwickler bei Parasoft
9. November 2023
10 min lesen

Sobald Sie Selenium schon eine Weile verwenden und mit dem Schreiben von Testfällen vertraut sind, können Sie sich auf Techniken und Designprinzipien konzentrieren, um Ihre UI-Testautomatisierung auf die nächste Stufe zu bringen. Schauen Sie sich diese für Selenium-Benutzer vorbereiteten Techniken und Praktiken an.

Bevor wir anfangen

In diesem Artikel wird davon ausgegangen, dass Sie Selenium verwenden und sich mit dem Schreiben von Testfällen auskennen. Sie haben bereits den Versuch durchlaufen, DOMs zu überprüfen, um Ihre XPaths zu erstellen. Möglicherweise verwenden Sie das Page Object Model. Mittlerweile sind Sie wahrscheinlich ziemlich gut darin, im Internet nach Lösungen zu suchen. Wenn Sie Hilfe in dieser Abteilung benötigen, kann ich es nur wärmstens empfehlen Dieser Artikel von meinem Kollegen mit einigen großartigen Selenium-Hacks.

Was folgt, ist in Techniken und Entwurfsmuster unterteilt. Möglicherweise wissen Sie bereits alles über einige davon. Das ist in Ordnung, springen Sie einfach zu den Abschnitten, die Sie interessieren. Ich werde hier Java verwenden, aber die Techniken und Praktiken sollten allgemein anwendbar sein.

7 Selen-Tipps für erfahrene Tester

1. Bessere Webelement-Locators

Schlechte Locators verursachen falsche Testfehler. Sie verschwenden Zeit und lenken von der beabsichtigten Funktion des Tests ab. Schlechte Locators hängen normalerweise von einer instabilen Qualität der Webseite ab, unabhängig davon, ob es sich um einen dynamischen Wert oder die Position des Elements auf der Seite handelt. Wenn sich diese Eigenschaften ausnahmslos ändern, bricht der Locator. Gute Locators funktionieren einfach. Sie identifizieren das beabsichtigte Element korrekt und lassen den Test seine Aufgabe erfüllen.

Der Schlüssel hier besteht darin, die Eigenschaften des Elements zu identifizieren, die stabil sind, und dann die minimale Teilmenge dieser Eigenschaften auszuwählen, die dieses Element eindeutig identifizieren, um Ihre Gefährdung durch Änderungen zu verringern. Wir alle wissen, dass absolute XPaths schlecht sind, aber es ist gut, genau zu verstehen, warum.

/html/body/div[25]/div[2]/div/span/span[2]/div/h2/p

Nehmen Sie einen Unterabschnitt dieses XPath:  div [25] / div [2] . Dies stellt die folgenden Eigenschaften dar:

  • Der Knoten befindet sich irgendwo in einem Div
  • Dieser Div ist der zweite Div
  • Dieser Div befindet sich direkt in einem anderen Div
  • Dieser Div ist der 25. Div

In diesem kleinen Unterabschnitt haben wir mindestens 4 Eigenschaften, die zur Identifizierung eines Elements verwendet werden. Wenn sich einer von ihnen ändert, bricht unser Locator. Wir haben keinen Grund zu der Annahme, dass sie sich nicht ändern werden, da keiner von ihnen das Element tatsächlich beschreibt. Wenn wir uns diesen XPath ansehen, können wir nicht einmal den Zweck des Elements erraten.

Locators, die den Zweck des Elements widerspiegeln, brechen mit geringerer Wahrscheinlichkeit. Während sich die Position oder das Erscheinungsbild des Elements ändern kann, sollte sich seine Funktion nicht ändern. Im Idealfall können Sie anhand des Locators erraten, was das Element tut.

//p[contains(@class, “content”)][contains(.,”Guidance for Success”)]

Aus dem oben Gesagten ist klar, dass das Element einen Inhalt darstellt, der „Anleitung zum Erfolg“ beschreibt. Beachten Sie, dass das Klassenattribut zwar normalerweise zur Steuerung des Erscheinungsbilds verwendet wird, aber auch die Funktion des Elements beschreibt.

Oft sind die einzigartigen, stabilen Eigenschaften eines Elements nicht im Element selbst zu finden. Stattdessen müssen Sie zu einem Verwandten des Elements gehen, dessen Funktion im DOM gut beschrieben ist. Nehmen Sie das obige Beispiel im Element „Anleitung zum Erfolg“. Obwohl dieser Locator gut ist, kann sich das Kopieren von Text oft ändern oder ist möglicherweise keine Option, wenn die Site mehrere Sprachen unterstützt. Beim Durchsuchen des DOM stellen wir möglicherweise fest, dass das übergeordnete Div eine beschreibende ID hat. In diesem Fall bevorzugen wir möglicherweise einen Locator wie:

//div[@id=”success_guide”]//p

2. Explizite Wartezeiten

Die Versuchung besteht darin, implizit zu warten und zu hoffen, dass die meisten Probleme gelöst werden. Das Problem ist, dass dieser umfassende Ansatz alle Situationen gleich behandelt, ohne die meisten Probleme im Zusammenhang mit dem Warten zu lösen. Was passiert, wenn das Element nach einigen Sekunden vorhanden ist, aber noch nicht zum Klicken bereit ist? Was ist, wenn das Element vorhanden ist, aber durch eine Überlagerung verdeckt wird? Die Lösung besteht darin, gut verarbeitet zu verwenden explizite Wartezeiten.

Explizite Wartebedingungen haben folgende Form:

WebDriverWait wait = new WebDriverWait(webdriver, timeOutInSeconds)

wait.until(/* some condition is true */)

Explizite Wartezeiten sind mächtig, weil sie beschreibend sind. Sie ermöglichen es Ihnen, die notwendigen Bedingungen anzugeben, um fortzufahren. Ein häufiges Beispiel hierfür ist, wenn der Test auf ein Element klicken muss. Es reicht nicht aus, dass das Element nur vorhanden ist. es muss sichtbar und aktiviert sein. Durch explizite Wartezeiten können Sie diese Anforderungen direkt im Test beschreiben. Wir können sicher sein, dass die Bedingungen erfüllt sind, bevor der Test fortgesetzt wird, um Ihre Tests stabiler zu machen:

WebDriverWait wait = new WebDriverWait(webdriver, timeOutInSeconds)

wait.until(ExpectedConditions.elementToBeClickable(element))

Beschreibende explizite Wartezeiten ermöglichen es Ihnen auch Schreibtests die sich auf die Verhaltensaspekte der Anwendung konzentrieren. Da sich das Anwendungsverhalten nicht oft ändert, können Sie so stabilere Tests erstellen. Im obigen Beispiel muss ein Element sichtbar und anklickbar sein, es darf aber auch nicht von einem anderen Element verdeckt werden. Wenn Ihr Element von einem großen „Lade“-Overlay bedeckt ist, schlägt der Klick fehl. Wir können eine explizite Wartezeit erstellen, die dieses Ladeverhalten beschreibt und darauf wartet, dass die notwendigen Bedingungen erfüllt sind:

WebDriverWait wait = new WebDriverWait(webdriver, timeOutInSeconds)

wait.until(ExpectedConditions.invisibilityOf(loadingOverlay))

3. Erwartete Bedingungen

Möglicherweise haben Sie das bemerkt Erwartete Bedingungen Dienstprogrammmethoden, die in den obigen Beispielen verwendet werden. Diese Klasse enthält eine Vielzahl hilfreicher Bedingungen, die beim Schreiben Ihrer Selen-Tests verwendet werden müssen. Wenn Sie es noch nicht getan haben, lohnt es sich, sich einen Moment Zeit zu nehmen, um die vollständige API durchzugehen. Sie werden häufig verwenden  ExpectedConditions.elementToBeClickable (..) oder ExpectedConditions.invisibilityOf (..), aber Sie können auch Verwendungen für finden alertIsPresent (), jsReturnsValue (…) oder titleContains (..). Sie können die Bedingungen sogar mit verketten ExpectedConditions.and (..) oder ExpectedConditions.or (..).

4. JavaScript ausführen

WebDrivers bieten die Möglichkeit, JavaScript im Kontext des Browsers auszuführen. Dies ist eine einfache Funktion mit unglaublicher Vielseitigkeit. Dies kann für allgemeine Aufgaben verwendet werden, z. B. zum Erzwingen des Bildlaufs einer Seite zu einem Element, wie bei:

driver.executeScript("arguments[0].scrollIntoView(false)", element)

Es kann auch verwendet werden, um die in einer Anwendung wie JQuery oder React verwendeten JavaScript-Bibliotheken zu nutzen. Sie können beispielsweise überprüfen, ob der Text in einem Rich-Editor geändert wurde, indem Sie Folgendes aufrufen:

driver.executeScript(“return EDITOR.instances.editor.checkDirty()”)

Die Funktion executeScript öffnet die gesamte Bibliotheks-API für Ihren Test. Diese APIs bieten häufig nützliche Einblicke in den Status der Anwendung, die ansonsten mit WebElements nicht oder nur instabil abgefragt werden können.

Durch die Verwendung von Bibliotheks-APIs wird Ihr Test an eine Bibliotheksimplementierung gekoppelt. Bibliotheken können häufig während der Entwicklung einer Anwendung ausgetauscht werden. Daher ist Vorsicht geboten, wenn Sie executeScript auf diese Weise verwenden. Sie sollten in Betracht ziehen, diese bibliotheksspezifischen Aufrufe hinter einer abstrakteren Oberfläche zu abstrahieren, um die Instabilität Ihrer Tests zu verringern, z. B. beim Bot-Muster (siehe unten).

5. Das Bot-Muster beherrschen

Der Bot-Muster abstrahiert Selenium-API-Aufrufe in Aktionen. Die Aktionen können dann während Ihrer Tests verwendet werden, um sie lesbarer und prägnanter zu machen.

Wir haben bereits einige Beispiele gesehen, bei denen dies in diesem Artikel nützlich wäre. Da ein Element anklickbar sein muss, bevor wir es anklicken, möchten wir möglicherweise immer warten, bis das Element vor jedem Klick anklickbar ist:

void test() {
    /* test code */
    WebDriverWait wait = new WebDriverWait(driver, 5);
    wait.until(ExpectedConditions.elementToBeClickable(element));
    element.click();

    wait.until(ExpectedConditions.elementToBeClickable(element2));
    element2.click();
}

Anstatt die Wartebedingung jedes Mal zu schreiben, wenn der Test auf ein Element klickt, kann der Code in eine eigene Methode abstrahiert werden:

public class Bot {
    public void waitAndClick(WebElement element, long timeout) {
    WebDriverWait wait = new WebDriverWait(driver, timeout);
    wait.until(ExpectedConditions.elementToBeClickable(element));
    element.click();
    }
}

Dann wird unser Code:

void test() {
    /* test code */
    bot.waitAndClick(element, 5);
    bot.waitAndClick(element2, 5);
}

Der Bot kann auch erweitert werden, um bibliotheksspezifische Implementierungen zu erstellen. Wenn die Anwendung jemals eine andere Bibliothek verwendet, kann der gesamte Testcode gleich bleiben und nur der Bot muss aktualisiert werden:

public class Bot {
    private WebDriver driver;
    private RichEditorBot richEditor;

    public Bot(WebDriver driver, RichEditorBot richEditor) {
        this.driver = driver;
        this.richEditor = richEditor;
    }
    public boolean isEditorDirty() {
        richEditor.isEditorDirty();
    }
}

public class RichEditorBot() {
    public boolean isEditorDirty() {
        return ((JavascriptExecutor) driver).executeScript(“return
        EDITOR.instances.editor.checkDirty()”);
    }
}

void test() {
    /* test code */
    bot.isEditorDirty();
}

Ein Beispiel-Bot ist als Teil der WebDriverExtensions-Bibliothek sowie als bibliotheksspezifische Implementierung verfügbar:

Das Bot-Muster und das Seitenobjektmodell können zusammen verwendet werden. In Ihren Tests ist die Abstraktion der obersten Ebene das Seitenobjekt, das die Funktionselemente jeder Komponente darstellt. Die Seitenobjekte enthalten dann einen Bot, der in den Seitenobjektfunktionen verwendet werden kann, wodurch ihre Implementierungen einfacher und verständlicher werden:

public class LoginComponent {
    private Bot bot;

    @FindBy(id = “login”)
    private WebElement loginButton;

    public LoginComponent(Bot bot) {
        PageFactory.initElements(bot.getDriver(), this);
        this.bot = bot;
    }

    public void clickLogin() {
        bot.waitAndClick(loginButton, 5);
    }
}

6. Vereinfachung der WebDriver-Verwaltung

Das Instanziieren und Konfigurieren einer WebDriver-Instanz wie ChromeDriver oder FirefoxDriver direkt im Testcode bedeutet, dass der Test nun zwei Bedenken hat:

    1. Erstellen eines bestimmten WebDrivers.
    2. Testen einer Anwendung.

Eine WebDriver Factory trennt diese Bedenken, indem sie die gesamte WebDriver-Instanziierung und -Konfiguration aus dem Test verlagert. Dies kann auf viele Arten erreicht werden, aber das Konzept ist einfach: Erstellen Sie eine Factory, die einen vollständig konfigurierten WebDriver bereitstellt.

Beziehen Sie in Ihrem Testcode den WebDriver aus der Fabrik, anstatt ihn direkt zu erstellen. Jetzt können alle Bedenken bezüglich des WebDrivers an einem einzigen Ort bearbeitet werden. Alle Änderungen können an dieser einen Stelle vorgenommen werden und jeder Test erhält den aktualisierten WebDriver.

Das Web Driver Factory-Projekt verwendet dieses Konzept, um die Lebensdauer von WebDrivers über mehrere Tests hinweg zu verwalten. Diese komplexe Aufgabe wird auf die Factory abstrahiert, sodass Tests nur einen WebDriver mit den bereitgestellten Optionen anfordern können:

Die WebDriver-Factory erleichtert die Wiederverwendung eines einzelnen Tests in mehreren Browsern. Alle Konfigurationen können über eine externe Datei verwaltet werden. Der Test fragt die WebDriver-Factory nur nach einer Instanz des WebDriver und die Factory kümmert sich um die Details. Ein Beispiel hierfür wird zur Unterstützung paralleler Grid-Tests im TestNG-Framework verwendet:

7. Erweitern des Seitenobjektmodells

Das Page Object-Modell bietet eine Abstraktionsebene, die die Funktionen der Komponenten einer Anwendung darstellt und gleichzeitig die Details der Interaktion von Selenium mit diesen Komponenten verbirgt. Dies ist ein leistungsstarkes Entwurfsmuster, das Code wiederverwendbar und verständlicher macht. Allerdings kann das Erstellen einer Klasse für jede Seite und Komponente einen großen Aufwand verursachen. Es gibt das Boilerplate für jede Klasse und dann gemeinsame Komponenten zwischen den Klassen, z. B. die Initialisierung der Instanz und die Weitergabe des WebDriver- oder Bot-Objekts. Dieser Overhead kann durch die Erweiterung des Page Object-Modells reduziert werden. Wenn Sie das Bot-Muster zusammen mit Ihrem Seitenobjektmodell verwenden, benötigt jedes Seitenobjekt eine Instanz des Bot. Das könnte so aussehen:

public class LoginComponent {
    private Bot bot;

    @FindBy(id = “login”)
    private WebElement loginButton;

    public LoginComponent(Bot bot) {
        PageFactory.initElements(bot.getDriver(), this);
        this.bot = bot;
    }

    public void clickLogin() {
        bot.waitAndClick(loginButton, 5);
    }
}

Anstatt den Bot-Code in jeden Konstruktor aufzunehmen, könnte dieser Code in eine andere Klasse verschoben werden, die jede Komponente erweitert. Dadurch kann sich der Code der einzelnen Komponenten auf Details der Komponente konzentrieren und nicht auf den Initialisierungscode oder die Weitergabe des Bots:

public class Component {
    private Bot bot;

    public Component(Bot bot) {
        PageFactory.initElements(bot.getDriver(), this);
        this.bot = bot;
    }

    public Bot getBot() {
        return bot;
    }
}

public class LoginComponent extends Component {
    @FindBy(id = “login”)
    private WebElement loginButton;

    public LoginComponent(Bot bot) {
        super(bot);
    }

    public void clickLogin() {
        getBot().waitAndClick(loginButton, 5);
    }
}

Ebenso ist es üblich, dass eine Komponente überprüft, ob sie zum richtigen Zeitpunkt instanziiert wird, um das Debuggen zu erleichtern, wenn Komponenten falsch verwendet werden. Wir möchten vielleicht überprüfen, ob der Titel korrekt ist:

public class LoginPage extends Component {
    public LoginPage(Bot bot) {
        super(bot);
        bot.waitForTitleContains(“Please login”);
    }
}

Anstatt diesen Aufruf an den Bot in jede Klasse aufzunehmen, können wir diese Überprüfung in eine spezielle Version von Component verschieben, die andere Seiten erweitern. Dies bietet einen kleinen Vorteil, der sich beim Erstellen vieler Seitenobjekte summiert:

public class TitlePage extends Component {
    public LoginPage(Bot bot, String title) {
        super(bot);
        bot.waitForTitleContains(title);
    }
}

public class LoginPage extends TitlePage {
    public LoginPage(Bot bot) {
        super(bot, “Please login”);
    }
}

Andere Bibliotheken bieten Hilfsklassen für genau diesen Zweck an. Die Selenium Java-Bibliothek enthält das LoadableComponent-Objekt, das die Funktionalität abstrahiert und das Laden einer Seite überprüft:

Die WebDriverExtensions gehen noch weiter, indem sie einen Großteil des Codes um Seitenobjekte in Anmerkungen abstrahieren und so einfachere, leichter zu lesende Komponenten erstellen:

Best Practices für Selentests

Best Practices stellen sicher, dass Sie Ihre Selenium-Testszenarien sofort und langfristig optimal nutzen. Die folgenden Best Practices nutzen die oben aufgeführten Tipps, um robuste, plattform- und browserübergreifende Szenarios zu erstellen. Wenn Sie diese Vorgehensweisen im Hinterkopf behalten, können Sie den Übergang von der Erstellung weniger optimaler Skripte zu Szenarien vereinfachen, die vollständig optimiert und widerstandsfähig gegenüber Änderungen sind.

Wartbarer Testcode

Verbessern Sie die Effizienz und Produktivität, indem Sie Tests schreiben, die leicht zu warten sind. Sie verbringen weniger Zeit mit der Aktualisierung von Tests und können sich mehr auf echte Probleme konzentrieren, wenn sich Webanwendungen ändern. Viele der Techniken in diesem Blog sind teilweise deshalb nützlich, weil sie die Wartbarkeit verbessern.

Das Schreiben von Testszenarien, die durch bloßes Ansehen leicht verständlich sind, oder selbstdokumentierender Code erleichtern die Wartung. Webelement-Locators, die das Element beschreiben, explizite Wartebedingungen, die notwendige Vorbedingungen angeben, und Seitenobjekte, die mit tatsächlichen Seiten übereinstimmen, erleichtern das Lesen und Verstehen des Codes. Sogar das Bot-Muster hilft dabei, Testskripte selbstdokumentierend zu machen, indem es echte Benutzeraktionen mit im Bot definierten Funktionsnamen abbildet.

Beschreibende Webelement-Locators und explizite Wartezeiten brechen im Laufe der Zeit auch seltener ab, da sie sich auf die Funktionen der Webanwendung und nicht auf Implementierungsdetails konzentrieren. Locators wie zum Beispiel die folgenden:

//p[contains(@class, “content”)][contains(.,”Guidance for Success”)]

können das Element viel besser beschreiben als solche, die sich auf unabhängige, strukturelle Details verlassen, wie zum Beispiel:

/html/body/div[25]/div[2]/div/span/span[2]/div/h2/p

Es ist weniger wahrscheinlich, dass sich die Funktionen einer Webanwendung ändern als die zugrunde liegende Implementierung der Funktionen. Daher ist es weniger wahrscheinlich, dass für die Funktionen geschriebene Testskripte Aktualisierungen erfordern.

Datengesteuertes Testen

Durch die Bereitstellung einer Vielzahl von Daten für Ihre Testszenarien wird Ihre Anwendung besser trainiert und eine breitere Testabdeckung bereitgestellt.

Test-Frameworks wie JUnit und TestNG bieten integrierte Funktionssätze zur Unterstützung datengesteuerter Tests. Jedes Testszenario, bei dem Daten eingegeben werden, etwa das Ausfüllen eines Formulars oder die Anmeldung als Benutzer, kann von datengesteuerten Tests profitieren.

Wenn Sie Ihre Szenarien durch datengesteuerte Tests verbessern, vermeiden Sie die Tendenz, unterschiedliche logische Abläufe durch Ihr Szenario für unterschiedliche Daten zu erstellen. Teilen Sie Ihre Szenarien stattdessen nach der Art der erwarteten Daten auf. Dies ermöglicht klar definierte Testszenarien mit einfachen, linearen Schritten.

Cross-Browser-Tests

Auch wenn Details von Browser zu Browser unterschiedlich sein können, bleibt die Kernfunktionalität von Webanwendungen in der Regel konsistent, sodass Tests, die sich durch beschreibende Webelement-Locators und explizite Wartezeiten auf diese Kernfunktionalität konzentrieren, weniger wahrscheinlich abbrechen, wenn sie in verschiedenen Browsern ausgeführt werden.

Das Bot-Muster ist ein Ort, an dem geringfügige Unterschiede zwischen Browsern behandelt werden können. Wenn beispielsweise verschiedene Browser unterschiedliche JavaScripts benötigen, um dieselbe Aktion auszuführen, kann der Bot als Ort zum Abstrahieren dieses Unterschieds dienen. Beide JavaScript-Implementierungen können in einer einzigen Funktion enthalten sein, die die Aktion beschreibt und je nach Browser die Verzweigung zwischen den beiden JavaScript-Implementierungen übernimmt. Der Testskriptcode kann dann die Funktion auf dem Bot aufrufen, ohne sich um Implementierungsdetails kümmern zu müssen, wodurch der Testszenariocode einfacher zu lesen ist und browserspezifische Details ausgeblendet werden.

Ebenso kann der Seitenobjektcode verwendet werden, um Unterschiede zwischen Browsern in der Webanwendungsschnittstelle zu abstrahieren. Wenn zum Beispiel in einigen Browsern für die Anmeldung zusätzliche Schritte erforderlich sind, kann dieser Unterschied in einer High-Level-Funktion wie „doLogin“ behandelt werden. Testszenarien müssen lediglich die Anmeldeinformationen kennen, nicht jedoch die Details, die für die Anmeldung mit verschiedenen Browsern erforderlich sind.

Parallele Testausführung

Um Ihre Szenarien über mehrere Browser und Umgebungen hinweg auszuführen, sollten Sie die Tests parallel ausführen, um Zeit zu sparen. Dies ist in der Regel ein letzter Schritt, nachdem Sie sichergestellt haben, dass Ihre Testszenarien nahtlos in allen Browsern laufen und gegebenenfalls datengesteuert sind.
Selenium Grid bietet in Kombination mit Funktionen in Frameworks wie JUnit und TestNG integrierte Unterstützung für paralleles Testen. Andere Dienste wie BrowserStack bieten parallele Tests, vorgefertigte Umgebungen und Diagnosefunktionen, um Ihre parallelen Testläufe einfacher zu verwalten.

Master Selenium für effizientes Testen

Dieser Artikel hat nur einige nützliche Techniken und Entwurfsmuster angesprochen. Zu diesem Thema wurden Bücher geschrieben. Gute Tests zu schreiben bedeutet, gute Software zu schreiben, und gute Software zu schreiben ist eine komplexe Aufgabe.

Ich habe einige Informationen behandelt, die ich im Laufe der Jahre gelernt habe, um bessere Selentests zu erstellen. Parasoft Selenic nutzt unser Fachwissen, um die Aufgabe weiter zu vereinfachen. Mit den richtigen Werkzeugen und Kenntnissen können wir den Prozess verbessern und stabile, lesbare und wartbare Tests erstellen.

Erfahren Sie, wie Sie mit KI zuverlässige Selenium-Web-UI-Tests erstellen können.