From 7d051253810b3855f095b6e753e077fb555407aa Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Wed, 3 Jun 2026 22:45:13 -0700 Subject: [PATCH] test(e2e): Playwright harness parameterized by BASE_URL + Mailpit mail sink Co-Authored-By: Claude Opus 4.8 --- e2e/lib/mailpit.js | 26 ++++++++++++++ e2e/package-lock.json | 76 ++++++++++++++++++++++++++++++++++++++++ e2e/package.json | 11 ++++++ e2e/playwright.config.js | 12 +++++++ 4 files changed, 125 insertions(+) create mode 100644 e2e/lib/mailpit.js create mode 100644 e2e/package-lock.json create mode 100644 e2e/package.json create mode 100644 e2e/playwright.config.js diff --git a/e2e/lib/mailpit.js b/e2e/lib/mailpit.js new file mode 100644 index 0000000..32f3bfa --- /dev/null +++ b/e2e/lib/mailpit.js @@ -0,0 +1,26 @@ +const MAILSINK = process.env.MAILSINK_URL || 'http://localhost:8025' + +export async function waitForLatestOtc(toAddress, { attempts = 20, delayMs = 500 } = {}) { + for (let i = 0; i < attempts; i++) { + const res = await fetch(`${MAILSINK}/api/v1/messages`) + if (res.ok) { + const data = await res.json() + const msg = (data.messages || []).find( + (m) => (m.To || []).some((t) => t.Address === toAddress), + ) + if (msg) { + const full = await fetch(`${MAILSINK}/api/v1/message/${msg.ID}`) + const body = await full.json() + const text = `${body.Text || ''} ${body.HTML || ''}` + const code = text.match(/\b(\d{6})\b/) + if (code) return code[1] + } + } + await new Promise((r) => setTimeout(r, delayMs)) + } + throw new Error(`no OTC email for ${toAddress} arrived in Mailpit`) +} + +export async function clearMailpit() { + await fetch(`${MAILSINK}/api/v1/messages`, { method: 'DELETE' }) +} diff --git a/e2e/package-lock.json b/e2e/package-lock.json new file mode 100644 index 0000000..4e47414 --- /dev/null +++ b/e2e/package-lock.json @@ -0,0 +1,76 @@ +{ + "name": "rfc-e2e", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rfc-e2e", + "devDependencies": { + "@playwright/test": "^1.49.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000..076e1a2 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,11 @@ +{ + "name": "rfc-e2e", + "private": true, + "type": "module", + "scripts": { + "e2e": "playwright test" + }, + "devDependencies": { + "@playwright/test": "^1.49.0" + } +} diff --git a/e2e/playwright.config.js b/e2e/playwright.config.js new file mode 100644 index 0000000..fb5024a --- /dev/null +++ b/e2e/playwright.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from '@playwright/test' + +export default defineConfig({ + testDir: '.', + timeout: 30_000, + expect: { timeout: 10_000 }, + use: { + baseURL: process.env.BASE_URL || 'http://localhost:8080', + trace: 'on-first-retry', + }, + reporter: [['list']], +})