Testing with the Chrome Virtual Authenticator
A virtual authenticator turns every interesting ceremony outcome into something a test can produce on demand: a refusal, a device with no biometric, an authenticator that never answers. This page covers the settings that matter, the setup and teardown that keeps a suite reliable, and the scenarios worth encoding. It sits under Passkey Testing and Conformance.
Capability Reference
| Setting | Values | What it lets you test |
|---|---|---|
| protocol | ctap2 or u2f |
modern ceremonies versus legacy behaviour |
| transport | internal, usb, nfc, ble, hybrid |
which authenticator class the client believes it has |
hasResidentKey |
true or false | discoverable credentials and the storage-full fallback |
hasUserVerification |
true or false | whether the device can verify a user at all |
isUserVerified |
true or false | whether it does on this ceremony |
| automatic presence | on or off | prompt satisfied automatically, or left hanging |
The last row is the one that unlocks timeout testing, and it is the least documented.
Root Cause Analysis
1. Authenticators leaking between tests. An authenticator added in one test and not removed remains attached for the next, carrying its credentials with it. The symptom is a test that passes in isolation and fails in a suite, or worse, one that passes in a suite for the wrong reason.
2. Testing only with user verification enabled. The default configuration verifies the user automatically, which means every ceremony reports the verification bit set. A policy that requires verification then passes every test and fails for the first user with a security key that has no PIN.
3. Assuming a resident key. With hasResidentKey true, an empty allow-list works. Turn it off and the same ceremony finds nothing — which is exactly the behaviour of a real authenticator whose storage is full, and exactly the path most suites never exercise.
4. Waiting out real timeouts. A test that configures an authenticator not to answer and then waits for the browser’s default window will take minutes. Set a short timeout in the ceremony options instead; the code path is identical.
Step-by-Step Resolution
Step 1 — enable the virtual environment before adding anything
The environment is a session-level toggle; adding an authenticator without it silently does nothing in some drivers.
Step 2 — add an authenticator per scenario, not per suite
Each test should describe the device it needs. Sharing one configuration across a suite means changing it for one test breaks the others.
Step 3 — remove it in teardown
Unconditionally, including after a failed assertion. A test framework that skips teardown on failure will cascade one failure into several.
Step 4 — configure the scenario explicitly
Never rely on defaults. Stating hasUserVerification and isUserVerified in every test makes the intent readable and stops a driver default change from silently altering what is being tested.
Step 5 — assert on your own telemetry as well as the outcome
The interesting assertion in a failure test is often not the error but the reason code your server recorded, because that is what your dashboards will show.
Verification and Testing
Prove the harness itself works before trusting it. Register a credential against a virtual authenticator, then authenticate with it, and assert the assertion verifies server-side. If that round trip does not work, nothing built on top of it means anything.
Then prove isolation: run the same registration test twice in one suite and assert both succeed. If the second fails with a duplicate refusal, teardown is not running.
Finally, prove the negative configurations do what you think. A test asserting that a ceremony fails should also assert how it failed — a test that passes because the driver threw a configuration error rather than because the ceremony was refused is a test that will keep passing after the behaviour it guards has broken.
Pitfalls
1. One shared authenticator for the whole suite. Couples every test to every other test.
2. Relying on automatic user verification. Hides every unverified-ceremony bug.
3. Asserting only that an error occurred. Assert the name and, where you have one, the server-side reason code.
Frequently Asked Questions
Does a virtual authenticator produce real cryptography?
Yes. It generates genuine key pairs and produces genuine signatures, which is why an assertion from one verifies against your real server-side code. What it does not simulate is hardware key storage, secure-element behaviour, or anything requiring a physical interaction.
Can I test attestation with one?
To a limited extent. A virtual authenticator produces attestation statements, but they will not chain to a vendor root you would trust in production, so a policy that requires a verified chain will reject them. Attestation verification is better covered by captured fixtures than by the virtual environment.
Does it work in headless mode?
Yes, which is the point — the whole arrangement exists so that ceremony behaviour can be tested in continuous integration without a person present. The virtual environment is driven through the same automation protocol as the rest of the browser.
How do I test the cross-device flow?
You cannot. Setting the transport to hybrid changes what the client believes about the authenticator, which is useful for testing how your interface presents the option, but the actual flow depends on a Bluetooth proximity exchange and a relay that no virtual device provides.
Can I simulate multiple authenticators at once?
Yes, and it is worth doing for the allow-list tests: two authenticators, one holding a credential and one not, proves that the client routes the ceremony using the transports you supplied rather than by asking everything in turn.
Should these tests run on every commit?
Yes. They are fast — the ceremonies complete in milliseconds — and they cover the layer where user-visible regressions actually happen. Reserve the slower conformance tooling for pre-release.
A Scenario Matrix Worth Encoding
Once the harness is reliable, the useful exercise is enumerating device configurations rather than test cases, because each configuration exercises a different branch of your production code with no extra assertions.
Internal transport, resident key, verification available and performed. The modern platform passkey. This is the configuration most of your users have, and it should be the default in tests that are not specifically about something else.
Internal transport, resident key, verification available but not performed. A device where the user declined or the platform did not require it. Your policy for verified actions must reject this ceremony, and the test proves the flag is actually read rather than assumed.
USB transport, no resident key, no verification. An older hardware security key. This configuration breaks two things at once in most implementations: an empty allow-list finds nothing, and a policy requiring verification cannot be satisfied. Both are legitimate real-world states.
USB transport, resident key, verification available. A modern security key with a PIN. Useful as the counterpart to the previous case, and the configuration to use when testing enterprise-style policies.
Two authenticators attached, one holding a credential. Proves the allow-list and its transports actually route the ceremony instead of the client interrogating every device in turn.
Any configuration with automatic presence disabled. The abandoned ceremony, and the only way to exercise the timeout path without waiting.
Six configurations, each a few lines of setup, and between them they cover every branch that a real population will hit. Naming them as named fixtures in your test helpers — rather than inlining the configuration in each test — keeps the intent readable and means adding a seventh later is a single change.
Related
- Passkey Testing and Conformance — the parent topic area and the four testing layers
- Writing Integration Tests for Passkey Flows — the scenarios to encode once the harness works
- Verifying the User Verification Flag — the policy these configurations let you test
- Resolving WebAuthn Timeouts and AbortError — the timeout path a non-answering authenticator exercises