PRACTICAL GUIDE / Selenium virtual authenticator passkeys
Test Passkeys and WebAuthn with Selenium Virtual Authenticator
Test passkey registration and sign-in with Selenium virtual authenticators, explicit WebAuthn options, negative paths, and reliable diagnostics.
In this guide10 sections
- Model the Two WebAuthn Ceremonies
- Declare Authenticator Capabilities Explicitly
- Register Through the Product Surface
- Correlate Browser and Server Evidence
- Exercise Sign-in and User-Verification Failure
- Cover Discoverable and Non-Resident Credentials
- Diagnose Failures at the Correct Boundary
- Keep the Test Secure and Isolated
- Passkey Automation Checklist
- Close on Authorization, Not Animation
What you will learn
- Model the Two WebAuthn Ceremonies
- Declare Authenticator Capabilities Explicitly
- Register Through the Product Surface
- Correlate Browser and Server Evidence
Passkey automation is trustworthy only when it verifies the complete WebAuthn ceremony. A click on "Create passkey" is merely the trigger. The browser must request a credential from an authenticator, the application must validate the returned attestation or assertion, and the account must enter the intended authenticated state.
Selenium's virtual authenticator API gives a test direct control over authenticator capabilities and stored credentials. That makes success and failure paths deterministic without scripting a native biometric dialog. It does not replace testing on real operating systems and devices, but it is the right layer for repeatable browser-level coverage of registration, sign-in, credential deletion, and user-verification policy.
Model the Two WebAuthn Ceremonies
Registration and authentication are different protocols with different evidence. During registration, the server issues creation options containing a challenge and relying-party data. The browser asks the authenticator to create a key pair, then the server validates the response and stores the public credential. During sign-in, the server issues a new request challenge, the authenticator signs it with the private key, and the server verifies the assertion before creating a session.
The Selenium virtual authenticator documentation exposes options, credential inspection, credential removal, and simulated user verification. Build tests around those boundaries instead of treating the authenticator as a shortcut around application behavior.
Animated field map
Passkey Ceremony Under Test
The virtual authenticator creates or retrieves a credential, while the application still owns challenge validation and session creation.
01 / create authenticator
Create Authenticator
Declare CTAP2, resident-key, consent, transport, and verification behavior.
02 / register credential
Register Credential
Run the application's creation ceremony and store the resulting public credential.
03 / application challenge
Application Challenge
Issue a fresh relying-party challenge for the sign-in attempt.
04 / user verification
User Verification
Simulate verification success or failure according to the test case.
05 / signin assertion
Sign-in Assertion
Verify the signed response and create a session only for an accepted ceremony.
Declare Authenticator Capabilities Explicitly
Defaults hide intent. A passkey-oriented case should state that the authenticator uses CTAP2, can hold resident credentials, supports user verification, and will simulate user consent. Transport is also part of the scenario; choose the transport your product claims to support rather than cycling through values without a product reason.
import org.openqa.selenium.virtualauthenticator.HasVirtualAuthenticator;
import org.openqa.selenium.virtualauthenticator.VirtualAuthenticator;
import org.openqa.selenium.virtualauthenticator.VirtualAuthenticatorOptions;
HasVirtualAuthenticator virtualAuth = (HasVirtualAuthenticator) driver;
VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions()
.setProtocol(VirtualAuthenticatorOptions.Protocol.CTAP2)
.setTransport(VirtualAuthenticatorOptions.Transport.USB)
.setHasResidentKey(true)
.setHasUserVerification(true)
.setIsUserConsenting(true)
.setIsUserVerified(true);
VirtualAuthenticator authenticator =
virtualAuth.addVirtualAuthenticator(options);hasUserVerification describes capability; isUserVerified controls the simulated result. Those are not interchangeable. An authenticator can support verification and still report that the current ceremony was not verified. Similarly, resident-key support permits discoverable credentials but does not prove that the application requested or created one.
Register Through the Product Surface
For the primary end-to-end case, create the user through an approved fixture, sign in with a bootstrap method, and register the passkey through the same UI a user sees. The example locators are intentionally application-facing: the test waits for a durable account state, then inspects the authenticator rather than trusting a toast.
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.virtualauthenticator.Credential;
int credentialsBefore = authenticator.getCredentials().size();
driver.get(appBaseUrl + "/account/security");
driver.findElement(By.cssSelector("[data-testid='add-passkey']")).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
wait.until(ExpectedConditions.attributeToBe(
By.cssSelector("[data-testid='passkey-status']"),
"data-state",
"registered"));
List<Credential> credentials = authenticator.getCredentials();
assertEquals(credentialsBefore + 1, credentials.size());
assertTrue(credentials.get(credentials.size() - 1).isResidentCredential());The wait must represent a completed server transition, not merely the disappearance of a dialog. A product can close its modal before the credential write finishes. A status element backed by a refreshed account response, or a test-only API that reads the stored credential record, gives the test a stable completion boundary.
Correlate Browser and Server Evidence
getCredentials() proves that the virtual authenticator now owns credential material. It does not prove that the backend stored the correct public key, linked it to the intended user, or enforced the expected relying-party identifier. Add a privileged test fixture endpoint that returns non-secret registration metadata: account ID, credential ID, relying-party ID, discoverable flag, and lifecycle status.
Compare the credential ID after safe base64url encoding, not by calling toString() on a byte array. Do not expose private keys through the fixture. The private key belongs in the authenticator boundary; the application should retain only public credential data. Clean up by credential ID so a failed test cannot delete an unrelated registration.
Exercise Sign-in and User-Verification Failure
After registration, end the bootstrap session before testing passkey sign-in. Otherwise, a pre-existing cookie can make the assertion ceremony irrelevant while the page still reaches an authenticated route. Clear the application's cookies or use the product's logout endpoint, then start from its unauthenticated login surface.
Run one accepted attempt with setUserVerified(true) and assert a fresh authenticated session. Then log out, switch the same authenticator to setUserVerified(false), and repeat against a request configured with userVerification required.
authenticator.setUserVerified(false);
driver.get(appBaseUrl + "/login");
driver.findElement(By.cssSelector("[data-testid='signin-passkey']")).click();
wait.until(ExpectedConditions.visibilityOfElementLocated(
By.cssSelector("[data-testid='passkey-error']")));
assertTrue(driver.getCurrentUrl().endsWith("/login"));
assertEquals(0, driver.manage().getCookies().stream()
.filter(cookie -> cookie.getName().equals("app_session"))
.count());The cookie name is an application contract, not a Selenium convention. If the product uses several cookies, assert via an authenticated test endpoint as well. The decisive negative result is that authorization was not granted; exact browser error wording may vary and is usually a weaker assertion.
Cover Discoverable and Non-Resident Credentials
A resident credential can be discovered by the authenticator without the server first identifying a credential in an allow list. A non-resident credential depends on server-provided credential information. These modes create different user journeys and failure risks. Test account-first and usernameless entry separately if the product supports both.
For a non-resident case, create an authenticator with resident-key support disabled and register through the product flow that supplies a username before authentication. For a discoverable case, begin without an account identifier and verify that the selected credential maps to the correct account. Avoid asserting that every CTAP2 credential is resident; the actual credential returned by getCredentials() tells you what was created.
Diagnose Failures at the Correct Boundary
If adding the authenticator fails, inspect driver support, session capabilities, and the option combination. If registration reaches the browser but creates no credential, capture the page's WebAuthn rejection, console errors, and the creation options returned by the test environment without logging the raw challenge as reusable test data. A credential present in the authenticator but absent on the server points to response validation, relying-party origin, attestation policy, or persistence.
If sign-in returns an assertion but the server rejects it, compare credential ID, origin, relying-party ID, challenge freshness, verification flags, and counter policy. If no assertion is returned, inspect the authenticator's credential inventory and the request's allow list. This boundary-first triage is faster than retrying the UI because each symptom names a different owner.
Keep the Test Secure and Isolated
Use a dedicated test relying party and disposable accounts. Never inject production credential material, copy real private keys into source control, or record full WebAuthn payloads in broadly visible CI logs. A virtual authenticator is programmable security-sensitive state, so create it per test and remove it in a finally block before quitting the driver.
try {
runRegistrationAndAuthenticationChecks(driver, authenticator);
} finally {
virtualAuth.removeVirtualAuthenticator(authenticator);
driver.quit();
}Virtual automation cannot judge biometric usability, native account pickers, device roaming, Bluetooth or NFC reliability, operating-system policy, or cross-device recovery. Keep a smaller real-device matrix for those risks. The browser suite should own protocol and application behavior; the device matrix should own hardware and platform integration.
Passkey Automation Checklist
- Create the virtual authenticator before starting the ceremony.
- State protocol, transport, resident-key, consent, and verification options.
- Register through the product UI against a disposable account.
- Wait for a server-backed completion state rather than a transient toast.
- Compare authenticator credentials with non-secret backend metadata.
- Remove bootstrap cookies before asserting passkey sign-in.
- Test verification accepted and verification rejected as separate cases.
- Cover discoverable and allow-list flows only when the product supports them.
- Capture browser, WebAuthn, and backend evidence without private key material.
- Remove the authenticator and account data even when an assertion fails.
Close on Authorization, Not Animation
A serious passkey test proves that the right credential was created, bound to the right relying party and account, accepted only under the intended verification policy, and converted into authorization exactly once. Use Selenium's virtual authenticator to control the ceremony, then make application state and credential inventory carry the verdict. Anything less is a polished click test around a security protocol.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Selenium documentation
Selenium Project
Canonical WebDriver, Grid, waits, element, and browser automation guidance.
- 02WebDriver standard
W3C
The browser automation protocol specification behind WebDriver implementations.
FAQ / QUICK ANSWERS
Questions testers ask
Does a Selenium virtual authenticator test a real fingerprint or security key?
No. It exercises the browser and WebAuthn ceremony with a software-defined authenticator. Keep separate device tests for biometric prompts, hardware transport, operating-system account selection, and recovery behavior.
Why should a passkey test use CTAP2 with resident-key support?
A discoverable passkey flow needs an authenticator capable of resident credentials. CTAP2 plus resident-key support models that capability, while a non-resident setup is useful for testing credential flows that depend on a server-supplied allow list.
Can the same virtual credential be reused after the browser session ends?
Do not assume so. The virtual authenticator belongs to the WebDriver session. For deterministic tests, register through the UI in that session or inject a credential whose key material and relying-party data are controlled by the test.
How do I test a failed user-verification ceremony?
Configure an authenticator that supports user verification, require verification in the application's WebAuthn request, call setUserVerified(false), and assert that no authenticated session or protected navigation is created.
What is the strongest registration assertion?
Combine browser evidence from getCredentials with application evidence from a test API or database boundary. A success banner alone cannot prove that the server stored the expected credential for the correct account and relying party.
RELATED GUIDES
Continue the learning route
GUIDE 01
Enable WebDriver BiDi and Manage Event Subscriptions in Selenium
Enable Selenium WebDriver BiDi, verify WebSocket negotiation, scope event subscriptions, prevent races, and clean handlers up deterministically.
GUIDE 02
Custom Expected Conditions for Business-Level UI Readiness
Design custom Selenium Expected Conditions that wait for coherent business state, return typed evidence, and explain synchronization failures clearly.
GUIDE 03
Selenium Java Tutorial: Build a Maintainable Test Suite
Selenium Java tutorial for beginners covering Maven setup, WebDriver, waits, TestNG, JUnit, page objects, debugging, CI, and reliable UI tests.
GUIDE 04
Selenium Wait Commands: Implicit, Explicit, and Fluent Waits
Selenium wait commands explained with implicit, explicit, and fluent waits, practical examples, timing mistakes, flake fixes, and stable patterns.