[object Object]

← back to Wallco Ai

add PDP theme smoke test — verifies every theme renders + v11 Wallpaper/Mural toggle re-wires all dependent surfaces

8919030bd2961b6078a1934f6f658ba6abfb3809 · 2026-06-01 16:48:16 -0700 · Steve Abrams

Files touched

Diff

commit 8919030bd2961b6078a1934f6f658ba6abfb3809
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 1 16:48:16 2026 -0700

    add PDP theme smoke test — verifies every theme renders + v11 Wallpaper/Mural toggle re-wires all dependent surfaces
---
 scripts/smoke-test-themes.js | 168 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 168 insertions(+)

diff --git a/scripts/smoke-test-themes.js b/scripts/smoke-test-themes.js
new file mode 100644
index 0000000..18f9c95
--- /dev/null
+++ b/scripts/smoke-test-themes.js
@@ -0,0 +1,168 @@
+#!/usr/bin/env node
+/**
+ * smoke-test-themes.js — PDP theme smoke test.
+ *
+ * Standing rule (Steve 2026-06-01): every PDP theme must be FULLY WIRED and
+ * covered by a smoke test. This proves two things against a running site:
+ *   1. EVERY theme variant (/design/:id/<slug>) renders from /api/design —
+ *      i.e. the loading shell is replaced and the design title/price appear,
+ *      with no uncaught page errors. Catches a theme that ships but never wires
+ *      to the data layer.
+ *   2. The v11-best Wallpaper⇄Wall Mural format toggle re-renders EVERY
+ *      mode-dependent surface (sold-by line, last spec row, Buy CTA label,
+ *      at-scale preview badge/pill, calculator). Catches a dead toggle that
+ *      looks right but does nothing.
+ *
+ * Usage:
+ *   node scripts/smoke-test-themes.js                 # live, auto-picks a published design
+ *   BASE=http://127.0.0.1:9905 node scripts/smoke-test-themes.js
+ *   node scripts/smoke-test-themes.js <designId> <BASE>
+ *
+ * Playwright is resolved from a sibling project if not installed locally.
+ */
+'use strict';
+
+function loadChromium() {
+  const tries = [
+    'playwright',
+    '/Users/stevestudio2/Projects/animals/node_modules/playwright',
+  ];
+  for (const t of tries) { try { return require(t).chromium; } catch {} }
+  console.error('FATAL: playwright not found (tried: ' + tries.join(', ') + ')');
+  process.exit(2);
+}
+
+const BASE = process.argv[3] || process.env.BASE || 'https://wallco.ai';
+const THEME_SLUGS = [
+  'compact','bento','tabs','hero','vertical','drawer','editorial','spec',
+  'config','stack','best','roomsplit','roomdrawer','roomzine','roomsticky',
+  'roommin','roomshow',
+];
+
+let PASS = 0, FAIL = 0;
+const fails = [];
+function ok(name)        { PASS++; console.log('  ✓ ' + name); }
+function bad(name, why)  { FAIL++; fails.push(name + ' — ' + why); console.log('  ✗ ' + name + ' — ' + why); }
+
+async function pickDesignId() {
+  const https = BASE.startsWith('https') ? require('https') : require('http');
+  return new Promise((resolve) => {
+    https.get(BASE + '/api/designs?limit=8', (r) => {
+      let s = ''; r.on('data', c => s += c); r.on('end', () => {
+        try {
+          const d = JSON.parse(s);
+          const items = d.designs || d.items || (Array.isArray(d) ? d : []);
+          resolve(items.length ? items[0].id : null);
+        } catch { resolve(null); }
+      });
+    }).on('error', () => resolve(null));
+  });
+}
+
+(async () => {
+  const id = process.argv[2] || await pickDesignId();
+  if (!id) { console.error('FATAL: no design id (API gave none)'); process.exit(2); }
+  console.log(`\nPDP theme smoke test — base=${BASE} design=#${id}\n`);
+
+  const chromium = loadChromium();
+  let browser;
+  try {
+    browser = await chromium.launch();
+  } catch (e) {
+    // Bundled browser missing — fall back to the system Google Chrome install.
+    browser = await chromium.launch({ channel: 'chrome' });
+  }
+  const ctx = await browser.newContext({ viewport: { width: 1400, height: 1100 } });
+
+  // ── Part 1: every theme variant renders from /api/design ────────────────
+  console.log('Part 1 — all theme variants render:');
+  for (const slug of THEME_SLUGS) {
+    const page = await ctx.newPage();
+    const errs = [];
+    page.on('pageerror', e => errs.push(e.message));
+    try {
+      const resp = await page.goto(`${BASE}/design/${id}/${slug}`, { waitUntil: 'domcontentloaded', timeout: 25000 });
+      if (!resp || resp.status() !== 200) { bad(slug, 'HTTP ' + (resp && resp.status())); await page.close(); continue; }
+      // loading shell must be replaced by real content
+      await page.waitForFunction(() => {
+        const b = document.body && document.body.innerText || '';
+        return b.length > 80 && !/Loading design/i.test(b);
+      }, { timeout: 15000 });
+      const txt = await page.evaluate(() => document.body.innerText);
+      if (errs.length)            bad(slug, 'pageerror: ' + errs[0]);
+      else if (/Design not found|error/i.test(txt) && txt.length < 200) bad(slug, 'rendered error state');
+      else                        ok(slug + ' renders');
+    } catch (e) {
+      bad(slug, e.message.split('\n')[0]);
+    }
+    await page.close();
+  }
+
+  // ── Part 2: v11-best format toggle re-wires every dependent surface ─────
+  console.log('\nPart 2 — v11-best Wallpaper⇄Wall Mural toggle:');
+  const page = await ctx.newPage();
+  const perr = [];
+  page.on('pageerror', e => perr.push(e.message));
+  try {
+    await page.goto(`${BASE}/design/${id}/best`, { waitUntil: 'domcontentloaded', timeout: 25000 });
+    await page.waitForSelector('.fmt-tab[data-mode="mural"]', { timeout: 15000 });
+    const grab = () => page.evaluate(() => {
+      const t = s => { const el = document.querySelector(s); return el ? el.textContent.trim() : null; };
+      const pill = document.querySelector('#scale-pill');
+      return {
+        soldby:  t('#meta-soldby'),
+        specsold:t('#spec-soldby'),
+        lastk:   t('#spec-lastk'),
+        lastv:   t('#spec-lastv'),
+        cta:     t('#cta-buy'),
+        badge:   t('#atscale-badge'),
+        pillHidden: pill ? getComputedStyle(pill).display === 'none' : null,
+        muralSel: document.querySelector('.fmt-tab[data-mode="mural"]')?.getAttribute('aria-selected'),
+        wallSel:  document.querySelector('.fmt-tab[data-mode="wallpaper"]')?.getAttribute('aria-selected'),
+        total:   t('#calc-total'),
+      };
+    });
+
+    const before = await grab();
+
+    // → Wall Mural
+    await page.click('.fmt-tab[data-mode="mural"]');
+    await page.waitForTimeout(150);
+    const mu = await grab();
+    (mu.muralSel === 'true')                              ? ok('mural tab becomes selected')      : bad('mural tab selected', mu.muralSel);
+    (/Per panel/i.test(mu.soldby || ''))                 ? ok('meta sold-by → "Per panel…"')      : bad('meta sold-by', mu.soldby);
+    (mu.specsold === mu.soldby)                          ? ok('spec sold-by matches')             : bad('spec sold-by', mu.specsold);
+    (mu.lastk === 'Panel size')                          ? ok('last spec key → "Panel size"')     : bad('last spec key', mu.lastk);
+    (/×\s*11\s*ft/.test(mu.lastv || ''))            ? ok('last spec val → panel "… × 11 ft"'): bad('last spec val', mu.lastv);
+    (mu.cta === 'Order wall mural →')               ? ok('Buy CTA → "Order wall mural →"')   : bad('buy cta', mu.cta);
+    (/Wall mural/i.test(mu.badge || ''))                 ? ok('at-scale badge → mural copy')      : bad('atscale badge', mu.badge);
+    (mu.pillHidden === true)                             ? ok('scale pill hidden in mural mode')  : bad('scale pill hidden', mu.pillHidden);
+
+    // → back to Wallpaper
+    await page.click('.fmt-tab[data-mode="wallpaper"]');
+    await page.waitForTimeout(150);
+    const wp = await grab();
+    (wp.wallSel === 'true')                              ? ok('wallpaper tab becomes selected')   : bad('wallpaper tab selected', wp.wallSel);
+    (/Per roll/i.test(wp.soldby || ''))                  ? ok('meta sold-by → "Per roll…"')       : bad('meta sold-by(revert)', wp.soldby);
+    (wp.lastk === 'Roll length')                         ? ok('last spec key → "Roll length"')    : bad('last spec key(revert)', wp.lastk);
+    (wp.lastv === '27 ft')                               ? ok('last spec val → "27 ft"')          : bad('last spec val(revert)', wp.lastv);
+    (wp.cta === 'Buy by the roll →')                ? ok('Buy CTA → "Buy by the roll →"')    : bad('buy cta(revert)', wp.cta);
+    (/At-scale/i.test(wp.badge || ''))                   ? ok('at-scale badge → tile copy')       : bad('atscale badge(revert)', wp.badge);
+    (wp.pillHidden === false)                            ? ok('scale pill visible in tile mode')  : bad('scale pill visible', wp.pillHidden);
+
+    // mode-dependent surfaces actually differ between modes
+    (mu.lastk !== wp.lastk && mu.cta !== wp.cta && mu.soldby !== wp.soldby)
+      ? ok('toggle genuinely re-renders (mural ≠ wallpaper)')
+      : bad('toggle no-op', 'mural and wallpaper render identical');
+
+    perr.length ? bad('v11-best page errors', perr[0]) : ok('no v11-best page errors');
+  } catch (e) {
+    bad('v11-best toggle', e.message.split('\n')[0]);
+  }
+  await page.close();
+  await browser.close();
+
+  console.log(`\n──────── ${PASS} passed, ${FAIL} failed ────────`);
+  if (FAIL) { console.log('FAILURES:'); fails.forEach(f => console.log('  • ' + f)); process.exit(1); }
+  console.log('All theme smoke checks passed.');
+})().catch(e => { console.error('SMOKE HARNESS ERROR:', e); process.exit(1); });

← dafdb2d generator: replace dead DW-orders palette with style-guide p  ·  back to Wallco Ai  ·  deploy: run PDP theme smoke test as step [5b/6] after health a1e2cc7 →