Jedes Mal, wenn eine automatisierte Test-Suite läuft, führt das erneute Authentifizieren über UI-Formulare, 2-Faktor-Authentifizierung (2FA) und Weiterleitungen zu einem massiven Performance-Engpass. Diesen versteckten Zeitfresser kannst du mit dem Session-Verwaltung in Cypress über cy.session ganz einfach eliminieren.

Indem du deinen authentifizierten Browser-Zustand einmalig im Cache speicherst, überspringst du redundante Login-Schritte in deiner gesamten Suite und beschleunigst deine End-to-End-Tests extrem. Hier erfährst du Schritt für Schritt, wie du das Ganze einrichtest.

Table of Contents

    Das Problem: Die kumulativen Kosten von UI-Logins

    End-to-End-Tests erfordern einen authentifizierten Benutzer. Der naive Standardansatz besteht darin, die Login-Benutzeroberfläche zu Beginn jedes einzelnen Tests durchzuspielen: Benutzername eingeben, Passwort eingeben, absenden, 2FA abwickeln und auf die Antwort des Backends warten.

    Dieser Ansatz funktioniert zwar, bringt aber zwei erhebliche Probleme mit sich:

    1. Der Performance-Zeitfresser

    Stell dir eine Test-Suite vor, bei der jeder Login etwa 15 Sekunden dauert (Verarbeitung des Formulars, Keycloak-Weiterleitungen, SMS/2FA und das spätere Laden der Daten). Wenn du viele Spec-Dateien hast (in unserem Fall über 100 Specs), die einen beforeEach-Hook für den Login nutzen, zahlst du diese 15-Sekunden-Strafe hunderte Male pro Durchlauf. Das ist keine Testlogik, sondern reine Verschwendung.

    2. Das Flakiness-Risiko

    Jeder UI-Login stellt eine potenzielle Fehlerquelle dar. Ein langsamer 2FA-Anbieter, eine instabile OAuth-Weiterleitung oder ein kurzer Hickup des Identity-Providers können einen gesamten Test zum Scheitern bringen, der eigentlich gar nichts mit der zu prüfenden Funktion zu tun hat.
    Unser Ziel ist es also, uns so selten wie möglich über die Benutzeroberfläche einzuloggen, den authentifizierten Zustand zu erfassen und ihn überall sonst wiederzuverwenden.

    Blick hinter die Kulissen: Was «Eingeloggt sein» eigentlich bedeutet

    Bevor wir das Problem lösen, müssen wir verstehen, was wir da überhaupt aufbewahren. Wenn sich ein Benutzer authentifiziert, stellt der Server dem Browser bestimmte Status-Tokens zur Verfügung:

    • Cookies: Oft ein HTTP-only-Session-Cookie oder ein Auth-Token.
    • localStorage: Wo Single Page Applications (SPAs) häufig Access- und Refresh-Tokens ablegen.
    • sessionStorage: Temporäre Flags für die jeweilige Sitzung.

    «Eingeloggt sein» bedeutet schlichtweg, dass diese Werte existieren. Wenn wir sie einmal erfassen und in einen frischen Browser-Kontext wiedereinspielen, hält uns die Anwendung sofort für authentifiziert – ganz ohne UI-Interaktion. Und genau das automatisiert cy.session.

    Einführung in Sessions

    Seit Cypress 12 ist cy.session als primitives Kernelement fest verankert, um Snapshots vom Browser-Zustand zu erstellen und diese wiederherzustellen.

    cy.session(id, setup, options)
    

    Die drei Grundpfeiler

    • id: Ein eindeutiger Schlüssel für die Session. Wenn die ID mit einem Cache-Eintrag übereinstimmt, verwendet Cypress diesen wieder. Jede Variable, die den Benutzerstatus verändert (Benutzername, Rolle, Umgebungsvariablen), muss Teil dieser ID sein.
    • setup: Ein Callback mit den ursprünglichen UI-Login-Schritten. Cypress führt diesen Code nur aus, wenn der Cache keinen Treffer landet.
    • options: Konfigurationseinstellungen, in denen die wahre Optimierungskraft steckt (validate und cacheAcrossSpecs).

    Der Ausführungszyklus

    [Aufruf cy.session]
    │
    ▼
    
    Alle Cookies und Speicher leeren (Leeres Blatt)
    │
    ▼
    
    Cache nach passender [id] durchsuchen
    │
    ├──► [Treffer] ──► Zustand wiederherstellen ──► validate() ausführen ──► Fertig (Millisekunden)
    │
    └──► [Fehlschlag] ─► setup() ausführen (UI-Login) ──► Zustand speichern ──► Im Cache ablegen
    

    Zwei Punkte, an denen Entwickler oft scheitern.

    1. Sessions stellen den Zustand wieder her, nicht den Ort

    Nachdem eine Session wiederhergestellt wurde, befindest du dich auf einer leeren Seite. Cypress navigiert dich nicht automatisch dorthin zurück, wo die Session erfasst wurde.
    Aus diesem Grund musst du immer nach dem Muster «Zuerst die Session, dann die Navigation» vorgehen:

    LoginPage.loginSessionRemembered()          // Session wiederherstellen oder erstellen
    WorldPage.navigateTo(home)                  // ERST DANN zur aktiven Anwendungsseite navigieren
    WorldPage.checkPageURL(home)                // Jetzt greifen die Assertions ohne Fehler
    

    2. Die Testisolation verlangt es so

    Seit Cypress 12 ist testIsolation standardmässig aktiviert (testIsolation: true). Jeder einzelne Test startet mit einem vollständig bereinigten Browser-Kontext. cy.session ist der offizielle und vorgegebene Weg, die Authentifizierung schnell zurückzuholen, ohne die Unabhängigkeit der Tests zu verletzen.

    Validate: Vertrauen ist gut, Kontrolle ist besser

    Zwischengespeicherte Sessions können ablaufen. Tokens laufen ab, Backends invalidieren Sitzungen oder es vergeht Zeit zwischen lokalen CI-Durchläufen. Wenn Cypress blind eine abgelaufene Session einspielt, schlägt deine gesamte nachfolgende Test-Suite fehl. Die Option validate ist ein Schutz-Callback, der sofort nach der Wiederherstellung einer Session ausgeführt wird.

    • Wenn der Test besteht: Der Test wird sofort fortgesetzt.
    • Wenn er fehlschlägt/einen Fehler wirft: Cypress verwirft den ungültigen Cache und löst automatisch den Setup-Block für einen frischen UI-Login aus.

    Die Validierungen solltest du schlank halten. Suche nach einem Signal dafür, dass die Session noch aktiv ist – zum Beispiel, indem du eine geschützte Route aufrufst und sicherstellst, dass du nicht zum Identity-Provider gebounced wirst:

    validate() {
    cy.visit('/home')
    cy.url().should('not.contain', '/auth-server') // Bounced? Falls ja, schlägt die Validierung fehl.
    }
    

    Cache Across Specs: Der wahre Geschwindigkeitsverstärker

    Standardmässig speichert cy.session den Zustand nur innerhalb einer einzelnen Spec-Datei. Über eine Suite mit mehreren Specs hinweg würde dein aufwendiger UI-Login also immer noch mehrfach ausgeführt werden.

    Wenn du cacheAcrossSpecs: true setzt, übersteht die serialisierte Session den gesamten globalen Testlauf:

    cy.session(id, setup, { cacheAcrossSpecs: true })
    

    Mit dieser Einstellung wird der UI-Login genau ein einziges Mal für die gesamte Testausführung gestartet. Jede nachfolgende Spec stellt den Zustand in Millisekunden aus dem globalen Cache wieder her.

    Die goldene Regel bei der Verwendung von cacheAcrossSpecs lautet: Dein Setup-Callback darf keine äusseren Variablen referenzieren, die nicht in deiner Session-ID enthalten sind. Wenn sich solche Variablen zwischen den Specs verschieben, erzeugt der Cache stillschweigend den falschen Session-Zustand.

    So setzen wir es um

    Unsere Anwendung verfügt über zwei getrennte authentifizierte Bereiche: das Kundenportal und das interne Mitarbeiterportal (Service Portal). Beide nutzen unabhängige Authentifizierungsbereiche.

    1. Umsetzung im Kundenportal

    static loginSessionRemembered(userAccount = ..., password = ..., code = ..., isExternal = false) {
        cy.session(
            [userAccount, password, code, isExternal],   // Eindeutiges ID-Array deckt Benutzer-Variationen ab
            () => {
                // Setup-Block: Echter UI-Login-Ablauf
                // Navigieren -> Cookies akzeptieren -> Anmeldedaten eingeben -> Absenden -> 2FA abwickeln -> Daten laden
            },
            {
                cacheAcrossSpecs: true,
                validate() {
                    // Direktes cy.visit vermeidet interne Nebeneffekte der App während der Validierung
                    cy.visit(portalBasePath.en + portalRoutes.home, { timeout: 60000 })
                    cy.url().should('not.contain', Env.getAuthBaseUrl())
                },
            }
        )
    }
    

    2. Umsetzung im Service Portal

    static loginToServicePortalSessionRemembered(username, password) {
        cy.session(
            [username, password], // Stellt sicher, dass Tests mit verschiedenen Mitarbeitern isolierte Caches nutzen
            () => {
                WorldPage.goToServicePortal()
                // Anmeldedaten eingeben + absenden
            },
            {
                cacheAcrossSpecs: true,
                validate() {
                    WorldPage.goToServicePortal()
                    cy.url().should('not.contain', Env.getServicePortalAuthBaseUrl())
                },
            }
        )
    }
    

    Der Migrationsplan

    Die Einführung von Session-Caching erfordert eine Umstellung der Reihenfolge deiner Testschritte, da die Seite nach dem Wiederherstellen zunächst leer ist.

    // Alter Anti-Pattern
    WorldPage.navigateTo(home)
    
    PortalLoginPage.acceptAllCookies()
    PortalLoginPage.login(...)            // Navigation wurde intern über das Absenden des Formulars gelöst
    
    // Neues Session-Muster
    PortalLoginPage.loginSessionRemembered(...)
    WorldPage.navigateTo(home)          // Dem Browser explizit sagen, wohin er navigieren soll
    

    Die Vor- und Nachteile

    Die Vorteile

    • Geschwindigkeit: Der komplette Login-Prozess (Keycloak + 2FA + Daten laden) läuft etwa einmal pro Testdurchlauf statt über 100 Mal.
    • Zuverlässigkeit: Bedeutend weniger Auth-Interaktionen bedeuten wesentlich weniger instabile Logins, die gar nichts mit der zu testenden Funktion zu tun haben.
    • Ideal für Parallelausführung: Mit cacheAcrossSpecs ist das Aufteilen der Suite auf mehrere CI-Container extrem effizient. Jeder Container loggt sich genau einmal für seinen zugewiesenen Teil ein, anstatt einmal pro Spec.

    Woran du siehst, dass es funktioniert

    • Die erste Spec in deinem Durchlauf ist langsam, aber alle nachfolgenden Specs laufen blitzschnell ab.
    • Hinweis zur Fehlersuche: Wenn jede einzelne Spec langsam bleibt, wird dein Cache nicht wiederverwendet. Überprüfe noch einmal, ob deine ID-Schlüssel absolut stabil sind, und stelle sicher, dass validate nicht zufällig fehlschlägt.

    Anzeichen für Probleme

    • Ständiges Neu-Einloggen: Wenn ein ganzer Durchlauf kontinuierlich den Cache verwirft, um sich neu einzuloggen, ist die validate-Prüfung wahrscheinlich zu streng. Eine Assertion gegen eine nicht gesetzte Umgebungsvariable wird beispielsweise immer fehlschlagen.
    • Fehler nach der Wiederherstellung: Wenn Tests sofort nach einer erfolgreichen Session-Wiederherstellung abstürzen, liegt das meist an einem Verstoss gegen die Leerseiten-Regel. Füge den expliziten Navigationsschritt direkt nach dem Session-Aufruf ein.

    Die wichtigsten Erkenntnisse

    1. Verstehe die Session: Eine Session besteht aus Cookies + localStorage + sessionStorage. Erfasse sie einmal und verwende sie überall wieder.
    2. Smarte Ausführung: cy.session(id, setup, options) führt deinen aufwendigen Setup-Code nur aus, wenn der Cache den Eintrag wirklich nicht kennt.
    3. Globale Gewinne: cacheAcrossSpecs: true macht aus «einmal pro Spec» ein «einmal pro Durchlauf» – das bringt bei grossen Suiten den grössten Performance-Schub.
    4. Halte die Validierung schlank: validate stellt sicher, dass überholte oder abgelaufene Sessions erkannt werden. Halte die Prüfung mit einer einfachen Navigation und URL-Prüfung leichtgewichtig.
    5. Denke an den Ort: Nach dem Wiederherstellen einer Session stehst du auf einer komplett leeren Seite. Denke immer daran: Zuerst die Session, dann die Navigation.
    6. Eindeutige Abgrenzung: Alles, was einen spezifischen Benutzerkontext eindeutig unterscheidet, gehört in das id-Array.
    7. Absichten trennen: Cache niemals den Login-Status für Tests, die explizit dafür geschrieben wurden, den Login-Prozess selbst zu überprüfen.




    Leave a Reply


    The reCAPTCHA verification period has expired. Please reload the page.