← back to NationalPaperHangers
tests: e2e suite runner — npm run test:e2e runs all e2e-*.js sequentially, exit-78 = skip; claim-flow detects 429 from prior runs and skips cleanly. 36/0/1
698afa012831bbc8376ff5eba6e88d3fd5232bc9 · 2026-05-06 19:37:33 -0700 · Steve
Files touched
M package.jsonM tests/e2e-claim-flow.jsA tests/run-e2e.js
Diff
commit 698afa012831bbc8376ff5eba6e88d3fd5232bc9
Author: Steve <steve@designerwallcoverings.com>
Date: Wed May 6 19:37:33 2026 -0700
tests: e2e suite runner — npm run test:e2e runs all e2e-*.js sequentially, exit-78 = skip; claim-flow detects 429 from prior runs and skips cleanly. 36/0/1
---
package.json | 2 ++
tests/e2e-claim-flow.js | 9 +++++++-
tests/run-e2e.js | 58 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 68 insertions(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 1807e01..6693a84 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"schema": "psql $PGDATABASE < db/schema.sql",
"seed": "psql $PGDATABASE < db/seed.sql",
"test": "node --test tests/smoke.test.js tests/compliance.test.js",
+ "test:e2e": "node tests/run-e2e.js",
+ "test:all": "npm test && npm run test:e2e",
"dm-drafts": "node scripts/generate-ig-dm-drafts.js",
"go-live-check": "node scripts/go-live-check.js",
"gen-secrets": "node scripts/gen-secrets.js"
diff --git a/tests/e2e-claim-flow.js b/tests/e2e-claim-flow.js
index 68b63f8..51b28d2 100644
--- a/tests/e2e-claim-flow.js
+++ b/tests/e2e-claim-flow.js
@@ -82,7 +82,14 @@ function findChromium() {
try {
// ─── STEP 1 · GET /claim ──────────────────────────────────
console.log(`\n[step 1] GET /installer/${TARGET_SLUG}/claim`);
- await page.goto(`${BASE}/installer/${TARGET_SLUG}/claim`, { waitUntil: 'domcontentloaded' });
+ const r1 = await page.goto(`${BASE}/installer/${TARGET_SLUG}/claim`, { waitUntil: 'domcontentloaded' });
+ // The claim route is rate-limited (5/hr/IP). Back-to-back e2e runs trip it;
+ // exit with code 78 (skipped) rather than fail the suite.
+ if (r1 && r1.status() === 429) {
+ console.log(' ⊘ SKIP — claim rate-limit (429) tripped from prior runs; wait ~60min and retry');
+ await pg.end(); await browser.close();
+ process.exit(78);
+ }
const emailInput = await page.$('input[type="email"], input[name="email"]');
assert(!!emailInput, 'claim form has email input');
diff --git a/tests/run-e2e.js b/tests/run-e2e.js
new file mode 100644
index 0000000..d65ed4f
--- /dev/null
+++ b/tests/run-e2e.js
@@ -0,0 +1,58 @@
+#!/usr/bin/env node
+// Runs every tests/e2e-*.js sequentially. Aggregates pass/fail counts.
+// Exits non-zero if any test bailed.
+//
+// Usage:
+// npm run test:e2e
+// BASE=https://nationalpaperhangers.com npm run test:e2e (against prod)
+
+const { spawnSync } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+
+const TESTS_DIR = path.join(__dirname);
+const e2eFiles = fs.readdirSync(TESTS_DIR)
+ .filter(f => /^e2e-.*\.js$/.test(f))
+ .sort();
+
+if (!e2eFiles.length) {
+ console.error('No e2e-*.js files found in tests/');
+ process.exit(1);
+}
+
+console.log(`\n=== NPH e2e suite — ${e2eFiles.length} test file(s) ===\n`);
+
+let totalPass = 0, totalFail = 0, totalSkip = 0;
+const results = [];
+
+for (const f of e2eFiles) {
+ const fp = path.join(TESTS_DIR, f);
+ console.log(`▶ running ${f}`);
+ console.log('─'.repeat(60));
+ const t0 = Date.now();
+ const res = spawnSync('node', [fp], { stdio: 'pipe', encoding: 'utf8', env: process.env });
+ const dt = ((Date.now() - t0) / 1000).toFixed(1);
+ const out = (res.stdout || '') + (res.stderr || '');
+ process.stdout.write(out);
+ // Convention: exit 78 = skipped (e.g. rate-limit, missing fixture, prereq).
+ const skipped = res.status === 78;
+ const m = out.match(/\[result\]\s+(\d+)\s+pass\s+·\s+(\d+)\s+fail/);
+ const filePass = m ? parseInt(m[1], 10) : 0;
+ const fileFail = m ? parseInt(m[2], 10) : (res.status === 0 || skipped ? 0 : 1);
+ totalPass += filePass; totalFail += fileFail; if (skipped) totalSkip++;
+ results.push({ file: f, pass: filePass, fail: fileFail, skipped, dt, exit: res.status });
+ console.log('');
+}
+
+console.log('═'.repeat(60));
+console.log(`Suite summary:`);
+for (const r of results) {
+ const tag = r.skipped ? '⊘' : (r.fail ? '✗' : '✓');
+ const note = r.skipped ? ' · SKIPPED' : '';
+ console.log(` ${tag} ${r.file.padEnd(34)} ${r.pass} pass · ${r.fail} fail (${r.dt}s, exit ${r.exit})${note}`);
+}
+console.log('─'.repeat(60));
+console.log(` TOTAL ${totalPass} pass · ${totalFail} fail · ${totalSkip} skipped`);
+console.log('');
+
+process.exit(totalFail ? 1 : 0);
← eb62adf tests: e2e claim flow — landing form → email-domain check →
·
back to NationalPaperHangers
·
/find shows ALL installers by default (was 5 claimed-only) · c75cd32 →