[object Object]

← back to NationalPaperHangers

tests: e2e-public-pages — 12 routes × 4 assertions = 48; status 200 + content-marker + no-error-template + no-console-errors. Runs clean against prod (no auth/DB writes)

cead6419ba176d315af0cca0b5c93ca71a43f7b2 · 2026-05-06 19:55:02 -0700 · Steve

Files touched

Diff

commit cead6419ba176d315af0cca0b5c93ca71a43f7b2
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed May 6 19:55:02 2026 -0700

    tests: e2e-public-pages — 12 routes × 4 assertions = 48; status 200 + content-marker + no-error-template + no-console-errors. Runs clean against prod (no auth/DB writes)
---
 tests/e2e-public-pages.js | 91 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 91 insertions(+)

diff --git a/tests/e2e-public-pages.js b/tests/e2e-public-pages.js
new file mode 100644
index 0000000..04b8c1c
--- /dev/null
+++ b/tests/e2e-public-pages.js
@@ -0,0 +1,91 @@
+// e2e: every auth-free public route renders cleanly.
+// Designed to run against prod without writing anything.
+//   BASE=https://nationalpaperhangers.com node tests/e2e-public-pages.js
+//
+// Per route, asserts:
+//   • HTTP 200
+//   • a route-specific content marker is present
+//   • the error template ("Something went wrong") did NOT render
+//   • no JS errors landed in the browser console while loading
+
+const { chromium } = require('playwright-core');
+
+const BASE = process.env.BASE || 'http://localhost:9765';
+
+function findChromium() {
+  const paths = [
+    '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
+    '/Applications/Chromium.app/Contents/MacOS/Chromium',
+    process.env.CHROME_PATH,
+  ].filter(Boolean);
+  for (const p of paths) { try { require('fs').accessSync(p); return p; } catch {} }
+  return null;
+}
+
+// Each row: [path, marker text, optional skip-if-empty]
+const routes = [
+  ['/',                    'National Paper Hangers'],
+  ['/find',                'verified installer'],
+  ['/map',                 'map'],
+  ['/watch',               'Watch'],
+  ['/for-installers',      'installer'],
+  ['/about',               'About'],
+  ['/privacy',             'Privacy'],
+  ['/terms',               'Terms'],
+  ['/installer/atelier-bond-nyc',       'Atelier'],
+  ['/installer/atelier-bond-nyc/book',  'consultation'],
+  ['/login',               'Log in'],
+  ['/signup',              'Apply to list'],
+];
+
+(async () => {
+  const exe = findChromium();
+  if (!exe) { console.error('FAIL: no Chromium'); process.exit(2); }
+  const browser = await chromium.launch({ executablePath: exe, headless: true });
+  const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
+  const page = await ctx.newPage();
+  let pass = 0, fail = 0;
+  const assert = (c, m) => { if (c) { console.log(`  ✓ ${m}`); pass++; } else { console.log(`  ✗ ${m}`); fail++; } };
+
+  for (const [routePath, marker] of routes) {
+    const url = BASE + routePath;
+    console.log(`\n[${routePath}]`);
+    const consoleErrors = [];
+    const handler = msg => { if (msg.type() === 'error') consoleErrors.push(msg.text()); };
+    page.on('console', handler);
+
+    let resp;
+    try {
+      resp = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 12000 });
+    } catch (e) {
+      assert(false, `navigation succeeded — ${e.message}`);
+      page.off('console', handler);
+      continue;
+    }
+    const status = resp ? resp.status() : 0;
+    assert(status === 200, `HTTP 200 (got ${status})`);
+
+    const html = await page.content();
+    const noErrPage = !/Something went wrong|We hit a snag/i.test(html);
+    assert(noErrPage, 'no error template rendered');
+
+    const hasMarker = new RegExp(marker, 'i').test(html);
+    assert(hasMarker, `content marker "${marker}" present`);
+
+    // Filter out CSP-noise + analytics/3rd-party errors + lazy-asset 404s.
+    // "Failed to load resource: 404" is typically a missing image/SVG that
+    // an editorial template references — not a JS bug.
+    const ourErrors = consoleErrors.filter(e =>
+      !/google.*analytics|gtag|stripe|favicon|net::ERR_BLOCKED|tagmanager/i.test(e) &&
+      !/Failed to load resource:.*404/i.test(e)
+    );
+    assert(ourErrors.length === 0, `no first-party JS console errors (${ourErrors.length} ${ourErrors.length === 1 ? 'error' : 'errors'})`);
+    if (ourErrors.length) ourErrors.slice(0, 3).forEach(e => console.log('      console:', e.slice(0, 120)));
+
+    page.off('console', handler);
+  }
+
+  await browser.close();
+  console.log(`\n[result] ${pass} pass · ${fail} fail`);
+  process.exit(fail ? 1 : 0);
+})();

← c75cd32 /find shows ALL installers by default (was 5 claimed-only) ·  ·  back to NationalPaperHangers  ·  tests: graceful rate-limit handling — public-pages soft-pass a0527b7 →