/** * passkeys-firefox.spec.ts — Firefox content-script proxy E2E suite. * * The MAIN-world monkey-patch only runs under Firefox with a real Gecko * credentials API to delegate to. Playwright's bundled chromium runner cannot * exercise it. To run this suite, build with `npm run build:firefox` and * launch via `web-ext run --target=firefox-desktop --source-dir=dist-firefox`, * then drive Firefox with playwright's `firefox` browser type plus the * preferences below to load the temporary add-on. Until that harness lands * in CI the suite is gated by PEACH_E2E_FIREFOX_PASSKEYS=1, so the default * `npx playwright test` run skips it cleanly. * * The list below is the verification matrix from the v2.12.0 spec. Each * item maps to a manual smoke-test step on a real RP (github.com, * webauthn.io, google.com account settings). */ import { test } from "@playwright/test"; const ENABLED = process.env.PEACH_E2E_FIREFOX_PASSKEYS === "1"; const skip = ENABLED ? test : test.skip; test.describe("Firefox passkey content-script proxy (MAIN world)", () => { // 1. navigator.credentials.create is replaced on document_start. skip("navigator.credentials.create is replaced on page load (MAIN world)", async () => {}); // 2. navigator.credentials.get is replaced on document_start. skip("navigator.credentials.get is replaced on page load (MAIN world)", async () => {}); // 3. Registration: approval UI appears, vault gets a passkey entry with // privateKeyJwk and browser:'firefox'. skip("registration: approval UI appears, passkey stored in vault", async () => {}); // 4. Authentication: approval UI appears, RP receives a valid assertion. skip("authentication: approval UI appears, assertion returned", async () => {}); // 5. clientDataJSON.origin equals the page origin in both responses. skip("clientDataJSON.origin equals the page origin in both responses", async () => {}); // 6. clientDataJSON.origin must NEVER contain `moz-extension://`. skip("clientDataJSON.origin does not contain moz-extension://", async () => {}); // 7. signCount strictly increments across two consecutive get ceremonies. skip("signCount increments between two authentication ceremonies", async () => {}); // 8. user_cancelled: page receives DOMException with name 'NotAllowedError'. skip("user_cancelled: DOMException with NotAllowedError name thrown", async () => {}); // 9. Non-publicKey credentials.create requests fall through to the // platform unchanged (PasswordCredential, FederatedCredential). skip("non-passkey credentials.create falls through to original", async () => {}); // 10. Non-publicKey credentials.get falls through. skip("non-passkey credentials.get falls through to original", async () => {}); // 11. No passkey for rpId: PASSKEY_HAS_CREDENTIALS returns false → fall // through to the platform authenticator. No Peach UI appears. skip("no passkey for rpId: falls through to platform authenticator", async () => {}); // 12. Free tier: pro_required fires; page-side dispatches // 'peach:passkey-pro-gate' event; ceremony falls through to platform. skip("free tier: pro gate fires, falls through to platform", async () => {}); // 13. Round-trip: register passkey → authenticate → vault entry's // signCount and lastUsedAt update. skip("round-trip: register passkey then authenticate updates vault entry", async () => {}); });