← back to Secrets Manager
dw-scroll-verify.js
202 lines
/**
* dw-scroll-verify.js — Verify the v6 fix works correctly.
* Injects the fixed dw-card-hover.js logic and verifies:
* 1. No duplicate overlays after load-more
* 2. Observer fires only on product-node mutations, not overlay insertions
* 3. Infinite scroll loads subsequent pages correctly
*/
const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const TARGET_URL = 'https://www.designerwallcoverings.com/collections/decoglass-wallpaper?page=6';
const FIXED_JS_PATH = '/Users/macstudio3/Projects/Designer-Wallcoverings/shopify/theme-LIVE-591/assets/dw-card-hover.js';
const VIEWPORT = { width: 1440, height: 900 };
(async () => {
const fixedJs = fs.readFileSync(FIXED_JS_PATH, 'utf8');
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] });
const context = await browser.newContext({
viewport: VIEWPORT,
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
});
// Intercept and replace the live dw-card-hover.js with the fixed version
await context.route('**/assets/dw-card-hover.js*', async (route) => {
route.fulfill({ status: 200, contentType: 'application/javascript', body: fixedJs });
});
const page = await context.newPage();
const boostApiCalls = [];
page.on('request', req => { if (req.url().includes('bc-sf-filter')) boostApiCalls.push(req.url()); });
console.log('=== DW Scroll Verify — v6 Fix Validation ===\n');
console.log('Injecting fixed dw-card-hover.js via route interception...\n');
await page.goto(TARGET_URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForSelector('.boost-sd__product-item', { timeout: 20000 });
await page.waitForTimeout(2000);
const initial = await page.evaluate(() => ({
cards: document.querySelectorAll('.boost-sd__product-item').length,
overlays: document.querySelectorAll('.dw-hover-overlay').length,
dwHoverActive: window.__dwCardHover,
processedCards: document.querySelectorAll('[data-dw-hover]').length,
}));
console.log(`Initial: ${initial.cards} cards, ${initial.overlays} overlays, processed=${initial.processedCards}, dwHoverActive=${initial.dwHoverActive}`);
// Set up mutation tracking
await page.evaluate(() => {
window.__v6ObsCallbacks = 0;
window.__v6OverlayTriggeredCallbacks = 0;
window.__v6ProductTriggeredCallbacks = 0;
const tracker = new MutationObserver((muts) => {
window.__v6ObsCallbacks++;
let hasOverlay = false, hasProduct = false;
for (const m of muts) {
for (const n of m.addedNodes) {
if (n.nodeType !== 1) continue;
if (n.classList && n.classList.contains('dw-hover-overlay')) hasOverlay = true;
if (n.classList && (n.classList.contains('boost-sd__product-item') || n.classList.contains('product-list-item'))) hasProduct = true;
}
}
if (hasOverlay) window.__v6OverlayTriggeredCallbacks++;
if (hasProduct) window.__v6ProductTriggeredCallbacks++;
});
tracker.observe(document.body, { childList: true, subtree: true });
});
// Click load-more 3 times and verify
let totalLoadMoreClicks = 0;
for (let round = 0; round < 3; round++) {
const btnSel = '.boost-sd__pagination-button--load-more';
const btnExists = await page.locator(btnSel).count();
if (!btnExists) { console.log(` Round ${round+1}: no load-more button, stopping`); break; }
const cardsBefore = await page.locator('.boost-sd__product-item').count();
const cbsBefore = await page.evaluate(() => window.__v6ObsCallbacks);
await page.locator(btnSel).first().scrollIntoViewIfNeeded();
await page.locator(btnSel).first().click();
await page.waitForTimeout(2500);
totalLoadMoreClicks++;
const snap = await page.evaluate(() => {
const cards = document.querySelectorAll('.boost-sd__product-item');
let dups = 0, missing = 0, processedCount = 0;
for (const card of cards) {
const insideOvs = card.querySelectorAll('.dw-hover-overlay').length;
const isProcessed = card.dataset.dwHover === '1';
if (isProcessed) processedCount++;
if (insideOvs > 1) dups++;
if (insideOvs === 0 && !isProcessed) missing++;
}
return {
cards: cards.length,
overlays: document.querySelectorAll('.dw-hover-overlay').length,
dups,
missing,
processedCount,
obsCbs: window.__v6ObsCallbacks,
overlayTriggered: window.__v6OverlayTriggeredCallbacks,
productTriggered: window.__v6ProductTriggeredCallbacks,
};
});
const newCbs = snap.obsCbs - cbsBefore;
console.log(`Round ${round+1}: cards=${cardsBefore}→${snap.cards} (+${snap.cards - cardsBefore}), overlays=${snap.overlays}, dups=${snap.dups}, missing=${snap.missing}`);
console.log(` Observer: +${newCbs} callbacks this round (overlay-triggered=${snap.overlayTriggered}, product-triggered=${snap.productTriggered})`);
console.log(` data-dw-hover processed: ${snap.processedCount}/${snap.cards}`);
if (snap.dups > 0) console.log(` !!! FAIL: ${snap.dups} cards with duplicate overlays`);
else console.log(` PASS: no duplicate overlays`);
}
// Test: inject fake card and verify observer does NOT fire run() for overlay mutations
console.log('\n[Self-loop test] Inject card, verify observer fires selectively...');
const loopTest = await page.evaluate(() => {
window.__v6ObsCallbacks = 0;
window.__v6OverlayTriggeredCallbacks = 0;
const grid = document.querySelector('.boost-sd__product-list');
const existingCard = document.querySelector('.boost-sd__product-item');
if (!grid || !existingCard) return { error: 'no grid/card' };
const clone = existingCard.cloneNode(true);
// Remove processed flag and overlays to simulate fresh card
delete clone.dataset.dwHover;
for (const ov of clone.querySelectorAll('.dw-hover-overlay')) ov.remove();
grid.appendChild(clone);
return new Promise(resolve => {
setTimeout(() => {
resolve({
obsCbsAfter: window.__v6ObsCallbacks,
overlayTriggered: window.__v6OverlayTriggeredCallbacks,
totalCards: document.querySelectorAll('.boost-sd__product-item').length,
totalOverlays: document.querySelectorAll('.dw-hover-overlay').length,
cloneProcessed: clone.dataset.dwHover === '1',
cloneInsideOvs: clone.querySelectorAll('.dw-hover-overlay').length,
});
}, 600);
});
});
if (loopTest.error) {
console.log(` Error: ${loopTest.error}`);
} else {
console.log(` After inject: cards=${loopTest.totalCards}, overlays=${loopTest.totalOverlays}`);
console.log(` Observer callbacks: ${loopTest.obsCbsAfter} (overlay-triggered: ${loopTest.overlayTriggered})`);
console.log(` Clone processed (data-dw-hover=1): ${loopTest.cloneProcessed}`);
console.log(` Clone inside overlays: ${loopTest.cloneInsideOvs}`);
if (loopTest.overlayTriggered === 0) {
console.log(' PASS: overlay insertions did NOT trigger observer callbacks (self-loop eliminated)');
} else {
console.log(` WARN: ${loopTest.overlayTriggered} overlay-triggered callbacks (self-loop may still exist)`);
}
if (loopTest.cloneProcessed) {
console.log(' PASS: clone card was labeled and marked as processed');
} else {
console.log(' FAIL: clone card was NOT processed by run()');
}
}
// Final scroll test: does infinite scroll to bottom still trigger page 7/8?
console.log('\n[Scroll test] Verify bottom-scroll still triggers Boost next-page load...');
const boostBefore = boostApiCalls.length;
await page.evaluate(async () => {
const max = document.documentElement.scrollHeight - window.innerHeight;
for (let y = window.scrollY; y <= max; y += 100) {
window.scrollTo(0, y);
await new Promise(r => setTimeout(r, 15));
}
});
await page.waitForTimeout(3000);
const newBoostCalls = boostApiCalls.slice(boostBefore);
console.log(` New Boost API calls after scrolling to bottom: ${newBoostCalls.length}`);
newBoostCalls.forEach((url, i) => {
const m = url.match(/page=(\d+)/);
console.log(` [${i+1}] page=${m?.[1]||'?'}: ${url.substring(0, 100)}`);
});
const finalState = await page.evaluate(() => ({
cards: document.querySelectorAll('.boost-sd__product-item').length,
overlays: document.querySelectorAll('.dw-hover-overlay').length,
dups: Array.from(document.querySelectorAll('.boost-sd__product-item')).filter(c => c.querySelectorAll('.dw-hover-overlay').length > 1).length,
}));
console.log(`\nFinal state: ${finalState.cards} cards, ${finalState.overlays} overlays, ${finalState.dups} with duplicate overlays`);
console.log('\n=== VERIFICATION SUMMARY ===');
console.log(`Guard fix (data-dw-hover attribute): ${loopTest.cloneProcessed ? 'WORKING' : 'NEEDS CHECK'}`);
console.log(`Self-loop elimination: ${loopTest.overlayTriggered === 0 ? 'CONFIRMED — overlay mutations no longer trigger observer' : 'PARTIAL — some overlay triggers remain'}`);
console.log(`Infinite scroll integration: ${newBoostCalls.length > 0 ? `WORKING — ${newBoostCalls.length} new page loads triggered` : 'NO NEW LOADS (may be at end of collection or scroll not reaching sentinel)'}`);
console.log(`Duplicate overlays at end: ${finalState.dups === 0 ? 'NONE — clean' : finalState.dups + ' DUPLICATES — issue persists'}`);
await browser.close();
console.log('\nDone.');
})();