So fügen Sie Ihrem Projekt End-to-End-Tests mit Cypress hinzu

In diesem Beitrag gehe ich durch den Prozess des Hinzufügens von Cypress-End-to-End-Tests zu einem vorhandenen Projekt.

Foto von rawpixel auf Unsplash

Warum End-to-End-Tests?

Alle Testmethoden haben Vor- und Nachteile. End-to-End-Tests sind den tatsächlichen Benutzertests am nächsten, was einer der Hauptvorteile ist. Je näher der Test an der Nachahmung des Benutzers liegt, desto wahrscheinlicher ist es, dass Probleme auftreten, die beim Benutzer auftreten können.

Wenn Sie möchten, dass ein Benutzer das Twittern auf Twitter testet, können Sie ihm Folgendes mitteilen:

Gehen Sie zu https://twitter.com und melden Sie sich an. Klicken Sie in das Textfeld mit dem Platzhaltertext "Was passiert?" Und geben Sie "Dies ist ein Test-Tweet" ein. Klicken Sie auf die Schaltfläche mit dem Text "Tweet". Gehen Sie jetzt zu Ihrer Profilseite und schauen Sie sich den ersten Tweet an. Der Text sollte gleich "Dies ist ein Test-Tweet" sein.

Im Idealfall geben Sie Ihrem End-to-End-Testläufer ähnliche Anweisungen.

Sie könnten stattdessen Elemente anhand von Klassennamen oder IDs suchen lassen, aber was ist, wenn sich die Klassennamen oder IDs absichtlich ändern? Oder was, wenn sich der Text versehentlich ändert? Wenn Sie den Testläufer angewiesen haben, auf die Schaltfläche mit dem Klassennamen zu klicken, wurde der Test möglicherweise falsch bestanden. Sie könnten argumentieren:

Was ist, wenn Sie den Text absichtlich ändern möchten? Vielleicht möchten Sie den Text der Schaltfläche so ändern, dass "Senden" anstelle von "Tweet" angezeigt wird?

Das ist vielleicht ein gültiges Argument, aber Sie könnten auch argumentieren, dass der Test fehlschlagen soll, wenn sich der Text ändert. Letztendlich müssen Sie sich fragen: "Wenn sich dieser Text geändert hat, möchte ich, dass meine Tests unterbrochen werden?". Im Fall von "Senden" vs "Tweet" möchten Sie möglicherweise nicht, dass der Test unterbrochen wird, aber möglicherweise, wenn Text wurde versehentlich gelöscht oder falsch geschrieben, dann möchten Sie, dass sie unterbrochen werden. Sie können nicht wirklich beides haben, also müssen Sie die beste Entscheidung für sich und Ihre App treffen.

Einige Nachteile von End-to-End-Tests sind:

  • Sie sind "teuer", das heißt, sie brauchen viel Zeit zum Laufen. Für jeden Test muss ein vollständiger Browser mit tatsächlichen Browserereignissen instanziiert werden, was mehr Zeit in Anspruch nimmt als Unit- oder Integrationstests.
  • Es macht einen guten Job, um Probleme zu finden, aber es hilft Ihnen nicht, diese Probleme zu lösen. Ihr End-to-End-Test hat möglicherweise festgestellt, dass das Zahlungssystem defekt ist, aber Sie wissen nicht, welcher Ihrer 10 Microservices das Problem verursacht hat.

Welches End-to-End-Test-Framework soll ausgewählt werden?

Es gibt eine Reihe von End-to-End-Test-Frameworks, und es kann schwierig sein, das „richtige“ auszuwählen. Ich werde meine Gedanken ganz kurz mitteilen, obwohl ich zugegebenermaßen nur Cypress verwendet habe:

Test Cafe - Dies ist das neueste End-to-End-Test-Framework und es scheint sehr gut zu sein. Es lässt sich in Browser Stack integrieren, bietet gute Browserunterstützung, unterstützt alle Front-End-Frameworks, unterstützt die ES2015 + -Syntax und auch Typoskript. Es sieht so aus, als müsste man die kostenpflichtige Version haben, um aufgezeichnete Tests zu erhalten.

Puppenspieler - Dies ist Googles Open-Source-Lösung. Es scheint leicht und einfach zu starten. Es ist Open Source und läuft auf Chrom (kopflos oder nicht). Puppenspieler ist ein Test-Framework, das eine Fülle von Funktionen bietet, besser als keine End-to-End-Tests, aber keine vollständige Lösung. Sie haben auch kürzlich mitgeteilt, dass sie mit Firefox experimentieren.

Cypress - Es ist ein entwicklerfreundliches Open Source-Testframework. Cypress zeichnet Schnappschüsse und Videos Ihrer Tests auf, verfügt über eine Testrunner-Konsole und ist kostenlos. Entwicklern und QS-Ingenieuren fällt der Einstieg leicht. Derzeit werden nur Chrome-Varianten unterstützt, die Roadmap wird jedoch browserübergreifend unterstützt. Es gibt keine native Iframe-Unterstützung, es gibt jedoch Problemumgehungen. Cypress verfügt über ein eigenes versprechensbasiertes System, das Sie verwenden müssen (ES6-Versprechungen können nicht verwendet werden).

Hier finden Sie eine gute Quelle für einen ausführlichen Vergleich von Cypress und Test Cafe: https://medium.com/yld-engineering-blog/evaluating-cypress-and-testcafe-for-end-to-end-testing-fcd0303d2103

Foto von chuttersnap auf Unsplash

Anfangen

Das Projekt, das ich verwenden werde, ist https://ydkjs-exercises.com. Es handelt sich um eine einseitige Webanwendung, die Übungen enthält, mit denen Benutzer ihr Wissen beim Lesen testen können. Sie kennen kein JavaScript. Es verwendet React, React Router und die React Context API. Es gibt Unit- / Integrationstests mit Jest- und React-Testing-Library. Und jetzt werde ich End-to-End-Tests mit Cypress hinzufügen!

Ich werde den Fortschritt über Tags verfolgen, beginnend mit cypress-0, und die ganze Zahl bei jedem Schritt inkrementieren. Hier ist der Ausgangspunkt.

Der erste Schritt ist die Installation von Cypress als devDependency:

npm install cypress --save-dev

Die aktuelle Version von Cypress ist v3.1.1. In den Dokumenten wird erwähnt, dass das Cypress npm-Paket ein Wrapper um die Cypress-Binärdatei ist. Ab Version 3.0 wird die Binärdatei in ein globales Cache-Verzeichnis heruntergeladen, um projektübergreifend verwendet zu werden.

Lassen Sie uns nun Cypress öffnen. Wenn Sie eine npm-Version> 5.2 verwenden, können Sie diese folgendermaßen öffnen:

Npx-Zypresse geöffnet

Dies öffnet Cypress mit einem Begrüßungs-Modal, das uns mitteilt, dass sie eine Reihe von Dateien zu unserem Projekt hinzugefügt haben:

Wenn Sie auf klicken, um das Modal zu schließen, sehen Sie, dass es eine Reihe von Beispieltests gibt und dass wir sie in Chrome 70 ausführen können. Wenn Sie auf „Runs“ klicken, sehen Sie, dass Sie ein Cypress-Dashboard einrichten können, um zu schauen bei früheren Läufen. Darüber werden wir uns keine Sorgen machen, aber Sie könnten diese Funktion auf jeden Fall ausprobieren.

Ich habe mich entschieden, alle diese Beispieldateien in git zu verfolgen, weil ich möchte, dass zukünftige Mitwirkende Zugriff auf sie haben, wenn sie das Projekt aufteilen.

Hier ist der aktuelle Fortschritt bis zu diesem Punkt.

Ein Zypressen-Skript schreiben

Wir sind fast bereit, unseren ersten Test zu schreiben. Wir müssen ein Verzeichnis erstellen, in dem unsere Cypress-Tests gespeichert werden: cypress / integration / ydkjs

Jetzt müssen wir das Skript schreiben, das unseren Entwicklungsserver startet, unsere Cypress-Tests ausführt und dann unseren Entwicklungsserver stoppt. Dieses Projekt wurde mit Create React App gebootet, was bedeutet, dass es eine scripts / start.js-Datei enthält, mit der der Server gestartet wird. Ich werde den Code von dort kopieren, in eine neue scripts / cypress.js-Datei einfügen und einige Änderungen vornehmen.

Das folgende Code-Snippet ist das Kernstück unserer neuen Datei scripts / cypress.js.

return devServer.listen (port, HOST, err => {
    if (err) {
        return console.log (err);
    }
    if (isInteractive) {
        klar Konsole();
    }
    console.log (chalk.cyan ('Entwicklungsserver wird gestartet ... \ n'));
    Zypresse zurück
        .Lauf({
            spec: './cypress/integration/ydkjs/*.js',
        })
        .then (results => {
            devServer.close ();
        });
});

Es macht genau das, was wir gesagt haben. Es startet den Dev-Server, führt alle Testdateien in cypress / integration / ydkjs aus und stoppt dann den Dev-Server.

Jetzt können wir in cypress.json unsere baseUrl hinzufügen:

{
    "baseUrl": "http: // localhost: 3000"
}

Jetzt können wir unseren ersten Test schreiben! Nennen wir es cypress / integration / ydkjs / sidebar.js, und wir werden es zum Testen der Sidebar-Funktionalität verwenden. Schreiben wir zunächst einen Dummy-Test:

/ * globaler Kontext cy * /
/// 
context ('Sidebar', () => {
    beforeEach (() => {
        cy.visit ('/');
    });
    
    es ("tut etwas", () => {
        cy.contains ('YDKJS-Übungen');
    });
});

Alles, was wir hier tun, ist, die Basis-URL zu besuchen und ein Element zu finden, das „YDKJS-Übungen“ enthält. Beachten Sie, dass ich den Kommentar nur in der ersten Zeile hinzugefügt habe, damit eslint sich nicht über undefinierte Cypress-Variablen beschwert.

Ich habe auch ein neues Skript in meine package.json eingefügt:

"Skripte": {
    ...
    "cypress": "node scripts / cypress.js",
    ...
},

Jetzt kann ich npm run cypress aufrufen, wenn ich meine End-to-End-Cypress-Tests ausführen möchte. Wenn ich jetzt diesen Befehl im Terminal ausführe, sehe ich, dass mein Server startet, der Test ausgeführt und bestanden wird und dann der Server gestoppt wird. Woohoo!

Hier ist der Code bis zu diesem Punkt.

Schreiben wir ein paar echte Tests!

Nachdem wir unser Cypress-Skript eingerichtet haben, um den Server zu starten, die Tests auszuführen und den Server anzuhalten, können wir beginnen, einige Tests zu schreiben!

Wir haben bereits eine sidebar.js-Testdatei erstellt. Schreiben wir also einige Tests rund um unsere Sidebar-Funktion. Vielleicht sollte unser erster Test darin bestehen, sicherzustellen, dass die Seitenleiste geschlossen wird, wenn wir auf die Schaltfläche X klicken, und erneut geöffnet wird, wenn wir auf den Hamburger klicken.

Bevor wir die X-Schaltfläche finden und darauf klicken, müssen wir sicherstellen, dass die Seitenleiste beim Laden der Homepage sichtbar ist. Ich kann dies direkt nach dem Navigieren zur Homepage in die beforeEach-Methode einfügen, da ich immer sicherstellen möchte, dass die Seitenleiste sichtbar ist, wenn ich zum ersten Mal zur Homepage gehe.

beforeEach (() => {
    cy.visit ('/');
    cy.contains ('Fortschritt'). sollte ('existieren');
});

Beginnen wir nun mit dem Schreiben des Tests. Da es sich bei dem X eigentlich um eine SVG-Datei handelt, können wir Cypress nicht einfach anweisen, sie zu finden. Also finden wir es mit einem data-testid-Attribut oder cy.get ("[data-testid = closeSidebar]"). Click (). Ich weiß was du denkst ...

Ok, ich verstehe, dass Sie in diesem Fall keinen Text verwenden können. Aber warum ein Datenattribut verwenden? Warum nicht einfach einen Klassennamen oder eine ID verwenden?

Am besten verwenden Sie ein Datenattribut. Sie können Klassennamen verwenden, diese können sich jedoch ändern und sind am besten für das Stylen optimiert.

Bezüglich der IDs besteht das Hauptproblem darin, dass Sie nur eine pro Seite haben können, was ärgerlich sein könnte. Was ist, wenn Sie alle X-Schaltflächen auf der Seite anzeigen und festlegen möchten, dass zwei davon vorhanden sein sollen? Dies ist mit IDs nicht einfach möglich.

Unser abgeschlossener Test könnte ungefähr so ​​aussehen:

es ('wird geschlossen, wenn X angeklickt wird, und wird wieder geöffnet, wenn Hamburger angeklickt wird', () => {
    cy.get ('[data-testid = closeSidebar]'). click ();
    cy.contains ('Progress'). should ('not.exist');
    cy.get ('[data-testid = openSidebar]'). click ();
    cy.contains ('Fortschritt'). sollte ('existieren');
});

Ich gehe zur Homepage, stelle sicher, dass die Seitenleiste geöffnet ist, klicke dann auf die Schaltfläche X und stelle sicher, dass sie geschlossen ist, klicke dann auf den Hamburger und stelle sicher, dass die Seitenleiste wieder geöffnet ist. Wenn wir es laufen lassen, geht es vorbei!

Und Sie können ein Video des Tests in cypress / ydkjs / sidebar.js.mp4 sehen! Ziemlich ordentlich. Dies ist sehr hilfreich, wenn Ihre Tests fehlschlagen und Sie nicht wissen, warum.

Eine Sache, bei der Sie vorsichtig sein müssen, ist, dass Cypress ein auf Versprechen basierendes System ist. Wenn Sie cy.contains ausführen ('Progress'). Should ('not.exist'), wechselt Cypress erst dann zur nächsten Codezeile, wenn diese Zeile wahr ist. Wenn ein DOM-Element angezeigt wird, das "Progress" enthält, wartet es, bis es verschwindet oder das Zeitlimit überschritten wird und der Test fehlschlägt.

Dieses System ist schön, weil es das Schreiben dieser Tests sehr schnell und einfach macht. Es kann Sie jedoch manchmal beißen, wenn Sie mit asynchronen Aktionen zu tun haben. Vielleicht möchten Sie sicherstellen, dass ein DOM-Element beim Klicken auf eine Schaltfläche nicht angezeigt wird. Sie können einfach auf die Schaltfläche klicken und dann überprüfen, ob das DOM-Element vorhanden ist, oder? Aber was ist, wenn das DOM-Element eine Sekunde nach dem Klicken auf die Schaltfläche erstellt wird? Ihr Test würde bestehen, wenn er fehlschlagen sollte.

Lassen Sie uns einen weiteren Test schreiben.

Wenn wir in der Seitenleiste auf ein Buch klicken, möchten wir zu der Seite navigieren, die diesem Buch zugeordnet ist.

it ('navigiert zu / up-going, wenn Up & Going ausgewählt ist', () => {
    cy.contains (/ Up & Going \ (/). click ({force: true});
    cy.url (). should ('include', '/ up-going');
    cy.contains ('Kapitel 1: Into Programming'). should ('exist');
    cy.contains ('Kapitel 2: In JavaScript'). should ('exist');
});

In Bezug auf diesen Test sind einige Punkte zu beachten. Auf der Homepage von ydkjs-exercise befindet sich der Text „Up & Going“ an zwei Stellen. Einmal in der Seitenleiste und einmal in der Mitte der Seite. In der Seitenleiste ist der vollständige Text "Up & Going (0/41)", was bedeutet, dass der Benutzer 0 von 41 möglichen Fragen beantwortet hat. Auf der Hauptseite ist der Text nur "Up & Going". Um sicherzustellen, dass wir in der Seitenleiste auf das Up & Going klicken, verwende ich Regex, um auf das Element zu klicken, das "Up & Going (" enthält. Ich möchte nicht, dass es die 0 oder die 41 enthält, da sich diese Zahlen ändern können Dies könnte einer der Fälle sein, in denen die Verwendung eines Datenattributs besser ist als die Verwendung des Textes, wie ich es im obigen Codeausschnitt getan habe.

Ich muss das click -Ereignis erzwingen, da das anchor-Tag den Text enthält, dieser jedoch von einem Listenelement umbrochen wird. Danach teste ich, um sicherzustellen, dass die URL korrekt ist und der Inhalt auf der Seite korrekt ist.

Dies ist der Endzustand des Codes.

Fazit

Wie Sie sehen, haben Sie nach der Installation von Cypress das richtige Skript eingerichtet, um Ihren Entwicklungsserver zu starten, und Sie können die Tests schreiben. Die Arbeit mit Cypress ist ziemlich schnell und schmerzlos.

Sobald Sie sich damit vertraut gemacht haben, können Sie Ihren Testcode sogar wiederverwenden, indem Sie Ihre eigenen benutzerdefinierten Cypress-Befehle erstellen!

Sie können diese Tests vor dem Festschreiben oder in einer CI-Umgebung ausführen, um sicherzustellen, dass keine Regressionen in die Produktion gelangen.

Insgesamt ist Cypress eine absolut solide Wahl, wenn Sie Ihre Tests mit einigen End-to-End-Tests auf die nächste Stufe heben möchten!

Viel Spaß beim Codieren!