← back to Quadrille House Site
clickthrough.js
132 lines
#!/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/macstudio3/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); });