Unzuverlässige Automatisierungstests können für QS-Ingenieure ein Fluch sein, da sie zu Unsicherheit führen und die Zuverlässigkeit von Testsuiten beeinträchtigen. Basierend auf meiner Erfahrung als SDET- und QA-Automatisierungs-Mentor bietet dieser Artikel praktische Ratschläge zur Überwindung von Flockigkeit. Obwohl ich JavaScript- und Playwright-Beispiele bereitstelle, sind diese universellen Tipps in jeder Sprache und jedem Framework anwendbar und helfen Ihnen, robuste und zuverlässige Automatisierungstests zu schreiben. Lassen Sie uns eintauchen und dafür sorgen, dass Ihre Tests Bestand haben.
Gelegentlich werden Sie auf Situationen stoßen, in denen Sie warten müssen, bis Elemente im DOM angezeigt werden oder Ihre Anwendung einen bestimmten Status erreicht, um mit Ihrem Testszenario fortfahren zu können. Auch bei modernen, intelligenten Automatisierungs-Frameworks wie den Auto-Wait-Funktionen von Playwright wird es Fälle geben, in denen Sie benutzerdefinierte Wartemethoden implementieren müssen. Stellen Sie sich beispielsweise ein Szenario mit Textfeldern und einer Bestätigungsschaltfläche vor. Aufgrund eines bestimmten serverseitigen Verhaltens wird die Bestätigungsschaltfläche erst etwa fünf Sekunden nach dem Ausfüllen des Formulars sichtbar. In solchen Fällen ist es wichtig, der Versuchung zu widerstehen, zwischen zwei Testschritten eine implizite Wartezeit von fünf Sekunden einzufügen.
textField.fill('Some text') waitForTime(5000) confirmationButton.click()
Gehen Sie stattdessen intelligent vor und warten Sie den genauen Zustand ab. Dabei kann es sich um ein Textelement handeln, das nach einigem Ladevorgang erscheint, oder um ein sich drehendes Ladeelement, das verschwindet, nachdem der aktuelle Schritt erfolgreich abgeschlossen wurde. Sie können prüfen, ob Sie für den nächsten Testschritt bereit sind.
button.click() waitFor(textField.isVisible()) textField.fill()
Natürlich gibt es Fälle, in denen Sie auf etwas warten müssen, das Sie nicht intelligent überprüfen können. Ich schlage vor, an solchen Stellen Kommentare hinzuzufügen oder sogar die Begründung als Parameter in Ihre Wartemethoden einzufügen oder so etwas in der Art:
waitForTime(5000, {reason: "For unstable server processed something..."}
Ein zusätzlicher Vorteil bei der Verwendung sind die Testberichte, in denen Sie solche Erklärungen speichern können, sodass für jemand anderen oder sogar für Sie in Zukunft klar ist, was und warum diese fünf Sekunden Wartezeit hier vorliegen.
waitForTime(5000, {reason: "For unstable server processed something..."} button.click() waitFor(textField.isVisible()) textField.fill()
Locators sind ein entscheidender Bestandteil von Automatisierungstests, und das weiß jeder. Doch trotz dieser einfachen Tatsache verlassen sich viele Automatisierungsingenieure auf die Stabilität und verwenden so etwas in ihren Tests.
//*[@id="editor_7"]/section/div[2]/div/h3[2]
Das ist ein alberner Ansatz, weil die DOM-Struktur nicht so statisch ist; Einige verschiedene Teams können es manchmal ändern, und zwar nachdem Ihre Tests fehlgeschlagen sind. Verwenden Sie also die robusten Locators. Sie sind übrigens herzlich willkommen, meine XPath-bezogene Geschichte zu lesen.
Wenn Sie die Testsuite mit mehreren Tests in einem einzelnen Thread ausführen, ist es wichtig sicherzustellen, dass jeder Test unabhängig ist. Das bedeutet, dass der erste Test das System in seinen ursprünglichen Zustand zurückversetzen sollte, damit der nächste Test nicht aufgrund unerwarteter Bedingungen fehlschlägt. Wenn beispielsweise einer Ihrer Tests in LocalStorage gespeicherte Benutzereinstellungen ändert, ist es ein guter Ansatz, den LocalStorage-Eintrag für Benutzereinstellungen nach den ersten Testläufen zu löschen. Dadurch wird sichergestellt, dass der zweite Test mit den Standardbenutzereinstellungen ausgeführt wird. Sie können auch Aktionen wie das Zurücksetzen der Seite auf die Standardeintragsseite und das Löschen von Cookies und Datenbankeinträgen in Betracht ziehen, damit jeder neue Test mit identischen Anfangsbedingungen beginnt.
In Playwright können Sie beispielsweise die Annotationen beforeAll(), beforeEach(), afterAll() und afterEach() verwenden, um dies zu erreichen.
Schauen Sie sich die nächste Testsuite mit ein paar Tests an. Die erste besteht darin, die Darstellungseinstellungen des Benutzers zu ändern, und die zweite darin, die Standarddarstellung zu überprüfen.
test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goTo(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });
Wenn eine solche Testsuite parallel läuft, läuft alles reibungslos. Wenn diese Tests jedoch einzeln ausgeführt werden, kann es sein, dass der Test fehlschlägt, weil einer von ihnen einen anderen stört. Beim ersten Test veränderte sich das Erscheinungsbild von hell nach dunkel. Der zweite Test prüft, ob das aktuelle Erscheinungsbild hell ist. Dies wird jedoch fehlschlagen, da der erste Test das Standard-Erscheinungsbild bereits geändert hat.
Um ein solches Problem zu lösen, habe ich den Hook afterEach() hinzugefügt, der nach jedem Test ausgeführt wird. In diesem Fall navigiert es zur Standardansicht und löscht das Erscheinungsbild des Benutzers, indem es es aus dem lokalen Speicher entfernt.
test.afterEach(async ({ page }) => { await user.localStorage(appearanceSettings).clear() await user.goTo(views.home) }); test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goto(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });
Bei diesem Ansatz ist jeder Test in dieser Suite unabhängig.
Niemand ist vor unzuverlässigen Tests gefeit, und eine beliebte Lösung für dieses Problem ist die Verwendung von Wiederholungsversuchen. Sie können Wiederholungsversuche so konfigurieren, dass sie automatisch erfolgen, sogar auf der CI/CD-Konfigurationsebene. Sie können beispielsweise automatische Wiederholungsversuche für jeden fehlgeschlagenen Test einrichten und dabei eine maximale Anzahl von Wiederholungsversuchen von drei Malen festlegen. Jeder Test, der zunächst fehlschlägt, wird bis zu dreimal wiederholt, bis der Testausführungszyklus abgeschlossen ist. Der Vorteil besteht darin, dass Ihr Test, wenn er nur leicht schwankt, nach ein paar Wiederholungsversuchen erfolgreich sein kann.
Der Nachteil besteht jedoch darin, dass es fehlschlagen könnte, was zu einem zusätzlichen Laufzeitverbrauch führt. Darüber hinaus kann diese Praxis unbeabsichtigt zu einer Anhäufung unzuverlässiger Tests führen, da viele Tests beim zweiten oder dritten Versuch scheinbar „bestanden“ werden und Sie sie möglicherweise fälschlicherweise als stabil bezeichnen. Daher empfehle ich nicht, automatische Wiederholungsversuche global für Ihr gesamtes Testprojekt festzulegen. Verwenden Sie Wiederholungsversuche stattdessen selektiv in Fällen, in denen Sie die zugrunde liegenden Probleme im Test nicht umgehend lösen können.
Stellen Sie sich beispielsweise vor, Sie hätten einen Testfall, bei dem Sie einige Dateien mithilfe des „filechooser“-Ereignisses hochladen müssen. Sie erhalten den Fehler <Timeout überschritten beim Warten auf das Ereignis „filechooser“>, der darauf hinweist, dass der Playwright-Test auf das Eintreten eines „filechooser“-Ereignisses wartet, jedoch eine längere Zeitüberschreitung benötigt.
async function uploadFile(page: Page, file) { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); }
Dies kann verschiedene Gründe haben, beispielsweise ein unerwartetes Verhalten in der Anwendung oder eine langsame Umgebung. Da es sich um ein zufälliges Verhalten handelt, könnten Sie zunächst darüber nachdenken, für diesen Test einen automatischen Wiederholungsversuch zu verwenden. Wenn Sie jedoch den gesamten Test erneut versuchen, riskieren Sie, zusätzliche Zeit zu verschwenden und das gleiche Verhalten wie beim ersten Fehler in der Methode uploadFile() selbst zu erhalten. Wenn also der Fehler auftritt, müssen Sie nicht den gesamten Test erneut versuchen.
Dazu können Sie die standardmäßige try…catch-Anweisung verwenden.
async function uploadFile(page: Page, file) { const maxRetries = 3; let retryCount = 0; while (retryCount < maxRetries) { try { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); break; // Success, exit the loop } catch (error) { console.error(`Attempt ${retryCount + 1} failed: ${error}`); retryCount++; } } }
Ein solcher Ansatz spart zusätzliche Zeit und macht Ihren Test weniger flockig.
Zusammenfassend lässt sich sagen, dass der Weg zu zuverlässigen Automatisierungstests unkompliziert ist. Sie können gemeinsame Herausforderungen minimieren oder aktiv angehen und intelligente Strategien umsetzen. Denken Sie daran, dass diese Reise noch andauert und Ihre Tests mit zunehmender Beharrlichkeit zuverlässiger werden. Verabschieden Sie sich von der Flockigkeit und begrüßen Sie Stabilität. Ihr Qualitätsanspruch sichert den Projekterfolg. Viel Spaß beim Testen!
Auch hier veröffentlicht.