[object Object]

← back to Dw Theme Boost Fix

add Playwright interceptor race test (PASS against live) + extracted interceptor JS

29a6258d8d80580bdd3dcd60c63a166d102aa405 · 2026-07-13 08:48:16 -0700 · Steve Abrams

Files touched

Diff

commit 29a6258d8d80580bdd3dcd60c63a166d102aa405
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 13 08:48:16 2026 -0700

    add Playwright interceptor race test (PASS against live) + extracted interceptor JS
---
 verify/.gitignore                |   1 +
 verify/interceptor-race-test.mjs |  49 +++++++++++++++++++
 verify/interceptor.js            | 103 +++++++++++++++++++++++++++++++++++++++
 verify/search-probe.mjs          |  20 ++++++++
 4 files changed, 173 insertions(+)

diff --git a/verify/.gitignore b/verify/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/verify/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/verify/interceptor-race-test.mjs b/verify/interceptor-race-test.mjs
new file mode 100644
index 0000000..f67ac1c
--- /dev/null
+++ b/verify/interceptor-race-test.mjs
@@ -0,0 +1,49 @@
+// Pre-deploy verification of the Boost endpoint interceptor (read-only).
+// Loads the LIVE collection page twice — control (no patch) and patched
+// (interceptor injected via addInitScript, exactly the <head> position it
+// will occupy in the theme) — and records every request to Boost hosts.
+// PASS = patched run makes ZERO requests to staging.bc-solutions.net and
+// gets 200s from services.mybcapps.com; facets render.
+import { chromium } from 'playwright';
+import { readFileSync } from 'fs';
+
+const INTERCEPTOR = readFileSync(new URL('./interceptor.js', import.meta.url), 'utf8');
+const PAGE_URL = 'https://www.designerwallcoverings.com/collections/grasscloth-wallpaper-collection';
+
+async function run(label, inject) {
+  const browser = await chromium.launch();
+  const page = await browser.newPage({ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36' });
+  const hits = [];
+  page.on('response', r => {
+    const u = r.url();
+    if (u.includes('bc-solutions.net') || u.includes('mybcapps.com')) {
+      hits.push({ url: u.split('?')[0], status: r.status() });
+    }
+  });
+  if (inject) await page.addInitScript(INTERCEPTOR);
+  await page.goto(PAGE_URL, { waitUntil: 'load', timeout: 60000 });
+  await page.waitForTimeout(12000); // let Boost boot + fire filter calls
+  // poke the page like a user: scroll to trigger the grid
+  await page.mouse.wheel(0, 4000);
+  await page.waitForTimeout(6000);
+  const facetCount = await page.locator('.boost-sd__filter-option-item, .boost-sd__filter-option, [class*="boost-sd__filter"] li').count().catch(() => 0);
+  const cfg = await page.evaluate(() => {
+    const c = window.boostSDAppConfig; return c && c.api ? c.api.filterUrl : null;
+  });
+  await browser.close();
+  const staging = hits.filter(h => h.url.includes('staging.bc-solutions.net'));
+  const prod200 = hits.filter(h => h.url.includes('mybcapps.com') && h.status === 200);
+  console.log(`\n== ${label} ==`);
+  console.log('runtime filterUrl:', cfg);
+  console.log(`staging requests: ${staging.length}`, staging.slice(0, 4));
+  console.log(`prod mybcapps 200s: ${prod200.length}`, prod200.slice(0, 4).map(h => h.url));
+  console.log('rendered facet elements:', facetCount);
+  return { staging: staging.length, prod200: prod200.length, facetCount, cfg };
+}
+
+const control = await run('CONTROL (live page, no patch)', false);
+const patched = await run('PATCHED (interceptor injected)', true);
+
+const pass = patched.staging === 0 && patched.prod200 > 0;
+console.log(`\nVERDICT: ${pass ? 'PASS — interceptor wins the race' : 'FAIL — staging still called or no prod 200s'}`);
+process.exit(pass ? 0 : 1);
diff --git a/verify/interceptor.js b/verify/interceptor.js
new file mode 100644
index 0000000..c7fc2b7
--- /dev/null
+++ b/verify/interceptor.js
@@ -0,0 +1,103 @@
+
+(function () {
+  'use strict';
+  if (window.__dwBoostInfinite) return;
+  window.__dwBoostInfinite = true;
+
+  var TARGET = 'infinite_scroll';
+
+  // Verified production Boost SD endpoints for this shop (2026-07-13).
+  var PROD_API = {
+    filterUrl:     'https://services.mybcapps.com/bc-sf-filter/filter',
+    searchUrl:     'https://services.mybcapps.com/bc-sf-filter/search',
+    suggestionUrl: 'https://services.mybcapps.com/bc-sf-filter/search/suggest',
+    productsUrl:   'https://services.mybcapps.com/bc-sf-filter/search/products',
+    recommendUrl:  'https://services.mybcapps.com/discovery/recommend',
+    analyticsUrl:  'https://lambda.mybcapps.com/e',
+    cdn:           'https://boost-cdn-prod.bc-solutions.net'
+  };
+
+  // Rewrite ONLY staging bc-solutions endpoints to production. Anything that
+  // already points at a non-staging host is left untouched, so an upstream
+  // Boost fix (or an intentional custom endpoint) is never clobbered.
+  function isStaging(v) {
+    return typeof v === 'string' &&
+      (v.indexOf('staging.bc-solutions.net') !== -1 ||
+       v.indexOf('boost-cdn-staging') !== -1);
+  }
+  function patchApi(api) {
+    if (!api || typeof api !== 'object') return;
+    for (var k in PROD_API) {
+      if (!Object.prototype.hasOwnProperty.call(PROD_API, k)) continue;
+      try {
+        if (isStaging(api[k])) api[k] = PROD_API[k];
+      } catch (e) {}
+    }
+  }
+
+  // Recursively rewrite any "paginationType" key to infinite_scroll, plus the
+  // common Boost shapes (generalSettings + additionalElements.pagination).
+  // `seen` guards against cyclic / shared references so a self-referential
+  // Boost config can't loop or get re-walked needlessly.
+  function patchObj(o, depth, seen) {
+    if (!o || typeof o !== 'object' || depth > 6) return;
+    if (seen.indexOf(o) !== -1) return;
+    seen.push(o);
+    try {
+      if (Object.prototype.hasOwnProperty.call(o, 'paginationType') &&
+          typeof o.paginationType === 'string') {
+        o.paginationType = TARGET;
+      }
+      // Boost sometimes nests under .pagination: { paginationType, ... }
+      if (o.pagination && typeof o.pagination === 'object' &&
+          typeof o.pagination.paginationType === 'string') {
+        o.pagination.paginationType = TARGET;
+      }
+    } catch (e) {}
+    for (var k in o) {
+      if (!Object.prototype.hasOwnProperty.call(o, k)) continue;
+      var v = o[k];
+      if (v && typeof v === 'object') patchObj(v, depth + 1, seen);
+    }
+  }
+
+  function patchConfig(cfg) {
+    if (!cfg || typeof cfg !== 'object') return cfg;
+    try {
+      // One shared `seen` set across all walks keeps total work O(n) and
+      // cycle-safe — known surfaces get a full-depth targeted walk, then the
+      // catch-all root walk skips anything already visited (schema-drift safe).
+      var seen = [];
+      patchApi(cfg.api); // endpoint repoint — staging → production (2026-07-13)
+      if (cfg.generalSettings) patchObj(cfg.generalSettings, 0, seen);
+      if (cfg.additionalElements) patchObj(cfg.additionalElements, 0, seen);
+      patchObj(cfg, 0, seen);
+    } catch (e) {}
+    return cfg;
+  }
+
+  // (1) defineProperty setter — intercept Boost's assignment.
+  var _cfg = window.boostSDAppConfig;
+  try {
+    Object.defineProperty(window, 'boostSDAppConfig', {
+      configurable: true,
+      enumerable: true,
+      get: function () { return _cfg; },
+      set: function (val) { _cfg = patchConfig(val); }
+    });
+  } catch (e) {
+    // If defineProperty fails (already non-configurable), fall through to poll.
+  }
+
+  // (2) Patch immediately if it already exists.
+  if (_cfg) patchConfig(_cfg);
+
+  // (3) Short poll — re-apply in case Boost replaces / re-inits the object,
+  // and re-patch after a few frames so the value is correct at bundle init.
+  var ticks = 0;
+  var iv = setInterval(function () {
+    ticks++;
+    try { if (window.boostSDAppConfig) patchConfig(window.boostSDAppConfig); } catch (e) {}
+    if (ticks > 80) clearInterval(iv); // ~8s @ 100ms, well past Boost init
+  }, 100);
+})();
diff --git a/verify/search-probe.mjs b/verify/search-probe.mjs
new file mode 100644
index 0000000..00c875a
--- /dev/null
+++ b/verify/search-probe.mjs
@@ -0,0 +1,20 @@
+import { chromium } from 'playwright';
+const b = await chromium.launch();
+const p = await b.newPage();
+const hits = [];
+p.on('response', r => { const u=r.url(); if (u.includes('bc-solutions')||u.includes('mybcapps')) hits.push({u:u.split('?')[0],s:r.status()}); });
+await p.goto('https://www.designerwallcoverings.com/search?q=milano+tessile', {waitUntil:'load', timeout:60000});
+await p.waitForTimeout(12000);
+const products = await p.locator('.boost-sd__product-item, [class*="boost-sd__product"]').count().catch(()=>0);
+const bodyText = (await p.locator('body').innerText()).slice(0,0) || '';
+const noResult = await p.locator('text=/no results|nothing found|0 results/i').count().catch(()=>0);
+console.log('search page product elements:', products, '| no-result banners:', noResult);
+console.log('staging hits:', hits.filter(h=>h.u.includes('staging')).length, '| prod 200s:', hits.filter(h=>h.u.includes('mybcapps')&&h.s===200).length);
+// suggest: type in the header search box
+const box = p.locator('input[type="search"], input[name="q"]').first();
+await box.click({timeout:8000}).catch(e=>console.log('no search box click:', e.message.split('\n')[0]));
+await box.fill('milano').catch(()=>{});
+await p.waitForTimeout(5000);
+const sugg = await p.locator('[class*="boost-sd__search-suggestion"], [class*="suggestion"] [class*="product"]').count().catch(()=>0);
+console.log('suggest dropdown items:', sugg);
+await b.close();

← 780c362 repoint Boost SD api endpoints staging->production in config  ·  back to Dw Theme Boost Fix  ·  pagination user toggle: infinite default, switchable to page 965130f →