← back to Commercialrealestate
5x/deals-assert.js
53 lines
// Strong deals.html gate (what /3x's --selector cannot do): assert the 6 charts actually drew DATA,
// no tile renders NaN/$NaN, and the EMPTY-DATA path degrades without crashing or showing NaN.
const pw = require('/Users/macstudio3/.claude/skills/browserbase/node_modules/playwright-core');
const EXEC = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
const CHARTS = ['cPipe','cType','cCap','cMed','cGeo','cBand'];
async function run(label, mockEmpty){
const errors=[];
const b = await pw.chromium.launch({ executablePath: EXEC, headless: true });
const pg = await b.newPage({ viewport:{width:1320,height:1000} });
pg.on('pageerror', e=>errors.push('PAGEERROR: '+e.message));
pg.on('console', m=>{ if(m.type()==='error' && !/Failed to load resource/.test(m.text())) errors.push('CONSOLE: '+m.text()); });
if (mockEmpty){
await pg.route('**/api/segments', r=>r.fulfill({status:200,contentType:'application/json',body:'{"segments":[]}'}));
await pg.route('**/api/crcp/stats', r=>r.fulfill({status:200,contentType:'application/json',body:'{}'}));
await pg.route('**/data/ranked.json', r=>r.fulfill({status:200,contentType:'application/json',body:'{"ranked":[]}'}));
await pg.route('**/data/map-points.json', r=>r.fulfill({status:200,contentType:'application/json',body:'{"points":[]}'}));
await pg.route('**/data/demographics.json', r=>r.fulfill({status:200,contentType:'application/json',body:'{"byCity":{}}'}));
}
await pg.goto('http://127.0.0.1:9911/deals.html', { waitUntil:'networkidle' });
await pg.waitForTimeout(2200);
const r = await pg.evaluate((CHARTS)=>{
const charts = CHARTS.map(id=>{ const c=Chart.getChart(id); return { id, exists:!!c, points: c?(c.data.datasets[0]?.data?.length||0):0 }; });
const tiles = [...document.querySelectorAll('.tile .v')].map(v=>v.textContent);
const nanTiles = tiles.filter(t=>/nan|undefined/i.test(t));
// Top Deals by Unified Score: rows render, each score is a finite 0-100, in descending order, no NaN.
const tdRows = [...document.querySelectorAll('#tdlist .tdrow')];
const tdScores = tdRows.map(r=>{ const m=(r.querySelector('.sc')?.textContent||'').match(/(\d+)/); return m?+m[1]:null; }).filter(s=>s!=null);
let tdDesc=true; for(let i=1;i<tdScores.length;i++) if(tdScores[i]>tdScores[i-1]) tdDesc=false;
const td = { rows:tdRows.length, scores:tdScores.length, descending:tdDesc,
allFinite:tdScores.every(s=>isFinite(s)&&s>=0&&s<=100),
anyNaN:/NaN|\$NaN|undefined/.test(document.querySelector('#topdeals')?.textContent||''),
panelPresent:!!document.querySelector('#topdeals') };
return { charts, tiles, nanTiles, td };
}, CHARTS);
await b.close();
const chartsWithData = r.charts.filter(c=>c.points>0).length;
return { label, ...r, chartsWithData, jsErrors:errors };
}
(async()=>{
const normal = await run('NORMAL', false);
const empty = await run('EMPTY-DATA', true);
// Pass criteria:
const passNormal = normal.charts.every(c=>c.exists) && normal.chartsWithData>=5 && normal.nanTiles.length===0
&& normal.td.panelPresent && normal.td.rows>0 && normal.td.scores>1 && normal.td.allFinite && normal.td.descending && !normal.td.anyNaN
&& normal.jsErrors.length===0;
// empty: top-deals panel must degrade to 0 rows (or a "no scorable deals" note) with NO NaN, no crash.
const passEmpty = empty.nanTiles.length===0 && !empty.td.anyNaN && empty.td.panelPresent && empty.jsErrors.length===0;
console.log(JSON.stringify({ normal, empty, passNormal, passEmpty, VERDICT: (passNormal&&passEmpty)?'PASS':'FAIL' }, null, 2));
process.exit((passNormal&&passEmpty)?0:1);
})().catch(e=>{ console.error('FAIL', e.message); process.exit(1); });