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