Automatisierte Front-End-Tests sind fantastisch. Wir können einen Test mit Code schreiben, um eine Seite zu besuchen – oder nur eine einzelne Komponente zu laden – und dieser Testcode lässt Dinge anklicken oder Text eingeben, wie es ein Benutzer tun würde, und macht dann Aussagen über den Zustand der Anwendung nach den Interaktionen. Dies ermöglicht es uns zu bestätigen, dass alles, was in den Tests beschrieben wird, in der Anwendung wie erwartet funktioniert.
Da dieser Beitrag sich um einen der Bausteine jedes automatisierten UI-Tests dreht, gehe ich nicht von zu viel Vorwissen aus. Fühlen Sie sich frei, die ersten paar Abschnitte zu überspringen, wenn Sie mit den Grundlagen bereits vertraut sind.
Struktur eines Front-End-Tests
Es gibt ein klassisches Muster, das man beim Schreiben von Tests kennen sollte: Arrange (Vorbereiten), Act (Ausführen), Assert (Überprüfen). Bei Front-End-Tests übersetzt sich dies in eine Testdatei, die Folgendes tut:
- Arrange: Dinge für den Test vorbereiten. Eine bestimmte Seite besuchen oder eine bestimmte Komponente mit den richtigen Props mounten, einen bestimmten Zustand mocken, was auch immer.
- Act: Etwas mit der Anwendung tun. Einen Button anklicken, ein Formular ausfüllen, etc. Oder auch nicht, bei einfachen Zustandsprüfungen können wir dies überspringen.
- Assert: Etwas überprüfen. Hat das Absenden eines Formulars eine Dankesnachricht angezeigt? Wurden die richtigen Daten mit einem POST an das Backend gesendet?
Bei der Angabe, mit was interagiert werden soll und dann später, was auf der Seite überprüft werden soll, können wir verschiedene Element-Locator verwenden, um die Teile des DOM anzusteuern, die wir benötigen.
Ein Locator kann etwas wie die ID eines Elements, der Textinhalt eines Elements oder ein CSS-Selektor sein, wie .blog-post oder sogar article > div.container > div > div > p:nth-child(12). Alles über ein Element, das dieses Element für Ihren Test-Runner identifizieren kann, kann ein Locator sein. Wie Sie wahrscheinlich schon am letzten CSS-Selektor erkennen können, gibt es Locator in vielen Varianten.
Wir bewerten Locator oft danach, ob sie spröde oder stabil sind. Im Allgemeinen wünschen wir uns die stabilsten Element-Locator, damit unser Test das gesuchte Element immer finden kann, auch wenn sich der Code um das Element herum im Laufe der Zeit ändert. Dennoch kann die Maximierung der Stabilität um jeden Preis zu defensiven Testschreibweisen führen, die die Tests tatsächlich schwächen. Wir erzielen den größten Nutzen, indem wir eine Kombination aus Sprödigkeit und Stabilität haben, die mit dem übereinstimmt, worauf unsere Tests Wert legen sollen.
Auf diese Weise sind Element-Locator wie Klebeband. Sie sollten in einer Richtung sehr stark sein und in der anderen Richtung leicht reißen. Unsere Tests sollten zusammenhalten und weiterhin erfolgreich sein, wenn unwichtige Änderungen an der Anwendung vorgenommen werden, aber sie sollten leicht fehlschlagen, wenn wichtige Änderungen auftreten, die dem widersprechen, was wir im Test angegeben haben.
Leitfaden für Anfänger zu Element-Locators im Front-End-Testing
Zuerst tun wir so, als würden wir Anweisungen für eine tatsächliche Person schreiben, um ihre Arbeit zu erledigen. Ein neuer Torinspektor wurde gerade bei Gate Inspectors, Inc. eingestellt. Sie sind sein Chef und nach der Vorstellung aller sollen Sie ihm Anweisungen für die Inspektion seines ersten Tors geben. Wenn Sie möchten, dass er erfolgreich ist, würden Sie ihm wahrscheinlich keine Notiz wie diese schreiben:
Gehen Sie am gelben Haus vorbei, gehen Sie weiter, bis Sie zu dem Feld kommen, wo damals die Ziege von Mikes Mutter-Freundin verschwunden ist, dann biegen Sie links ab und sagen Sie mir, ob das Tor vor dem Haus auf der anderen Straßenseite, das Sie sehen, aufgeht oder nicht.
Diese Wegbeschreibung ist so ähnlich wie die Verwendung eines langen CSS-Selektors oder XPath als Locator. Sie ist spröde – und zwar die „schlechte Art von spröde“. Wenn das gelbe Haus neu gestrichen wird und Sie die Schritte wiederholen, können Sie das Tor nicht mehr finden und entscheiden sich vielleicht aufzugeben (oder in diesem Fall schlägt der Test fehl).
Ebenso, wenn Sie nichts über die Ziege-Situation von Mikes Mutter-Freundin wissen, können Sie nicht am richtigen Referenzpunkt anhalten, um zu wissen, welches Tor Sie überprüfen sollen. Das ist genau das, was die „schlechte Art von spröde“ schlecht macht – der Test kann aus allen möglichen Gründen fehlschlagen, und keiner dieser Gründe hat etwas mit der Benutzerfreundlichkeit des Tors zu tun.
Machen wir also einen anderen Front-End-Test, einen, der viel stabiler ist. Schließlich sind in dieser Gegend gesetzlich alle Tore an einer bestimmten Straße eindeutige Seriennummern vom Hersteller zu haben
Gehen Sie zum Tor mit der Seriennummer 1234 und prüfen Sie, ob es sich öffnet.
Das ähnelt eher dem Lokalisieren eines Elements anhand seiner ID. Es ist stabiler und nur ein Schritt. Alle Fehlerquellen des letzten Tests wurden entfernt. Dieser Test schlägt nur fehl, wenn das Tor mit dieser ID nicht wie erwartet aufgeht.
Nun stellt sich heraus, dass, obwohl keine zwei Tore die gleiche ID auf derselben Straße haben sollten, dies nirgends wirklich durchgesetzt wird. Und eines Tages erhält ein weiteres Tor an der Straße die gleiche ID.
Als der neu eingestellte Torinspektor das nächste Mal „Tor 1234“ testen will, findet er zuerst das andere, ist jetzt am falschen Haus und prüft das Falsche. Der Test könnte fehlschlagen, oder schlimmer noch: Wenn dieses Tor wie erwartet funktioniert, besteht der Test trotzdem, aber er testet nicht das beabsichtigte Subjekt. Er vermittelt falsche Sicherheit. Er würde weiterhin bestehen, auch wenn unser ursprüngliches Zieltor mitten in der Nacht von Tordieben gestohlen worden wäre.
Nach einem solchen Vorfall ist klar, dass IDs nicht so stabil sind, wie wir dachten. Also denken wir weiter und entscheiden, dass wir an der Innenseite des Tors eine spezielle ID nur für Testzwecke haben möchten. Wir schicken einen Techniker los, um die spezielle ID an nur dieses eine Tor anzubringen. Die neuen Testanweisungen sehen wie folgt aus:
Gehen Sie zum Tor mit der Test-ID „my-favorite-gate“ und prüfen Sie, ob es sich öffnet.
Dies ähnelt der Verwendung des beliebten Attributs data-testid. Attribute wie dieses sind großartig, weil sie im Code offensichtlich für automatisierte Tests gedacht sind und nicht geändert oder entfernt werden sollten. Solange das Tor dieses Attribut hat, werden Sie es immer finden. Genau wie bei IDs ist die Eindeutigkeit immer noch nicht durchgesetzt, aber sie ist etwas wahrscheinlicher.
Das ist so gut wie nicht spröde möglich und bestätigt die Funktionalität des Tors. Wir verlassen uns auf nichts außer dem Attribut, das wir absichtlich für Tests hinzugefügt haben. Aber hier versteckt sich ein kleines Problem…
Dies ist ein Benutzeroberflächentest für das Tor, aber der Locator ist etwas, das kein Benutzer verwenden würde, um das Tor zu finden.
Es ist eine verpasste Gelegenheit, denn in diesem fiktiven Landkreis müssen Tore mit der Hausnummer gekennzeichnet sein, damit die Leute die Adresse sehen können. Also sollten alle Tore eine eindeutige, für Menschen sichtbare Beschriftung haben, und wenn nicht, ist das an sich schon ein Problem.
Beim Lokalisieren des Tors mit der Test-ID, wenn sich herausstellt, dass das Tor neu gestrichen wurde und die Hausnummer überdeckt ist, würde unser Test trotzdem bestehen. Aber der Sinn des Tores ist es, dass Menschen das Haus erreichen können. Mit anderen Worten, ein funktionierendes Tor, das ein Benutzer nicht finden kann, sollte immer noch ein Testfehler sein, und wir wollen einen Locator, der dieses Problem aufdecken kann.
Hier ist ein weiterer Versuch, diese Testanweisung für den Torinspektor an seinem ersten Tag zu formulieren:
Gehen Sie zum Tor für Hausnummer 40 und prüfen Sie, ob es sich öffnet.
Diese Methode verwendet einen Locator, der dem Test Wert hinzufügt: Er beruht auf etwas, auf das auch Benutzer angewiesen sind, nämlich der Beschriftung des Tors. Sie fügt einen potenziellen Grund hinzu, warum der Test fehlschlagen könnte, bevor er die Interaktion erreicht, die wir eigentlich testen wollen, was auf den ersten Blick schlecht erscheinen mag. Aber in diesem Fall, da der Locator aus Sicht des Benutzers aussagekräftig ist, sollten wir dies nicht als „spröde“ abtun. Wenn das Tor nicht über seine Beschriftung gefunden werden kann, spielt es keine Rolle, ob es sich öffnet oder nicht – dies ist die „gute Art von spröde“.
Der DOM zählt
Viele Front-End-Testing-Ratschläge sagen uns, dass wir vermeiden sollen, Locator zu schreiben, die von der DOM-Struktur abhängen. Das bedeutet, dass Entwickler Komponenten und Seiten im Laufe der Zeit refaktorisieren können und die Tests bestätigen, dass benutzerorientierte Arbeitsabläufe nicht unterbrochen wurden, ohne die Tests aktualisieren zu müssen, um sie an die neue Struktur anzupassen. Dieses Prinzip ist nützlich, aber ich würde es leicht abändern und sagen, dass wir vermeiden sollten, Locator zu schreiben, die von irrelevanter DOM-Struktur in unseren Front-End-Tests abhängen.
Damit eine Anwendung korrekt funktioniert, sollte der DOM die Art und Struktur des Inhalts widerspiegeln, der sich zu jedem Zeitpunkt auf dem Bildschirm befindet. Ein Grund dafür ist die Zugänglichkeit. Ein DOM, das in diesem Sinne korrekt ist, ist für assistive Technologien viel einfacher richtig zu parsen und Benutzern zu beschreiben, die die gerenderten Inhalte nicht sehen. DOM-Struktur und schlichtes HTML machen einen großen Unterschied für die Unabhängigkeit von Benutzern, die auf assistive Technologien angewiesen sind.
Lassen Sie uns einen Front-End-Test starten, um etwas an das Kontaktformular unserer App zu senden. Wir werden Cypress dafür verwenden, aber die Prinzipien der strategischen Auswahl von Locatoren gelten für alle Front-End-Testing-Frameworks, die den DOM zum Lokalisieren von Elementen verwenden. Hier finden wir Elemente, geben Text ein, senden das Formular ab und verifizieren den erreichten „Danke“-Status.
// 👎 Not recommended
cy.get('#name').type('Mark')
cy.get('#comment').type('test comment')
cy.get('.submit-btn').click()
cy.get('.thank-you').should('be.visible')
In diesen vier Zeilen gibt es alle Arten von impliziten Überprüfungen. cy.get() prüft, ob das Element im DOM existiert. Der Test schlägt fehl, wenn das Element nach einer bestimmten Zeit nicht existiert, während Aktionen wie type und click überprüfen, ob die Elemente sichtbar, aktiviert und nicht von etwas anderem verdeckt sind, bevor eine Aktion ausgeführt wird.
So erhalten wir selbst bei einem einfachen Test wie diesem viel „gratis“, aber wir haben auch einige Abhängigkeiten von Dingen eingeführt, um die wir uns (und unsere Benutzer) uns nicht wirklich kümmern. Die spezifische ID und die Klassen, die wir prüfen, erscheinen stabil genug, besonders im Vergleich zu Selektoren wie div.main > p:nth-child(3) > span.is-a-button oder was auch immer. Diese langen Selektoren sind so spezifisch, dass eine geringfügige Änderung des DOM dazu führen könnte, dass ein Test fehlschlägt, weil das Element nicht gefunden werden kann, nicht weil die Funktionalität defekt ist.
Aber selbst unsere kurzen Selektoren, wie #name, bringen drei Probleme mit sich:
- Die ID kann im Code geändert oder entfernt werden, wodurch das Element übersehen wird, insbesondere wenn das Formular mehr als einmal auf einer Seite angezeigt werden könnte. Eine eindeutige ID muss möglicherweise für jede Instanz generiert werden, und das können wir nicht einfach vorab in einen Test einfügen.
- Wenn mehr als eine Instanz eines Formulars auf der Seite vorhanden ist und diese die gleiche ID haben, müssen wir entscheiden, welches Formular ausgefüllt werden soll.
- Die ID ist aus Benutzersicht für uns nicht wirklich wichtig, daher sind alle integrierten Überprüfungen irgendwie… nicht vollständig genutzt?
Für die Probleme eins und zwei ist die empfohlene Lösung oft die Verwendung dedizierter Datenattribute in unserem HTML, die ausschließlich zu Testzwecken hinzugefügt werden. Das ist besser, weil unsere Tests dann nicht mehr von der DOM-Struktur abhängen und wenn ein Entwickler den Code um eine Komponente herum ändert, die Tests weiterhin erfolgreich sind, ohne aktualisiert werden zu müssen, solange sie data-test="name-field" am richtigen input-Element beibehalten.
Das löst Problem drei jedoch nicht – wir haben immer noch einen Front-End-Interaktionstest, der von etwas abhängt, das für den Benutzer bedeutungslos ist.
Bedeutungsvolle Locator für interaktive Elemente
Element-Locator sind aussagekräftig, wenn sie von etwas abhängen, wovon wir uns tatsächlich abhängig machen wollen, weil etwas am Locator für das Benutzererlebnis wichtig ist. Im Fall von interaktiven Elementen würde ich argumentieren, dass der beste Selektor, den man verwenden sollte, der zugängliche Name des Elements ist. So etwas ist ideal:
// 👍 Recommended
cy.getByLabelText('Name').type('Mark')
Dieses Beispiel verwendet den byLabelText-Helfer von Cypress Testing Library. (Tatsächlich, wenn Sie Testing Library in irgendeiner Form verwenden, hilft sie Ihnen wahrscheinlich bereits dabei, zugängliche Locator wie diese zu schreiben.)
Dies ist nützlich, da nun die integrierten Prüfungen (die wir durch den cy.type()-Befehl kostenlos erhalten) die Zugänglichkeit des Formularfeldes beinhalten. Alle interaktiven Elemente sollten einen zugänglichen Namen haben, der für assistive Technologien zugänglich ist. So weiß ein Screenreader-Benutzer beispielsweise, wie das Formularfeld heißt, in das er tippt, um die benötigten Informationen einzugeben.
Die Möglichkeit, diesen zugänglichen Namen für ein Formularfeld bereitzustellen, erfolgt normalerweise über ein label-Element, das mit dem Feld über eine ID verknüpft ist. Der Befehl getByLabelText von Cypress Testing Library verifiziert, dass das Feld ordnungsgemäß beschriftet ist, aber auch, dass das Feld selbst ein Element ist, das eine Beschriftung haben darf. Folgendes HTML wäre zum Beispiel korrekt fehlerhaft, bevor der type()-Befehl versucht wird, da trotz vorhandener label eine div zu beschriften ungültiges HTML ist:
<!-- 👎 Not recommended -->
<label for="my-custom-input">Editable DIV element:</label>
<div id="my-custom-input" contenteditable="true" />
Da dies ungültiges HTML ist, könnte die Screenreader-Software dieses Label nie korrekt mit diesem Feld verknüpfen. Um dies zu beheben, würden wir das Markup aktualisieren, um ein echtes input-Element zu verwenden:
<!-- 👍 Recommended -->
<label for="my-real-input">Real input:</label>
<input id="my-real-input" type="text" />
Das ist fantastisch. Wenn der Test nun nach Bearbeitungen am DOM an dieser Stelle fehlschlägt, liegt das nicht an irrelevanten strukturellen Änderungen, wie Elemente angeordnet sind, sondern daran, dass unsere Bearbeitungen etwas kaputt gemacht haben an einem Teil des DOM, um den sich unsere Front-End-Tests explizit kümmern und der für Benutzer relevant wäre.
Bedeutungsvolle Locator für nicht-interaktive Elemente
Bei nicht-interaktiven Elementen sollten wir uns Gedanken machen. Lassen Sie uns ein wenig Urteilsvermögen walten lassen, bevor wir auf die Attribute data-cy oder data-test zurückgreifen, die immer für uns da sind, wenn der DOM überhaupt keine Rolle spielt.
Bevor wir uns mit den generischen Locatoren befassen, denken wir daran: Der Zustand des DOM ist unser Ganzes™ als Webentwickler (zumindest glaube ich das). Und der DOM treibt die UX für alle, die die Seite nicht visuell erleben. Oft gibt es also aussagekräftige Elemente, die wir im Code verwenden könnten oder sollten und die wir in einen Test-Locator aufnehmen können.
Und wenn nicht, weil zum Beispiel der Anwendungscode nur generische Container wie div und span enthält, sollten wir zuerst die Anwendungsebene reparieren, während wir den Test hinzufügen. Andernfalls besteht die Gefahr, dass unsere Tests tatsächlich festlegen, dass die generischen Container erwartet und erwünscht sind, was es jemandem etwas erschwert, diese Komponente für mehr Zugänglichkeit zu ändern.
Dieses Thema öffnet eine Büchse der Pandora bezüglich der Funktionsweise von Barrierefreiheit in einer Organisation. Oft, wenn niemand darüber spricht und es kein Teil der Praxis in unseren Unternehmen ist, nehmen wir Barrierefreiheit nicht ernst als Front-End-Entwickler. Aber am Ende des Tages sind wir die Experten dafür, was der „richtige Markup“ für das Design ist und was bei der Entscheidung darüber zu berücksichtigen ist. Ich diskutiere diese Seite der Dinge viel ausführlicher in meinem Vortrag von Connect.Tech 2021 mit dem Titel „Researching and Writing Accessible Vue… Thingies“.
Wie wir oben gesehen haben, gibt es bei Elementen, die wir traditionell als interaktiv betrachten, eine ziemlich gute Faustregel, die sich leicht in unsere Front-End-Tests einbauen lässt: interaktive Elemente sollten korrekt mit dem Element verknüpfte, wahrnehmbare Beschriftungen haben. Daher sollten wir bei jedem interaktiven Element, wenn wir es testen, dieses aus dem DOM mit dieser erforderlichen Beschriftung auswählen.
Für Elemente, die wir nicht als interaktiv betrachten – wie die meisten inhaltsrendernden Elemente, die Textstücke beliebiger Art anzeigen, abgesehen von einigen grundlegenden Landmarks wie main – würden wir keine Lighthouse-Audit-Fehler auslösen, wenn wir den Großteil unseres nicht-interaktiven Inhalts in generische div- oder span-Container legen. Aber der Markup wird für assistive Technologien nicht sehr informativ oder hilfreich sein, da er die Art und Struktur des Inhalts nicht für jemanden beschreibt, der ihn nicht sehen kann. (Um dies auf die Spitze zu treiben, siehe Manuel Matuzovics exzellenten Blogbeitrag „Building the most inaccessible site possible with a perfect Lighthouse score.“)
Das von uns gerenderte HTML ist der Ort, an dem wir wichtige Kontextinformationen an jeden kommunizieren, der den Inhalt nicht visuell wahrnimmt. Das HTML wird zum Aufbau des DOM verwendet, das DOM wird zum Erstellen des Accessibility Tree des Browsers verwendet, und der Accessibility Tree ist die API, die assistive Technologien aller Art nutzen können, um den Inhalt und die möglichen Aktionen für eine behinderte Person, die unsere Software nutzt, auszudrücken. Ein Screenreader ist oft das erste Beispiel, das uns einfällt, aber der Accessibility Tree kann auch von anderer Technologie genutzt werden, wie zum Beispiel Displays, die Webseiten in Braille umwandeln, und andere.
Automatisierte Barrierefreiheitsprüfungen sagen uns nicht, ob wir wirklich das richtige HTML für den Inhalt erstellt haben. Die „Richtigkeit“ des HTML ist eine Ermessensentscheidung, die wir Entwickler treffen, darüber, welche Informationen wir für notwendig halten, im Accessibility Tree zu kommunizieren.
Sobald wir diese Entscheidung getroffen haben, können wir entscheiden, wie viel davon in die automatisierten Front-End-Tests einbezogen werden kann.
Nehmen wir an, wir haben entschieden, dass ein Container mit der ARIA-role status die „Danke“- und Fehlermeldungen für ein Kontaktformular enthalten wird. Dies könnte nützlich sein, damit das Feedback zum Erfolg oder Misserfolg des Formulars von einem Screenreader angekündigt werden kann. CSS-Klassen wie .thank-you und .error könnten zur Steuerung des visuellen Zustands angewendet werden.
Wenn wir dieses Element hinzufügen und dafür einen UI-Test schreiben möchten, könnten wir nach dem Ausfüllen und Absenden des Formulars eine Überprüfung wie diese schreiben:
// 👎 Not recommended
cy.get('.thank-you').should('be.visible')
Oder sogar ein Test, der einen nicht-spröden, aber dennoch bedeutungslosen Selektor wie diesen verwendet:
// 👎 Not recommended
cy.get('[data-testid="thank-you-message"]').should('be.visible')
Beide könnten mit cy.contains() neu geschrieben werden:
// 👍 Recommended
cy.contains('[role="status"]', 'Thank you, we have received your message')
.should('be.visible')
Dies würde bestätigen, dass der erwartete Text erschienen ist und sich im richtigen Container befand. Im Vergleich zum vorherigen Test hat dies einen viel größeren Wert in Bezug auf die Überprüfung tatsächlicher Funktionalität. Wenn ein Teil dieses Tests fehlschlägt, wollen wir das wissen, weil sowohl die Nachricht als auch der Elementselektor für uns wichtig sind und nicht trivial geändert werden sollten.
Wir haben hier definitiv eine gewisse Abdeckung ohne viel zusätzlichen Code gewonnen, aber wir haben auch eine andere Art von Sprödigkeit eingeführt. Wir haben Klartext-Strings in unseren Tests, und das bedeutet, wenn die „Danke“-Nachricht in etwas wie „Danke, dass Sie sich gemeldet haben!“ geändert wird, schlägt dieser Test plötzlich fehl. Dasselbe gilt für alle anderen Tests. Eine kleine Änderung in der Schreibweise einer Beschriftung würde das Aktualisieren jedes Tests erfordern, der Elemente mit dieser Beschriftung ansteuert.
Wir können dies verbessern, indem wir die gleiche Quelle der Wahrheit für diese Strings in Front-End-Tests verwenden, wie wir es in unserem Code tun. Und wenn wir derzeit menschenlesbare Sätze direkt im HTML unserer Komponenten eingebettet haben… nun, dann haben wir einen Grund, diese Dinge dort herauszuziehen.
Menschenlesbare Strings könnten die magischen Zahlen des UI-Codes sein
Eine magische Zahl (oder weniger aufregend, eine „unbenannte numerische Konstante“) ist dieser super-spezifische Wert, den man manchmal im Code sieht und der für das Endergebnis einer Berechnung wichtig ist, wie eine gute alte 1.023033 oder etwas Ähnliches. Da diese Zahl aber unbeschriftet ist, ist ihre Bedeutung unklar und somit unklar, was sie tut. Vielleicht wendet sie eine Steuer an. Vielleicht kompensiert sie einen Fehler, den wir nicht kennen. Wer weiß?
Auf jeden Fall gilt dasselbe für Front-End-Tests und der allgemeine Rat ist, magische Zahlen zu vermeiden wegen ihrer mangelnden Klarheit. Code-Reviews werden sie oft fangen und fragen, wofür die Zahl ist. Können wir sie in eine Konstante verschieben? Dasselbe tun wir auch, wenn ein Wert an mehreren Stellen wiederverwendet werden soll. Anstatt den Wert überall zu wiederholen, können wir ihn in einer Variablen speichern und die Variable nach Bedarf verwenden.
Beim Schreiben von Benutzeroberflächen über die Jahre hinweg habe ich Textinhalte in HTML- oder Vorlagendateien als sehr ähnlich zu magischen Zahlen in anderen Kontexten betrachtet. Wir fügen absolute Werte in unseren gesamten Code ein, aber in Wirklichkeit wäre es vielleicht nützlicher, diese Werte woanders zu speichern und sie zur Build-Zeit (oder sogar über eine API, je nach Situation) einzubinden.
Dafür gibt es mehrere Gründe:
- Ich habe früher mit Kunden gearbeitet, die alles von einem Content-Management-System aus steuern wollten. Inhalte, selbst Formularbeschriftungen und Statusmeldungen, die nicht im CMS lebten, sollten vermieden werden. Kunden wollten die volle Kontrolle, damit Inhaltsänderungen keine Codeänderungen und erneute Bereitstellung der Website erforderten. Das macht Sinn; Code und Inhalt sind unterschiedliche Konzepte.
- Ich habe in vielen mehrsprachigen Codebasen gearbeitet, wo der gesamte Text über ein Internationalisierungs-Framework eingebunden werden muss, wie dieses:
<label for="name">
<!-- prints "Name" in English but something else in a different language -->
{{content[currentLanguage].contactForm.name}}
</label>
- Was Front-End-Tests angeht, sind unsere UI-Tests robuster, wenn wir anstatt eine spezifische „Danke“-Nachricht zu überprüfen, die wir im Test hart kodieren, etwas wie dieses tun:
const text = content.en.contactFrom // we would do this once and all tests in the file can read from it
cy.contains(text.nameLabel, '[role="status"]').should('be.visible')
Jede Situation ist anders, aber ein System von Konstanten für Strings ist ein großer Vorteil beim Schreiben robuster UI-Tests, und ich würde es empfehlen. Dann, wenn und wann Übersetzung oder dynamische Inhalte für unsere Situation notwendig werden, sind wir viel besser darauf vorbereitet.
Ich habe auch gute Argumente gegen das Importieren von Textstrings in Tests gehört. Zum Beispiel finden einige, dass Tests lesbarer und generell besser sind, wenn der Test selbst den erwarteten Inhalt unabhängig von der Inhaltsquelle spezifiziert.
Das ist ein berechtigter Punkt. Ich bin weniger überzeugt, weil ich denke, dass Inhalte eher über ein Redaktions-/Veröffentlichungsmodell gesteuert werden sollten, und ich möchte, dass der Test prüft, ob die erwarteten Inhalte aus der Quelle gerendert wurden, nicht bestimmte Strings, die zum Zeitpunkt der Testschreibung korrekt waren. Aber viele Leute sind anderer Meinung, und ich sage, solange der Kompromiss innerhalb eines Teams verstanden ist, ist jede Wahl akzeptabel.
Dennoch ist es eine gute Idee, Code und Inhalt im Front-End generell zu trennen. Und manchmal kann es sogar sinnvoll sein, beides zu mischen – zum Beispiel, indem man Strings in unseren Komponententests importiert und in unseren End-to-End-Tests nicht. Auf diese Weise sparen wir etwas Duplizierung und gewinnen Vertrauen, dass unsere Komponenten korrekte Inhalte anzeigen, während wir gleichzeitig Front-End-Tests haben, die unabhängig die erwarteten Texte im redaktionellen, benutzerorientierten Sinne überprüfen.
Wann data-test-Locator verwendet werden sollten
CSS-Selektoren wie [data-test="success-message"] sind immer noch nützlich und können sehr hilfreich sein, wenn sie gezielt eingesetzt werden, anstatt ständig verwendet zu werden. Wenn wir der Meinung sind, dass es keine aussagekräftige, zugängliche Möglichkeit gibt, ein Element anzuvisieren, sind data-test-Attribute immer noch die beste Option. Sie sind viel besser als zum Beispiel die Abhängigkeit von einem Zufall, wie auch immer die DOM-Struktur an dem Tag ist, an dem Sie den Test schreiben, und dann zurückzufallen auf den Stil „zweites Listenelement im dritten div mit der Klasse card“.
Es gibt auch Zeiten, in denen Inhalte dynamisch erwartet werden und es keine einfache Möglichkeit gibt, Strings aus einer gemeinsamen Quelle zu extrahieren, um sie in unseren Tests zu verwenden. In diesen Situationen hilft ein data-test-Attribut, um das spezifische Element zu erreichen, das uns wichtig ist. Es kann immer noch mit einer zugänglichkeitsfreundlichen Überprüfung kombiniert werden, zum Beispiel:
cy.get('h2[data-test="intro-subheading"]')
Hier möchten wir finden, was das data-test-Attribut von intro-subheading hat, aber trotzdem zulassen, dass unser Test überprüft, ob es sich um ein h2-Element handelt, wenn das erwartet wird. Das data-test-Attribut wird verwendet, um sicherzustellen, dass wir das spezifische h2 erhalten, an dem wir interessiert sind, und nicht irgendein anderes h2, das sich auf der Seite befinden könnte, falls aus irgendeinem Grund der Inhalt dieses h2 zum Zeitpunkt des Tests nicht bekannt ist.
Selbst in Fällen, in denen wir den Inhalt kennen, könnten wir immer noch Datenattribute verwenden, um sicherzustellen, dass die Anwendung diesen Inhalt an der richtigen Stelle rendert:
cy.contains('h2[data-test="intro-subheading"]', 'Welcome to Testing!')
data-test-Selektoren können auch eine Bequemlichkeit sein, um zu einem bestimmten Teil der Seite zu gelangen und dann innerhalb dieses Bereichs Überprüfungen vorzunehmen. Das könnte wie folgt aussehen:
cy.get('article[data-test="ablum-card-blur-great-escape"]').within(() => {
cy.contains('h2', 'The Great Escape').should('be.visible')
cy.contains('p', '1995 Album by Blur').should('be.visible')
cy.get('[data-test="stars"]').should('have.length', 5)
})
An diesem Punkt geraten wir in Nuancen, da es sehr wohl andere gute Möglichkeiten geben kann, diesen Inhalt anzusteuern, es ist nur ein Beispiel. Aber am Ende des Tages ist es gut, wenn man sich um solche Details kümmert, denn dann haben wir zumindest ein Verständnis für die Zugänglichkeitsfunktionen, die im getesteten HTML eingebettet sind, und dass wir diese in unsere Tests einbeziehen wollen.
Wenn der DOM wichtig ist, testen Sie ihn
Front-End-Tests bringen uns viel mehr Wert, wenn wir sorgfältig darüber nachdenken, wie wir den Tests mitteilen, mit welchen Elementen sie interagieren sollen und welche Inhalte sie erwarten sollen. Wir sollten zugängliche Namen für interaktive Komponenten bevorzugen und spezifische Elementnamen, ARIA-Rollen usw. für nicht-interaktive Inhalte einbeziehen – wenn diese für die Funktionalität relevant sind. Diese Lokalisatoren schaffen, wo praktikabel, die richtige Kombination aus Stärke und Zerbrechlichkeit.
Und für alles andere gibt es natürlich data-test.
Das ist einer der besten technischen Blog-Posts, die ich seit langem gelesen habe.
Voller praktischer Informationen: warum Abfragen auf dem Accessibility Tree statt auf dem breiteren DOM durchgeführt werden sollten, wann data-testid verwendet werden sollte, dass „magische Zahlen“-Strings extern zu den Tests gespeichert werden sollten und zumindest in Konstanten, wenn nicht in einem externen System.
Aber auch sehr zum Nachdenken anregend: Ich habe den fantastischen kostenlosen Udacity-Kurs über Barrierefreiheit absolviert und integriere ihn seitdem von Anfang an neben der Responsivität in meine Programmierung.
Danke für das Teilen Ihres Wissens!