[object Object]

← back to Dw Yolo Loop

Artmura landing: add Playwright component click-through (39/39 live regression test)

4ec71ec936ce81019b1bc7994e92be43738350ea · 2026-06-12 11:53:44 -0700 · Steve Abrams

Files touched

Diff

commit 4ec71ec936ce81019b1bc7994e92be43738350ea
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 12 11:53:44 2026 -0700

    Artmura landing: add Playwright component click-through (39/39 live regression test)
---
 artmura-site/clickthrough.js | 151 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 151 insertions(+)

diff --git a/artmura-site/clickthrough.js b/artmura-site/clickthrough.js
new file mode 100644
index 0000000..45a8bf3
--- /dev/null
+++ b/artmura-site/clickthrough.js
@@ -0,0 +1,151 @@
+/* Live click-through of every Artmura landing component. Reports PASS/FAIL per component. */
+const { chromium } = require('/Users/stevestudio2/Projects/ventura-corridor/node_modules/playwright');
+const BASE = process.env.BASE || 'https://artmura.designerwallcoverings.com';
+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/artmura', navTxt.some(t=>/Shop the Collection/.test(t) && /collections\/artmura/.test(t)));
+
+  ok('Hero eyebrow', (await page.textContent('#heroEyebrow'))?.includes('Designer Wallcoverings'));
+  ok('Hero H1 = Artmura', (await page.textContent('#heroH1'))?.trim() === 'Artmura');
+  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);
+  ok('Grid count shown', total === 161, 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', chips.length >= 2, chips.length+' chips');
+  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 bar present', dots.length >= 2, dots.length+' color buckets');
+  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) && /collections%2Fartmura/.test(pinHref));
+
+  // shopbar
+  ok('Shopbar → real collection', (await page.getAttribute('#shopbar','href'))?.includes('/collections/artmura'));
+
+  // ---------- 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 >= 4, specRows.length+' rows');
+
+  // gallery thumb swap
+  const thumbs = await page.$$('.thumbs .t');
+  if (thumbs.length > 1) {
+    const before = await page.$eval('#main', el => el.style.backgroundImage);
+    await thumbs[1].click(); await page.waitForTimeout(250);
+    const after = await page.$eval('#main', el => el.style.backgroundImage);
+    ok('Gallery thumb swaps main image', before !== after, 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/artmura', { waitUntil:'domcontentloaded' });
+  ok('Real /collections/artmura 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); });

← 8382b47 Artmura: apply Cost/.75/.85 retail markup + hide prices on l  ·  back to Dw Yolo Loop  ·  Vendor-landing template: generalize to any DW line + dw-vend 584bf8a →