porchlight/docs/plans/2026-02-18-playwright-migration-webauthn-e2e-design.md
2026-04-10 11:28:51 +02:00

4.2 KiB

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

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