Custom Cypress Command – Login am Beispiel einer PHP Session

Thorsten Rintelen

Seit über 20 Jahren Software-Developer, Spezialisiert auf Angular, Teamleiter Entwicklung bei der traperto GmbH

e2e Tests mit Cypress

Cypress ist ein Framework fürs Testing von Web-Anwendungen. Das Open-Source-Framework ähnelt auf den ersten Blick Selenium, ist aber fundamental anders und angeblich besser, einfacher und schneller.

Damit ist Cypress eine (die?) moderne Art des End-to-end-Testings. Ein sehr cooler Client auf Basis von Electron und die Möglichkeiten im Continuous Integration (CI) Bereich haben mir den Start mit e2e Testing einfach gemacht.
Ich würde sogar sagen: Es macht Spaß! Ja, Testen kann Spaß machen!

Dass Cypress modern ist, zeigt im übrigen auch, dass ein Testing in verschiedenen Browsern durchgeführt werden kann, aber nicht mehr im IE11 (oder älter). Dieser Zopf ist abgeschnitten worden. Für wen also der IE11 (oder älter) noch wichtig ist, ist mit Cypress nicht gut aufgestellt.

Einen guten Einstieg in das Testing mit Cypress bietet dieser Artikel von webdave, der als Grundlage für diesen Blog-Artikel verwendet werden kann.

Test mit Login an einem PHP Backend

Es gibt immer wieder den Fall, dass Seiten und Funktionen getestet werden sollen, die sich hinter einem Login verbergen. In meinem Fall handelt es sich um ein PHP Backend, bei dem der Login an Hand von Name und Passwort durchgeführt wird. Im Backend wird dann eine PHP Session gestartet.

Da Cypress automatisch vor jedem Test Cookies zurücksetzt, setze ich den Login in ein beforeEach, so dass der Login vor jedem Test neu durchgeführt wird. (Das Verhalten kann umgangen werden, siehe https://docs.cypress.io/api/cypress-api/cookies.html).

Mit cy.request kann ich einen Befehl an das Backend senden, in meinem Fall ein POST Request an den Login Endpunkt. Diesem übergebe ich im Body den user und das password. Da ich unsere Tests natürlich gegen ein speziell für den Test vorbereitetes Backend laufen lasse und die Tests nicht den Weg auf das Produktivsystem finden, brauchen wir uns keine Gedanken um die Passwort-Sicherheit zu machen. 

beforeEach(() => {
    cy.request('POST', `https://localhost:5555/login`, {
        user: 'myUser'
        password: 'myPassword',
    }).then(response => {
        expect(response.body).to.have.property('data');
        expect(response.body).to.have.property('errors');
    });
});

In dem Promise kann ich, wenn ich möchte, die Rückgabewerte des Backends prüfen. Hier Beispielhaft angedeutet, erwarte ich in der Antwort vom Backend mindestens die Keys data und errors. 

Erstellen eines Custom Commands

Spätestens in der dritten Test-Datei die einen Login benötigt, erkenne ich als cleverer Entwickler, dass das Kopieren des Logins in weitere Tests keine zufriedenstellende Lösung ist. Um einen solchen Code-Block auszulagern, bietet Cypress die Möglichkeit, eigene Commands zu bauen.

In der Ordnerstruktur von Cypress findet sich im Ordner support die Datei commands.ts, in der eigene Commands hinterlegt werden können. Mit „Cypress.Commands.add“ kann ich neue Commands erstellen, mit „Cypress.Commands.overwrite“ bestehende überschrieben.

In meinem Beispiel benötige ich ein Login Command. Das Login Command bekommt den User und das Password übergeben, der ausgeführte cy.request entspricht dann dem bereits bekanntem Muster mit dem Login-Endpunkt.

Cypress.Commands.add('login', (user: string, password: string) => {
    cy.request('POST', `${path}/consumer/login`, {
        user: user,
        password: password,
    }).then(response => {
        expect(response.body).to.have.property('data');
        expect(response.body).to.have.property('errors');
    });
});

Dieses Command kann ich jetzt im beforeEach einsetzen. Damit habe ich den Code sauber zentralisiert und den eigentlichen Test übersichtlicher gestaltet. 

beforeEach(() => {
    cy.login('myUser', 'myPassword');
});

(Zufälliger?) Login anhand einer Rolle

Bei uns im System haben wir viele verschiedene Rollen der Nutzer. So ist ein Nutzer z.B. „lerner“, „trainer“, „seminar-admin“ oder „admin“. In meinem Test brauche ich für die verschiedenen Ansichten und Berechtigungen im System den Login von  Nutzern mit unterschiedlichen Rollen. Dabei ist es mir im Test dann egal, ob ich den Nutzer 1 oder den Nutzer 2 einlogge, so lange er z.B. der Rolle „trainer“ angehört.

Für diesen Fall erweitere ich jetzt mein Login Command. Dieses bekommt eine Liste mit „accounts“, in der ich die Nutzer (mit Name und Passwort) einer Rolle zuordne.

Das Login Command bekommt jetzt nur noch die Rolle des Nutzers reingereicht und sucht sich aus der Liste den passenden Eintrag raus. An dieser Stelle wäre es jetzt natürlich auch möglich, an einer Rolle mehrere Accounts zu hinterlegen und diesen zufällig auszuwählen.

const accounts = [
    {
        role: 'admin',
        user: 'admin-1',
        password: 'rfty8y7uyu',
    },
];
 
Cypress.Commands.add('login', (role: string) => {
    const account = accounts.find(account => account.role === role);
 
    if (!account) {
        throw new TypeError(`Account for role ${role} not found`);
    }
 
    cy.request('POST', `${path}/consumer/login`, {
        user: account.user,
        password: account.password,
    }).then(response => {
        expect(response.body).to.have.property('data');
        expect(response.body).to.have.property('errors');
    });
});

Der Aufruf im Test ändert sich dann nur minimal und sieht wie folgt aus.

beforeEach(() => {
    cy.login('lerner');
});

Fazit

Custom Command in Cypress sind ein sehr einfacher und effektiver Weg, die Tests übersichtlich zu halten und Code auszulagern.
Ein kleiner Grund mehr, warum mir das Testing mit Cypress Spaß macht!