# Playwright Test Migration + WebAuthn E2E Tests ## Problem The existing 7 E2E tests use a hand-rolled test runner (`helpers.js` with `run()`/`assert()`). This works but lacks structured reporting, retry logic, parallel execution, and proper lifecycle hooks. Additionally, the WebAuthn (passkey) authentication flow has no E2E coverage. The existing tests acknowledge this gap -- `test_login.js` checks that the WebAuthn button exists but doesn't exercise the actual flow. ## Decision Two-phase approach: 1. Migrate all existing E2E tests from the custom runner to `@playwright/test` 2. Add comprehensive WebAuthn E2E tests using CDP virtual authenticators ## Phase 1: Migrate to @playwright/test ### Infrastructure - Add `@playwright/test` to `package.json` - Create `playwright.config.js` with: - `baseURL` from `TARGET_URL` env var (default `http://localhost:8099`) - Chromium-only project (WebAuthn CDP requires Chromium) - `testDir` pointing to the e2e directory - `testMatch` for `*.spec.js` files - Update `run.sh` to call `npx playwright test` instead of looping over `test_*.js` ### Test conversion Each `test_*.js` becomes `*.spec.js`: - `run(async (page, assert) => { ... })` becomes `test('...', async ({ page }) => { ... })` - `assert(condition, msg)` becomes `expect(condition).toBeTruthy()` or specific matchers - Shared setup moves to `test.beforeAll()` / `test.beforeEach()` - `TARGET_URL` usage replaced by Playwright's `baseURL` (use relative paths) ### What stays the same - `run.sh` still starts the app, seeds data, runs tests, tears down - `setup_db.py` unchanged - Test logic/assertions are equivalent ### Files removed - `helpers.js` -- replaced by Playwright Test's built-in fixtures and `expect` ## Phase 2: WebAuthn E2E tests ### Approach: CDP Virtual Authenticator + Route Interception Chromium DevTools Protocol exposes `WebAuthn.addVirtualAuthenticator` which creates a software authenticator that the browser's WebAuthn API treats as real. This lets us test the full stack: button click -> `navigator.credentials` -> server round-trip -> redirect. For error scenarios, we use Playwright's `page.route()` to intercept network requests and return error responses. ### Virtual authenticator configuration ```js const cdpSession = await page.context().newCDPSession(page); await cdpSession.send('WebAuthn.enable'); const { authenticatorId } = await cdpSession.send('WebAuthn.addVirtualAuthenticator', { options: { protocol: 'ctap2', transport: 'internal', hasResidentKey: true, hasUserVerification: true, isUserVerified: true, automaticPresenceSimulation: true, } }); ``` ### Test scenarios **Registration (from credentials page, requires active session):** - Register a passkey via the "Add security key" button - Verify the new passkey appears in the credential list - Verify registration uses resident key (discoverable credential) **Authentication (usernameless login):** - Full round-trip: register passkey -> logout -> login via passkey button - No username needed -- browser's passkey picker selects the credential - Verify redirect to `/manage/credentials` after successful login - Verify session is established (can access protected pages) **Deletion:** - Register passkey + have password, delete the passkey - Cannot delete last credential (only has passkey, no password) **Error handling (route interception):** - Server error on authentication begin - Server error on authentication complete - Expired session (complete without prior begin) ### Test data Extend `setup_db.py` to create a user for WebAuthn tests: - User with password credential (for logging in to register a passkey) - The test flow: login with password -> register passkey -> logout -> login with passkey ### Files changed/created | File | Change | |------|--------| | `tests/e2e/package.json` | Add `@playwright/test` dependency | | `tests/e2e/playwright.config.js` | New: Playwright Test configuration | | `tests/e2e/run.sh` | Update to use `npx playwright test` | | `tests/e2e/helpers.js` | Remove (replaced by Playwright Test) | | `tests/e2e/test_*.js` -> `*.spec.js` | Migrate all 7 tests to Playwright Test syntax | | `tests/e2e/test_webauthn.spec.js` | New: WebAuthn E2E test suite | | `tests/e2e/setup_db.py` | Add WebAuthn test user fixture |