E2E-Testing mit Playwright: Der Weg der Mitte

Seite 2: Inbetriebnahme – Schritt für Schritt

Inhaltsverzeichnis

Playwright lässt sich mit Java, .NET, Python, JavaScript und TypeScript verwenden. Die nachfolgend gezeigten Tests nutzen TypeScript. Der erste Schritt ist die Installation der drei unterstützten Browser Chromium, Firefox und WebKit, die bei Playwright versionsgebunden sind. Beispielsweise kommt Playwright 1.26 mit Chromium 106, Firefox 104 und WebKit 16.0. Jedes neue Minor Release macht einen neuen Browserdownload erforderlich.

Es empfiehlt sich für den Anfang, ein neues dediziertes Node.js-Projekt mit dem Befehl npm init playwright e2e zu erstellen. Neben dem Projekt erzeugt der Befehl eine erste Testdatei und die Playwright-Konfiguration. Soll Playwright in ein bestehendes Projekt integriert werden, ist lediglich das Package @playwright/test über npm install zu installieren. Der Download der Browser ist auch hier manuell mittels npx playwright install auszuführen.

Nun wechselt man in das Verzeichnis seines Projekts (im Beispiel e2e) und führt dort npx playwright test aus. Das führt die eingangs automatisch erstellte Testdatei mit allen drei Browsern aus. Nach einem erfolgreichen Durchlauf zeigt npx playwright show-report einen Bericht an.

Das folgende Beispiel zeigt das Schreiben zweier einfacher Tests. Die zu testende Anwendung, die unter https://genuine-narwhal-f0f8ad.netlify.app erreichbar ist, ist eine imaginäre Reiseagentur und bietet zwei Funktionen an:

  1. "Holidays", wo sich Urlaube einsehen und eine Informationsbroschüre anfordern lassen.
  2. "Customers" als Kundenverwaltung.

Der erste Test überprüft die Lauffähigkeit der "Holidays"-Funktion. Dazu ist die Datei tests/holidays.spec.ts zu erstellen und der in Listing 1 gezeigte Inhalt einzufügen.

import { test, expect } from '@playwright/test';

test.describe('initial test', () => {
  test('holidays', async ({ page }) => {
    await page.goto('https://genuine-narwhal-f0f8ad.netlify.app/');
    await page.locator('[data-testid=btn-holidays]').click();

    await expect(page.locator('mat-drawer-content')).toContainText(
      'Choose among our Holidays'
    );
  });
});

Listing 1: Erstellen der Datei tests/holidays.spec.ts

Dieser Test öffnet eine Website, klickt auf ein DOM-Element, das das Attribut [data-testid=btn-holidays] beinhaltet, und verifiziert danach, dass das DOM-Element mit dem Tag <mat-content-drawer> ein Kindelement hat, das "Choose among our Holidays" als Text beinhaltet.

Wie das import-Statement in Zeile 1 zeigt, bringt Playwright seine eigenen Methoden für das Erstellen von Tests, Suites und Assertions mit. Im Gegensatz zu anderen E2E-Testing-Frameworks ist also keine zusätzliche Bibliothek wie Chai oder Jest für die Assertions nötig.

Der Befehl test.describe erstellt eine neue Testsuite und enthält die einzelnen mit dem Befehl test definierten Tests. Jeder Test besteht aus einem Namen und dem eigentlichen Code in Form einer asynchronen Funktion. Der aktuelle Test enthält drei asynchrone Befehle. Mit await page.goto("..."); navigiert Playwright zu der zu testenden Website. page.click(...) ist der Klick auf den Link und der letzte Befehl ist schlussendlich die Verifizierung. Playwright verwendet einen eigenen expect-Befehl, der mit speziellen Matchern kommt, die auf die Anforderungen von E2E-Tests abgestimmt sind. Dieser Test verwendet den Matcher toContainText.

Der zweite Test soll aktive Eingaben durchführen. Man erstellt mit "Luise Dervist" eine neue Kundin und verifiziert, dass sie in der entsprechenden Übersicht angezeigt wird. Hierzu dient die in Listing 2 erstellte Datei tests/customers.spec.ts.

import { test, expect } from '@playwright/test';

test('should add a new customer', async ({ page }) => {
  await page.goto('https://genuine-narwhal-f0f8ad.netlify.app/');
  await page.locator('data-testid=btn-customers').click();
  await page.locator('data-testid=btn-add-customer').click();
  await page.locator('data-testid=inp-firstname').fill('Luise');
  await page.locator('data-testid=inp-lastname').fill('Dervist');
  await page.locator('data-testid=inp-country').click();
  await page.locator('text=Greece').click();
  await page.locator('data-testid=inp-birthdate').fill('12.9.2001');
  await page.locator('data-testid=btn-submit').click();

  await expect(
    page.locator('data-testid=row-customer', {
      hasText: 'Dervist, Luise',
    })
  ).toBeVisible();
});

Listing 2: Erstellen der Datei tests/customers.spec.ts

Wenn es schnell gehen soll, kann der Test mit nur mit einem Browser ausgeführt werden. Der Befehl dafür lautet

npx playwright test tests/customers.ts --project chromium

Dieser Test verwendet drei neue Features: Zum Ersten ermöglicht fill das Eintragen von Eingaben in Formularfelder. Zweitens sind die eckigen Klammern in den Selektoren verschwunden. Wo im ersten Test noch [data-testid=btn-customers] zu finden war, reicht jetzt ein data-testid=btn-customers. Der Grund liegt darin, dass Playwright eine Selektion nach data-test*-Attributen implizit versteht und entsprechend richtig durchführt.

Zu guter Letzt erscheint im finalen expect mit toBeVisible ein neuer Matcher. Das Interessante ist jedoch die Verwendung von page.locator. hasText ist ein zweites Argument. Das ist in diesem Fall notwendig, da mehrere DOM-Elemente mit dieser testid vorhanden sind und hier dasjenige ausgewählt werden soll, das den Namen "Dervist, Luise" enthält.

Playwright stellt Entwicklern eine Vielzahl an Tools zur Verfügung.

CodeGen

Playwright besitzt einen Codegenerator namens codegen. Seine Bedienung ist simpel: Entwickler oder Entwicklerinnen führen im Playwright-Browser die Aktionen durch, die später ein Test ausführen soll. Dabei zeichnet codegen die Aktionen auf und erstellt validen Playwright-Testcode.

Nach der Ausführung von npx playwright codegen öffnen sich zwei Fenster. Eines ist der Browser, das andere der Playwright Inspector, der unter anderem auch die Codegenerierung durchführt. Im Browser navigiert man nun zur Adresse https://genuine-narwhal-f0f8ad.netlify.app/ und klickt dort zuerst auf Holidays und danach auf den Button Get a Brochure für den ersten Urlaub "Vienna / Wien". Der Browser sollte zu einer Seite springen, die ein Input-Feld mit einer Adresse anzeigt. Dort gibt man "Domgasse 5" ein und klickt auf Send. Unter den Buttons sollte die Textnachricht "Brochure sent" erscheinen.

Ein Wechsel in den Playwright Inspector zeigt den bereits vorgefertigten Code, der sich eins zu eins in eine Datei kopieren lässt. Grundsätzlich ist dieser Code allerdings als Entwurf anzusehen. Teilweise kommen unnötige Befehle wie toHaveUrl oder waitForNavigation darin vor und es lassen sich manuell besser geeignete Selektoren auswählen. Dennoch bietet der Codegenerator eine schnelle Einstiegsmöglichkeit, um Tests zu erstellen.

Debug-Modus

Ebenso wie der Codegenerator basiert auch der Debugging-Modus auf dem Playwright Inspector. Er lässt sich mit dem Befehl

npx playwright test tests/customers.spec.ts --project chromium --debug

starten. Daraufhin öffnen sich wieder Chromium und der Playwright Inspector. Im Inspector erscheint nun der Testcode und es ist bereits in der ersten Zeile ein Breakpoint gesetzt. Wie in einem Debugger üblich, lässt sich der Programmcode zeilenweise ausführen und der untere Bereich zeigt wichtige Kontextinformationen zu den einzelnen Befehlen.

In der Mitte befindet sich das Eingabefeld Playwright Selector. Damit lässt sich zu jedem Zeitpunkt ein Selektor hineinschreiben, und wenn Playwright das entsprechende DOM-Element findet, highlightet es dieses im Browser.

Ein Klick auf Step over führt den ersten Befehl aus. Im Browser erscheint ein roter Punkt, der sich über den Customers-Button legt. Im Eingabefeld für den Selektor können Entwickler nun nach Lust und Laune experimentieren. Beispielsweise ließe sich btn-customers durch btn-bookings ersetzen. Es sollte dann der Bookings-Button im Browser hervorgehoben sein.

Der Playwright-Debugger mit Selektorfunktion im Einsatz

Trace Viewer

Wenn ein Test fehlschlägt, hilft der Trace Viewer von Playwright bei der Fehlersuche, der im Vergleich zur Konkurrenz seinesgleichen sucht.

Zunächst einmal soll der aktuelle Kundentest fehlschlagen. Anstatt auf "Dervist, Luise" zu überprüfen, ändert man den letzten Befehl so ab, dass "Luise Dervist" als Text vorhanden sein soll. Der Test lässt sich dann wieder mit npx playwright test tests/customers.spec.ts starten. Er wird fehlschlagen und Playwright eine URL mit dem Report anbieten. Nach Öffnen der URL führt ein Klick auf customers.spec.ts zur Detailansicht, wo ganz am Ende ein Link mit dem Namen "trace" erscheint.

Dieser Link führt zur eigentlichen Tracing-Ansicht. Dort gibt es eine Zeitleiste, anhand derer sich der Verlauf des Tests visuell nachvollziehen lässt. Zudem sind auf der rechten Seite pro Befehl die vom Debugger bekannten Kontextinformationen zu sehen, die im Fehlerfall hilfreich sind. Im vorliegenden Fall teilt Playwright mit, dass das expect mit dem entsprechenden Text nach einer Wartezeit von 5 Sekunden fehlgeschlagen ist.

Der Playwright Trace Viewer

Visual Studio Code Extensions

Für Visual Studio Code existiert eine offizielle Playwright Extension, für andere Editoren wie WebStorm zum aktuellen Zeitpunkt noch nicht. Daher sollte man Visual Studio Code zum Schreiben der Tests verwenden. Mit der Extension lassen sich direkt einzelne Tests oder Suites standardmäßig oder im Debug-Modus ausführen. Im Debug-Modus funktioniert sogar das Highlighting der Selektoren wie beim nativen Debugging über den Inspector.

Eine Community-Erweiterung öffnet den Trace Viewer direkt aus Visual Studio Code heraus. Der Codegenerator muss nativ ausgeführt werden, da es dafür noch keine Integration in Visual Studio Code gibt.

Die Selektion von Elementen ist ein kritisches Feature jedes E2E-Testing-Frameworks. Es muss sichergestellt werden, dass das Element bereit ist, man also Aktionen wie einen Klick an ihm ausführen kann. Sollte das nicht funktionieren und eine Aktion zu früh gestartet werden oder der Test frühzeitig fehlschlagen, weil das Element gerade noch rendert und noch nicht im DOM ist, tritt die gefürchtete Flakiness ein. Wie bereits im Trace Viewer und im Debugger gesehen, führt Playwright eine Reihe von Checks durch, bevor es schlussendlich eine Aktion oder eine Assertion durchführt. Playwright ist sehr geduldig und gibt der Anwendung ausreichend Zeit, das Element erst einmal zu rendern und zu aktivieren. Die eingebaute Wartefunktion verbirgt sich hinter dem Befehl page.locate. Die Befehle click oder fill werden erst dann ausgeführt, wenn das Element bereit zur Verwendung ist.

Es gibt verschiedene Möglichkeiten, Elemente zu selektieren. Die zwei Tests haben vorrangig das Attribut data-testid verwendet, aber es gibt noch weitere. Die Hauptselektoren sind CSS, Text, Role und die verwendeten data-testid-Selektoren. Daneben unterstützt Playwright noch XPath sowie Spezialselektoren für React und Vue. Ein ungewöhnlicher Selektor ist überdies der Layout-Selektor. So kann man beispielsweise Elemente selektieren, die links, rechts, oben, unten oder "in der Nähe" von einem bestimmten Element positioniert sind. Anwendungsfälle hierfür sind allerdings eher die Ausnahme.

Die CSS-Selektoren sind allgemein bekannt und tragen ihren Namen, weil sie sich auch in CSS einsetzen lassen. Das heißt, ein Element nach einer CSS-Klasse navbar würde man mit page.locator('.navbar') selektieren. Für das id-Attribut würde im Lokator #navbar stehen, und für einen Inputselektor nach dem Typattribut ließe sich ein input['type=text'] heranziehen. Eine Verschachtelung à la #navbar p.input input ist ebenfalls möglich. Ist es gewünscht, nach einem Element mit dem Text "Latitia Bellitissa" zu selektieren, besteht die Wahl zwischen page.locator('text=Bellitissa, Latitia') und page.locator('"Bellitissa, Latitia"'). Auch das Verwenden regulärer Ausdrücke ist hier möglich.

Die Selektion nach dem Attribut data-testid wurde bereits gezeigt. Allerdings lassen sich die Selektoren auch untereinander kombinieren. Dafür ist es nötig, den Typ des Selektors voranzustellen. Soll beispielsweise Frau Bellitissa in einem <mat-table> in einer Tabellenzeile mit der bekannten data-testid "row-customer" und davon direkt auf das DOM-Element, das den Namen beinhaltet, springen, dann lässt sich dieser Selektor verwenden:

page.locator(
  'css=mat-table >> data-testid=row-customer >> text="Bellitissa, Latitia"'
);

Eine zulässige Kurzform wäre

page.locator(
  'mat-table >> data-testid=row-customer >> "Bellitissa, Latitia"'
);

Es ist an dieser Stelle wichtig, die einzelnen Selektoren mittels >> voneinander zu trennen.

Daneben besitzt der Lokator ein optionales zweites Argument, um beispielsweise zusätzlich nach einem Text zu filtern. Das ist dann sinnvoll, wenn der Selektor mehrere Elemente zurückliefert und dann eines herauszufiltern ist, das einen bestimmten Text enthält:

page.locator('mat-table >> data-testid=row-customer', { hasText: 'Bellitissa, Latitia', });

Ein ähnlicher Selektor kam bereits im Customers-Test zum Einsatz. Im Unterschied zum vorigen Beispiel wird hier allerdings die komplette Tabellenzeile markiert, anstatt nur das DOM-Element mit dem entsprechenden Text. Neben hasText lässt sich auch mit has ein vollständiger und somit verschachtelter Lokator angeben. Eine sehr mächtige Funktion!