Top-Tipps für Selen-Experten
Von Tony Yoshicedo
5. November 2019
8 min lesen
Sobald Sie Selenium eine Weile verwendet haben und mit dem Schreiben von Testfällen vertraut sind, können Sie sich auf Techniken und Entwurfsprinzipien konzentrieren, um Ihre UI-Testautomatisierung auf die nächste Stufe zu bringen.
Bevor wir anfangen
In diesem Artikel wird davon ausgegangen, dass Sie Selen verwendet haben und Testfälle problemlos schreiben können. Sie haben bereits DOMs überprüft, um Ihre eigenen XPaths zu erstellen. Möglicherweise verwenden Sie das Seitenobjektmodell. Inzwischen sind Sie wahrscheinlich ziemlich gut darin, nach Lösungen im Internet zu suchen. (Wenn Sie Hilfe in dieser Abteilung benötigen, kann ich dies nur 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.
Techniken
Bessere 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 [enthält (@class, "content")] [enthält (., "Guidance for Success")]
Mit dem oben Gesagten ist klar, dass das Element Inhalte darstellt, die „Guidance for Success“ beschreiben. Beachten Sie, dass das Klassenattribut, das normalerweise zur Steuerung des Erscheinungsbilds verwendet wird, 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 „Guidance for Success“. Obwohl dieser Locator gut ist, kann sich das Kopieren von Text häufig ä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
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 (/ * eine Bedingung ist wahr * /)
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))
Mit beschreibenden expliziten Wartezeiten können Sie auch Tests schreiben, die sich auf die Verhaltensaspekte der Anwendung konzentrieren. Da sich das Anwendungsverhalten nicht häufig ändert, können Sie stabilere Tests erstellen. In dem obigen Beispiel muss ein Element sichtbar sein und aktiviert werden, damit es angeklickt werden kann. Es muss jedoch auch nicht durch ein anderes Element verdeckt werden. Wenn Ihr Element von einer großen Ladeüberlagerung bedeckt ist, schlägt der Klick fehl. Wir können eine explizite Wartezeit erstellen, die dieses Ladeverhalten beschreibt und darauf wartet, dass die erforderlichen Bedingungen erfüllt sind:
WebDriverWait wait = new WebDriverWait (Webdriver, timeOutInSeconds) wait.until (ExpectedConditions.invisibilityOf (loadOverlay))
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 (..).
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 ("Argumente [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 ("EDITOR.instances.editor.checkDirty () zurückgeben")
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).
Design
Das Bot-Muster
Das 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 () {/ * Testcode * / WebDriverWait wait = new WebDriverWait (Treiber, 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:
öffentliche Klasse Bot {public void waitAndClick (WebElement-Element, lange Zeitüberschreitung) {WebDriverWait wait = new WebDriverWait (Treiber, Zeitüberschreitung); wait.until (ExpectedConditions.elementToBeClickable (element)); element.click (); }}
Dann wird unser Code:
void test () {/ * Testcode * / 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:
öffentliche Klasse Bot {privater WebDriver-Treiber; private RichEditorBot richEditor; öffentlicher Bot (WebDriver-Treiber, RichEditorBot richEditor) {this.driver = Treiber; this.richEditor = richEditor; } public boolean isEditorDirty () {richEditor.isEditorDirty (); }} öffentliche Klasse RichEditorBot () {public boolean isEditorDirty () {return ((JavascriptExecutor) Treiber) .executeScript ("return EDITOR.instances.editor.checkDirty ()"); }} void test () {/ * Testcode * / bot.isEditorDirty (); }}
Ein Beispiel-Bot ist als Teil der WebDriverExtensions-Bibliothek sowie als bibliotheksspezifische Implementierung verfügbar:
- https://github.com/webdriverextensions/webdriverextensions/blob/master/src/main/java/com/github/webdriverextensions/Bot.java
- https://github.com/webdriverextensions/webdriverextensions/blob/master/src/main/java/com/github/webdriverextensions/vaadin/VaadinBot.java
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:
öffentliche Klasse LoginComponent {privater Bot-Bot; @FindBy (id = "login") privates WebElement loginButton; public LoginComponent (Bot bot) {PageFactory.initElements (bot.getDriver (), this); this.bot = bot; } public void clickLogin () {bot.waitAndClick (loginButton, 5); }}
WebDriver-Fabrik
Das Instanziieren und Konfigurieren einer WebDriver-Instanz wie ChromeDriver oder FirefoxDriver direkt im Testcode bedeutet, dass der Test jetzt zwei Probleme hat: Erstellen eines bestimmten WebDriver und Testen einer Anwendung. Eine WebDriver Factory trennt diese Bedenken, indem alle WebDriver-Instanziierungen und -Konfigurationen aus dem Test entfernt werden.
Dies kann auf verschiedene Arten erreicht werden, aber das Konzept ist einfach: Erstellen Sie eine Factory, die einen vollständig konfigurierten WebDriver bereitstellt. Holen Sie sich in Ihrem Testcode den WebDriver ab Werk, anstatt ihn direkt zu erstellen. Jetzt können alle Bedenken bezüglich des WebDriver an einem einzigen Ort behandelt werden. Alle Änderungen können an diesem einen Ort 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. Die gesamte Konfiguration kann über eine externe Datei erfolgen. Der Test fragt die WebDriver-Factory nur nach einer Instanz des WebDriver, und die Factory verarbeitet die Details. Ein Beispiel hierfür wird verwendet, um parallele Grid-Tests im TestNG-Framework zu unterstützen:
Erweitern des Seitenobjektmodells
Das Seitenobjektmodell bietet eine Abstraktionsebene, die die Funktionen der Komponenten einer Anwendung darstellt und gleichzeitig die Details der Interaktion von Selen mit diesen Komponenten verbirgt. Dies ist ein leistungsstarkes Entwurfsmuster, das Code wiederverwendbar und verständlicher macht. Das Erstellen einer Klasse für jede Seite und Komponente kann jedoch mit viel Aufwand verbunden sein. Es gibt das Boilerplate für jede Klasse und dann gemeinsam genutzte Komponenten zwischen Klassen, z. B. das Initialisieren der Instanz und das Weitergeben des WebDriver- oder Bot-Objekts. Dieser Overhead kann durch Erweitern des Seitenobjektmodells reduziert werden.
Wenn Sie das Bot-Muster zusammen mit Ihrem Seitenobjektmodell verwenden, benötigt jedes Seitenobjekt eine Instanz des Bots. Das könnte so aussehen:
öffentliche Klasse LoginComponent {privater Bot-Bot; @FindBy (id = "login") privates 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:
öffentliche Klasse Komponente {privater Bot-Bot; öffentliche Komponente (Bot bot) {PageFactory.initElements (bot.getDriver (), this); this.bot = bot; } public Bot getBot () {return bot; }} öffentliche Klasse LoginComponent erweitert 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:
öffentliche Klasse LoginPage erweitert Component {public LoginPage (Bot bot) {super (bot); bot.waitForTitleContains ("Bitte anmelden"); }}
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 erweitert Component {public LoginPage (Bot bot, String title) {super (bot); bot.waitForTitleContains (Titel); }} public class LoginPage erweitert 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:
Es gibt immer mehr zu lernen
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 Know-how, um die Aufgabe weiter zu vereinfachen. Mit den richtigen Tools und Kenntnissen können wir den Prozess verbessern und stabile, lesbare und wartbare Tests erstellen.