Cypress vs. Playwright – Die relevanten Unterschiede

Cypress und Playwright sind die derzeit wohl beliebtesten Testing-Frameworks für Websites. Nachdem ich beide nun länger selbst verwendet habe, möchte ich meine Erkenntnisse daraus teilen und die wesentlichen praxisrelevanten Unterschiede aufzeigen.

Grundsätzlich kann man mit beiden Frameworks Internetseiten testen. Ich finde, es gibt kein Ausschlusskriterium für eines der beiden Frameworks – wenn man Cypress verwenden kann, kann man auch Playwright verwenden und umgekehrt. Die Unterschiede liegen also eher nicht darin, was man damit macht, sondern wie man es macht. Darin unterscheiden sich die beiden E2E-Frameworks in vielen Punkten, von denen überraschend viele letztlich eigentlich gar nicht so wichtig sind. Ich fokussiere mich daher auf die Dinge, die man wirklich merkt und die man am ehesten bei dem jeweiligen Framework vermisst.

Schwachpunkte Cypress

Kompliziertes Cross-Origin

Cypress steuert die Website aus dem Browser heraus, es ist also quasi selbst eine Website, die die zu testende Seite bei sich einbindet und dann darauf zugreift. Playwright hingegen nutzt den eher „klassischen“ Ansatz und öffnet ein Browserfenster, welches von außen gesteuert wird. Während der Ansatz von Cypress auch Vorteile bietet (siehe unten bei den Nachteilen von Playwright), gibt es das große Manko, dass es schnell nervig wird, wenn man im Test auf unterschiedlichen Origins, sprich unterschiedlichen Domains, unterwegs ist, wie z.B. einer vorgeschalteten Login-Seite. Früher ging dies überhaupt nicht, mittlerweile gibt es mit „cy.origin()“ einen Weg, bei dem man aber gerne mal auf die ein oder andere Einschränkung oder manchmal auch Bug trifft. Letztlich geht es irgendwie, aber es ist spürbar mit Komplikationen verbunden.

Kein await

Fast alle Dinge laufen in beiden Testing-Frameworks asynchron ab, da immer auf die zu testende Seite gewartet wird. In JavaScript nutzt man dann normalerweise das „await“ Keyword, Cypress ist aber so konzipiert, dass es synchron aussieht, intern die Befehle aber sammelt und dann asynchron ausführt. Man spart sich so zwar das ständige „await“ im Code und für Nicht-Entwickler sieht es vielleicht erstmal einfacher aus, ansonsten ist es aber nicht sonderlich intuitiv und falls man doch mal explizit warten muss, muss man ein then-Callback anlegen, was den Code unschön verschachtelt. Auch selbstgeschriebene, eigene Befehle werden damit komplizierter.

Drängen auf die Cypress Cloud

Cypress ist kostenlos und die Entwicklung wird durch das optionale, kostenpflichtige Cloudangebot finanziert. Die Cloud dient im Wesentlichen dazu, dass man in einer CI-Pipeline die Tests auf verschiedenen Runnern verteilt laufen lassen kann, um Zeit zu sparen. Das ginge auch ohne die Cloud, ist aber komplizierter als es sein könnte und wird in der Doku auch nicht wirklich erwähnt. Die weiteren Cloud-Funktionen sind weniger nützlich als sie zunächst erscheinen mögen, mir haben sie jedenfalls keinen Mehrwert gebracht.

Schwachpunkte Playwright

Schlechteres UI

Playwright läuft in einem eigenen Fenster und zeigt darin nur eine Kopie der zu testenden Seite an, mit der sich nicht interagieren lässt. Die Konsoleneinträge und Netzwerkverbindungen werden in einer Liste ohne die üblichen Filtermethoden und Details angezeigt. Man muss konfigurieren, dass parallel auch der echte Browser mit den normalen Dev-Tools geöffnet wird und offenbleibt. Allgemein ist das UI in vielen Punkten nicht so schön wie das von Cypress und komplizierter zu nutzen.

Schlechtere Programmierschnittstelle

Nachdem man alles eingerichtet hat, ist das Schreiben von Tests die Hauptaufgabe. Dabei finde ich in fast allen Situationen den Code von Cypress etwas leichter zu schreiben und zu verstehen als den von Playwright. Auch ist der Code von Playwright meist länger, selbst ohne das an sich nützliche „await“ (s.o.). Hier ein paar Beispiele, jeweils erst die Cypress-Variante und dann die von Playwright:

// Prüfen ob Element vorhanden:
cy.getByTestId('main-view'); // Cypress (schlägt schon fehl wenn Element nicht gefunden)
await expect(page.getByTestId('main-view')).toBeVisible(); // Playwright
// "getByTestId" musste ich in Cypress selbst hinzufügen, in Playwright kommt es von Haus aus mit

// Prüfen, ob Element nicht vorhanden:
cy.getByTestId('progress-spinner').should('not.exist'); // Die "Should"-Strings sind ein Union-Type und haben Autovervollständigung
await expect(page.getByTestId('progress-spinner')).not.toBeAttached();

// Prüfen, ob Element mehrmals vorhanden:
cy.getByTestId('table-row').should('have.length.above', 1);
expect(await page.getByTestId('table-row').count()).toBeGreaterThan(1);

// Ein Häkchen in einer Checkbox setzen:
cy.get('input[type="checkbox"]').check();
await page.locator('input[type="checkbox"]').setChecked(true); // nicht "check", das liest nur

// Einen Icon-Button in der Toolbar anklicken:
cy.get('my-toolbar').find('[svgicon="arrow-left"]').parents('a').click();
await page.locator('my-toolbar a').filter({has: page.locator('[svgicon="arrow-left"]')}).click();

// Code für jedes zurückgegebene Element ausführen:
cy.getByTestId('order-number').then((elements: JQuery<HTMLSpanElement>) => {
  for (const element of elements) {
    // ...
  }
}
// Playwright:
const locators = page.getByTestId('order-number');
await expect(locators.first()).toBeVisible(); // Sonst resolved Playwright sofort mit [], falls noch nicht vorhanden
for (const locator of await locators.all()) {
  // ...
}

// Warten, dass ein XHR-Request durchgeführt wurde:
cy.intercept('**/query').as('query');
// ...
cy.wait('@query');
// Playwright:
const responsePromise = page.waitForResponse((response) => response.url().endsWith('/query'));
// ...
await responsePromise;

Umständliche Authentifizierung

Die beiden vorherigen Nachteile werden vereint, wenn man sich auf der zu testenden Seite einloggen muss. Während es in Cypress eine sehr praktische cy.session() Funktion gibt, muss man in Playwright aufwändiger eine eigene Speicherung der Browserdaten in einer Datei implementieren. Damit man sich beim Schreiben der Tests nicht jedes Mal erneut die Authentifizierung ansehen muss, muss man benutzerunfreundlich in den Test-Filtern herumklicken.

Fazit

Es wäre einfacher, wenn eines der beiden Test-Frameworks klar besser wäre, oder es einen Grund gäbe, warum man eines ausschließen kann. Den gibt es aber nicht. Man kann beide verwenden und die Vor- und Nachteile halten sich gut in der Waage. Ich habe derzeit eine leichte Präferenz für Cypress, hauptsächlich aufgrund des schöneren Test-Codes und da es insgesamt etwas ausgereifter wirkt.

Ich hoffe, dass sich die beiden Tools in Zukunft aufeinander zubewegen, um die Vorteile zu vereinen, wie die Code-Syntax von Cypress, aber die Steuerung des Browsers von außen bei Cross-Origin Seiten von Playwright. Solch radikale Architekturänderungen sind aber wohl eher unwahrscheinlich. Wer weiß, vielleicht gibt es in zehn Jahren ja mal das „perfekte“ Testing-Framework ;)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.