← back to Dw Yolo Loop

artmura-site/clickthrough.js

161 lines

/* Live click-through of every Artmura landing component. Reports PASS/FAIL per component. */
const { chromium } = require('/Users/macstudio3/Projects/ventura-corridor/node_modules/playwright');
const BASE = process.env.BASE || 'https://artmura.designerwallcoverings.com';
const LINE = process.env.LINE || 'Artmura';                       // expected hero H1 / line name
const COLL = process.env.COLLECTION_SLUG || 'artmura';            // expected /collections/<slug>
const results = [];
const ok = (n, cond, extra='') => { results.push({ n, pass: !!cond, extra }); console.log(`${cond?'✓':'✗'} ${n}${extra?'  — '+extra:''}`); };

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
  const errs = []; page.on('console', m => { if (m.type()==='error') errs.push(m.text()); });
  page.on('pageerror', e => errs.push(e.message));

  // ---------- HOMEPAGE ----------
  await page.goto(BASE, { waitUntil: 'networkidle' });
  await page.waitForSelector('.card', { timeout: 15000 });

  const wm = (await page.textContent('#cUL'))?.trim();
  ok('Wordmark = Designer Wallcoverings', wm === 'Designer Wallcoverings', wm);
  ok('Wordmark links to DW', (await page.getAttribute('#cUL','href'))?.includes('designerwallcoverings.com'));

  const navTxt = await page.$$eval('#cUR a', as => as.map(a=>a.textContent.trim()+' → '+a.getAttribute('href')));
  ok('Nav rendered (4 links)', navTxt.length === 4, navTxt.join(' | '));
  ok(`Nav "Shop the Collection" → /collections/${COLL}`, navTxt.some(t=>/Shop the Collection/.test(t) && new RegExp('collections/'+COLL).test(t)));

  ok('Hero eyebrow', (await page.textContent('#heroEyebrow'))?.includes('Designer Wallcoverings'));
  ok(`Hero H1 = ${LINE}`, (await page.textContent('#heroH1'))?.trim() === LINE);
  const slides = await page.$$('.hero .slide'); ok('Hero rotating slides present', slides.length >= 2, slides.length+' slides');

  const total = parseInt(await page.textContent('#cnt'), 10);
  const EXP = process.env.EXPECTED_COUNT ? parseInt(process.env.EXPECTED_COUNT, 10) : null;
  ok('Grid count shown', EXP ? total === EXP : total > 0, total+' products');
  const cardCount = (await page.$$('.card')).length;
  ok('Grid renders cards', cardCount === total, cardCount+' cards');

  // NO visible prices anywhere on the page
  const bodyTxt = await page.textContent('body');
  ok('No $ prices visible on homepage', !/\$\d/.test(bodyTxt));
  ok('Cards show "View →"', (await page.textContent('.card .pr'))?.includes('View'));

  // book chips filter
  const chips = await page.$$('#chips .chip');
  ok('Book chips present (All + any books)', chips.length >= 1, chips.length+' chips'+(chips.length<2?' — line has no collection_book (optional)':''));
  if (chips.length > 1) {
    await chips[1].click(); await page.waitForTimeout(400);
    const after = parseInt(await page.textContent('#cnt'),10);
    ok('Book chip filters grid', after > 0 && after <= total, after+' after filter');
    await (await page.$$('#chips .chip'))[0].click(); await page.waitForTimeout(300); // reset to All
  }

  // color filter
  const dots = await page.$$('#colorbar .cdot');
  ok('Color filter (present or N/A)', dots.length === 0 || dots.length >= 2, dots.length ? dots.length+' buckets' : 'no colors file — color filter off (optional enhancement)');
  if (dots.length > 1) {
    await dots[1].click(); await page.waitForTimeout(400);
    const after = parseInt(await page.textContent('#cnt'),10);
    ok('Color filter narrows grid', after > 0 && after <= total, after+' items');
    await (await page.$$('#colorbar .cdot'))[0].click(); await page.waitForTimeout(300);
  }

  // pattern select
  const patOpts = await page.$$eval('#pattern option', os => os.length);
  ok('Pattern select populated', patOpts > 1, patOpts+' options');

  // sort select (price options removed)
  const sortOpts = await page.$$eval('#sort option', os => os.map(o=>o.value));
  ok('Sort options present', sortOpts.length >= 5, sortOpts.join(','));
  ok('Price sort options removed', !sortOpts.includes('price_asc') && !sortOpts.includes('price_desc'));
  await page.selectOption('#sort','color'); await page.waitForTimeout(400);
  ok('Sort by Color works (grid still full)', (await page.$$('.card')).length === total);

  // density slider
  await page.fill('#density','6').catch(()=>{});
  await page.$eval('#density', el => { el.value='6'; el.dispatchEvent(new Event('input',{bubbles:true})); });
  await page.waitForTimeout(200);
  const cols = await page.$eval(':root', () => getComputedStyle(document.documentElement).getPropertyValue('--cols'));
  ok('Density slider sets --cols', cols.trim()==='6', '--cols='+cols.trim());

  // homepage share cluster
  const shareBtns = await page.$$('#shareCollection .sbtn');
  ok('Homepage share icons (5)', shareBtns.length === 5, shareBtns.length+' icons');
  const pinHref = await page.getAttribute('#shareCollection .sbtn.pin','href');
  ok('Pinterest share → real collection', /pinterest\.com/.test(pinHref) && new RegExp('collections%2F'+COLL).test(pinHref));

  // shopbar
  ok('Shopbar → real collection', (await page.getAttribute('#shopbar','href'))?.includes('/collections/'+COLL));

  // ---------- PDP (click a card) ----------
  const firstHandle = await page.$eval('.card', c => c.dataset.h);
  await page.click('.card'); await page.waitForURL(/\/product\//, { timeout: 10000 });
  await page.waitForSelector('.pdp .rail h1', { timeout: 10000 });
  ok('Card click → PDP route', page.url().includes('/product/'+firstHandle), page.url());

  ok('PDP wordmark = Designer Wallcoverings', (await page.textContent('.corner.ul'))?.trim()==='Designer Wallcoverings');
  ok('PDP breadcrumb starts at DW', (await page.textContent('.crumbs'))?.includes('Designer Wallcoverings'));
  ok('PDP title present', (await page.textContent('.rail h1'))?.length > 0);

  const pdpBody = await page.textContent('.pdp');
  ok('PDP shows NO $ price', !/\$\d/.test(pdpBody));

  const cta = await page.getAttribute('.rail .cta','href');
  ok('PDP main CTA → real DW product', /designerwallcoverings\.com\/products\//.test(cta), cta);
  const sampleCta = (await page.$$('.rail .cta'))[1];
  ok('PDP sample CTA → real product', /designerwallcoverings\.com\/products\//.test(await sampleCta.getAttribute('href')));

  const specRows = await page.$$('.spec .row');
  ok('PDP spec table rows', specRows.length >= 1, specRows.length+' rows'+(specRows.length<4?' — sparse metafields on this SKU (data-dependent)':''));

  // gallery thumb swap
  const thumbs = await page.$$('.thumbs .t');
  if (thumbs.length > 1) {
    const before = await page.$eval('#main', el => el.style.backgroundImage);
    // click a thumb whose image actually differs from the current main (images are deduped server-side)
    let swapped = false;
    for (const t of thumbs) {
      const src = await t.evaluate(el => el.dataset.s);
      if (before.includes(src)) continue;
      await t.click(); await page.waitForTimeout(250);
      if ((await page.$eval('#main', el => el.style.backgroundImage)) !== before) { swapped = true; break; }
    }
    ok('Gallery thumb swaps main image', swapped, thumbs.length+' thumbs');
  } else ok('Gallery (single image, no thumbs)', true, '1 image');

  // share on PDP → real product
  const pdpShare = await page.$$('#share .sbtn');
  ok('PDP share icons (5)', pdpShare.length === 5, pdpShare.length+' icons');
  const pdpPin = await page.getAttribute('#share .sbtn.pin','href');
  ok('PDP Pinterest → real product', /pinterest\.com/.test(pdpPin) && /products/.test(decodeURIComponent(pdpPin)));

  // pairs well with
  await page.waitForTimeout(600);
  const pairsHidden = await page.getAttribute('#pairs','hidden');
  const pairCards = await page.$$('#pairGrid .pcard');
  ok('"Pairs well with" renders', pairsHidden===null && pairCards.length>0, pairCards.length+' recs');

  // canonical → real product
  const canon = await page.$eval('link[rel=canonical]', l => l.href).catch(()=>null);
  ok('PDP canonical → real DW product', canon && /designerwallcoverings\.com\/products\//.test(canon), canon||'');

  // click a "pairs" card → navigates to another PDP
  if (pairCards.length) {
    await pairCards[0].click(); await page.waitForTimeout(600);
    ok('Pairs card navigates', page.url().includes('/product/'));
  }

  // ---------- real collection page resolves ----------
  const collResp = await page.goto('https://www.designerwallcoverings.com/collections/'+COLL, { waitUntil:'domcontentloaded' });
  ok(`Real /collections/${COLL} is live`, collResp.status()===200, 'http '+collResp.status());

  ok('No console/page errors', errs.length===0, errs.slice(0,3).join(' | '));

  await page.screenshot({ path: '/tmp/artmura-home.png' }).catch(()=>{});
  await browser.close();

  const passed = results.filter(r=>r.pass).length;
  console.log(`\n=== ${passed}/${results.length} components PASS ===`);
  const fails = results.filter(r=>!r.pass);
  if (fails.length) { console.log('FAILS:'); fails.forEach(f=>console.log('  ✗ '+f.n+(f.extra?'  ('+f.extra+')':''))); process.exit(1); }
})().catch(e => { console.error('RUNNER ERROR', e); process.exit(2); });