Eine E2E-Testreise Teil 2: WebdriverIO zu Cypress

Veröffentlicht: 2019-11-21

Hinweis: Dies ist ein Beitrag von #frontend@twiliosendgrid. Weitere technische Beiträge finden Sie in der technischen Blogrolle.

Bei allen unseren Frontend-Apps hatten und haben wir das folgende Ziel: eine Möglichkeit bieten, konsistente, debuggbare, wartbare und wertvolle E2E-Automatisierungstests (End-to-End) für unsere Frontend-Anwendungen zu schreiben und mit CICD (Continuous Integration) zu integrieren und kontinuierlicher Einsatz).

Um den Zustand zu erreichen, den wir heute haben, in dem möglicherweise Hunderte von E2E-Tests ausgelöst werden oder nach einem Zeitplan in allen unseren Frontend-Teams in SendGrid ausgeführt werden, mussten wir auf dem Weg nach und nach viele potenzielle Lösungen erforschen und mit ihnen experimentieren, bis wir dieses Ziel erreicht hatten Ziel.

Wir haben versucht, unsere eigene benutzerdefinierte Ruby Selenium-Lösung zu entwickeln, die von engagierten Testingenieuren namens SiteTestUI alias STUI entwickelt wurde und in der alle Teams zu einem Repository beitragen und browserübergreifende Automatisierungstests durchführen konnten. Es erlag leider langsamen, fehlerhaften Tests, Fehlalarmen, fehlendem Kontext zwischen Repos und Sprachen, zu vielen Händen in einem Korb, schmerzhaften Debugging-Erfahrungen und mehr Zeit, die für die Wartung als für die Bereitstellung von Wert aufgewendet wurde.

Wir haben dann mit einer anderen vielversprechenden Bibliothek in WebdriverIO experimentiert, um Tests in JavaScript zu schreiben, die zusammen mit den Anwendungsrepositorys jedes Teams angeordnet sind. Während dies einige Probleme löste, mehr Teamverantwortung ermöglichte und es uns ermöglichte, Tests mit Buildkite, unserem CICD-Anbieter, zu integrieren, hatten wir immer noch unzuverlässige Tests, die schmerzhaft zu debuggen und schwer zu schreiben waren, zusätzlich zum Umgang mit allzu ähnlichen Selenium-Bugs und Macken .

Wir wollten ein weiteres STUI 2.0 vermeiden und begannen, andere Optionen zu prüfen. Wenn Sie mehr über unsere Erfahrungen und Strategien bei der Migration von STUI zu WebdriverIO erfahren möchten, lesen Sie Teil 1 der Blogbeitragsserie.

In diesem letzten Teil zwei der Blogpost-Reihe werden wir unseren Weg von STUI und WebdriverIO zu Cypress behandeln und wie wir ähnliche Migrationen beim Einrichten der gesamten Infrastruktur, beim Schreiben organisierter E2E-Tests, bei der Integration in unsere Buildkite-Pipelines und bei der Skalierung durchlaufen haben andere Front-End-Teams in der Organisation.

TLDR: Wir haben Cypress gegenüber STUI und WebdriverIO übernommen und alle unsere Ziele erreicht, wertvolle E2E-Tests zur Integration mit CICD zu schreiben. Viele unserer Arbeiten und Erkenntnisse aus WebdriverIO und STUI haben sich gut darauf übertragen, wie wir Cypress-Tests heute verwenden und integrieren.

Inhaltsverzeichnis

Erkundung und Landung auf Cypress

Umstellung von STUI und WebdriverIO auf Cypress

Schritt 1: Installieren von Abhängigkeiten für Cypress

Schritt 2: Umgebungskonfigurationen und Skripte

Erster Durchgang von Umgebungskonfigurationen und Skripten

Entwicklung von Umgebungskonfigurationen und Skripten

Schritt 3: E2E-Tests lokal implementieren

Schritt 4: Dockerisieren der Tests

Schritt 5: Integration mit CICD

Schritt 6: Vergleich von Cypress und WebdriverIO/STUI

Schritt 7: Skalierung auf andere Frontend-Teams

Worauf wir uns bei Cypress freuen

Mit Cypress in die Zukunft

Erkundung und Landung auf Cypress

Als wir nach Alternativen zu WebdriverIO suchten, sahen wir andere Selenium-Wrapper wie Protractor und Nightwatch mit einem ähnlichen Funktionsumfang wie WebdriverIO, aber wir hatten das Gefühl, dass wir höchstwahrscheinlich auf lange Setups, unbeständige Tests und mühsames Debugging und Wartung stoßen würden.

Glücklicherweise stolperten wir über ein neues E2E-Testframework namens Cypress, das schnelle Setups, schnelle und debuggbare Tests im Browser, Stubbing von Netzwerkschichtanforderungen und vor allem den Verzicht auf Selenium unter der Haube zeigte.

Wir bestaunten tolle Features wie Videoaufnahmen, die Cypress-GUI, den kostenpflichtigen Dashboard-Service und die Parallelisierung zum Ausprobieren. Wir waren bereit, Kompromisse bei der browserübergreifenden Unterstützung zugunsten wertvoller Tests einzugehen, die konsequent gegen Chrome bestehen, mit einem Repertoire an Tools, die uns zur Verfügung stehen, um die Tests für unsere Entwickler und QAs zu implementieren, zu debuggen und zu warten.

Wir schätzen auch die Test-Frameworks, Assertion-Bibliotheken und alle anderen Tools, die für uns ausgewählt wurden, um einen standardisierteren Ansatz für Tests in allen unseren Frontend-Teams zu haben. Unten haben wir einen Screenshot der Unterschiede zwischen einer Lösung wie WebdriverIO und Cypress bereitgestellt, und wenn Sie mehr über die Unterschiede zwischen Cypress- und Selenium-Lösungen erfahren möchten, können Sie deren Dokumentation zur Funktionsweise lesen.

Vor diesem Hintergrund haben wir uns verpflichtet, Cypress als weitere Lösung zum Schreiben schneller, konsistenter und debugbarer E2E-Tests zu testen, die schließlich während der CICD in Buildkite integriert werden sollen. Wir haben uns auch bewusst ein weiteres Ziel gesetzt, Cypress mit den vorherigen Selenium-basierten Lösungen zu vergleichen, um letztendlich anhand von Datenpunkten zu bestimmen, ob wir weiter suchen oder unsere Testsuiten in Zukunft mit Cypress erstellen sollten. Wir planten, bestehende WebdriverIO-Tests und alle anderen Tests mit hoher Priorität, die sich noch in STUI befinden, auf Cypress-Tests umzustellen und unsere Entwicklererfahrungen, Geschwindigkeit, Stabilität, Testlaufzeiten und Wartung der Tests zu vergleichen.

Umstellung von STUI und WebdriverIO auf Cypress

Als wir von STUI und WebdriverIO zu Cypress umgestiegen sind, haben wir es systematisch mit der gleichen Strategie auf hoher Ebene angegangen, die wir verwendet haben, als wir unsere Migration von STUI zu WebdriverIO in unseren Frontend-Anwendungsrepositorys versucht haben. Weitere Einzelheiten darüber, wie wir solche Schritte für WebdriverIO durchgeführt haben, finden Sie in Teil 1 der Blog-Post-Reihe. Die allgemeinen Schritte für den Übergang zu Cypress umfassten Folgendes:

  1. Installieren und Einrichten von Abhängigkeiten für die Verbindung mit Cypress
  2. Einrichten von Umgebungskonfigurationen und Skriptbefehlen
  3. Implementieren von E2E-Tests, die lokal gegen verschiedene Umgebungen bestehen
  4. Dockerisieren der Tests
  5. Integrieren von Dockerized-Tests mit Buildkite, unserem CICD-Anbieter

Um unsere sekundären Ziele zu erreichen, haben wir auch zusätzliche Schritte hinzugefügt, um Cypress mit den vorherigen Selenium-Lösungen zu vergleichen und Cypress schließlich für alle Frontend-Teams in der Organisation zu skalieren:

6. Vergleich von Cypress in Bezug auf Entwicklererfahrungen, Geschwindigkeit und Stabilität der Tests mit WebdriverIO und STUI
7. Skalierung auf andere Frontend-Teams

Schritt 1: Installieren von Abhängigkeiten für Cypress

Um Cypress schnell zum Laufen zu bringen, mussten wir lediglich „npm install cypress“ in unsere Projekte einfügen und Cypress zum ersten Mal starten, damit es automatisch mit einer „cypress.json“-Konfigurationsdatei und einem cypress -Ordner angelegt wurde mit Starter-Fixtures, Tests und anderen Setup-Dateien für Befehle und Plugins. Wir haben es sehr geschätzt, dass Cypress mit Mocha als Testrunner, Chai für Assertions und Chai-jQuery und Sinon-Chai für noch mehr Assertions verwendet und verkettet wurde. Im Vergleich zu den Anfängen mit WebdriverIO oder STUI mussten wir nicht mehr viel Zeit damit verbringen, zu recherchieren, welche Testrunner-, Reporter-, Assertions- und Servicebibliotheken installiert und verwendet werden sollten. Wir haben sofort einige der generierten Tests mit der Cypress-GUI ausgeführt und die vielen Debugging-Funktionen untersucht, die uns zur Verfügung stehen, wie Zeitreise-Debugging, Selector Playground, aufgezeichnete Videos, Screenshots, Befehlsprotokolle, Browser-Entwicklertools usw.

Wir richten es auch später mit Eslint und TypeScript für zusätzliche statische Typprüfungs- und Formatierungsregeln ein, die beim Übertragen von neuem Cypress-Testcode zu befolgen sind. Wir hatten anfangs einige Probleme mit der TypeScript-Unterstützung und einigen Dateien, die JavaScript-Dateien sein mussten, wie die, die sich um die Plugin-Dateien drehten, aber zum größten Teil konnten wir die meisten unserer Dateien auf unsere Tests, Seitenobjekte und Befehle tippen.

Hier ist eine beispielhafte Ordnerstruktur, der eines unserer Frontend-Teams gefolgt ist, um Seitenobjekte, Plugins und Befehle zu integrieren:

Schritt 2: Umgebungskonfigurationen und Skripte

Nachdem wir Cypress schnell installiert und für die lokale Ausführung eingerichtet hatten, brauchten wir eine Möglichkeit, unsere Cypress-Tests mit unterschiedlichen Einstellungen für jede Umgebung auszuführen, und wollten die gleichen Anwendungsfälle unterstützen, die uns unsere WebdriverIO-Befehle ermöglichten. Hier ist eine Liste, die die meisten Anwendungsfälle veranschaulicht, wie wir diese Tests ausführen wollten und warum wir sie unterstützen wollten:

  • Gegen einen Webpack-Entwicklungsserver, der auf localhost läuft (z. B. http://localhost:8000), und dieser Entwicklungsserver würde auf eine bestimmte Umgebungs-API (z. B. https://testing.api.com oder https://staging.api) verweisen. com) wie Testen oder Staging.
    Warum? Manchmal müssen wir Änderungen an unserer lokalen Web-App vornehmen, z. B. das Hinzufügen spezifischerer Selektoren für unsere Tests, um robuster mit den Elementen zu interagieren, oder wir waren gerade dabei, eine neue Funktion zu entwickeln, und mussten die vorhandenen Automatisierungstests anpassen und validieren würde lokal gegen unsere neuen Codeänderungen passieren. Wann immer sich der Anwendungscode geändert hat und wir noch nicht in die bereitgestellte Umgebung hochgeladen haben, haben wir diesen Befehl verwendet, um unsere Tests für unsere lokale Web-App auszuführen.
  • Gegen eine bereitgestellte App für eine bestimmte Umgebung (z. B. https://testing.app.com oder https://staging.app.com) wie Testen oder Staging
    Warum? In anderen Fällen ändert sich der Anwendungscode nicht, aber wir müssen möglicherweise unseren Testcode ändern, um einige Unregelmäßigkeiten zu beheben, oder wir fühlen uns sicher genug, um Tests hinzuzufügen oder zu löschen, ohne Frontend-Änderungen vorzunehmen. Wir haben diesen Befehl stark genutzt, um Tests lokal für die bereitgestellte App zu aktualisieren oder zu debuggen, um genauer zu simulieren, wie unsere Tests in CICD ausgeführt werden.
  • Ausführung in einem Docker-Container gegen eine bereitgestellte App für eine bestimmte Umgebung wie Testen oder Staging
    Warum? Dies ist für CICD gedacht, damit wir E2E-Tests auslösen können, die in einem Docker-Container beispielsweise gegen die bereitgestellte Staging-App ausgeführt werden, und sicherstellen, dass sie bestanden werden, bevor Code in der Produktion oder in geplanten Testläufen in einer dedizierten Pipeline bereitgestellt wird. Bei der anfänglichen Einrichtung dieser Befehle haben wir viele Versuche und Irrtümer durchgeführt, um Docker-Container mit unterschiedlichen Umgebungsvariablenwerten hochzufahren und zu testen, ob die richtigen Tests erfolgreich ausgeführt wurden, bevor wir sie mit unserem CICD-Anbieter Buildkite verbunden haben.

Erster Durchgang von Umgebungskonfigurationen und Skripten

Als wir zum ersten Mal mit der Einrichtung von Cypress experimentierten, taten wir dies in dem Repo, das https://app.sendgrid.com abdeckt, eine Web-App, die Funktionsseiten wie Absenderauthentifizierung, E-Mail-Aktivität und E-Mail-Validierung enthält, und wir zwangsläufig teilten unsere Entdeckungen und Erkenntnisse mit den Teams hinter unserer Web-App für Marketingkampagnen, die die Domäne https://mc.sendgrid.com umfasst. Wir wollten unsere E2E-Tests in unserer Staging-Umgebung ausführen und nutzten die Befehlszeilenschnittstelle von Cypress und Optionen wie --config oder --env , um unsere Anwendungsfälle zu erfüllen.

Um die Cypress-Tests gegen die Web-App lokal auf beispielsweise http://127.0.0.1:8000 oder gegen die bereitgestellte Staging-App-URL auszuführen, haben wir das baseUrl Konfigurationsflag im Befehl angepasst und zusätzliche Umgebungsvariablen wie testEnv , um zu helfen Laden Sie bestimmte Vorrichtungen oder umgebungsspezifische Testdaten in unsere Cypress-Tests. Beispielsweise können sich die verwendeten API-Schlüssel, die erstellten Benutzer und andere Ressourcen je nach Umgebung unterscheiden. Wir testEnv verwendet, um diese Fixtures umzuschalten oder eine spezielle bedingte Logik hinzuzufügen, wenn einige Funktionen in einer Umgebung nicht unterstützt wurden oder die Testkonfiguration davon abwich, und wir würden auf die Umgebung über einen Aufruf wie Cypress.env(“testEnv”) in unseren Spezifikationen zugreifen.

Wir haben dann unsere Befehle cypress:open:* so organisiert, dass sie die Cypress-GUI öffnen, damit wir unsere Tests auswählen können, die über die Benutzeroberfläche ausgeführt werden sollen, als wir lokal entwickelt haben, und cypress:run:* , um die Ausführung von Tests im Headless-Modus zu bezeichnen, der maßgeschneiderter war zum Ausführen in einem Docker-Container während CICD. Was nach open oder run kam, war die Umgebung, sodass unsere Befehle sich leicht lesen ließen wie npm run cypress:open:localhost:staging , um die GUI zu öffnen und Tests gegen einen lokalen Webpack-Entwicklungsserver auszuführen, der auf Staging-APIs verweist, oder npm run cypress:run:staging , um Tests im Headless-Modus für die bereitgestellte Staging-App und -API auszuführen. Die package.json Cypress-Skripte kamen wie folgt heraus:

Entwicklung von Umgebungskonfigurationen und Skripten

In einem anderen Projekt haben wir unsere Cypress-Befehle und -Konfigurationen weiterentwickelt, um einige Node-Logik in der Datei cypress/plugins/index.js zu nutzen, um eine Basisdatei cypress.json und separate Konfigurationsdateien zu haben, die basierend auf einer aufgerufenen Umgebungsvariable gelesen werden configFile , um eine bestimmte Konfigurationsdatei zu laden. Die geladenen Konfigurationsdateien würden dann mit der Basisdatei zusammengeführt, um schließlich auf einen Staging- oder Mock-Back-End-Server zu verweisen.

Falls Sie sich mehr über den Schein-Backend-Server wundern, haben wir einen Express-Server mit Backend-Endpunkten entwickelt, die einfach unterschiedliche Antworten statischer JSON-Daten und Statuscodes (z. B. 200, 4XX, 5XX) zurückgeben, abhängig von den Abfrageparametern, die in den Anforderungen übergeben werden. Dadurch wurde das Frontend entsperrt, um die Seitenflüsse mit tatsächlichen Netzwerkaufrufen an den Schein-Backend-Server weiterzuentwickeln, wobei die Antworten emulieren, wie die tatsächliche API aussehen wird, wenn sie in Zukunft verfügbar ist. Wir könnten auch leicht unterschiedliche Erfolgsniveaus und Fehlerreaktionen für unsere verschiedenen UI-Zustände simulieren, die ansonsten in der Produktion schwer zu reproduzieren wären, und da wir deterministische Netzwerkaufrufe tätigen würden, wären unsere Cypress-Tests weniger unzuverlässig beim Auslösen desselben Netzwerks Anfragen und Antworten jedes Mal.

Wir hatten eine cypress.json -Basisdatei, die gemeinsame Eigenschaften für allgemeine Zeitüberschreitungen, eine Projekt-ID zum Verbinden mit dem Cypress Dashboard-Dienst, über den wir später sprechen werden, und andere Einstellungen wie unten gezeigt enthielt:

Wir haben einen config im cypress -Ordner erstellt, um jede unserer Konfigurationsdateien zu speichern, z. B. localhostMock.json , um unseren lokalen Webpack-Entwicklungsserver gegen einen lokalen Mock-API-Server auszuführen, oder staging.json , um gegen die bereitgestellte Staging-App und -API ausgeführt zu werden. Diese zu unterscheidenden und mit der Basiskonfiguration zusammenzuführenden Konfigurationsdateien sahen folgendermaßen aus:

Die CICD-Konfigurationsdateien hatten eine noch einfachere JSON-Datei, da wir die Umgebungsvariablen dynamisch festlegen mussten, um die unterschiedliche Docker-Service-Frontend-Basis-URL und Mock-Server-API-Hosts zu berücksichtigen, auf die wir später eingehen werden.

In unserer Datei cypress/plugins/index.js haben wir Logik hinzugefügt, um eine Umgebungsvariable namens configFile set aus einem Cypress-Befehl zu lesen, der schließlich die entsprechende Datei im Konfigurationsordner lesen und mit der cypress.json config unten zusammenführen würde:

Um vernünftige Cypress-Befehle mit Umgebungsvariablen für unsere Anwendungsfälle zu schreiben, haben wir uns ein Makefile zunutze gemacht, das dem folgenden ähnelte:

Mit diesen Befehlen, die ordentlich in einem Makefile angeordnet sind, könnten wir schnell Dinge wie make cypress_open_staging oder make cypress_run_staging in unseren „package.json“-npm-Skripten ausführen.

Früher haben wir einige Befehle in einer langen Zeile platziert, die schwierig ohne Fehler zu bearbeiten waren. Glücklicherweise hat das Makefile dazu beigetragen, die Dinge viel besser zu verteilen, indem Umgebungsvariablen lesbar über mehrere Zeilen in die Cypress-Befehle eingefügt wurden. Wir könnten schnell Umgebungsvariablen wie configFile für die hochzuladende Umgebungskonfigurationsdatei, BASE_URL für den Besuch unserer Seiten, API_HOST für verschiedene Backend-Umgebungen oder SPECS zur Bestimmung der auszuführenden Tests festlegen oder exportieren, bevor wir einen der Makefile-Befehle starten.

Wir haben auch Makefile-Befehle für andere langwierige npm-Skripte und Docker-Befehle verwendet, z. B. um unsere Webpack-Assets zu erstellen, Abhängigkeiten zu installieren oder Befehle gleichzeitig mit anderen auszuführen. Wir würden dann schließlich einige Makefile-Befehle in den Abschnitt der package.json Skripte übersetzen, obwohl dies nicht notwendig wäre, wenn jemand nur das Makefile verwenden wollte, und es würde wie folgt aussehen:

Wir haben absichtlich viele der CICD-Befehle von Cypress weggelassen, da es sich nicht um Befehle handelte, die in der täglichen Entwicklung verwendet würden, und haben die package.json daher schlanker gehalten. Am wichtigsten ist, dass wir sofort alle Cypress-Befehle im Zusammenhang mit dem Mock-Server und dem lokalen Webpack-Entwicklungsserver im Vergleich zu den Staging-Umgebungen sehen konnten und welche die GUI „öffnen“ und nicht im Headless-Modus „laufen“.

Schritt 3: E2E-Tests lokal implementieren

Als wir mit der Implementierung von E2E-Tests mit Cypress begannen, haben wir auf unsere bestehenden Tests von WebdriverIO und STUI verwiesen, um sie zu konvertieren, und neuere Tests für andere Funktionen mit hoher Priorität hinzugefügt, die von einfachen Zustandsprüfungen bis hin zu komplizierten Happy-Path-Flows reichen. Das Übersetzen der vorhandenen Seitenobjekte und Testdateien von WebdriverIO oder STUI in entsprechende Seitenobjekte und Spezifikationen in Cypress erwies sich als ein Kinderspiel. Dies führte tatsächlich zu einem viel saubereren Code als zuvor mit weniger explizitem Warten auf Elemente und einer besseren Verkettbarkeit von Behauptungen und anderen Cypress-Befehlen.

Beispielsweise blieben die allgemeinen Schritte der Tests aus Sicht des Endbenutzers gleich, sodass die Konvertierungsarbeit darin bestand, die WebdriverIO- oder STUI-API auf die folgende Weise der Cypress-API zuzuordnen:

  • Viele Befehle erschienen und funktionierten im Wesentlichen ähnlich zu dem Punkt, an dem wir fast nur $ oder browser durch cy oder Cypress ersetzten, dh eine Seite über $(“.button”).click() zu cy.get(“.button”).click() , browser.url() zu cy.visit() , oder $(“.input”).setValue() zu cy.get(“.input”).type()
  • Die Verwendung $ oder $$ wird normalerweise zu einem cy.get(...) oder cy.contains(...) dh $$(“.multiple-elements-selector”) oder $(“.single-element-selector”) umgewandelt in cy.get(“.any-element-selector”) , cy.contains(“text”) oder cy.contains(“.any-selector”)
  • Entfernen $(“.selector”).waitForVisible(timeoutInMs) , $(“.selector”).waitUntil(...) oder $(“.selector”).waitForExist() Aufrufe zugunsten von Cypress standardmäßig Behandeln Sie die Wiederholungen und das wiederholte Abrufen der Elemente mit cy.get('.selector') und cy.contains(textInElement) . Wenn wir ein längeres Timeout als den Standard benötigen, würden wir insgesamt cy.get('.selector', { timeout: longTimeoutInMs }) und dann nach dem Abrufen des Elements den nächsten Aktionsbefehl verketten, um etwas mit dem Element zu tun, dh cy.get(“.selector”).click() .
  • Benutzerdefinierte Befehle mit Browser. addCommand('customCommand, () => {})` turned into `Cypress.Commands.add('customCommand', () => {}) und führte `cy.customCommand()` aus
  • Das Erstellen von Netzwerkanfragen für Setup oder Teardown über die API unter Verwendung einer Bibliothek namens node-fetch und das Einschließen in browser.call(() => return fetch(...)) und/oder browser.waitUntil(...) führte zu HTTP-Anforderungen in einem Cypress Node-Server über cy.request(endpoint) oder ein benutzerdefiniertes Plugin, das wir definiert haben, und Aufrufe wie cy.task(taskToHitAPIOrService) .
  • Früher, als wir warten mussten, bis eine wichtige Netzwerkanfrage möglicherweise ohne nennenswerte Änderungen an der Benutzeroberfläche beendet wurde, mussten wir manchmal auf die Verwendung von browser.pause(timeoutInMs) , aber mit Cypress haben wir dies mit der Netzwerk-Stubbing-Funktionalität verbessert und konnten zuhören und warten Sie mit cy.server() , cy.route(“method”, “/endpoint/we/are/waiting/for).as(“endpoint”)`, and `cy.wait(“@endpoint”) , bevor Sie die Aktion starten, die die Anfrage auslösen würde.

Nachdem wir einen Großteil der WebdriverIO-Syntax und -Befehle in Cypress-Befehle übersetzt hatten, haben wir dasselbe Konzept übernommen, nämlich ein Basisseitenobjekt für gemeinsam genutzte Funktionen und erweiterte Seitenobjekte für jede Seite, die wir für die Tests benötigten. Hier ist ein Beispiel für ein Basisseitenobjekt mit einer gemeinsamen open() Funktionalität, die von allen Seiten gemeinsam genutzt werden soll.

Ein erweitertes Seitenobjekt würde Getter für Elementselektoren hinzufügen, die open() Funktionalität mit seiner Seitenroute implementieren und Hilfsfunktionen wie unten gezeigt bereitstellen.

Unsere eigentlichen erweiterten Seitenobjekte verwendeten auch eine einfache Objektkarte, um alle unsere CSS-Selektoren von Elementen an einem Ort zu verwalten, die wir als Datenattribute in unsere React-Komponenten stecken, in Unit-Tests referenzieren und als unsere Selektoren in Cypress-Seitenobjekten verwenden würden. Außerdem nutzten unsere Seitenobjektklassen den Klassenkonstruktor manchmal unterschiedlich, wenn beispielsweise ein Seitenobjekt für eine Reihe ähnlich aussehender und funktionierender Seiten wie unsere Unterdrückungsseiten wiederverwendet wurde und wir Argumente übergeben würden, um die Route oder bestimmte Eigenschaften zu ändern.

Als Nebenbemerkung mussten die Teams keine Seitenobjekte verwenden, aber wir schätzten die Konsistenz des Musters, um die Seitenfunktionalität und DOM-Elementselektorreferenzen zusammen mit einer Standardklassenobjektstruktur beizubehalten, um gemeinsame Funktionalität auf allen Seiten zu teilen. Andere Teams zogen es vor, viele verschiedene Dateien mit wenig Hilfsfunktionen und ohne die Verwendung von ES6-Klassen zu erstellen, aber das Wichtigste, was man mitnehmen konnte, war, eine organisierte, vorhersehbare Methode bereitzustellen, um alles zu kapseln und Tests für eine bessere Entwicklereffizienz und Wartbarkeit zu schreiben.

Wir haben uns an dieselbe allgemeine Teststrategie wie bei unseren älteren WebdriverIO-Tests gehalten und versucht, den Test so weit wie möglich über die API einzurichten. Wir wollten insbesondere vermeiden, unseren Setup-Status über die Benutzeroberfläche aufzubauen, um keine Flockigkeit und Zeitverschwendung für die Teile einzuführen, die wir nicht testen wollten. Die meisten Tests beinhalteten diese Strategie:

  • Einrichten oder Abbauen über die API – Wenn wir das Erstellen einer Entität über die Benutzeroberfläche testen müssten, würden wir sicherstellen, dass die Entität zuerst über die API gelöscht wird. Unabhängig davon, wie der vorherige Testlauf erfolgreich oder fehlgeschlagen war, musste der Test ordnungsgemäß über die API eingerichtet oder abgebaut werden, um sicherzustellen, dass sich der Test konsistent verhält und mit den richtigen Bedingungen beginnt.
  • Anmeldung bei einem dedizierten Testbenutzer über die API – Wir haben dedizierte Testbenutzer pro Seite oder sogar pro Automatisierungstest erstellt, damit unsere Tests isoliert sind und sich nicht gegenseitig die Ressourcen zerstampfen, wenn sie parallel ausgeführt werden. Wir haben die gleiche Anfrage wie unsere Anmeldeseite über die API gestellt und das Cookie gespeichert, bevor der Test beginnt, damit wir die authentifizierte Seite direkt besuchen und mit den eigentlichen Testschritten beginnen können.
  • Automatisierung der Schritte aus Sicht des Endbenutzers – Nachdem wir uns über die API beim Benutzer angemeldet hatten, besuchten wir die Seite direkt und automatisierten die Schritte, die ein Endbenutzer ausführen würde, um einen Feature-Flow abzuschließen und zu überprüfen, ob der Benutzer die richtigen Dinge sieht und damit interagiert nach dem Weg.

Um den Test wieder auf seinen erwarteten ursprünglichen Zustand zurückzusetzen, melden wir uns bei einem dedizierten Testbenutzer über die API mit einem globalen cy.login Befehl an, setzen ein Cookie, um den Benutzer angemeldet zu halten, und führen die API-Aufrufe durch, die für die Rückgabe erforderlich sind Benutzer entweder durch cy.request(“endpoint”) - oder cy.task(“pluginAction”) -Aufrufe in den gewünschten Ausgangszustand bringen und die authentifizierte Seite besuchen, die wir direkt testen wollten. Dann würden wir die Schritte automatisieren, um einen Benutzerfunktionsablauf zu erreichen, wie im folgenden Testlayout gezeigt.

Erinnern Sie sich an die benutzerdefinierten Befehle, über die wir gesprochen haben, um sich anzumelden, cy.login() , und sich abzumelden, cy.logout() ? Wir haben sie auf diese Weise einfach in Cypress implementiert, sodass sich alle unsere Tests auf die gleiche Weise über die API bei einem Benutzer anmelden würden.

Darüber hinaus wollten wir bestimmte komplexe E-Mail-Abläufe automatisieren und verifizieren, die wir zuvor mit WebdriverIO oder STUI nicht gut bewältigen konnten. Einige Beispiele waren das Exportieren von E-Mail-Aktivitäten in eine CSV-Datei, das Durchlaufen des Ablaufs „An Kollegen senden“ für die Absenderauthentifizierung oder das Exportieren von E-Mail-Validierungsergebnissen in eine CSV-Datei. Cypress verhindert, dass man in einem Test auf mehrere Superdomains zugreift, daher war die Navigation zu einem E-Mail-Client über eine Benutzeroberfläche, die wir nicht besitzen, ungenau und keine Option.

Wir haben stattdessen Cypress-Plug-ins über ihre cy.task(“pluginAction”) Befehle entwickelt, um einige Bibliotheken innerhalb des Cypress Node-Servers zu verwenden, um eine Verbindung zu einem IMAP-Test-E-Mail-Client/Posteingang wie SquirrelMail herzustellen, um nach übereinstimmenden E-Mails in einem Posteingang nach Aufforderung zu einer Aktion zu suchen in der Benutzeroberfläche und für das Verfolgen von Umleitungslinks aus diesen E-Mails zurück in unsere Webanwendungsdomäne, um zu überprüfen, ob bestimmte Downloadseiten angezeigt wurden, und um effektiv einen ganzen Kundenfluss abzuschließen. Wir haben Plugins implementiert, die darauf warten, dass E-Mails mit bestimmten Betreffzeilen im SquirrelMail-Posteingang ankommen, E-Mails löschen, E-Mails senden, E-Mail-Ereignisse auslösen, Backend-Dienste abfragen und viel nützlichere Setups und Teardowns über die API für unsere Tests durchführen.

Um mehr Einblick in das zu geben, was wir tatsächlich mit Cypress getestet haben, haben wir eine Fülle hochwertiger Fälle wie diese behandelt:

  • Integritätsprüfungen für alle unsere Seiten, auch bekannt als Tour durch die App – Wir wollten sicherstellen, dass Seiten, die mit einigen Inhalten geladen wurden, manchmal dazu führen, dass bestimmte Backend-Dienste oder das Frontend-Hosting ausfallen. Wir haben auch empfohlen, diese Tests zuerst durchzuführen, um das mentale Muskelgedächtnis für den Aufbau von Seitenobjekten mit Selektoren und Hilfsfunktionen aufzubauen und um schnelle, funktionierende Tests zu erhalten, die gegen eine Umgebung ausgeführt werden.
  • CRUD-Operationen auf einer Seite – Wir würden die Tests über die API immer entsprechend zurücksetzen und dann speziell das Erstellen, Lesen, Aktualisieren oder Löschen in der Benutzeroberfläche testen. Wenn wir beispielsweise getestet haben, wie wir eine Domänenauthentifizierung über die Benutzeroberfläche erstellen können, mussten wir unabhängig davon, wie der letzte Testlauf endete, sicherstellen, dass die Domäne, die wir über die Benutzeroberfläche erstellen wollten, zuerst über die API gelöscht wurde, bevor wir fortfahren konnten mit den automatisierten UI-Schritten, um die Domäne zu erstellen und Kollisionen zu vermeiden. Wenn wir getestet haben, ob eine Unterdrückung über die Benutzeroberfläche gelöscht werden kann, haben wir darauf geachtet, die Unterdrückung zuerst über die API zu erstellen und dann mit den Schritten fortzufahren.
  • Testen von Suchfiltern auf einer Seite – Wir haben das Festlegen einer Reihe erweiterter Suchfilter mit E-Mail-Aktivität getestet und die Seite mit Abfrageparametern besucht, um sicherzustellen, dass die Filter automatisch ausgefüllt wurden. Wir haben auch Daten über die API für die E-Mail-Validierung hinzugefügt und erneut verschiedene Suchfilter gestartet und validiert, dass die Tabelle mit den Suchfiltern auf dieser Seite übereinstimmt.
  • Unterschiedliche Benutzerzugriffe – Bei Twilio SendGrid haben wir übergeordnete Konten, die Teamkollegen mit unterschiedlichen Bereichen oder Zugriffsberechtigungen oder untergeordnete Benutzer haben können, die ebenfalls unterschiedliche Zugriffsrechte haben und sich ähnlich wie ein übergeordnetes Konto verhalten. Teamkollegen mit Nur-Lese- oder Administratorzugriff für bestimmte Seiten und Unterbenutzer konnten bestimmte Dinge auf einer Seite sehen oder nicht sehen, und das machte es einfach, die Anmeldung bei diesen Benutzertypen zu automatisieren und zu überprüfen, was sie in den Cypress-Tests sehen oder nicht sehen.
  • Unterschiedliche Benutzerpakete – Unsere Benutzer können auch in den Arten von kostenlosen und kostenpflichtigen Paketen wie Essentials, Pro und Premier variieren, und diese Pakete können auch bestimmte Dinge auf einer Seite sehen oder nicht sehen. Wir haben uns bei Benutzern mit verschiedenen Paketen angemeldet und schnell die Funktionen, Kopien oder Seiten überprüft, auf die die Benutzer in den Cypress-Tests Zugriff hatten.

Schritt 4: Dockerisieren der Tests

Beim Ausführen jedes Buildkite-Pipelineschritts auf einer neuen AWS-Maschine in der Cloud konnten wir nicht einfach npm run cypress:run:staging aufrufen, da diese Maschinen keinen Knoten, Browser, unseren Anwendungscode oder andere Abhängigkeiten haben, um Cypress tatsächlich auszuführen Prüfungen. Als wir zuvor WebdriverIO eingerichtet haben, mussten wir drei separate Dienste in einer Docker Compose-Datei zusammenstellen, damit die richtigen Selenium-, Chrome- und Anwendungscode-Dienste für die Ausführung von Tests zusammenarbeiten.

Mit Cypress war es viel einfacher, da wir nur das Cypress-Basis-Docker-Image cypress cypress/base benötigten, um die Umgebung in einer Docker-Datei Dockerfile , und nur einen Dienst in einer docker-compose.yml Datei mit unserem Anwendungscode, um Cypress auszuführen Prüfungen. Wir werden eine Möglichkeit durchgehen, da es andere Cypress-Docker-Images gibt, die verwendet werden können, und andere Möglichkeiten, die Cypress-Tests in Docker einzurichten. Wir empfehlen Ihnen, in der Cypress-Dokumentation nach Alternativen zu suchen

Um einen Dienst mit all unserem Anwendungs- und Testcode aufzurufen, der zum Ausführen der Cypress-Tests erforderlich ist, haben wir eine Dockerfile namens Dockerfile.cypress , alle node_modules installiert und den Code in das Arbeitsverzeichnis des Images in einer Node-Umgebung kopiert. Dies würde von unserem cypress -Docker-Compose-Dienst verwendet werden, und wir haben das Dockerfile Setup auf folgende Weise erreicht:

Mit dieser Dockerfile.cypress können wir Cypress integrieren, um ausgewählte Spezifikationen für eine bestimmte Umgebungs-API und eine bereitgestellte App über einen Docker Compose-Dienst namens cypress auszuführen. Alles, was wir tun mussten, war, einige Umgebungsvariablen wie SPECS und BASE_URL zu interpolieren, um ausgewählte Cypress-Tests gegen eine bestimmte Basis-URL über den Befehl npm run cypress:run:cicd:staging , der so aussieht ”cypress:run:cicd:staging”: “cypress run --record --key --config baseUrl=$BASE_URL --env testEnv=staging” .

Diese Umgebungsvariablen werden entweder über die Einstellungen/Konfigurationsdateien der Buildkite-Pipeline festgelegt oder dynamisch exportiert, wenn Cypress-Tests ausgelöst werden, die von unserer Bereitstellungspipeline ausgeführt werden. Eine docker-compose.cypress.yml sah ähnlich aus:

Es gibt auch ein paar andere Dinge zu beachten. Beispielsweise können Sie die Umgebungsvariable VERSION sehen, die es uns ermöglicht, auf ein bestimmtes getaggtes Docker-Image zu verweisen. Wir werden später zeigen, wie wir ein Docker-Image taggen und dann dasselbe Docker-Image herunterziehen, damit dieser Build mit dem richtigen Code für die Cypress-Tests ausgeführt wird.

Darüber hinaus werden Sie auch die BUILDKITE_BUILD_ID bemerken, die zusammen mit anderen Buildkite-Umgebungsvariablen für jeden Build, den wir starten, kostenlos ist, und das ci-build-id Flag. Dies aktiviert die Parallelisierungsfunktion von Cypress, und wenn wir eine bestimmte Anzahl von Maschinen festlegen, die den Cypress-Tests zugewiesen werden, weiß es automatisch, wie diese Maschinen hochgefahren und unsere Tests getrennt werden, um sie über alle diese Maschinenknoten laufen zu lassen, um unseren Test zu optimieren und zu beschleunigen Laufzeiten.

Schließlich nutzten wir auch die Volume-Montage und die Artefakte-Funktion von Buildkite. Wir laden die Videos und Screenshots hoch, damit sie direkt über die Registerkarte „Artefakte“ der Buildkite-Benutzeroberfläche zugänglich sind, falls uns unsere bezahlten Testaufzeichnungen für den Monat ausgehen oder wir irgendwie nicht auf den Dashboard-Service zugreifen können. Immer wenn man den Cypress-Befehl „run“ im Headless-Modus ausführt, gibt es eine Ausgabe in den Ordnern cypress cypress/videos und cypress/screenshots screenshots, die man lokal überprüfen kann, und wir mounten diese Ordner einfach und laden sie als Ausfallsicherheit für uns in Buildkite hoch.

Schritt 5: Integration mit CICD

Nachdem wir die Cypress-Tests erfolgreich in einem Docker-Container in verschiedenen Umgebungen ausgeführt hatten, begannen wir mit der Integration mit Buildkite, unserem CICD-Anbieter. Buildkite bot Möglichkeiten zum Ausführen von Schritten in einer .yml -Datei auf unseren AWS-Maschinen mit Bash-Skripten und Umgebungsvariablen, die entweder im Code oder über die Buildkite-Pipeline-Einstellungen des Repositorys in der Webbenutzeroberfläche festgelegt wurden. Buildkite ermöglichte es uns auch, diese Testpipeline von unserer Hauptbereitstellungspipeline mit exportierten Umgebungsvariablen auszulösen, und wir würden diese Testschritte für andere isolierte Testpipelines wiederverwenden, die nach einem Zeitplan ausgeführt würden, den unsere QAs überwachen und prüfen würden.

Auf hoher Ebene teilten unsere Test-Buildkite-Pipelines für Cypress und auch unsere früheren WebdriverIO-Pipelines die folgenden ähnlichen Schritte:

  • Richten Sie die Docker-Images ein . Erstellen, taggen und pushen Sie die für die Tests erforderlichen Docker-Images in die Registrierung, damit wir sie in einem späteren Schritt herunterziehen können.
  • Führen Sie die Tests basierend auf Umgebungsvariablenkonfigurationen aus . Ziehen Sie die markierten Docker-Images für den spezifischen Build herunter und führen Sie die richtigen Befehle für eine bereitgestellte Umgebung aus, um ausgewählte Testsuiten aus den festgelegten Umgebungsvariablen auszuführen.

Hier ist ein Beispiel für eine pipeline.cypress.yml -Datei, die das Einrichten der Docker-Images im Schritt „Cypress-Docker-Image erstellen“ und das Ausführen der Tests im Schritt „Cypress-Tests ausführen“ demonstriert:

Eine Sache, die zu beachten ist, ist der erste Schritt, „Build Cypress Docker Image“, und wie er das Docker-Image für den Test einrichtet. Es verwendete den Docker-Compose- build -Befehl, um den cypress -Dienst mit dem gesamten Anwendungstestcode zu erstellen, und markierte ihn mit der latest Umgebungsvariablen und der ${VERSION} -Umgebungsvariable, damit wir schließlich dasselbe Image mit dem richtigen Tag für diesen Build in a abrufen können zukünftigen Schritt. Jeder Schritt kann irgendwo auf einer anderen Maschine in der AWS-Cloud ausgeführt werden, sodass die Tags das Image für die spezifische Buildkite-Ausführung eindeutig identifizieren. Nach dem Taggen des Images haben wir das neueste Image mit Versions-Tags zur Wiederverwendung in unsere private Docker-Registrierung hochgeladen.

Im Schritt „Cypress-Tests ausführen“ ziehen wir das Image herunter, das wir im ersten Schritt erstellt, markiert und gepusht haben, und starten den Cypress-Dienst, um die Tests auszuführen. Basierend auf Umgebungsvariablen wie SPECS und BASE_URL würden wir spezifische Testdateien für eine bestimmte bereitgestellte App-Umgebung für diesen spezifischen Buildkite-Build ausführen. Diese Umgebungsvariablen würden über die Buildkite-Pipelineeinstellungen festgelegt oder dynamisch von einem Bash-Skript ausgelöst, das ein Buildkite-Auswahlfeld analysiert, um zu bestimmen, welche Testsuiten in welcher Umgebung ausgeführt werden sollen.

Wenn wir auswählen, welche Tests während unserer Buildkite CICD-Bereitstellungspipeline ausgeführt werden sollen, und eine dedizierte Pipeline für ausgelöste Tests mit bestimmten exportierten Umgebungsvariablen auslösen, befolgen wir die Schritte in der Datei pipeline.cypress.yml , um dies zu erreichen. Ein Beispiel für das Auslösen der Tests nach dem Bereitstellen von neuem Code in einer Feature-Branch-Umgebung aus der Bereitstellungspipeline sieht folgendermaßen aus:

Die ausgelösten Tests würden in einer separaten Pipeline ausgeführt, und nachdem wir dem Link „Build #639“ gefolgt wären, würden wir zu den Build-Schritten für den ausgelösten Testlauf wie unten gelangen:

Durch die Wiederverwendung derselben pipeline.cypress.yml -Datei für unsere dedizierten Cypress Buildkite-Pipelines, die nach einem Zeitplan ausgeführt werden, haben wir Builds wie denjenigen, der unsere „P1“-E2E-Tests mit der höchsten Priorität ausführt, wie auf dem Foto unten gezeigt:

Alles, was wir tun müssen, ist, die richtigen Umgebungsvariablen für Dinge wie die auszuführenden Spezifikationen und die zu treffende Backend-Umgebung in den Einstellungen der Buildkite-Pipeline festzulegen. Dann können wir einen Cron-geplanten Build konfigurieren, der sich auch in den Pipeline-Einstellungen befindet, um alle eine bestimmte Anzahl von Stunden zu starten, und wir können loslegen. We would then create many other separate pipelines for specific feature pages as needed to run on a schedule in a similar way and we would only vary the Cron schedule and environment variables while once again uploading the same `pipeline.cypress.yml` file to execute.

In each of those “Run Cypress tests” steps, we can see the console output with a link to the recorded test run in the paid Dashboard Service, the central place to manage your team's test recordings, billing, and other Cypress stats. Following the Dashboard Service link would take us to a results view for developers and QAs to take a look at the console output, screenshots, video recordings, and other metadata if required such as this:

Step 6: Comparing Cypress vs. WebdriverIO/STUI

After diving into our own custom Ruby Selenium solution in STUI, WebdriverIO, and finally Cypress tests, we recorded our tradeoffs between Cypress and Selenium wrapper solutions.

Vorteile

  • It's not another Selenium wrapper – Our previous solutions came with a lot of Selenium quirks, bugs, and crashes to work around and resolve, whereas Cypress arrived without the same baggage and troubles to deal with in allowing us full access to the browser.
  • More resilient selectors – We no longer had to explicitly wait for everything like in WebdriverIO with all the $(.selector).waitForVisible() calls and now rely on cy.get(...) and c y.contains(...) commands with their default timeout. It will automatically keep on retrying to retrieve the DOM elements and if the test demanded a longer timeout, it is also configurable per command. With less worrying about the waiting logic, our tests became way more readable and easier to chain.
  • Vastly improved developer experience – Cypress provides a large toolkit with better and more extensive documentation for assertions, commands, and setup. We loved the options of using the Cypress GUI, running in headless mode, executing in the command-line, and chaining more intuitive Cypress commands.
  • Significantly better developer efficiency and debugging – When running the Cypress GUI, one has access to all of the browser console to see some helpful output, time travel debug and pause at certain commands in the command log to see before and after screenshots, inspect the DOM with the selector playground, and discern right away at which command the test failed. In WebdriverIO or STUI we struggled with observing the tests run over and over in a browser and then the console errors would not point us toward and would sometimes even lead us astray from where the test really failed in the code. When we opted to run the Cypress tests in headless mode, we got console errors, screenshots, and video recordings. With WebdriverIO we only had some screenshots and confusing console errors. These benefits resulted in us cranking out E2E tests much faster and with less overall time spent wondering why things went wrong. We recorded it took less developers and often around 2 to 3 times less days to write the same level of complicated tests with Cypress than with WebdriverIO or STUI.
  • Network stubbing and mocking – With WebdriverIO or STUI, there was no such thing as network stubbing or mocking in comparison to Cypress. Now we can have endpoints return certain values or we can wait for certain endpoints to finish through cy.server() and cy.route() .
  • Less time to set up locally – With WebdriverIO or STUI, there was a lot of time spent up front researching which reporters, test runners, assertions, and services to use, but with Cypress, it came bundled with everything and started working after just doing an npm install cypress.
  • Less time to set up with Docker – There are a bunch of ways to set up WebdriverIO with Selenium, browser, and application images that took us considerably more time and frustration to figure out in comparison to Cypress's Docker images to use right out of the gate.
  • Parallelization with various CICD providers – We were able to configure our Buildkite pipelines to spin up a certain number of AWS machines to run our Cypress tests in parallel to dramatically speed up the overall test run time and uncover any flakiness in tests using the same resources. The Dashboard Service would also recommend to us the optimal number of machines to spin up in parallel for the best test run times.
  • Paid Dashboard Service – When we run our Cypress tests in a Docker container in a Buildkite pipeline during CICD, our tests are recorded and stored for us to look at within the past month through a paid Dashboard Service. We have a parent organization for billing and separate projects for each frontend application to check out console output, screenshots, and recordings of all of our test runs.
  • Tests are way more consistent and maintainable – Tests passed way more consistently with Cypress in comparison to WebdriverIO and STUI where the tests kept on failing so much to the point where they were often ignored. Cypress tests failing more often signaled actual issues and bugs to look into or suggested better ways to refactor our tests to be less flaky. With WebdriverIO and STUI, we wasted a lot more time in maintaining those tests to be somewhat useful, whereas with Cypress, we would every now and then adjust the tests in response to changes in the backend services or minor changes in the UI.
  • Tests are faster – Builds passed way more consistently and overall test run times would be around 2 to 3 times faster when run serially without parallelization. We used to have overall test runs that would take hours with STUI and around 40 minutes with WebdriverIO, but now with way more tests and with the help of parallelization across many machine nodes, we can run over 200 tests in under 5 minutes .
  • Room to grow with added features in the future – With a steady open-source presence and dedicated Cypress team working towards releasing way more features and improvements to the Cypress infrastructure, we viewed Cypress as a safer bet to invest in rather than STUI, which would require us to engineer and solve a lot of the headaches ourselves, and WebdriverIO, which appeared to feel more stagnant in new features added but with the same baggage as other Selenium wrappers.

Nachteile

  • Lack of cross-browser support – As of this writing, we can only run our tests against Chrome. With WebdriverIO, we could run tests against Chrome, Firefox, Safari, and Opera. STUI also provided some cross-browser testing, though in a much limited form since we created a custom in-house solution
  • Cannot integrate with some third-party services – With WebdriverIO, we had the option to integrate with services like BrowserStack and Sauce Labs for cross-browser and device testing. However, with Cypress there are no such third-party integrations but there are some plugins with services like Applitools for visual regression testing available. STUI, on the other hand, also had some small integrations with TestRail , but as a compromise, we log out the TestRail links in our Cypress tests so we can refer back to them if we needed to.
  • Requires workarounds to test with iframes – There are some issues around handling iframes with Cypress. We ended up creating a global Cypress command to wrap how to deal with retrieving an iframe's contents as there is no specific API to deal with iframes like how WebdriverIO does.

To summarize our STUI, WebdriverIO, and Cypress comparison, we analyzed the overall developer experience (related to tools, writing tests, debugging, API, documentation, etc.), test run times, test passing rates, and maintenance as displayed in this table:

Following our analysis of the pros and cons of Cypress versus our previous solutions, it was pretty clear Cypress would be our best bet to accomplish our goal of writing fast, valuable, maintainable, and debuggable E2E tests we could integrate with CICD.

Though Cypress lacked features such as cross-browser testing and other integrations with third-party services that we could have had with STUI or WebdriverIO, we most importantly need tests that work more often than not and with the right tools to confidently fix broken ones. If we ever needed cross-browser testing or other integrations we could always still circle back and use our knowledge from our trials and experiences with WebdriverIO and STUI to still run a subset of tests with those frameworks.

We finally presented our findings to the rest of the frontend organization, engineering management, architects, and product. Upon demoing the Cypress test tools and showcasing our results between WebdriverIO/STUI and Cypress, we eventually received approval to standardize and adopt Cypress as our E2E testing library of choice for our frontend teams.

Step 7: Scaling to Other Frontend Teams

After successfully proving that using Cypress was the way to go for our use cases, we then focused on scaling it across all of our frontend teams' repos. We shared lessons learned and patterns of how to get up and running, how to write consistent, maintainable Cypress tests, and of how to hook those tests up during CICD or in scheduled Cypress Buildkite pipelines.

To promote greater visibility of test runs and gain access to a private monthly history of recordings, we established our own organization to be under one billing method to pay for the Dashboard Service with a certain recorded test run limit and maximum number of users in the organization to suit our needs.

Once we set up an umbrella organization, we invited developers and QAs from different frontend teams and each team would install Cypress, open up the Cypress GUI, and inspect the “Runs” and “Settings” tab to get the “Project ID” to place in their `cypress.json` configuration and “Record Key” to provide in their command options to start recording tests to the Dashboard Service. Finally, upon successfully setting up the project and recording tests to the Dashboard Service for the first time, logging into the Dashboard Service would show that team's repo under the “Projects” tab like this:

When we clicked a project like “mako”, we then had access to all of the test runs for that team's repo with quick access to console output, screenshots, and video recordings per test run upon clicking each row as shown below:

For more insights into our integration, we set up many separate dedicated test pipelines to run specific, crucial page tests on a schedule like say every couple hours to once per day. We also added functionality in our main Buildkite CICD deploy pipeline to select and trigger some tests against our feature branch environment and staging environment.

Wie zu erwarten war, ging dies schnell durch unsere zugewiesenen aufgezeichneten Testläufe für den Monat, zumal es mehrere Teams gibt, die auf unterschiedliche Weise zu Tests beitragen und diese auslösen. Aus Erfahrung empfehlen wir, darauf zu achten, wie viele Tests nach einem Zeitplan ausgeführt werden, wie häufig diese Tests ausgeführt werden und welche Tests während CICD ausgeführt werden. Es kann einige redundante Testläufe und andere Bereiche geben, die sparsamer sein müssen, wie z. B. das Zurückwählen der Häufigkeit geplanter Testläufe oder möglicherweise das vollständige Abschaffen einiger, um Tests nur während CICD auszulösen.

Die gleiche Regel der Sparsamkeit gilt für das Hinzufügen von Benutzern, da wir betont haben, dass nur Entwickler und QAs in den Frontend-Teams Zugriff erhalten, die den Dashboard-Service stark nutzen werden, und nicht das obere Management und andere Leute außerhalb dieser Teams, um diese begrenzten Plätze zu besetzen.

Worauf wir uns bei Cypress freuen

Wie wir bereits erwähnt haben, hat Cypress in der Open-Source-Community und mit seinem engagierten Team, das für die Bereitstellung hilfreicherer Funktionen für unsere E2E-Tests verantwortlich ist, viel Potenzial und Wachstumspotenzial gezeigt. Viele der Nachteile, die wir hervorgehoben haben, werden derzeit angegangen, und wir freuen uns auf Dinge wie:

  • Cross-Browser-Unterstützung – Dies ist ein großer Punkt, da ein Großteil der Zurückweisung von uns bei der Einführung von Cypress auf die Verwendung von nur Chrome im Vergleich zu Selenium-basierten Lösungen zurückzuführen ist, die Browser wie Firefox, Chrome und Safari unterstützten. Glücklicherweise haben sich zuverlässigere, wartbare und debugfähige Tests für unser Unternehmen durchgesetzt, und wir hoffen, unsere Testsuiten in Zukunft mit mehr Cross-Browser-Tests erweitern zu können, wenn das Cypress-Team eine solche Cross-Browser-Unterstützung veröffentlicht.
  • Network Layer Rewrite – Dies ist auch eine große Sache, da wir dazu neigen, die Fetch-API in unseren neueren React-Bereichen stark zu verwenden, und in älteren Backbone/Marionette-Anwendungsbereichen verwendeten wir immer noch jQuery AJAX und normale XHR-basierte Aufrufe. Wir können Anfragen in den XHR-Bereichen leicht ausblenden oder abhören, mussten aber einige hackige Workarounds mit fetch polyfills durchführen, um den gleichen Effekt zu erzielen. Das Neuschreiben der Netzwerkschicht soll helfen, diese Probleme zu lindern.
  • Inkrementelle Verbesserungen des Dashboard-Dienstes – Wir haben in letzter Zeit bereits einige neue UI-Änderungen am Dashboard-Dienst gesehen und hoffen, dass er mit mehr Statistik-Visualisierungen und Aufschlüsselungen nützlicher Daten weiter wächst. Wir verwenden auch die Parallelisierungsfunktion intensiv und sehen uns unsere fehlgeschlagenen Testaufzeichnungen im Dashboard-Dienst oft an, sodass alle iterativen Verbesserungen des Layouts und/oder der Funktionen schön zu sehen wären.

Mit Cypress in die Zukunft

Für unsere Organisation haben wir die Entwicklereffizienz, das Debugging und die Stabilität der Cypress-Tests geschätzt, wenn sie im Chrome-Browser ausgeführt werden. Wir haben konsistente, wertvolle Tests mit geringerem Wartungsaufwand und mit vielen Tools zur Entwicklung neuer Tests und zur Korrektur bestehender Tests erzielt.

Insgesamt überwogen die uns zur Verfügung stehende Dokumentation, API und Tools alle Nachteile bei weitem. Nachdem wir die GUI von Cypress und den kostenpflichtigen Dashboard-Service kennengelernt hatten, wollten wir definitiv nicht zu WebdriverIO oder unserer benutzerdefinierten Ruby Selenium-Lösung zurückkehren.

Wir haben unsere Tests mit Buildkite verbunden und unser Ziel erreicht , eine Möglichkeit zu bieten, konsistente, debugfähige, wartbare und wertvolle E2E-Automatisierungstests für unsere Frontend-Anwendungen zu schreiben, die in CICD integriert werden können . Wir haben den restlichen Frontend-Teams, höheren Ingenieuren und Produktbesitzern die Vorteile der Einführung von Cypress und der Abschaffung von WebdriverIO und STUI bewiesen.

Hunderte von Tests später über Frontend-Teams in Twilio SendGrid und wir haben viele Fehler in unserer Staging-Umgebung entdeckt und konnten schnell alle fehlerhaften Tests auf unserer Seite mit viel mehr Zuversicht als je zuvor beheben. Entwickler und QAs fürchten nicht mehr den Gedanken, E2E-Tests zu schreiben, sondern freuen sich jetzt darauf, sie für jede einzelne neue Funktion zu schreiben, die wir veröffentlichen, oder für jede ältere Funktion, die etwas mehr Abdeckung gebrauchen könnte.