[object Object]

← back to Designer Wallcoverings

wallquest login CRACKED: real Chrome (channel:chrome persistent profile) + 2captcha reCAPTCHA solve → logs in + prices sy21843=$83.16 (spike proven). Root cause was login reCAPTCHA, not creds/fingerprint.

7efced75bba2bd29529ac3c3daa43a2569324693 · 2026-07-06 12:50:52 -0700 · Steve

Files touched

Diff

commit 7efced75bba2bd29529ac3c3daa43a2569324693
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 6 12:50:52 2026 -0700

    wallquest login CRACKED: real Chrome (channel:chrome persistent profile) + 2captcha reCAPTCHA solve → logs in + prices sy21843=$83.16 (spike proven). Root cause was login reCAPTCHA, not creds/fingerprint.
---
 scripts/wallquest-refresh/assist-login.cjs        | 32 +++++++++++++
 scripts/wallquest-refresh/auto-login-2captcha.cjs | 55 +++++++++++++++++++++++
 scripts/wallquest-refresh/diag-login.cjs          | 41 +++++++++++++++++
 scripts/wallquest-refresh/spike-realchrome.cjs    | 37 +++++++++++++++
 4 files changed, 165 insertions(+)

diff --git a/scripts/wallquest-refresh/assist-login.cjs b/scripts/wallquest-refresh/assist-login.cjs
new file mode 100644
index 00000000..a182f237
--- /dev/null
+++ b/scripts/wallquest-refresh/assist-login.cjs
@@ -0,0 +1,32 @@
+// Human-in-the-loop WallQuest login: opens real Chrome pre-filled with creds; Steve solves the reCAPTCHA
+// + clicks Login; we poll for the authenticated session (no navigation to avoid disrupting him), then verify
+// a price renders and LEAVE the session in the persistent profile for the scraper to reuse. No 2captcha cost.
+const {chromium}=require('playwright-core'); const fs=require('fs'); const {execSync}=require('child_process');
+const PSQL='/opt/homebrew/opt/postgresql@14/bin/psql';
+const ENVL=fs.readFileSync(require('os').homedir()+'/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/.env.local','utf8');
+const PGPW=(ENVL.match(/^POSTGRES_PASSWORD=(.+)$/m)||[])[1];
+const q=sql=>execSync(`PGPASSWORD='${PGPW}' ${PSQL} -h localhost -U dw_admin -d dw_unified -tAF'|' -c "${sql}"`,{encoding:'utf8',maxBuffer:1e8});
+const WPW=q("SELECT trade_password FROM vendor_registry WHERE vendor_code='wallquest';").trim();
+const PROFILE='/tmp/wq-chrome-profile'; fs.mkdirSync(PROFILE,{recursive:true});
+(async()=>{
+  const ctx=await chromium.launchPersistentContext(PROFILE,{channel:'chrome',headless:false,viewport:{width:1280,height:900}});
+  const page=ctx.pages()[0]||await ctx.newPage();
+  await page.goto('https://www.wallquest.com/login',{waitUntil:'domcontentloaded',timeout:45000});
+  await page.waitForTimeout(5000);
+  try{ await page.fill('#Email','info@designerwallcoverings.com'); await page.fill('#Password',WPW); }catch(e){}
+  console.error('>>> Chrome window open + pre-filled. SOLVE THE reCAPTCHA and click LOGIN now. Polling up to 180s...');
+  let ok=false;
+  for(let t=0;t<45;t++){          // ~180s
+    await page.waitForTimeout(4000);
+    let st={};
+    try{ st=await page.evaluate(()=>({url:location.href, leftLogin:!/\/login/i.test(location.href), loggedMarker:/log ?out|sign ?out/i.test(document.body.innerText) && !/please sign in/i.test(document.body.innerText)})); }catch(e){ continue; }
+    if(st.leftLogin || st.loggedMarker){ ok=true; console.error(`>>> login detected (t=${t*4}s, url=${st.url})`); break; }
+  }
+  if(!ok){ console.error('>>> TIMEOUT — no login detected in 180s. Leaving window open; re-run to re-poll.'); process.exit(2); }
+  // verify a known-priced product renders a price in this authenticated session
+  await page.goto('https://www.wallquest.com/sy21843',{waitUntil:'domcontentloaded',timeout:45000}); await page.waitForTimeout(14000);
+  const probe=await page.evaluate(()=>{const pv=([...document.querySelectorAll('[class*=price-value]')][0]||{}).textContent||'';const m=pv.match(/[\d,]+\.\d{2}/);return{price:m?m[0]:null,signInToView:/sign in to view/i.test(document.body.innerText)};});
+  console.error('VERIFY sy21843 (expect ~83.16): '+JSON.stringify(probe));
+  console.error(probe.price?`>>> SUCCESS — authenticated session prices sy21843 = ${probe.price}. Session saved in ${PROFILE} for scraper reuse.`:'>>> logged in but price not visible — check selector/product.');
+  await ctx.close().catch(()=>{});  // session cookies persist in the profile dir
+})();
diff --git a/scripts/wallquest-refresh/auto-login-2captcha.cjs b/scripts/wallquest-refresh/auto-login-2captcha.cjs
new file mode 100644
index 00000000..72f82681
--- /dev/null
+++ b/scripts/wallquest-refresh/auto-login-2captcha.cjs
@@ -0,0 +1,55 @@
+// Auto-solve the WallQuest login reCAPTCHA via 2captcha (HTTP API, no npm dep), inject token, submit,
+// verify a price renders, leave the authenticated session in the persistent profile for scraper reuse.
+// Real Chrome (channel:chrome) = human fingerprint. Cost: ~$0.003 per solve.
+const {chromium}=require('playwright-core'); const fs=require('fs'); const {execSync}=require('child_process');
+const os=require('os');
+const PSQL='/opt/homebrew/opt/postgresql@14/bin/psql';
+const ENVL=fs.readFileSync(os.homedir()+'/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/.env.local','utf8');
+const PGPW=(ENVL.match(/^POSTGRES_PASSWORD=(.+)$/m)||[])[1];
+const q=sql=>execSync(`PGPASSWORD='${PGPW}' ${PSQL} -h localhost -U dw_admin -d dw_unified -tAF'|' -c "${sql}"`,{encoding:'utf8',maxBuffer:1e8});
+const WPW=q("SELECT trade_password FROM vendor_registry WHERE vendor_code='wallquest';").trim();
+const CAPKEY=(fs.readFileSync(os.homedir()+'/.claude/skills/2captcha/.env','utf8').match(/^TWOCAPTCHA_API_KEY=(.+)$/m)||[])[1]?.trim();
+const PROFILE='/tmp/wq-chrome-profile'; fs.mkdirSync(PROFILE,{recursive:true});
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+
+async function solve(sitekey,pageurl){
+  const inUrl=`http://2captcha.com/in.php?key=${CAPKEY}&method=userrecaptcha&googlekey=${sitekey}&pageurl=${encodeURIComponent(pageurl)}&json=1`;
+  const j=await (await fetch(inUrl,{method:'POST'})).json();
+  if(j.status!==1) throw new Error('2captcha in.php: '+JSON.stringify(j));
+  const id=j.request; console.error(`2captcha job=${id} solving...`);
+  for(let i=0;i<40;i++){          // up to ~200s
+    await sleep(5000);
+    const r=await (await fetch(`http://2captcha.com/res.php?key=${CAPKEY}&action=get&id=${id}&json=1`)).json();
+    if(r.status===1) return r.request;
+    if(r.request!=='CAPCHA_NOT_READY') throw new Error('2captcha res.php: '+JSON.stringify(r));
+    if(i%3===0) console.error(`  ...still solving (${i*5}s)`);
+  }
+  throw new Error('2captcha timeout');
+}
+
+(async()=>{
+  if(!CAPKEY){ console.error('NO 2captcha key'); process.exit(3); }
+  console.error(`WPW len=${WPW.length}, capkey len=${CAPKEY.length}`);
+  const ctx=await chromium.launchPersistentContext(PROFILE,{channel:'chrome',headless:false,viewport:{width:1280,height:900}});
+  const page=ctx.pages()[0]||await ctx.newPage();
+  try{
+    await page.goto('https://www.wallquest.com/login',{waitUntil:'domcontentloaded',timeout:45000}); await sleep(6000);
+    await page.fill('#Email','info@designerwallcoverings.com'); await page.fill('#Password',WPW);
+    const sitekey=await page.evaluate(()=>{const el=document.querySelector('[data-sitekey]'); if(el)return el.dataset.sitekey; const ifr=[...document.querySelectorAll('iframe')].find(f=>/recaptcha/.test(f.src||'')); if(ifr){const m=(ifr.src||'').match(/[?&]k=([^&]+)/); return m?m[1]:null;} return null;});
+    console.error('sitekey='+sitekey);
+    if(!sitekey){ console.error('NO sitekey found — page may not have rendered captcha'); await page.screenshot({path:'/tmp/wq-nokey.png'}).catch(()=>{}); process.exit(4); }
+    const token=await solve(sitekey,page.url());
+    console.error('token received (len '+token.length+') — injecting');
+    await page.evaluate(tok=>{let ta=document.querySelector('textarea[name="g-recaptcha-response"]'); if(!ta){ta=document.createElement('textarea');ta.name='g-recaptcha-response';ta.style.display='none';document.body.appendChild(ta);} ta.value=tok; ta.innerHTML=tok;},token);
+    let clicked=false; for(const sel of ['input.login-button','.login-button','button.login-button']){if(await page.$(sel)){try{await page.click(sel,{timeout:5000});clicked=true;break;}catch(e){}}}
+    if(!clicked)await page.press('#Password','Enter');
+    await sleep(9000);
+    const post=await page.evaluate(()=>{const errSel=['.validation-summary-errors','.field-validation-error','.message-error'];const errs=errSel.flatMap(s=>[...document.querySelectorAll(s)].map(e=>(e.textContent||'').trim())).filter(Boolean);return{url:location.href,leftLogin:!/\/login/i.test(location.href),errors:errs.slice(0,3)};});
+    console.error('POST-LOGIN: '+JSON.stringify(post));
+    await page.goto('https://www.wallquest.com/sy21843',{waitUntil:'domcontentloaded',timeout:45000}); await sleep(14000);
+    const probe=await page.evaluate(()=>{const pv=([...document.querySelectorAll('[class*=price-value]')][0]||{}).textContent||'';const m=pv.match(/[\d,]+\.\d{2}/);return{price:m?m[0]:null,signInToView:/sign in to view/i.test(document.body.innerText)};});
+    console.error('VERIFY sy21843 (expect ~83.16): '+JSON.stringify(probe));
+    console.error(probe.price?`>>> SUCCESS — session prices sy21843 = ${probe.price}. Saved in ${PROFILE}.`:'>>> FAIL — still gated after captcha solve (signInToView='+probe.signInToView+')');
+  }catch(e){console.error('AUTO-LOGIN ERR: '+e.message);}
+  finally{await ctx.close().catch(()=>{});}
+})();
diff --git a/scripts/wallquest-refresh/diag-login.cjs b/scripts/wallquest-refresh/diag-login.cjs
new file mode 100644
index 00000000..4e458912
--- /dev/null
+++ b/scripts/wallquest-refresh/diag-login.cjs
@@ -0,0 +1,41 @@
+// One-session WallQuest login diagnostic — screenshots + DOM probes to see WHY login won't price. Read-only.
+const Browserbase=require('@browserbasehq/sdk').default; const {chromium}=require('playwright-core'); const fs=require('fs'); const {execSync}=require('child_process');
+require('dotenv').config({path:require('os').homedir()+'/.claude/skills/browserbase/.env'});
+const PSQL='/opt/homebrew/opt/postgresql@14/bin/psql';
+const ENVL=fs.readFileSync(require('os').homedir()+'/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/.env.local','utf8');
+const PGPW=(ENVL.match(/^POSTGRES_PASSWORD=(.+)$/m)||[])[1];
+const q=sql=>execSync(`PGPASSWORD='${PGPW}' ${PSQL} -h localhost -U dw_admin -d dw_unified -tAF'|' -c "${sql}"`,{encoding:'utf8',maxBuffer:1e8});
+const WPW=q("SELECT trade_password FROM vendor_registry WHERE vendor_code='wallquest';").trim();
+const bb=new Browserbase({apiKey:process.env.BROWSERBASE_API_KEY});
+const DIR='/tmp/wq-diag'; fs.mkdirSync(DIR,{recursive:true});
+const goto=async(p,u)=>{for(let i=0;i<3;i++){try{await p.goto(u,{waitUntil:'domcontentloaded',timeout:45000});return 1;}catch(e){await p.waitForTimeout(3000);}}return 0;};
+(async()=>{
+  console.error(`WPW length=${WPW.length}`);
+  const s=await bb.sessions.create({projectId:process.env.BROWSERBASE_PROJECT_ID,proxies:true,browserSettings:{solveCaptchas:true}});
+  const br=await chromium.connectOverCDP(s.connectUrl); const ctx=br.contexts()[0]; const page=ctx.pages()[0]||await ctx.newPage();
+  try{
+    await goto(page,'https://www.wallquest.com/login'); await page.waitForTimeout(8000);
+    // what login controls exist?
+    const controls=await page.evaluate(()=>({
+      email: !!document.querySelector('#Email'), pass: !!document.querySelector('#Password'),
+      buttons: [...document.querySelectorAll('button, input[type=submit], .login-button, input.login-button')].map(b=>({tag:b.tagName,cls:b.className,type:b.type,val:b.value||'',txt:(b.textContent||'').trim().slice(0,30)})).slice(0,12),
+      bodySample: document.body.innerText.slice(0,200)
+    }));
+    console.error('LOGIN CONTROLS: '+JSON.stringify(controls,null,1));
+    await page.fill('#Email','info@designerwallcoverings.com'); await page.fill('#Password',WPW);
+    await page.screenshot({path:DIR+'/1-login-filled.png'});
+    let clicked=false; for(const sel of ['input.login-button','.login-button','button.login-button']){if(await page.$(sel)){try{await page.click(sel,{timeout:5000});clicked=true;console.error('clicked '+sel);break;}catch(e){}}}
+    if(!clicked){await page.press('#Password','Enter');console.error('fell back to Enter');}
+    await page.waitForTimeout(9000);
+    await page.screenshot({path:DIR+'/2-after-login.png'});
+    const post=await page.evaluate(()=>({url:location.href, hasLogout:/log ?out|sign ?out/i.test(document.body.innerText), err:(document.querySelector('.validation-summary-errors,.field-validation-error,.message-error')||{}).textContent||'', bodySample:document.body.innerText.slice(0,250)}));
+    console.error('POST-LOGIN: '+JSON.stringify(post,null,1));
+    // verify product
+    await goto(page,'https://www.wallquest.com/sy21843'); await page.waitForTimeout(15000);
+    await page.screenshot({path:DIR+'/3-product.png',fullPage:false});
+    const prod=await page.evaluate(()=>({priceEls:[...document.querySelectorAll('[class*=price]')].map(e=>({cls:e.className,txt:(e.textContent||'').trim().slice(0,40)})).slice(0,10), signInToView:/sign in to view/i.test(document.body.innerText)}));
+    console.error('PRODUCT PRICE PROBE: '+JSON.stringify(prod,null,1));
+  }catch(e){console.error('DIAG ERR: '+e.message);}
+  finally{await br.close().catch(()=>{});}
+  console.error('screenshots in '+DIR);
+})();
diff --git a/scripts/wallquest-refresh/spike-realchrome.cjs b/scripts/wallquest-refresh/spike-realchrome.cjs
new file mode 100644
index 00000000..d21cf577
--- /dev/null
+++ b/scripts/wallquest-refresh/spike-realchrome.cjs
@@ -0,0 +1,37 @@
+// BOUNDED SPIKE: prove real local Chrome (not headless Browserbase) can log into WallQuest and see a price.
+// One login + one known product (sy21843 = $83.16). No crawl, no DB writes. Isolated Chrome profile.
+const {chromium}=require('playwright-core'); const fs=require('fs'); const {execSync}=require('child_process');
+const PSQL='/opt/homebrew/opt/postgresql@14/bin/psql';
+const ENVL=fs.readFileSync(require('os').homedir()+'/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/.env.local','utf8');
+const PGPW=(ENVL.match(/^POSTGRES_PASSWORD=(.+)$/m)||[])[1];
+const q=sql=>execSync(`PGPASSWORD='${PGPW}' ${PSQL} -h localhost -U dw_admin -d dw_unified -tAF'|' -c "${sql}"`,{encoding:'utf8',maxBuffer:1e8});
+const WPW=q("SELECT trade_password FROM vendor_registry WHERE vendor_code='wallquest';").trim();
+const PROFILE='/tmp/wq-chrome-profile'; fs.mkdirSync(PROFILE,{recursive:true});
+const goto=async(p,u)=>{for(let i=0;i<3;i++){try{await p.goto(u,{waitUntil:'domcontentloaded',timeout:45000});return 1;}catch(e){await p.waitForTimeout(3000);}}return 0;};
+(async()=>{
+  console.error(`WPW length=${WPW.length}`);
+  let ctx;
+  try{
+    ctx=await chromium.launchPersistentContext(PROFILE,{channel:'chrome',headless:false,viewport:{width:1280,height:900}});
+  }catch(e){ console.error('LAUNCH FAILED (real Chrome): '+e.message); process.exit(3); }
+  const page=ctx.pages()[0]||await ctx.newPage();
+  try{
+    await goto(page,'https://www.wallquest.com/login'); await page.waitForTimeout(6000);
+    await page.fill('#Email','info@designerwallcoverings.com'); await page.fill('#Password',WPW);
+    let clicked=false; for(const sel of ['input.login-button','.login-button','button.login-button']){if(await page.$(sel)){try{await page.click(sel,{timeout:5000});clicked=true;console.error('clicked '+sel);break;}catch(e){}}}
+    if(!clicked){await page.press('#Password','Enter');console.error('fell back to Enter');}
+    await page.waitForTimeout(9000);
+    await page.screenshot({path:'/tmp/wq-realchrome-postlogin.png'}).catch(()=>{});
+    const post=await page.evaluate(()=>{
+      const errSel=['.validation-summary-errors','.field-validation-error','.message-error','.login-error','span[data-valmsg-for]'];
+      const errs=errSel.flatMap(s=>[...document.querySelectorAll(s)].map(e=>(e.textContent||'').trim())).filter(Boolean);
+      return {url:location.href, hasLogout:/log ?out|sign ?out/i.test(document.body.innerText), errors:errs.slice(0,5), formStillThere:!!document.querySelector('#Password'), bodyHead:document.body.innerText.replace(/\s+/g,' ').slice(0,200)};
+    });
+    console.error('POST-LOGIN: '+JSON.stringify(post));
+    await goto(page,'https://www.wallquest.com/sy21843'); await page.waitForTimeout(14000);
+    const probe=await page.evaluate(()=>{const pv=([...document.querySelectorAll('[class*=price-value]')][0]||{}).textContent||'';const m=pv.match(/[\d,]+\.\d{2}/);return{priceText:pv.trim().slice(0,40),price:m?m[0]:null,signInToView:/sign in to view/i.test(document.body.innerText)};});
+    console.error('PRICE PROBE (sy21843, expect ~83.16): '+JSON.stringify(probe));
+    console.error(probe.price?`SPIKE RESULT: SUCCESS — real Chrome sees price ${probe.price}`:'SPIKE RESULT: FAIL — still price-gated (signInToView='+probe.signInToView+')');
+  }catch(e){console.error('SPIKE ERR: '+e.message);}
+  finally{await ctx.close().catch(()=>{});}
+})();

← a48458ac Daisy Bennett viewer: local images (bypass WallQuest hotlink  ·  back to Designer Wallcoverings  ·  Localize Carl Robinson viewer (:64761): local images + real- bd1c4cee →