← 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); });