← back to Pattern Vault
spoonflower: drive real Chrome (channel:chrome) w/ persistent profile + webdriver-hiding instead of bundled Chromium, to clear Cloudflare
958c6bec14a002c1da73ac3d000e78345a59336c · 2026-07-16 09:24:58 -0700 · Steve Abrams
Files touched
M whimsical-compare/daily/scripts/capture_sf_session.jsM whimsical-compare/daily/scripts/upload_sf.js
Diff
commit 958c6bec14a002c1da73ac3d000e78345a59336c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 16 09:24:58 2026 -0700
spoonflower: drive real Chrome (channel:chrome) w/ persistent profile + webdriver-hiding instead of bundled Chromium, to clear Cloudflare
---
.../daily/scripts/capture_sf_session.js | 24 ++++++++++++-----
whimsical-compare/daily/scripts/upload_sf.js | 31 ++++++++++++++++++----
2 files changed, 43 insertions(+), 12 deletions(-)
diff --git a/whimsical-compare/daily/scripts/capture_sf_session.js b/whimsical-compare/daily/scripts/capture_sf_session.js
index 8457dec..9bc21fa 100644
--- a/whimsical-compare/daily/scripts/capture_sf_session.js
+++ b/whimsical-compare/daily/scripts/capture_sf_session.js
@@ -40,9 +40,19 @@ console.log(`🔐 capturing session for ${EMAIL} → ${STATE}`);
(async () => {
fs.mkdirSync(path.dirname(STATE), { recursive: true });
- const b = await chromium.launch({ headless: !HEADFUL });
- const ctx = await b.newContext({ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/126 Safari/537.36' });
- const pg = await ctx.newPage();
+ // REAL Google Chrome (channel:'chrome'), NOT Playwright's bundled Chromium. Cloudflare
+ // fingerprints bundled Chromium and loops the "verify you are human" check forever even after
+ // you solve it. A persistent profile keeps the cf_clearance cookie + login together so the
+ // clearance you earn once survives into upload_sf.js. NO userAgent override — a spoofed UA that
+ // mismatches the real Chrome binary is itself a CF red flag and voids cf_clearance.
+ const PROFILE = process.env.SF_PROFILE || path.join(os.homedir(), '.config/pattern-vault/sf-chrome-profile');
+ fs.mkdirSync(PROFILE, { recursive: true });
+ const ctx = await chromium.launchPersistentContext(PROFILE, {
+ headless: !HEADFUL, channel: 'chrome', viewport: null,
+ ignoreDefaultArgs: ['--enable-automation'], // drop the automation banner
+ args: ['--disable-blink-features=AutomationControlled'], // hide navigator.webdriver from CF
+ });
+ const pg = ctx.pages()[0] || await ctx.newPage();
try {
await pg.goto('https://www.spoonflower.com/login', { waitUntil: 'networkidle', timeout: 60000 });
// Dismiss the cookie-consent banner — it overlays and intercepts the Sign In button.
@@ -74,7 +84,7 @@ console.log(`🔐 capturing session for ${EMAIL} → ${STATE}`);
console.log(' Waiting up to 3 min for login to complete…\n');
let ok = false;
for (let i = 0; i < 90; i++) { await pg.waitForTimeout(2000); if (!pg.url().includes('/login')) { ok = true; break; } }
- if (!ok) { console.error('❌ timed out (still on /login) — captcha/login not completed.'); await b.close(); process.exit(1); }
+ if (!ok) { console.error('❌ timed out (still on /login) — captcha/login not completed.'); await ctx.close(); process.exit(1); }
} else {
const submit = pg.locator('button[type="submit"], button:has-text("Log In"), button:has-text("Sign In")').first();
await submit.scrollIntoViewIfNeeded().catch(() => {});
@@ -84,7 +94,7 @@ console.log(`🔐 capturing session for ${EMAIL} → ${STATE}`);
if (pg.url().includes('/login')) {
await pg.screenshot({ path: '/tmp/sf-capture-fail.png' });
console.error(`❌ still on /login after submit — captcha/challenge. Screenshot: /tmp/sf-capture-fail.png`);
- await b.close(); process.exit(1);
+ await ctx.close(); process.exit(1);
}
}
const url = pg.url();
@@ -94,10 +104,10 @@ console.log(`🔐 capturing session for ${EMAIL} → ${STATE}`);
await ctx.storageState({ path: STATE });
const cookies = JSON.parse(fs.readFileSync(STATE, 'utf8')).cookies?.length || 0;
console.log(`✅ session saved (${cookies} cookies). landed: ${url}${who ? ' | ' + who : ''}`);
- await b.close();
+ await ctx.close();
} catch (e) {
try { await pg.screenshot({ path: '/tmp/sf-capture-fail.png' }); } catch {}
console.error('❌ capture error:', e.message, '(screenshot /tmp/sf-capture-fail.png)');
- await b.close(); process.exit(1);
+ await ctx.close(); process.exit(1);
}
})();
diff --git a/whimsical-compare/daily/scripts/upload_sf.js b/whimsical-compare/daily/scripts/upload_sf.js
index 13f3d74..78b9555 100755
--- a/whimsical-compare/daily/scripts/upload_sf.js
+++ b/whimsical-compare/daily/scripts/upload_sf.js
@@ -66,8 +66,29 @@ async function messyType(el, text) {
if (!fs.existsSync(STATE)) { result.sessionExpired = true; result.error = 'no session file'; console.log(JSON.stringify(result)); return; }
- const b = await chromium.launch({ headless: (process.env.SF_HEADLESS === '1') ? true : !ARMED, args: ['--start-maximized'] });
- const ctx = await b.newContext({ viewport: ARMED ? null : { width: 1400, height: 900 }, storageState: STATE, userAgent: UA });
+ // REAL Google Chrome + the SAME persistent profile capture_sf_session.js cleared Cloudflare in.
+ // The old chromium.launch() drove Playwright's bundled Chromium, which CF fingerprints and blocks;
+ // the spoofed Chrome/126 UA (const UA) mismatched the binary and made it worse. channel:'chrome'
+ // + no UA override = one honest fingerprint, and the profile carries a valid cf_clearance.
+ const PROFILE = process.env.SF_PROFILE || require('path').join(require('os').homedir(), '.config/pattern-vault/sf-chrome-profile');
+ require('fs').mkdirSync(PROFILE, { recursive: true });
+ const ctx = await chromium.launchPersistentContext(PROFILE, {
+ headless: (process.env.SF_HEADLESS === '1') ? true : !ARMED,
+ channel: 'chrome',
+ viewport: ARMED ? null : { width: 1400, height: 900 },
+ ignoreDefaultArgs: ['--enable-automation'], // drop the automation banner
+ args: ['--start-maximized', '--disable-blink-features=AutomationControlled'], // hide navigator.webdriver
+ });
+ // Bridge the existing Spoonflower login from the legacy storageState file (portable auth cookies)
+ // in case this profile is fresh. cf_clearance is fingerprint-bound and intentionally NOT imported —
+ // real Chrome earns its own valid one.
+ try {
+ if (fs.existsSync(STATE)) {
+ const saved = JSON.parse(fs.readFileSync(STATE, 'utf8')).cookies || [];
+ const authCookies = saved.filter(c => !/^cf_|__cf|cf_clearance/i.test(c.name));
+ if (authCookies.length) await ctx.addCookies(authCookies).catch(() => {});
+ }
+ } catch {}
// DIAGNOSED 2026-07-09: an intermittent consent/marketing overlay (bounceexchange iframe +
// "Your Privacy Choices / Agree & Continue") intercepts the uploader clicks. Block those
// vendors at the network layer so the overlay never loads. (Spoonflower's own uploader is unaffected.)
@@ -76,7 +97,7 @@ async function messyType(el, text) {
if (/bounceexchange\.com|bounce\.|onetrust|cookielaw|osano|consent|quantcast|trustarc/i.test(u)) return route.abort();
return route.continue();
}).catch(() => {});
- const pg = await ctx.newPage();
+ const pg = ctx.pages()[0] || await ctx.newPage();
// iframe-aware consent dismissal — the "Agree & Continue" button lives in an iframe, so scan
// every frame (not just the top document) and click it wherever it is.
@@ -95,7 +116,7 @@ async function messyType(el, text) {
// session health check
await pg.goto('https://www.spoonflower.com/en/my_account', { waitUntil: 'domcontentloaded', timeout: 45000 }).catch(() => {});
await sleep(2500);
- if (pg.url().includes('/login')) { result.sessionExpired = true; await b.close(); console.log(JSON.stringify(result)); return; }
+ if (pg.url().includes('/login')) { result.sessionExpired = true; await ctx.close(); console.log(JSON.stringify(result)); return; }
// reach the uploader the proven way: homepage -> account menu -> "UPLOAD A DESIGN"
async function openUploader() {
@@ -209,6 +230,6 @@ async function messyType(el, text) {
}
}
- await b.close();
+ await ctx.close();
console.log(JSON.stringify(result));
})().catch(e => { console.log(JSON.stringify({ error: String(e), uploaded: [], failed: [] })); process.exit(1); });
← 378dec5 auto-save: 2026-07-13T06:53:18 (2 files) — data/trend-board.
·
back to Pattern Vault
·
spoonflower: add plain-real-Chrome manual-login fallback (sf ee917f9 →