Erfahren Sie, wie Sie E2E-Tests für Passkeys browserübergreifend mit Playwright, Nightwatch, Selenium und Puppeteer unter Verwendung des virtuellen WebAuthn-Authenticators einrichten.
Anders
Created: June 17, 2025
Updated: June 20, 2025
Unsere Mission ist es, das Internet zu einem sichereren Ort zu machen, und der neue Login-Standard Passkeys bietet eine überlegene Lösung, um dies zu erreichen. Deshalb möchten wir Ihnen helfen, Passkeys und ihre Eigenschaften besser zu verstehen.
Passkeys werden als Authentifizierungsmethode immer breiter akzeptiert und stützen sich auf die Web Authentication (WebAuthn) als zugrunde liegenden Standard. Ihr Popularitätsanstieg ist recht neu, was Dokumentationen und andere Ressourcen relativ rar macht. Dies, zusammen mit der komplexen Natur der Implementierung von Passkeys, kann es für Entwickler schwierig machen, relevante Informationen zum Entwerfen, Implementieren und insbesondere zum Testen von Passkeys für ihre Plattformen und Dienste zu finden.
Dieser Leitfaden soll diese Lücke füllen und konzentriert sich auf Aspekte des virtuellen WebAuthn-Authenticators, die in seiner offiziellen Dokumentation nicht gründlich behandelt werden. Zum Beispiel besprechen wir Konfigurationsoptionen für den virtuellen Authenticator, die in der Dokumentation nicht selbsterklärend sind, sowie Workarounds für bestimmte Anwendungsfälle, für die der virtuelle Authenticator keine bequeme Lösung bietet. Ansonsten ist dieser Leitfaden auch für Entwickler hilfreich, die einfach nach leicht verständlichen Beispielen für die Verwendung des virtuellen Authenticators im Testcode suchen.
Unser Leitfaden verwendet Beispiele aus Playwright, um eine einfache Anleitung zum effektiven Testen der Passkey-Implementierung in Ihrem Projekt zu geben. Playwright ist ein End-to-End (E2E)-Testframework, das das Chrome DevTools Protocol (CDP) als Protokoll für die Browser-Automatisierung verwendet. Wenn Sie speziell nach technischen Beispielen für das Testen von Passkeys in Playwright suchen, können Sie direkt zu Abschnitt 5 springen. Wenn Sie hingegen andere E2E-Testframeworks wie Puppeteer oder Selenium verwenden und Passkeys auf diesen Frameworks testen möchten, werden die Testcode-Implementierungen identisch oder sehr ähnlich zu den in diesem Leitfaden bereitgestellten Beispielen sein, je nachdem, welches Framework Sie verwenden. Im nächsten Abschnitt geben wir einen Überblick über die verschiedenen E2E-Frameworks und wie relevant dieser Leitfaden für diese Frameworks sein wird.
Recent Articles
📖
Passkeys in nativen Apps: Native vs. WebView-Implementierung
👤
Passkey-Fehlerbehebung: Lösungen für Passkey-Probleme und -Fehler
👤
So aktivieren Sie Passkeys unter Windows
⚙️
Passkey-Tutorial: So implementieren Sie Passkeys in Web-Apps
⚙️
E2E-Tests für Passkeys mit Playwright über den virtuellen WebAuthn-Authenticator
Browser-Automatisierung ist, wie der Name schon sagt, der Prozess der Automatisierung sich wiederholender Benutzeraktionen im Browser zum Zweck des Web-Scrapings von Daten oder, in unserem Fall, zum Testen von Webanwendungen. WebDriver und das Chrome DevTools Protocol (CDP) sind zwei der wichtigsten Browser-Automatisierungsprotokolle, die für diesen Leitfaden relevant sind, da sie jeweils eine Implementierung des virtuellen WebAuthn-Authenticators bereitstellen.
WebDriver ist eine ferngesteuerte Schnittstelle, die als Vermittler in der Kommunikation zwischen dem Client und dem Browser angesehen werden kann. Der Fokus dieses Protokolls liegt darauf, eine plattform- und sprachneutrale Schnittstelle bereitzustellen, die alle wichtigen Browser unterstützt, einschließlich solcher, die nicht auf Chromium basieren, wie Firefox und Safari. Da die WebDriver-Schnittstelle eine Verbindung sowohl mit dem Client als auch mit dem Browser verwalten muss, opfert dieser Ansatz Geschwindigkeit und Stabilität zugunsten einer breiteren Browser-Unterstützung (d.h. höhere Instabilität/Flakiness). Zu den namhaften WebDriver-Clients gehören Selenium und Nightwatch.
Entnommen von jankaritech
Das Chrome DevTools Protocol (CDP) hingegen hat keinen Vermittler wie die WebDriver-Schnittstelle zwischen dem Client und dem Browser. Zusätzlich erfolgt die Kommunikation zwischen dem Client und dem Browser über eine Socket-Verbindung, im Gegensatz zur langsameren HTTP-Verbindung zwischen dem Client und der WebDriver-Schnittstelle im vorherigen Ansatz. Diese Punkte machen CDP viel schneller und weniger instabil als WebDriver. Der Nachteil ist, dass dieses Protokoll nur für Chromium-basierte Browser wie Chrome und Edge unterstützt wird. Playwright und Puppeteer sind Beispiel-Clients, die CDP zur Kommunikation mit Browsern verwenden.
Entnommen von jankaritech
Puppeteer ist, ähnlich wie Playwright, ein E2E-Framework, das direkt auf dem CDP aufbaut. Das bedeutet, dass Puppeteer und Playwright beide dieselbe Implementierung des virtuellen WebAuthn-Authenticators verwenden und dass die API-Kommunikation über den virtuellen WebAuthn-Authenticator via Socket-Verbindung ebenfalls identisch ist.
Um dies zu demonstrieren, vergleichen wir den Testcode in Playwright und Puppeteer für den
Aufruf der getCredentials
-Methode, die eine Liste aller bisher im virtuellen
Authenticator registrierten Anmeldedaten zurückgibt. Wir fügen auch einen einfachen
Event-Listener für das credentialAdded
-Ereignis hinzu, das ausgelöst wird, wenn ein
Passkey-Anmeldedatensatz erfolgreich registriert wird. Lassen Sie sich nicht von den
Details der Implementierung einschüchtern, da diese in den späteren Abschnitten erklärt
werden. Diese Beispiele sollen lediglich zeigen, wie ähnlich die Implementierungen
zwischen den beiden Frameworks sind.
Ben Gould
Head of Engineering
I’ve built hundreds of integrations in my time, including quite a few with identity providers and I’ve never been so impressed with a developer experience as I have been with Corbado.
10,000+ devs trust Corbado & make the Internet safer with passkeys. Got questions? We've written 150+ blog posts on passkeys.
Join Passkeys CommunityPlaywright:
const client = await page.context().newCDPSession(page); await client.send('WebAuthn.enable'); const authenticatorId = const result = await client.send('WebAuthn.addVirtualAuthenticator', { options }); ... // get all credentials registered in the virtual authenticator const result = await client.send('WebAuthn.getCredentials', { authenticatorId }); console.log(result.credentials); // add a listener to the credentialAdded event to output a log to the console whenever a passkey credential is registered client.on('WebAuthn.credentialAdded', () => { console.log('Credential Added!'); });
Puppeteer:
const client = await page.target().createCDPSession(); await client.send('WebAuthn.enable'); const authenticatorId = const result = await client.send('WebAuthn.addVirtualAuthenticator', { options }); ... // get all credentials registered in the virtual authenticator const result = await client.send('WebAuthn.getCredentials', { authenticatorId }); console.log(result.credentials); // add a listener to the credentialAdded event to output a log to the console whenever a passkey credential is registered client.on('WebAuthn.credentialAdded', () => { console.log('Credential Added!'); });
Obwohl die Methoden zur Initialisierung der CDP-Sitzung am Anfang der Testcodes leicht unterschiedlich waren, ist der Aufruf von Methoden und die Behandlung von Ereignissen in der CDP WebAuthn Virtual Authenticator API identisch. Das bedeutet, wenn Sie den virtuellen WebAuthn-Authenticator in Puppeteer verwenden möchten, können Sie diesem Leitfaden Zeile für Zeile folgen.
Selenium und Nightwatch sind E2E-Testframeworks, die sich auf WebDriver für die Browser-Automatisierung stützen. Obwohl die Implementierung des virtuellen WebAuthn-Authenticators für WebDriver von seiner Implementierung für CDP getrennt ist, sind ihre API-Spezifikationen ähnlich. Für fast jede Methode in der CDP WebAuthn Virtual Authenticator API finden Sie eine entsprechende Methode in der WebDriver WebAuthn Virtual Authenticator API. Es ist jedoch zu beachten, dass es zwar möglich war, Event-Listener für das erfolgreiche Hinzufügen oder Bestätigen eines Passkeys in der CDP WebAuthn Virtual Authenticator API anzuhängen, dies im WebDriver-Pendant jedoch nicht möglich ist.
Selenium:
const driver = await new Builder().forBrowser('chrome').build(); const options = new VirtualAuthenticatorOptions(); await driver.addVirtualAuthenticator(options); ... // get all credentials registered in the virtual authenticator const credentials = await driver.getCredentials();
Es ist offensichtlich, dass die Syntax zum Einrichten der Instanz des virtuellen Authenticators und zum Tätigen von API-Aufrufen sich von der entsprechenden CDP-Implementierung unterscheidet. Da die API-Spezifikationen der beiden virtuellen WebAuthn-Authenticators jedoch sehr ähnlich sind, wäre es möglich, diesem Leitfaden zu folgen, um eine entsprechende Implementierung auf einem WebDriver-basierten E2E-Testframework zu schreiben.
Cypress ist ein E2E-Testframework, das nicht primär auf WebDriver oder CDP aufbaut wie die oben genannten Frameworks. Es verwendet natives JavaScript, um mit dem Browser zu kommunizieren. Es bietet jedoch einen Low-Level-Zugriff auf das CDP, was bedeutet, dass es möglich ist, rohe CDP-Befehle zu senden, um den virtuellen WebAuthn-Authenticator des CDP zu nutzen.
Da die Syntax für diesen Low-Level-Zugriff umständlich und sehr unterschiedlich zu den obigen Beispielen ist, werden wir in diesem Leitfaden nicht ins Detail gehen. Weitere Informationen zum Aufrufen von CDP-Befehlen in Cypress werden jedoch in diesem Leitfaden erklärt. Die übergeordneten Konzepte zur Verwendung des virtuellen CDP WebAuthn-Authenticators, die in diesem Leitfaden vorgestellt werden, sind dennoch für diejenigen relevant, die Passkeys auf Cypress testen möchten.
Es gibt viele Gründe, warum das Testen der Passkey-Implementierung von Natur aus anspruchsvoller ist als andere, einfachere Benutzeraktionen in einer Webumgebung. Die Notwendigkeit, dynamische Benutzerinteraktionen im Zusammenhang mit der biometrischen Authentifizierung zu handhaben, wie z.B. das Scannen von Fingerabdrücken oder die Gesichtserkennung, fügt eine Komplexitätsebene hinzu, die beim Schreiben von Tests möglicherweise nicht im Detail praktisch zu bewältigen ist. Da Sicherheit im Kontext der Authentifizierung naturgemäß ein Hauptanliegen ist, ist es auch notwendig sicherzustellen, dass die Passkey-Authentifizierung nahtlos über verschiedene Browser und Geräte hinweg integriert ist, ohne Raum für Sicherheits-Schwachstellen zu lassen.
Die Komplexität der Handhabung dynamischer Benutzerinteraktionen bei Passkey-Operationen sowie das Testen ihrer Integration in verschiedene Browser und Geräte wird durch die Verwendung des virtuellen WebAuthn-Authenticators erleichtert.
Der virtuelle WebAuthn-Authenticator ist eine softwarebasierte Darstellung des im WebAuthn-Standard spezifizierten Authenticator-Modells. Er emuliert das Verhalten eines physischen Authenticator-Geräts, wie z.B. eines Hardware-Sicherheitsschlüssels (z.B. YubiKey) oder eines biometrischen Scanners (z.B. bei Face ID, Touch ID oder Windows Hello), arbeitet aber vollständig in der Software (es findet also keine physische Authentifizierung oder das Scannen von Biometrie statt).
Es gibt zwei Hauptvorteile des virtuellen WebAuthn-Authenticators.
Da WebDriver und CDP Werkzeuge zur Browser-Automatisierung sind, ist es offensichtlich, dass der primäre Anwendungsfall der Implementierung des virtuellen WebAuthn-Authenticators in diesen Protokollen das automatisierte Testen ist. Durch die Nutzung dieser Protokolle ermöglicht der virtuelle Authenticator einfache, aber umfassende Tests von Passkey-Funktionalitäten in kontrollierten Umgebungen wie E2E-Testframeworks (z.B. Playwright, Cypress, Nightwatch).
Der virtuelle WebAuthn-Authenticator des CDP ist auch über die DevTools des Chrome-Browsers zugänglich und kann für manuelle Tests oder einfach zu Demonstrationszwecken verwendet werden. Mit dieser Funktion können Sie die Passkey-Eingabe auf einem Gerät simulieren, das Passkeys nicht nativ unterstützt. Entsprechend ist es auch möglich, eine nicht Passkey-fähige Umgebung auf einem Gerät zu simulieren, das Passkeys unterstützt.
Der obige Screenshot zeigt ein Beispiel für die Verwendung des virtuellen Authenticators in Chrome für manuelle Tests oder Demonstrationszwecke. Sie können sehen, dass verschiedene Konfigurationsoptionen für den virtuellen Authenticator möglich sind und das Hinzufügen und Löschen von Anmeldedaten ebenfalls verfolgt werden kann. Beziehen Sie sich auf diesen Leitfaden von Google für weitere Informationen zur Verwendung des virtuellen Authenticators in Ihrem Browser, einschließlich der Konfigurationsoptionen und empfohlenen Werte für jede.
Obwohl der virtuelle WebAuthn-Authenticator eine elegante Lösung für das Testen von Passkey-Implementierungen ist, gibt es einige Nachteile, die erwähnenswert sind.
Als rein softwarebasierte Lösung kann der virtuelle WebAuthn-Authenticator die einzigartigen Hardware-Eigenschaften und Sicherheitsmerkmale physischer Authenticators nicht nachbilden. Die Unterscheidung zwischen der Verwendung verschiedener Plattform-Authenticators (die in ein Gerät integriert sind, wie z.B. ein biometrischer Scanner auf einem Smartphone) und verschiedener plattformübergreifender Authenticators (die externe Geräte sind, wie Hardware-Sicherheitsschlüssel) kann mit dem virtuellen WebAuthn-Authenticator nicht simuliert werden. Obwohl die Black-Box-Vereinfachung der Komplexität, die mit verschiedenen Arten von Plattform- und plattformübergreifenden Authenticators verbunden ist, einer der Vorteile der Verwendung des virtuellen WebAuthn-Authenticators ist, sollten andere Lösungen in Betracht gezogen werden, wenn Sie die Nuancen der verschiedenen Authenticator-Typen simulieren und testen möchten.
Angesichts der relativ neuen Einführung von WebAuthn und der Neuheit der Passkey-Technologie reift das Ökosystem um virtuelle Authenticators noch. Dies führt zu einem Mangel an umfassender Dokumentation und ungelösten technischen Herausforderungen, insbesondere im Kontext der Integration virtueller Authenticators in automatisierte Testframeworks. Dieser Leitfaden zielt darauf ab, dieses Problem anzugehen, indem er umfassende Einblicke in das Testen von Passkeys in einer automatisierten Testumgebung bietet und sich gleichzeitig darauf konzentriert, die noch vorhandenen Unannehmlichkeiten bei der Verwendung dieser Tools anzusprechen und Workarounds für diese Probleme zu präsentieren.
Nach einer erfolgreichen Installation von Playwright und seinen Abhängigkeiten können Sie sofort mit dem Schreiben Ihres ersten Tests beginnen, indem Sie eine Datei mit einem Namen erstellen, der auf .spec.ts oder .test.ts endet, mit folgendem Inhalt:
import { test, expect } from "@playwright/test"; test("my first test", async ({ page }) => { await page.goto("https://passkeys.eu"); // start simulating user actions });
Um den virtuellen WebAuthn-Authenticator in Playwright zu verwenden, genügt es, zu Beginn eines Testfalls eine CDP-Sitzung zu initiieren und einen virtuellen Authenticator anzuhängen, wie folgt:
test('signup with passkey', async ({ page }) => { // Initialize a CDP session for the current page const client = await page.context().newCDPSession(page); // Enable WebAuthn environment in this session await client.send('WebAuthn.enable'); // Attach a virtual authenticator with specific options const result = await client.send('WebAuthn.addVirtualAuthenticator', { options: { protocol: 'ctap2', transport: 'internal', hasResidentKey: true, hasUserVerification: true, isUserVerified: true, automaticPresenceSimulation: false, }, }); const authenticatorId = result.authenticatorId; // Further test steps to simulate user interactions and assertions ... });
Optionen zur Konfiguration des virtuellen WebAuthn-Authenticators:
true
gesetzt ist, wird der
Resident Key
unterstützt (d.h. clientseitig
auffindbare Anmeldeinformationen).true
gesetzt ist, wird die
Benutzerverifizierung unterstützt. Dies auf true
zu setzen wird empfohlen, da es die Simulation von erfolgreichen und fehlgeschlagenen
Passkey-Eingaben ermöglicht.true
gesetzt ist, wird ein erfolgreiches
Authentifizierungsszenario emuliert, während false
einen Authentifizierungsfehler
nachahmt, z.B. wenn ein Benutzer die Passkey-Eingabe abbricht. Beachten Sie, dass diese
Einstellung nur wirksam ist, wenn hasUserVerification
auf true
gesetzt ist.true
gesetzt, erfolgt die Passkey-Eingabe
automatisch und sofort bei jeder Authentifizierungsaufforderung. Umgekehrt erfordert das
Setzen auf false
die manuelle Initiierung der
Passkey-Authentifizierungs-Simulation im Testcode.
Die Wahl der manuellen Simulation (false
) wird aus zwei Gründen empfohlen:
In diesem Abschnitt untersuchen wir die Verwendung der Methoden und Ereignisse des virtuellen WebAuthn-Authenticators im Kontext von sowohl gängigen als auch speziellen Anwendungsfällen.
Dies ist vielleicht die wichtigste und zugleich verwirrendste Aufgabe bei der Verwendung
des virtuellen WebAuthn-Authenticators in einem Testcode, da es keine explizite eingebaute
Methode gibt, um eine Passkey-Eingabe auszulösen. Die Lösung liegt in den
Konfigurationsoptionen des virtuellen WebAuthn-Authenticators, nämlich isUserVerified
und automaticPresenceSimulation
. Mit diesen Optionen können wir diese
Benutzerinteraktion über zwei verschiedene, unten beschriebene Ansätze simulieren.
automaticPresenceSimulation
auf true
gesetzt#Fall 1: Simulation einer erfolgreichen Passkey-Eingabe
test('signup with passkey', async ({ page }) => { ... await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); await page.getByRole('button', { name: 'Login' }).click(); // successful passkey input is automatically simulated (isUserVerified=true) await expect(page.getByRole('heading', { level: 1 })).toHaveText('Welcome!'); ... });
Die Simulation einer erfolgreichen Passkey-Eingabe erfordert normalerweise keine
zusätzlichen Zeilen im Testcode. Die letzte Zeile (await expect...
) wartet darauf, dass
sich die Seite ändert (ausgelöst durch die implizite erfolgreiche Passkey-Eingabe).
Fall 2: Simulation einer abgebrochenen Passkey-Eingabe (die keine Änderungen an der Benutzeroberfläche auslöst)
Das Testen einer fehlgeschlagenen oder abgebrochenen Passkey-Eingabe ist komplizierter, da sie möglicherweise zu keinen beobachtbaren Änderungen an der Benutzeroberfläche führt. Mit anderen Worten, das Warten auf eine Seitenänderung wie im vorherigen Beispiel ist nicht ausreichend, um sicherzustellen, dass die Passkey-Eingabe verarbeitet wurde. Zu prüfen, dass sich die Seite nach der impliziten Passkey-Eingabe nicht geändert hat, ist bedeutungslos, da die Prüfung mit ziemlicher Sicherheit stattfindet, bevor die Passkey-Eingabe abgeschlossen ist. Während der virtuelle Authenticator eine Möglichkeit bietet, auf die Verarbeitung einer erfolgreichen Passkey-Eingabe zu warten, indem auf ein Ereignis gelauscht wird (wie in Ansatz 2 besprochen), gibt es derzeit keine eingebaute Möglichkeit, eine fehlgeschlagene oder abgebrochene Passkey-Eingabe zu erkennen. Ein Workaround wäre, einfach einen festen Timeout hinzuzufügen, um auf den Abschluss der Passkey-Operation zu warten, bevor geprüft wird, dass die Benutzeroberfläche tatsächlich gleich geblieben ist.
test('signup with passkey', async ({ page }) => { // Simulate a set of user actions to trigger a passkey prompt ... await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); // Simulate passkey input when prompted in the test await inputPasskey(async () => { await page.waitForTimeout(300); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); }); // Further test steps ... });
In beiden Fällen ist die Lesbarkeit des Testcodes durch die Implizitheit der Passkey-Operation eingeschränkt. Wie bereits erwähnt, wäre es auch leicht zu übersehen, wenn eine Conditional UI angezeigt wird, in welchem Fall die Passkey-Operation automatisch ohne das Wissen des Testers abgeschlossen würde.
automaticPresenceSimulation
auf false
gesetzt#Das manuelle Auslösen einer Passkey-Eingabe durch Umschalten des Werts der Option
automaticPresenceSimulation
löst die im vorherigen Ansatz aufgetretenen Probleme,
insbesondere in Bezug auf die Lesbarkeit des Testcodes.
Fall 1: Simulation einer erfolgreichen Passkey-Eingabe
Die folgenden Code-Schnipsel simulieren eine erfolgreiche Passkey-Eingabe:
async simulateSuccessfulPasskeyInput(operationTrigger: () => Promise<void>) { // initialize event listeners to wait for a successful passkey input event const operationCompleted = new Promise<void>(resolve => { client.on('WebAuthn.credentialAdded', () => resolve()); client.on('WebAuthn.credentialAsserted', () => resolve()); }); // set isUserVerified option to true // (so that subsequent passkey operations will be successful) await client.send('WebAuthn.setUserVerified', { authenticatorId: authenticatorId, isUserVerified: true, }); // set automaticPresenceSimulation option to true // (so that the virtual authenticator will respond to the next passkey prompt) await client.send('WebAuthn.setAutomaticPresenceSimulation', { authenticatorId: authenticatorId, enabled: true, }); // perform a user action that triggers passkey prompt await operationTrigger(); // wait to receive the event that the passkey was successfully registered or verified await operationCompleted; // set automaticPresenceSimulation option back to false await client.send('WebAuthn.setAutomaticPresenceSimulation', { authenticatorId, enabled: false, }); }
test('signup with passkey', async ({ page }) => { ... // Simulate passkey input with a promise that triggers a passkey prompt as the argument await simulateSuccessfulPasskeyInput(() => page.getByRole('button', { name: 'Create account with passkeys' }).click() ); ... });
Die Hilfsfunktion mag auf den ersten Blick recht einschüchternd wirken. Es hilft zu verstehen, dass alle technischen Komplexitäten der Simulation einer Passkey-Operation in die Hilfsfunktion abstrahiert sind. Das bedeutet, dass der Code bei seiner Verwendung im Testcode einfach und klar wird, wie im zweiten Code-Schnipsel oben zu sehen ist.
Im Vergleich zum impliziten Ansatz in Abschnitt 6.1.1 erhöht dieser explizite Ansatz auch die Lesbarkeit des Codes. Dies wäre besonders hilfreich, wenn eine Conditional UI angezeigt wird, da dieser explizite Ansatz eine unbeabsichtigte, implizite Durchführung der Passkey-Operation ohne das Wissen des Entwicklers verhindert.
Lassen Sie uns nun jeden Teil der Hilfsfunktion verstehen.
Zuerst definieren wir das operationCompleted
-Promise, das entweder auf das
WebAuthn.credentialAdded
-Ereignis oder das WebAuthn.credentialAsserted
-Ereignis
wartet, die, wie der Name schon sagt, ausgelöst werden, wenn ein Passkey-Anmeldedatensatz
registriert bzw. verifiziert wird. Dieses Promise wird später verwendet.
Als Nächstes wird die Option isUserVerified
auf true
gesetzt, damit die nachfolgende
Passkey-Operation durch den virtuellen WebAuthn-Authenticator erfolgreich ist. Die
automaticPresenceSimulation
wird ebenfalls auf true
gesetzt, damit der virtuelle
WebAuthn-Authenticator auf die nächste Passkey-Aufforderung von der Webseite reagiert.
Das Warten auf das operationTrigger
-Promise ist notwendig, um eine Race Condition zu
vermeiden. Die Race Condition tritt auf, wenn die Webseite den Passkey anfordert, bevor
automaticPresenceSimulation
auf true
gesetzt ist. Um dies zu verhindern, muss die
Benutzeraktion, die die Passkey-Aufforderung auslöst, ausgeführt werden, nachdem
automaticPresenceSimulation
auf true
gesetzt wurde. Im obigen Beispiel klickt der
Benutzer auf die Schaltfläche mit dem Namen „Konto mit Passkeys erstellen“, um die
Passkey-Aufforderung auszulösen.
Nachdem die Benutzeraktion abgeschlossen ist, müssen wir auf den Abschluss der
erfolgreichen Passkey-Operation warten. Dies geschieht durch Warten auf das Promise, das
wir am Anfang der Hilfsfunktion definiert haben. Der Abschluss der erfolgreichen
Passkey-Operation wird durch die Auslösung des WebAuthn.credentialAdded
- oder
WebAuthn.credentialAsserted
-Ereignisses markiert. Im obigen Beispiel würde, da der
Benutzer einen Passkey registriert, das WebAuthn.credentialAdded
-Ereignis ausgelöst.
Schließlich wird die Option automaticPresenceSimulation
wieder auf false
gesetzt, um
zu verhindern, dass später im Testcode unbeabsichtigte Passkey-Operationen stattfinden.
Fall 2: Simulation einer abgebrochenen Passkey-Eingabe
Für eine abgebrochene Passkey-Eingabe müssen wir eine leichte Änderung an der
Implementierung für den vorherigen Fall vornehmen. Im Falle einer erfolgreichen
Passkey-Eingabe gibt es Ereignisse, nämlich WebAuthn.credentialAdded
und
WebAuthn.credentialAsserted
, die nach Abschluss der Operation ausgelöst werden. Der
virtuelle WebAuthn-Authenticator bietet jedoch kein Ereignis für eine abgebrochene oder
fehlgeschlagene Passkey-Eingabe. Daher müssen wir einen alternativen Weg finden, um den
Abschluss einer abgebrochenen oder fehlgeschlagenen Passkey-Operation zu überprüfen.
Die folgenden Code-Schnipsel simulieren eine fehlgeschlagene Passkey-Eingabe:
async simulateFailedPasskeyInput(operationTrigger: () => Promise<void>, postOperationCheck: () => Promise<void>) { // set isUserVerified option to false // (so that subsequent passkey operations will fail) await client.send('WebAuthn.setUserVerified', { authenticatorId: authenticatorId, isUserVerified: false, }); // set automaticPresenceSimulation option to true // (so that the virtual authenticator will respond to the next passkey prompt) await client.send('WebAuthn.setAutomaticPresenceSimulation', { authenticatorId: authenticatorId, enabled: true, }); // perform a user action that triggers passkey prompt await operationTrigger(); // wait for an expected UI change that indicates the passkey operation has completed await postOperationCheck(); // set automaticPresenceSimulation option back to false await client.send('WebAuthn.setAutomaticPresenceSimulation', { authenticatorId, enabled: false, }); }
test('signup with passkey', async ({ page }) => { // Simulate a set of user actions to trigger a passkey prompt ... await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); // Simulate passkey input when prompted in the test await inputPasskey(async () => { await page.waitForTimeout(300); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); }); // Further test steps ... });
In der Hilfsfunktion werden die Event-Listener durch einen Promise-Parameter
postOperationCheck
ersetzt, der auf eine erwartete UI-Änderung wartet, bevor
automaticPresenceSimulation
wieder auf false
gesetzt werden kann.
Im Testcode besteht der einzige Unterschied darin, dass die Hilfsfunktion mit einem zusätzlichen Promise aufgerufen werden muss, das auf die beabsichtigte UI-Änderung prüft. Im obigen Beispiel prüfen wir, dass die Webanwendung erfolgreich zu einer Seite navigiert ist, auf der die Überschrift den Text „Etwas ist schiefgelaufen...“ hat.
Wie in Abschnitt 6.1.1 besprochen, führt das Abbrechen einer Passkey-Eingabe möglicherweise zu keiner beobachtbaren Änderung der Benutzeroberfläche. Wie im dortigen Beispiel müssen wir in solchen Fällen eine feste Wartezeit einfügen, bevor wir überprüfen, ob die Benutzeroberfläche tatsächlich unverändert geblieben ist:
test('signup with passkey', async ({ page }) => { ... await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); // Simulate passkey input with a promise that triggers a passkey prompt as the argument await simulateFailedPasskeyInput( () => page.getByRole('button', { name: 'Create account with passkeys' }).click(), async () => { await page.waitForTimeout(300); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); } ); ... });
Die Bequemlichkeit der Verwendung eines virtuellen WebAuthn-Authenticators wird durch seine Fähigkeit erhöht, sich bei der Erstellung oder Löschung eines Passkeys durch die Webanwendung wie ein echter Authenticator zu verhalten. Ein Test muss lediglich Benutzeraktionen durchführen, um die Erstellung oder Löschung eines Passkeys in der Webanwendung zu simulieren, und der virtuelle WebAuthn-Authenticator ändert automatisch seine gespeicherten Anmeldeinformationen, ohne dass zusätzlicher Aufwand seitens des Testcodes erforderlich ist.
Hier ist das Beispiel eines Testcodes, der prüft, ob die Webanwendung einen neuen Passkey ordnungsgemäß im Authenticator registriert:
test('signup with passkey', async ({ page }) => { ... // Confirm there are currently no registered credentials const result1 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result1.credentials).toHaveLength(0); // Perform user actions to simulate creation of a passkey credential (e.g. user registration with passkey input) ... // Confirm the passkey was successfully registered const result2 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result2.credentials).toHaveLength(1); ... });
Wenn wir diesen Code-Schnipsel mit den Code-Schnipseln aus Abschnitt 6.1 kombinieren, können wir den Anmeldevorgang auf unserer Demo-Webseite testen. Das folgende Video ist eine Visualisierung des Tests im UI-Modus von Playwright:
Die Verifizierung eines Passkey-Anmeldedatensatzes mit dem virtuellen WebAuthn-Authenticator funktioniert ähnlich wie die Erstellung eines Passkeys, da der virtuelle Authenticator automatisch die Anzahl der mit einem bestimmten Anmeldedatensatz durchgeführten Verifizierungen nachverfolgt.
test('login with passkey', async ({ page }) => { ... // Confirm there is only one credential, and save its signCount const result1 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result1.credentials).toHaveLength(1); const signCount1 = result1.credentials[0].signCount; // Perform user actions to simulate verification of a passkey credential (e.g. login with passkey input) ... // Confirm the credential's new signCount is greater than the previous signCount const result2 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result2.credentials).toHaveLength(1); expect(result2.credentials[0].signCount).toBeGreaterThan(signCount1); ... });
Das folgende Video demonstriert einen Test für den Login-Ablauf auf unserer Demo-Webseite:
Das Löschen eines Passkeys aus einer Webanwendung sollte hingegen keine Informationen im virtuellen WebAuthn-Authenticator ändern. Die Webanwendung sollte nur in der Lage sein, Anmeldedaten zu löschen, die auf ihrem eigenen Server gespeichert sind. Nur der Benutzer selbst sollte in der Lage sein, einen Passkey-Anmeldedatensatz bewusst und manuell aus dem virtuellen WebAuthn-Authenticator zu löschen.
test('delete a registered passkey credential', async ({ page }) => { ... // Confirm there is currently one registered credential const result1 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result1.credentials).toHaveLength(1); // Perform user actions to simulate deletion of a passkey credential ... // Deleting a passkey credential from a website should not remove the credential from the authenticator const result2 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result2.credentials).toHaveLength(1); ... });
Das folgende Video demonstriert einen Test für das Löschen eines Passkey-Anmeldedatensatzes auf unserer Demo-Webseite:
Der intuitivste Weg, eine geräteübergreifende Authentifizierung von einem zweiten Gerät (das noch keinen registrierten Passkey hat) zu simulieren, besteht darin, einfach eine neue Instanz des virtuellen WebAuthn-Authenticators über den CDP-Befehl hinzuzufügen, wie folgt:
test('signup with passkey', async ({ page }) => { ... // add a virtual authenticator for the first device const authenticatorId1 = await client.send('WebAuthn.addVirtualAuthenticator', { options }); // perform test actions of the first device ... // add a virtual authenticator for the second device const authenticatorId2 = await client.send('WebAuthn.addVirtualAuthenticator', { options }); // perform test actions of the second device .. });
Um die Komplexität der Verwaltung der IDs mehrerer virtueller Authenticators zu vermeiden, ist es auch möglich, ein neues Gerät zu simulieren, indem man einfach die Anmeldedaten aus einem einzigen Authenticator löscht und sie bei Bedarf wieder hinzufügt:
test('signup with passkey', async ({ page }) => { ... const result = await client.send('WebAuthn.getCredentials', { authenticatorId }); const credential = result.credentials[0]; // assuming only one registered passkey const credentialId = credential.credentialId; await client.send('WebAuthn.removeCredential', { authenticatorId, credentialId }); // Perform test actions of the second device which doesn't have a registered passkey ... // Call if it's necessary to simulate the first device which has a registered passkey await client.send('WebAuthn.addCredential', { credential }); // Perform test actions of the first device ... });
Dieser Ansatz kann die Implementierung besonders vereinfachen, wenn ein neues Gerät simuliert werden muss, das alte Gerät aber nicht mehr verwendet wird. In diesem Fall müssen Sie lediglich die Anmeldedaten aus dem virtuellen Authenticator löschen und seine Anmeldedaten vollständig verwerfen:
test('signup with passkey', async ({ page }) => { ... const result = await client.send('WebAuthn.getCredentials', { authenticatorId }); const credential = result.credentials[0]; // assuming only one registered passkey const credentialId = credential.credentialId; await client.send('WebAuthn.clearCredentials', { authenticatorId }); // Perform test actions of the second device which doesn't have a registered passkey ... });
Die Untersuchung von Alternativen zum virtuellen WebAuthn-Authenticator kann Flexibilität bei der Art und Weise bieten, wie Passkey- / WebAuthn-Authentifizierungsprozesse in Projekten getestet werden.
Die Entwicklung von Mock-Services oder -Endpunkten kann das Authentifizierungsverhalten effektiv simulieren und Tests vereinfachen, indem die Feinheiten des eigentlichen Authentifizierungsmechanismus abstrahiert werden. Dieser Ansatz ist besonders vorteilhaft, wenn externe Authentifizierungsdienste verwendet werden, da er es ermöglicht, den Fokus auf die Integration und Funktionalität der Systemkomponenten zu legen, ohne sich in die Details der Authentifizierung vertiefen zu müssen.
Für eine gründliche Untersuchung der Authentifizierungsfunktionalitäten bietet der Einsatz echter Authenticators für Integrationstests einen detaillierten Einblick in die Interaktion mit Hardware-Sicherheitsschlüsseln (z.B. YubiKeys) oder biometrischen Geräten (z.B. bei Face ID, Touch ID oder Windows Hello). Obwohl dies aufgrund der komplexen Natur der Integration realer Geräte in automatisierte Tests typischerweise manuell durchgeführt wird, ist es machbar, benutzerdefinierte Automatisierungsskripte zu entwickeln. Diese Skripte können echte Authenticators mit End-to-End-Testframeworks verbinden und bieten eine Annäherung an reale Benutzerszenarien, was die Zuverlässigkeit des Authentifizierungsprozesses in Live-Umgebungen erhöht.
Nachdem wir die verschiedenen Optionen demonstriert und spezifische Code-Schnipsel für E2E-Tests von Passkeys / WebAuthn mit Playwright gezeigt haben, möchten wir zusätzlich einige allgemeinere Empfehlungen für Entwickler geben, die neu in diesem Thema sind.
Bevor Sie sich mit dem Testen von Passkeys oder anderen Authentifizierungsmechanismen befassen, ist es wichtig, die verfügbaren E2E-Testframeworks zu bewerten und die für die Anforderungen Ihres Projekts am besten geeignete Option auszuwählen. Berücksichtigen Sie den Kompromiss zwischen der Geschwindigkeit und Stabilität, die von CDP-basierten Frameworks wie Playwright und Puppeteer geboten werden, und der browserübergreifenden Kompatibilität, die von WebDriver-basierten Frameworks wie Selenium und Nightwatch bereitgestellt wird. Während CDP-basierte Frameworks eine schnellere und stabilere Browser-Automatisierung bieten, sind sie auf Chromium-basierte Browser beschränkt. Im Gegensatz dazu bieten WebDriver-basierte Frameworks eine breitere browserübergreifende Kompatibilität, einschließlich Unterstützung für nicht-Chromium-Browser wie Firefox und Safari, wenn auch mit potenziell langsamerer und weniger stabiler Leistung. Das Verständnis dieser Kompromisse hilft Ihnen, eine fundierte Entscheidung zu treffen und das Framework auszuwählen, das am besten zu den Anforderungen Ihres Projekts passt.
Obwohl der virtuelle WebAuthn-Authenticator den Prozess des Testens von
Passkey-Implementierungen vereinfacht, ist es für Entwickler entscheidend, ein solides
Verständnis der zugrunde liegenden Konzepte des WebAuthn-Standards und von Passkeys zu
haben. Machen Sie sich mit den verschiedenen Konfigurationen vertraut, die für den
virtuellen WebAuthn-Authenticator verfügbar sind, wie z.B. protocol
, transport
,
hasResidentKey
, hasUserVerification
und isUserVerified
. Das Verständnis dieser
Konfigurationen ermöglicht es Ihnen, den virtuellen Authenticator fein abzustimmen, um
verschiedene Authentifizierungsszenarien genau zu simulieren. Vertiefen Sie sich außerdem
in die Feinheiten der Passkey-Authentifizierung,
einschließlich ihrer Integration in verschiedene Browser und Geräte sowie potenzieller
Sicherheitsaspekte. Dieses grundlegende Wissen wird Sie befähigen, umfassende und
effektive Teststrategien für die Passkey-Authentifizierung in Ihren Webanwendungen zu
entwerfen.
Dieser Leitfaden befasste sich mit der Verwendung des virtuellen CDP WebAuthn-Authenticators mit Playwright und beleuchtete fortgeschrittene Konzepte sowie Probleme, die in der offiziellen Dokumentation nicht behandelt werden. Wir haben auch Alternativen zu CDP innerhalb von Playwright und anderen E2E-Testframeworks untersucht. Trotz unterschiedlicher Implementierungen stellen die standardisierten Spezifikationen des virtuellen WebAuthn-Authenticators die Relevanz dieses Leitfadens über verschiedene Web-Automatisierungsprotokolle und End-to-End-Testframeworks hinweg sicher. Um mehr über verschiedene Konzepte im Zusammenhang mit Passkeys zu erfahren, verweisen wir auf unser Glossar relevanter Terminologien, das Ihnen helfen könnte, den virtuellen WebAuthn-Authenticator entsprechend Ihren Bedürfnissen fein abzustimmen.
Enjoyed this read?
🤝 Join our Passkeys Community
Share passkeys implementation tips and get support to free the world from passwords.
🚀 Subscribe to Substack
Get the latest news, strategies, and insights about passkeys sent straight to your inbox.
Related Articles
Table of Contents