Geben Sie alle Dinge in Ihren Cypress-Tests in Schreibschrift ein #frontend@twiliosendgrid
Veröffentlicht: 2020-11-14Bei Twilio SendGrid schreiben wir die meisten unserer Frontend-Webanwendungen, insbesondere neue Seiten und Funktionen, heute mit TypeScript und React, um die Typüberprüfung, Wartbarkeit und Dokumentation unserer Codebasis zu verbessern. Als wir vor über zwei Jahren anfingen, Cypress-Tests zu schreiben, waren die meisten unserer Seitenobjekte, Helfer und Spezifikationsdateien noch in JavaScript implementiert, und wir waren hauptsächlich auf Cypress Version 3.xx. Neuere Tests wurden bereits in TypeScript geschrieben, aber wir hatte immer noch eine große Anzahl von Dateien zu konvertieren und zu TypeScript zu migrieren.
Wir wollten die Vorteile der vollständigen Eingabe unserer Komponenten, Unit-Tests und Cypress E2E-Tests nutzen . Was den Prozess vereinfachte, war die Migration auf eine neuere Version von Cypress, um die Vorteile der standardmäßigen TypeScript-Unterstützung seit Cypress 4.4.0 zu nutzen.
Wenn Sie einen Schritt zurücktreten und allgemeiner erfahren möchten, wie man über das Schreiben von E2E-Tests nachdenkt, können Sie sich gerne diesen Blogbeitrag ansehen. Wenn Sie auch einen 1000-Fuß-Überblick über die häufigsten Dinge sehen möchten, die wir beim Schreiben von Cypress-Tests in verschiedenen Umgebungen verwendet oder zuvor getan haben, können Sie sich auf diesen Blogbeitrag beziehen, bevor Sie mit dem Hinzufügen von TypeScript zu Ihren Cypress-Tests beginnen. Dieser Blogbeitrag geht davon aus, dass Sie mit Cypress-Tests vertraut sind und ihre API bereits verwendet haben.
Wenn Sie bereit sind zu sehen, wie wir die Dinge eingegeben haben, schauen wir uns zunächst einige anfängliche Änderungen an, wie das Hinzufügen von TypeScript-Unterstützung zu Cypress, wenn Sie von einer älteren Version kommen.
Migration von Cypress 3.x auf >= 4.4.0
Für diejenigen, die ihre Cypress-Infrastruktur bereits so konfiguriert haben, dass sie mit TypeScript in den Cypress-Versionen 3.xx bis 4.4.3 funktioniert, haben Sie höchstwahrscheinlich einige Versuche und Fehler beim Einrichten der richtigen Webpack-Präprozessorkonfiguration in Ihrer plugins/index.js
. Für das Twilio SendGrid-Team gab es einige Probleme mit bestimmten Dateien, die JavaScript-Dateien in den plugins
und support
-Ordnern sein mussten, und schwer zu debuggende Cypress-Fehler tauchten auf. Sobald etwas funktioniert, sollte es ungefähr so aussehen.
Nach dem Upgrade von einer früheren Version von 3.xx konnten wir unsere Webpack-Präprozessorkonfiguration löschen, unsere plugins/index.js
-Datei durch eine index.ts
-Datei ersetzen, ein wenig an der tsconfig.json unseres Cypress-Ordners tsconfig.json
und schließlich Verwenden von TS-Dateien in unserem Cypress-Ordner (wie some_page.spec.ts
, index.d.ts
oder page_object.ts
) – keine Webpack-Präprozessorkonfiguration mehr und es hat einfach funktioniert! Wir waren erfreut darüber, wie viel sauberer und schöner es war, Ihre eigene Webpack-Präprozessorkonfiguration nicht zu verwalten und eine bessere TypeScript-Abdeckung über unsere Dateien zu haben, wie unten gezeigt.
Nachdem die TypeScript-Unterstützung abgedeckt war, haben wir uns dann die reale Beispielanwendung des Cypress-Teams, das cypress-real-world-app repo, angesehen, um mehr darüber zu erfahren, wie man Dinge besser eintippt. Wir haben herausgefunden, wie man die cy.task(“pluginName”, { … })
und Cypress.env(“someEnvVar”)
für eine bessere Verkettung eingibt und Intellisense-Unterstützung beim Aktualisieren von Dateien eingibt. Wir haben auch die begleitende TypeScript-Dokumentation durchforstet. Dadurch lernten wir, Dinge wie unseren benutzerdefinierten Befehl cy.login()
und eine tsconfig.json
Konfigurationsdatei einzurichten. Die Beispielanwendung hat auch eine tsconfig.json
, auf die Sie verweisen können, die Ihnen eine großartige grundlegende TypeScript-Konfiguration bieten kann, die Sie an Ihre Vorlieben anpassen können. Wir empfehlen Ihnen, mit den neuesten Cypress-Versionen auf dem Laufenden zu bleiben, in die offiziellen Cypress-Ressourcen einzutauchen und mit Ihren Typdefinitionsdateien zu experimentieren.
Eingabe von benutzerdefinierten Befehlen
Wir haben einige globale benutzerdefinierte Befehle wie cy.login()
, um die Anmeldung über die API zu handhaben, damit sie in allen unseren Spezifikationsdateien wiederverwendet werden kann. Hier ist ein Beispiel dafür, wie Sie Ihren eigenen benutzerdefinierten Anmeldebefehl eingeben können, wenn Sie Benutzeranmeldeinformationen erhalten und ein Authentifizierungstoken zurückgeben.
Umgebungsvariablen eingeben
Für den Umgang mit mehreren Testumgebungen wie Dev und Staging nutzten wir die Konfiguration von Umgebungsvariablen wie: „testEnv“, um die Umgebung zu speichern, gegen die wir derzeit testen, z. B. Staging, „apiHost“, um den Backend-API-Host zu speichern, und andere Variablen. Sie können Ihre Cypress.env()
Aufrufe eingeben, um Funktionen und andere Objekte, die auf die Verwendung dieser Umgebungsvariablenwerte angewiesen sind, besser auszutippen.
Plugins eingeben
Wir haben viele cy.task()
in-Funktionen erstellt, um Dinge von API-Aufrufen, Abfragen von Diensten und Überprüfung auf übereinstimmende E-Mails in Test-E-Mail-Posteingängen zu handhaben. Früher wurde beim Verketten dieser Funktionsaufrufe wie cy.task().then((data) => {})
die verkettete betroffene Person als any
oder unknown
eingegeben, was für unsere TypeScript-Dateien nicht besonders gut war. Wir haben durch die Cypress-Beispiele herausgefunden, wie die Plugins besser auf der Grundlage des Plugin-Namens und der in den Funktionsaufrufen übergebenen Argumente eingegeben werden können. Dadurch konnten unsere TypeScript-Dateien erkennen, was der verkettete Datentyp wäre.
Ein subtiles Problem, auf das wir gestoßen sind, war, dass der Plugin-Name und die Argumente genau mit der Eingabe übereinstimmen müssen. Wir fanden es wichtig, den Mauszeiger über den verketteten .then()
Typ und das cy.task()
Argumentobjekt in Ihrem Editor zu verdoppeln Überprüfen Sie, ob die Typen korrekt übereinstimmen. Manchmal, wenn Sie ein verkettetes Subjekt aus einer anderen Cypress-Funktion wie cy.getCookie(“auth_token”).its(“value”).then((token) => { })
oder cy.wrap(data).then((data) => {})
müssen Sie auch diese verketteten Datenargumente eingeben, bevor Sie sie als Funktionsargument cy.task(..., { token, data })
, sonst sehen Sie immer noch cy.task(...).then((data) => { })
Datenteil typisiert als any
oder unknown
. Bei vielen verketteten Cypress-Funktionstypen wie .its(“value”).then((token: string) => {})
oder cy.wrap(data).then((data: DataType) => {})
, bevor Sie sie als Teil des Argumentobjekts cy.task()
, um sicherzustellen, dass die Typen wieder funktionieren.
Wir haben separate Plugin-Typescript-Dateien erstellt, die Funktionen zur Verwendung in unseren plugins/index.ts
. Da die Anzahl der Plugins wächst, empfehlen wir Ihnen, diese Plugin-Funktionsimplementierungen nach Seite oder Funktion zu organisieren, um Ihre plugins/index.ts
-Datei klein zu halten. Sie sollten es auf einen Blick leichter lesbar machen, wo Sie alle Ihre cy.task(...)
Funktionen in Ihrer Datei plugins/index.ts
. Sie können diese Aufgabenfunktionen schließlich folgendermaßen in Ihre index.d.ts
:
Alles zusammen in einer Typdeklarationsdatei
Wir haben alle unsere Typen für unsere benutzerdefinierten Befehle, Umgebungsvariablen und Plugins in einer index.d.ts
-Datei im support
-Ordner abgelegt. Wir empfehlen Ihnen, alle Ihre Cypress-Typen auch in einer Haupt-TypeScript-Definitionsdatei zu platzieren, um die Dinge zu organisieren. Um fehlende Typen in externen Abhängigkeiten zu umgehen, die in Ihrem Cypress-Testcode verwendet werden, können Sie auch Moduldateien wie „some-lib.d.ts“ definieren, die das declare module 'some-lib'
enthalten würden, um die TypeScript-Warnungen der Bibliothek zu umgehen. Sie können sogar die Funktion zum Importieren von Typen von TypeScript verwenden, um Typen/Schnittstellen einzufügen, die in Ihren anderen Plugins/Utils-Dateien definiert sind, um zu vermeiden, dass Ihre Typdefinitionen in mehreren Dateien dupliziert werden. Sie können diese Typen innerhalb eines Cypress-Namespace hinzufügen und folgendermaßen organisieren:
Eingeben von Testbefestigungsobjekten, Seitenobjekten und Spezifikationsdateien
Wenn wir verschiedene Benutzer und Metadaten für eine Testumgebung laden möchten, haben wir zuvor gezeigt, wie wir Umgebungsvariablen wie „testEnv“ mit Werten von „testing“ oder „staging“ kombinieren können, um „testing“ oder „ staging“-Objekte aus dem gesamten Test Fixtures-Objekt. Sie können diese Testvorrichtungs-Umgebungsobjekte mit Generika eingeben, um eine konsistente Struktur für alle Ihre Spezifikationen zu erhalten. Für jede Testumgebung können Sie dieselben Benutzeranmeldeinformationen und Metafelder verwenden, indem Sie einen generischen Typ für einen Test verwenden, um so viele Eigenschaften wie nötig hinzuzufügen. Siehe das Beispiel unten.
Das Eingeben der Seitenobjekte und das Eingeben der entsprechenden Spezifikationsdateien hängen von den Cypress-Befehlen, Plugins und anderen Dienstprogrammen ab, die Sie verwenden. Zum größten Teil erfordert das Eintippen der Seitenobjekte oder Spezifikationsdateien nicht viele Änderungen von ihren JavaScript-Gegenstücken (vorausgesetzt, Sie haben die Plugins und Umgebungsvariablenaufrufe bereits eingetippt). Gelegentlich müssen für eine von Ihnen definierte Seitenobjekt-Hilfsfunktion einige Argumente eingegeben werden, oder die Antwort, die von einem cy.request()
, muss möglicherweise mit as
wie response.body as SomeType
. Insgesamt kann Ihr Editor wie VSCode die verketteten Typen Ihrer cy.task()
oder cy.customCommand()
Aufrufe automatisch erkennen, ohne dass Sie Ihren Spezifikationsdateien weitere Typen hinzufügen müssen, um TypeScript-Warnungen zu kompensieren.
Hier ist ein Beispiel für Teile eines Seitenobjekts mit einigen Hilfsfunktionen und einer Spezifikationsdatei, die das Seitenobjekt, den benutzerdefinierten Anmeldebefehl und Plugin-Aufgaben verwendet.
Fazit
Das Hinzufügen von TypeScript zu unseren Cypress-Tests hat uns geholfen, Fehler zu vermeiden und unsere Entwicklererfahrung beim Schreiben von Cypress-Tests zu verbessern. Wenn wir die Funktionsaufrufe cy.task()
, Cypress.env()
und cy.customCommand()
verwenden, können wir eine bessere Typüberprüfung der Funktionsargumente und -ausgaben erhalten und die Vorteile der Codevervollständigung in unserer IDE wie VSCode nutzen.
Der Schlüssel besteht darin, Ihre eigene Typdeklarationsdatei zu erstellen, z. B. eine index.d.ts
-Datei, in der Sie die „Cypress“- und „Chainable“-Schnittstellen basierend auf den benutzerdefinierten Befehlen, Umgebungsvariablen und Task-Plugin-Funktionen überschreiben oder erweitern können verwenden. In Ihren Seitenobjekt- und Spezifikations-TypeScript-Dateien können Sie dann diese Cypress-Funktionen verwenden und den Mauszeiger über die Definition der erwarteten Eingabe- und Ausgabetypen bewegen oder ihr folgen.
Versuchen Sie außerdem, TypeScript mit Cypress zu verwenden, da es in seinen neueren Versionen eine standardmäßige Unterstützung für TypeScript bietet. Testen Sie, ob es Ihnen hilft, Funktionen klarer zu dokumentieren und eine falsche API-Nutzung zu vermeiden. Ihre TypeScript-Tests werden wahrscheinlich immer noch Ihren JavaScript-Cypress-Tests ähneln, sodass Sie einige Tests kontinuierlich konvertieren können, um die Ansätze zu vergleichen und gegenüberzustellen.
Wenn Sie an weiteren Beiträgen interessiert sind, die sich darauf beziehen, was wir aus unseren Cypress-Tests gelernt haben, sollten Sie sich die folgenden Artikel ansehen:
- Was beim Schreiben von E2E-Tests zu beachten ist
- 1.000-Fuß-Übersicht über das Schreiben von Cypress-Tests
- Umgang mit E-Mail-Flows in Cypress-Tests
- Ideen zum Konfigurieren, Organisieren und Konsolidieren Ihrer Cypress-Tests
- Integration von Cypress-Tests mit Docker, Buildkite und CICD