← back to Quadrille House Site
auto-save: 2026-06-23T14:52:21 (3 files) — .deploy.conf server.js clickthrough.js
5e48adae039b5c386be5b2e3344fca022ff1232c · 2026-06-23 14:52:22 -0700 · Steve Abrams
Files touched
M .deploy.confA clickthrough.jsM server.js
Diff
commit 5e48adae039b5c386be5b2e3344fca022ff1232c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 23 14:52:22 2026 -0700
auto-save: 2026-06-23T14:52:21 (3 files) — .deploy.conf server.js clickthrough.js
---
.deploy.conf | 2 +-
clickthrough.js | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
server.js | 2 +-
3 files changed, 133 insertions(+), 2 deletions(-)
diff --git a/.deploy.conf b/.deploy.conf
index e34d63d..50acc03 100644
--- a/.deploy.conf
+++ b/.deploy.conf
@@ -1,3 +1,3 @@
PROJECT_NAME=quadrille-house-site
DEPLOY_PATH=/root/Projects/quadrille-house-site
-HEALTH_URL=http://127.0.0.1:9942/api/config
+HEALTH_URL=http://127.0.0.1:9943/api/config
diff --git a/clickthrough.js b/clickthrough.js
new file mode 100644
index 0000000..4184be6
--- /dev/null
+++ b/clickthrough.js
@@ -0,0 +1,131 @@
+#!/usr/bin/env node
+/* Local smoke test for the Quadrille house microsite.
+ * BASE=http://127.0.0.1:9943 node clickthrough.js
+ * Verifies: DW house chrome, brand/line filter, sort+density (localStorage),
+ * lookbook (PDFs + rooms), held PDP quote flow, live PDP real link, no prices, no vendor URLs,
+ * pairs, share, schema.org, 0 console errors. */
+const PW = process.env.PW_PATH || require.resolve('playwright', { paths: ['/Users/stevestudio2/Projects/professional-directory/node_modules'] });
+const { chromium } = require(PW);
+const BASE = process.env.BASE || 'http://127.0.0.1:9943';
+
+const checks = [];
+const ok = (n, c, d = '') => checks.push({ n, c: !!c, d });
+
+(async () => {
+ const browser = await chromium.launch();
+ const page = await browser.newPage();
+ const consoleErrors = [];
+ page.on('console', m => { if (m.type() === 'error') consoleErrors.push(m.text()); });
+ page.on('pageerror', e => consoleErrors.push(String(e)));
+
+ // ---- HOME ----
+ await page.goto(BASE, { waitUntil: 'networkidle' });
+ await page.waitForSelector('.card', { timeout: 8000 });
+
+ const houseWordmark = await page.textContent('#cUL');
+ ok('House wordmark = Designer Wallcoverings', /Designer Wallcoverings/.test(houseWordmark), houseWordmark);
+ ok('Hero H1 = The Quadrille House', /Quadrille House/.test(await page.textContent('#heroH1')));
+
+ const navHrefs = await page.$$eval('#cUR a', as => as.map(a => a.href));
+ ok('Nav links point to designerwallcoverings.com', navHrefs.some(h => h.includes('designerwallcoverings.com')), navHrefs.join(' '));
+
+ const brandChips = await page.$$eval('#brandChips .chip', cs => cs.map(c => c.dataset.b));
+ ok('Brand/line filter present (All + 9 lines)', brandChips.length >= 10, brandChips.join(','));
+ ok('Brand filter has China Seas + Alan Campbell', brandChips.includes('China Seas') && brandChips.includes('Alan Campbell'));
+
+ const cardCount = await page.$$eval('.card', c => c.length);
+ ok('Grid renders all-house products', cardCount > 1000, `${cardCount} cards`);
+
+ // sort + density controls present + persist
+ ok('Sort select present', await page.$('#sort'));
+ ok('Density slider present', await page.$('#density'));
+ await page.selectOption('#sort', 'title');
+ await page.fill('#density', '5').catch(() => {});
+ await page.evaluate(() => { document.querySelector('#density').value = '5'; document.querySelector('#density').dispatchEvent(new Event('input')); });
+ const lsSort = await page.evaluate(() => localStorage.getItem('qhouse.sort'));
+ const lsDens = await page.evaluate(() => localStorage.getItem('qhouse.density'));
+ ok('Sort persisted to localStorage', lsSort === 'title', lsSort);
+ ok('Density persisted to localStorage', lsDens === '5', lsDens);
+
+ // brand filter click → Alan Campbell (held)
+ await page.click('#brandChips .chip[data-b="Alan Campbell"]');
+ await page.waitForTimeout(600);
+ const acCount = await page.textContent('#cnt');
+ ok('Filtering to Alan Campbell narrows grid', Number(acCount) === 434, acCount);
+
+ // About updates per brand
+ const aboutHeading = await page.textContent('#aboutHeading');
+ ok('About switches to brand (Alan Campbell)', /Alan Campbell/.test(aboutHeading), aboutHeading);
+ const collab = await page.textContent('#aboutCollab');
+ ok('About carries DW collaboration line', /collaboration with Designer Wallcoverings/i.test(collab), collab);
+
+ // no prices in rendered grid
+ const bodyText = await page.evaluate(() => document.body.innerText);
+ ok('No $ prices on landing', !/\$\d/.test(bodyText));
+ ok("No banned word 'Wallpaper' in rendered text", !/Wallpaper/.test(bodyText));
+
+ // ---- LOOKBOOK ----
+ const pdfCount = await page.$$eval('#lbPdfs .lb-pdf', a => a.length);
+ const roomCount = await page.$$eval('#lbRooms .lb-room', a => a.length);
+ ok('Lookbook PDFs rendered (32)', pdfCount === 32, `${pdfCount}`);
+ ok('Lookbook room imagery rendered', roomCount >= 20, `${roomCount}`);
+ const firstPdf = await page.$eval('#lbPdfs .lb-pdf', a => a.href);
+ ok('Lookbook PDF link points to /lookbooks/', /\/lookbooks\//.test(firstPdf), firstPdf);
+
+ // ---- HELD PDP (quote CTA) ----
+ const heldHandle = await page.evaluate(async () => {
+ const d = await (await fetch('/api/products?brand=Alan%20Campbell')).json();
+ return d.products[0].handle;
+ });
+ await page.goto(`${BASE}/product/${heldHandle}`, { waitUntil: 'networkidle' });
+ await page.waitForSelector('.pdp h1', { timeout: 8000 });
+ const heldCta = await page.textContent('.cta');
+ ok('Held PDP CTA = Request memo sample & quote', /Request memo sample/i.test(heldCta), heldCta);
+ ok('Held PDP has NO live store link in CTA', !(await page.$('a.cta[href*="/products/"]')));
+ ok('Held PDP shows specs (Composition)', /Composition/.test(await page.textContent('.spec')));
+ // open quote modal
+ await page.click('#reqBtn');
+ await page.waitForTimeout(300);
+ ok('Quote modal opens', await page.isVisible('#qmodal'));
+ // submit
+ await page.fill('#qform input[name=name]', 'Test Designer');
+ await page.fill('#qform input[name=email]', 'test@example.com');
+ await page.click('#qform button[type=submit]');
+ await page.waitForTimeout(500);
+ ok('Quote submit shows confirmation', await page.isVisible('#qdone'));
+ // pairs + share + schema
+ ok('Held PDP pairs section', await page.$('#pairs .pcard'));
+ ok('Held PDP share cluster', (await page.$$('#share .sbtn')).length >= 4);
+ const heldLd = await page.$$eval('script[type="application/ld+json"]', s => s.map(x => x.textContent).join(' '));
+ ok('Held PDP schema.org Product, no offer', /"@type":"Product"/.test(heldLd) && !/"offers"/.test(heldLd));
+ const heldCanon = await page.$eval('link[rel=canonical]', l => l.href);
+ ok('Held PDP self-canonical (no live PDP)', heldCanon.includes(heldHandle));
+
+ // ---- LIVE PDP (China Seas, real link) ----
+ const liveHandle = await page.evaluate(async () => {
+ const d = await (await fetch('/api/products?brand=China%20Seas')).json();
+ return d.products[0].handle;
+ });
+ await page.goto(`${BASE}/product/${liveHandle}`, { waitUntil: 'networkidle' });
+ await page.waitForSelector('.pdp h1', { timeout: 8000 });
+ const liveCta = await page.$eval('a.cta', a => ({ t: a.textContent, h: a.href }));
+ ok('Live PDP CTA = View & order on DW', /View & order on Designer Wallcoverings/i.test(liveCta.t), liveCta.t);
+ ok('Live PDP CTA links to real DW product', /designerwallcoverings\.com\/products\//.test(liveCta.h), liveCta.h);
+ const liveCanon = await page.$eval('link[rel=canonical]', l => l.href);
+ ok('Live PDP canonical → live DW product', /designerwallcoverings\.com\/products\//.test(liveCanon), liveCanon);
+ const liveBody = await page.evaluate(() => document.body.innerText);
+ ok('Live PDP shows no $ price', !/\$\d/.test(liveBody));
+
+ // ---- no vendor URL leaks anywhere ----
+ const html = await page.content();
+ ok('No quadrillefabrics.com store URL leak', !/quadrillefabrics\.com\/[a-z]/i.test(html));
+
+ ok('No console errors', consoleErrors.length === 0, consoleErrors.slice(0, 3).join(' | '));
+
+ await browser.close();
+
+ const pass = checks.filter(c => c.c).length;
+ console.log(`\n=== Quadrille house clickthrough: ${pass}/${checks.length} PASS ===\n`);
+ for (const c of checks) console.log(`${c.c ? 'PASS' : 'FAIL'} ${c.n}${c.d ? ' — ' + c.d : ''}`);
+ process.exit(pass === checks.length ? 0 : 1);
+})().catch(e => { console.error(e); process.exit(2); });
diff --git a/server.js b/server.js
index 0b1dc37..3f60c58 100644
--- a/server.js
+++ b/server.js
@@ -13,7 +13,7 @@ const express = require('express');
const fs = require('fs');
const path = require('path');
-const PORT = process.env.PORT || 9942;
+const PORT = process.env.PORT || 9943;
const DATA = path.join(__dirname, 'data');
const STORE_BASE = 'https://www.designerwallcoverings.com';
← 51645fc initial scaffold: Quadrille house multi-brand microsite (ser
·
back to Quadrille House Site
·
Quadrille house microsite: multi-brand filter, lookbook (32 33a2927 →