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