← back to Commercialrealestate

5x/tk10703-linkedin-assert.js

76 lines

// TK-10703 gate for linkedin.html:
//  HTTP 200 · 0 swallowed JS errors · view toggle (List↔Grid) switches layout · sort reorders rows ·
//  tel:/mailto: counts > 0 (firm-confirmed contacts via /api/brokers/all id/name+firm join) ·
//  BOTH light + dark theme flip surfaces.
const pw=require('/Users/macstudio3/.claude/skills/browserbase/node_modules/playwright-core');
const EXEC='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
const SID=process.argv[2];
const URL='http://127.0.0.1:9911/linkedin.html';
async function run(theme){
  const errors=[]; const b=await pw.chromium.launch({executablePath:EXEC,headless:true});
  const ctx=await b.newContext({viewport:{width:1360,height:1000}});
  await ctx.addCookies([{name:'crcp_sid',value:SID,domain:'127.0.0.1',path:'/'}]);
  const pg=await ctx.newPage();
  pg.on('pageerror',e=>errors.push('PAGEERR: '+e.message));
  pg.on('console',m=>{if(m.type()==='error'&&!/Failed to load resource/.test(m.text()))errors.push('CONSOLE: '+m.text());});
  await pg.addInitScript(t=>{try{localStorage.setItem('crcp-theme',t);localStorage.setItem('liView','list');}catch(e){}},theme);
  const resp=await pg.goto(URL,{waitUntil:'networkidle'});
  const http=resp.status();
  await pg.waitForTimeout(1800);
  const base=await pg.evaluate(()=>{
    const bodyBg=getComputedStyle(document.body).backgroundColor;
    const rowEl=document.querySelector('.row');
    const rowBg=rowEl?getComputedStyle(rowEl).backgroundColor:'';
    return {
      theme:document.documentElement.getAttribute('data-theme'),
      bodyBg, rowBg,
      tel:document.querySelectorAll('a[href^="tel:"]').length,
      mailto:document.querySelectorAll('a[href^="mailto:"]').length,
      rows:document.querySelectorAll('#list .row').length,
      contactStrips:document.querySelectorAll('#list .row .contact').length,
      sortOpts:document.querySelectorAll('#sortsel option').length,
      viewBtns:document.querySelectorAll('#viewseg button').length,
      anyNaN:/NaN|undefined/.test((document.querySelector('#list')?.textContent||'')+(document.querySelector('#count')?.textContent||'')),
    };
  });
  // view toggle: switch to grid, confirm #list gets .grid class (layout changes), back to list
  const viewSwitch=await pg.evaluate(async()=>{
    const list=document.querySelector('#list');
    const gridBtn=[...document.querySelectorAll('#viewseg button')].find(b=>/grid/i.test(b.textContent));
    const listBtn=[...document.querySelectorAll('#viewseg button')].find(b=>/list/i.test(b.textContent));
    if(gridBtn){gridBtn.click();await new Promise(r=>setTimeout(r,250));}
    const isGrid=list.classList.contains('grid') && getComputedStyle(list).display==='grid';
    if(listBtn){listBtn.click();await new Promise(r=>setTimeout(r,250));}
    const backList=!list.classList.contains('grid') && getComputedStyle(list).display==='flex';
    return {isGrid,backList};
  });
  // sort reorder: capture first name, change sort select to a different option, confirm reorder
  const sortReorder=await pg.evaluate(async()=>{
    const firstName=()=>document.querySelector('#list .row .nm')?.textContent||'';
    const before=firstName();
    const sel=document.querySelector('#sortsel'); if(!sel||sel.options.length<2) return {changed:false,before,after:before,note:'no sort select'};
    const cur=sel.value; let target=null;
    for(const o of sel.options){ if(o.value!==cur){target=o.value;break;} }
    sel.value=target; sel.dispatchEvent(new Event('change',{bubbles:true}));
    await new Promise(r=>setTimeout(r,350));
    const after=firstName();
    return {changed:before!==after,before,after};
  });
  await b.close();
  return {theme,http,...base,viewSwitch,sortReorder,jsErrors:errors};
}
(async()=>{
  const dark=await run('dark'); const light=await run('light');
  const themeFlips = dark.bodyBg!==light.bodyBg && dark.rowBg!==light.rowBg;
  const pass=r=>r.http===200 && r.jsErrors.length===0 && !r.anyNaN && r.tel>0 && r.mailto>0
    && r.rows>0 && r.contactStrips>0 && r.sortOpts>1 && r.viewBtns>=2
    && r.viewSwitch.isGrid && r.viewSwitch.backList && r.sortReorder.changed;
  const passDark=pass(dark), passLight=pass(light);
  const VERDICT=(passDark&&passLight&&themeFlips)?'PASS':'FAIL';
  console.log(JSON.stringify({
    darkSummary:{http:dark.http,tel:dark.tel,mailto:dark.mailto,jsErr:dark.jsErrors.length,contactStrips:dark.contactStrips,view:dark.viewSwitch,sort:dark.sortReorder,bodyBg:dark.bodyBg,rowBg:dark.rowBg},
    lightSummary:{http:light.http,tel:light.tel,mailto:light.mailto,jsErr:light.jsErrors.length,bodyBg:light.bodyBg,rowBg:light.rowBg},
    dark,light,themeFlips,passDark,passLight,VERDICT},null,2));
  process.exit(VERDICT==='PASS'?0:1);
})().catch(e=>{console.error('FAIL',e.message,e.stack);process.exit(1);});