Agent Browser WebAuthn
Use this with $agent-browser when a browser test must create or authenticate a passkey. The bundled helper keeps one Chrome CDP WebAuthn virtual authenticator attached while one child command drives the whole UI flow.
Workflow
- Start a fresh
agent-browsersession for each full E2E. - Open the target page before attaching WebAuthn, so the helper can bind to the correct page target.
- Set
WEBAUTHN_SKILL_DIRto this skill folder, or replace it with the absolute path shown in this skill's source locator. - Wrap the entire create -> restore -> connect -> reject -> sign driver in one helper invocation. Cleanup removes the authenticator and its credentials when the child exits, so never split ceremonies across helper runs.
WEBAUTHN_SKILL_DIR="/path/to/agent-browser-webauthn"
SESSION="passkey-e2e-$(date +%s)"
ARTIFACT_DIR="$(mktemp -d)"
export SESSION WEBAUTHN_EVENTS_FILE="$ARTIFACT_DIR/webauthn.jsonl"
export WEBAUTHN_CONTROL_FILE="$ARTIFACT_DIR/webauthn-control.txt"
: >"$WEBAUTHN_CONTROL_FILE"
# Start headed first. Once this passes, omit --headed for the headless canary.
agent-browser --session "$SESSION" --headed open http://localhost:3000
node "$WEBAUTHN_SKILL_DIR/scripts/agent-browser-webauthn-helper.mjs" run \
--session "$SESSION" --require-credential true --timeout-ms 300000 -- bash -lc '
set -euo pipefail
ab() { agent-browser --session "$SESSION" "$@"; }
ab find role button click --name "Create passkey"
ab wait --text "Connected"
ab reload
ab find role button click --name "Restore session"
ab wait --text "Restored"
ab find role button click --name "Connect passkey"
ab wait --text "Connected"
printf "uv:false\n" >"$WEBAUTHN_CONTROL_FILE"
until grep -q "\"controlApplied\":\"uv:false\"" "$WEBAUTHN_EVENTS_FILE"; do sleep 0.2; done
ab find role button click --name "Connect passkey"
ab wait --text "rejected"
printf "uv:true\n" >"$WEBAUTHN_CONTROL_FILE"
until grep -q "\"controlApplied\":\"uv:true\"" "$WEBAUTHN_EVENTS_FILE"; do sleep 0.2; done
ab find role button click --name "Sign"
ab wait --text "Signed"
'
grep -q '"event":"WebAuthn.credentialAdded"' "$WEBAUTHN_EVENTS_FILE"
grep -q '"event":"WebAuthn.credentialAsserted"' "$WEBAUTHN_EVENTS_FILE"
agent-browser --session "$SESSION" close
- Adapt only the app URL, button names, and result text. Keep ceremony triggers as native
agent-browser clickorfind ... clickcommands; never use evaluated DOM.click(). - Assert real app output after each operation. For Stellar Smart Account Kit, assert a
C...contract id or an explicit transaction/funding error. Do not treat a visible button click as success. - Require both credential events and inspect the final
WebAuthn.getCredentialsdiagnostic.--require-credential truefails clearly when no virtual credential was observed; without the flag the helper only warns and exits 0, so always pass it for credential flows. - Close the fresh session after smoke tests so virtual authenticators and IndexedDB state do not leak across runs.
Important Details
- Open the target before starting the helper so it attaches before the first ceremony. A reload keeps the same target; after a tab or page target is replaced, reattach by stopping and re-running the whole flow from a fresh session with the helper attached to the new target before creating a credential.
- Headed and headless both work with this recipe. Start headed when diagnosing Chrome/WebAuthn behavior, then repeat headless after the headed flow passes.
- The helper explicitly uses
WebAuthn.enable({enableUI:false})and the proven CTAP2 config:protocol: ctap2,transport: internal,hasResidentKey: true,hasUserVerification: true,isUserVerified: true, andautomaticPresenceSimulation: true. - Setup diagnostics include the target id and authenticator id. Credential events and redacted
WebAuthn.getCredentialsoutput go to stderr orWEBAUTHN_EVENTS_FILEas JSONL; private key material is never logged. - Set
WEBAUTHN_CONTROL_FILEto exercise rejection: writeuv:falsebefore the rejected ceremony anduv:truebefore the next successful ceremony, then wait for the matchingcontrolAppliedevent inWEBAUTHN_EVENTS_FILEbefore the next click—the helper applies directives on a 500 ms poll, so elapsed time does not prove one applied. - Setup, CDP calls, and the wrapped command have a 120-second timeout by default. Set
--timeout-msto a larger positive value for deliberately long flows. - Use a local or otherwise trusted CDP endpoint. Remote CDP URLs are rejected unless
--allow-remote-cdp trueis passed deliberately. - Launch the site on
localhostor HTTPS. WebAuthn requires a secure context; many passkey and smart-account SDKs also reject raw127.0.0.1for domain validation. - Always use a fresh session name for a full E2E. If a session has stale passkey/IndexedDB state, close it rather than trying to repair it in place.
- Do not put secrets in command arguments or page text assertions.
Requirements
agent-browserCLI.$agent-browserskill is recommended for current CLI workflow guidance but is not bundled automatically.- Node.js 22 or newer, for the built-in
WebSocketclient used by the helper.
Script
scripts/agent-browser-webauthn-helper.mjs- wrap any command while a virtual authenticator is attached to the session.scripts/agent-browser-webauthn-helper.test.mjs- run dependency-free CLI safety checks after changing the helper.