Files
hwacheon-network-viewer/tests/viewer.cjs
T

91 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Run start.py first. VIEWER_URL defaults to http://127.0.0.1:8080.
const {chromium}=require('playwright');
const fs=require('node:fs/promises');
const path=require('node:path');
const assert=require('node:assert/strict');
const base=process.env.VIEWER_URL||'http://127.0.0.1:8080';
const out=process.env.VIEWER_RESULTS_DIR?path.resolve(process.env.VIEWER_RESULTS_DIR):path.resolve(__dirname,'../test-results');
(async()=>{
await fs.mkdir(out,{recursive:true});
let browser,page;
const errors=[];
let report={status:'failed',url:base};
try{
browser=await chromium.launch({headless:true,...(process.env.PLAYWRIGHT_CHANNEL?{channel:process.env.PLAYWRIGHT_CHANNEL}:{})});
page=await browser.newPage({viewport:{width:1440,height:1000}});
page.on('pageerror',e=>errors.push(String(e)));
await page.goto(base,{waitUntil:'networkidle'});
await page.waitForFunction(()=>document.querySelector('#loading')?.hidden && document.querySelector('#scene'));
const data=await (await page.request.get(new URL('data/network.json',base).href)).json();
assert.equal(await page.locator('#node-count').textContent(),String(data.summary.counts.junction));
assert.equal(await page.locator('#pipe-count').textContent(),String(data.summary.counts.conduit));
// Check every facility through the same selectFacility function used by UI events.
// Separate click/search checks below exercise the actual event wiring.
const inspected=await page.evaluate(async()=>{
const {app,selectFacility}=await import('./app.js');
const failures=[];
const fmt=n=>Number(n).toLocaleString('ko-KR',{minimumFractionDigits:3,maximumFractionDigits:3});
const prop=(label)=>[...document.querySelectorAll('#details .properties > div')]
.find(r=>r.querySelector('dt')?.textContent===label)?.querySelector('dd')?.textContent;
for(const f of app.facilities){
selectFacility(f);
const check=(label,expected)=>{if(prop(label)!==expected)failures.push({id:f.id,label,actual:prop(label),expected});};
if(document.querySelector('#selected-id')?.textContent!==f.id)failures.push({id:f.id,label:'selected-id'});
if(['junction','outfall','storage'].includes(f.type)){
check('바닥 표고',`${fmt(f.invert)} m`);
check('원본 X / Y',f.xy?`${fmt(f.xy[0])}${fmt(f.xy[1])}`:'좌표 없음 · 미배치');
if(f.maxDepth!=null){check('최대 깊이',`${fmt(f.maxDepth)} m`);check('모델상 상단',`${fmt(f.top)} m`);}
}else if(f.type==='conduit'){
check('입력 길이',`${fmt(f.length)} m`);
check('시작 관저고',`${fmt(f.invertStart)} m`);check('도착 관저고',`${fmt(f.invertEnd)} m`);
check('단면',f.section.shape);
if(f.section.shape==='CIRCULAR')check('안지름',`Ø ${fmt(f.section.diameter)} m`);
else check('폭 × 높이',`${fmt(f.section.width)} × ${fmt(f.section.height)} m`);
check('병렬 관 수',String(f.section.barrels));
}
const placed=f.xy!=null||f.renderable;
if(document.querySelector('#focus-selected').disabled===Boolean(placed))failures.push({id:f.id,label:'placement button'});
}
return {count:app.facilities.length,failures};
});
assert.deepEqual(inspected.failures,[]);
const samples=[data.nodes.find(n=>n.type==='junction'&&n.xy),
data.links.find(l=>l.type==='conduit'&&l.renderable&&l.section.shape==='CIRCULAR'),
data.links.find(l=>l.type==='conduit'&&l.renderable&&l.section.shape==='RECT_CLOSED'),
...data.nodes.filter(n=>!n.xy),...data.links.filter(l=>!l.renderable)];
for(let i=0;i<samples.length;i++){
const f=samples[i];
await page.locator('#search').fill(f.id);
const result=page.locator('#search-results button').filter({has:page.locator('span',{hasText:new RegExp('^'+f.id.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+'$')})});
await result.click();
assert.equal(await page.locator('#selected-id').textContent(),f.id);
if(i<3)await page.screenshot({path:path.join(out,`facility-${i}.png`),fullPage:true});
}
await page.locator('#search').fill('__NO_SUCH_FACILITY__');
assert.equal(await page.locator('#search-results button').count(),0);
await page.locator('#actual-scale').click();
assert.equal(await page.locator('#height-scale').inputValue(),'1');
assert.equal(await page.locator('#size-scale').inputValue(),'1');
await page.locator('[data-view="plan"]').click();
await page.waitForFunction(()=>document.querySelector('[data-view="plan"]').getAttribute('aria-pressed')==='true');
await page.screenshot({path:path.join(out,'plan.png'),fullPage:true});
for(const box of await page.locator('[data-layer]').all()){await box.uncheck();await box.check();}
await page.locator('#open-audit').click();
for(const id of [...data.nodes.filter(n=>!n.xy),...data.links.filter(l=>!l.renderable)].map(f=>f.id))
assert.ok((await page.locator('#audit-list').textContent()).includes(id),`audit missing ${id}`);
await page.locator('#audit-dialog .close-dialog').click();
await page.locator('#reset').click();
await page.screenshot({path:path.join(out,'overview.png'),fullPage:true});
assert.deepEqual(errors,[]);
report={status:'passed',facilities_checked:inspected.count,search_click_samples:samples.map(f=>f.id),
checks:['all facility information panels','summary counts','real search/selection clicks','missing-facility explanations',
'unknown ID','scale reset','plan view','layer toggles','audit dialog','reset','no page errors'],
browser:browser.version(),limitations:['Information consistency; screenshot review is separate from dimensional geometry verification.']};
}catch(e){report.error=e.stack;if(page)await page.screenshot({path:path.join(out,'failure.png'),fullPage:true}).catch(()=>{});process.exitCode=1;}
finally{
try{await fs.writeFile(path.join(out,'ui-validation.json'),JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2));}
finally{if(browser)await browser.close();}
}
})().catch(e=>{console.error(e);process.exitCode=1;});