← 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
A verify/.gitignoreA verify/interceptor-race-test.mjsA verify/interceptor.jsA verify/search-probe.mjs
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 →