const {chromium}=require('playwright'); const assert=require('node:assert/strict'); const fs=require('node:fs/promises'); const path=require('node:path'); const out=path.resolve(__dirname,'../test-results'); (async()=>{ const browser=await chromium.launch({headless:true,...(process.env.PLAYWRIGHT_CHANNEL?{channel:process.env.PLAYWRIGHT_CHANNEL}:{})}); const page=await browser.newPage({viewport:{width:1440,height:1000}}); const errors=[];page.on('pageerror',e=>errors.push(e.message)); const report={status:'failed'}; await fs.mkdir(out,{recursive:true}); const state=()=>page.evaluate(async()=>{ const {app}=await import('./app.js');const r=app.renderer; r.draw();const gl=r.gl,b=new Uint8Array(r.canvas.width*r.canvas.height*4); gl.readPixels(0,0,r.canvas.width,r.canvas.height,gl.RGBA,gl.UNSIGNED_BYTE,b); let red=0,hash=0;for(let i=0;i220&&b[i+1]<80&&b[i+2]<120)red++;hash=(hash+b[i]*3+b[i+1]*5+b[i+2]*7)>>>0;} return {phase:r.auditPhase,enabled:r.auditEnabled,timer:r.auditTimer!==null,red,hash, targetCount:r.auditPickIds.size,targets:r.facilities.filter(f=>r.auditPickIds.has(f.pickId)).map(f=>f.id)}; }); try{ await page.clock.install({time:new Date('2026-10-28T00:00:00Z')}); await page.goto(process.env.VIEWER_URL||'http://127.0.0.1:8081/',{waitUntil:'networkidle'}); await page.waitForFunction(()=>document.querySelector('#loading').hidden); await page.clock.pauseAt(new Date('2026-10-28T00:01:00Z')); const before=await state();assert.equal(before.enabled,false); const data=await (await page.request.get(new URL('data/network.json',page.url()).href)).json(); const ids=new Set(data.issues.filter(i=>['length_mismatch','adverse_slope'].includes(i.code)).flatMap(i=>i.ids)); const expected=data.links.filter(l=>l.type==='conduit'&&l.renderable&&ids.has(l.id)).map(l=>l.id).sort(); assert.deepEqual(before.targets.sort(),expected); await page.locator('#audit-highlight').check({force:true}); const on=await state();assert.equal(on.phase,1);assert.ok(on.red>before.red+20); await page.screenshot({path:path.join(out,'audit-red.png'),fullPage:true}); await page.clock.runFor(1000); const normal=await state();assert.equal(normal.red,before.red);assert.equal(normal.hash,before.hash); await page.clock.runFor(1000);assert.equal((await state()).phase,1); await page.locator('#audit-highlight').uncheck({force:true});const stopped=await state(); assert.equal(stopped.timer,false);assert.equal(stopped.hash,before.hash); await page.locator('#audit-highlight').check({force:true});await page.locator('#audit-steady').check({force:true}); const fixed=await state();assert.equal(fixed.timer,false);assert.equal(fixed.phase,1); await page.clock.runFor(1150);assert.equal((await state()).phase,1); await page.locator('#show-conduit').uncheck({force:true});assert.equal((await state()).red,0);await page.locator('#show-conduit').check({force:true}); const target=expected[0];await page.locator('#search').fill(target);await page.locator('#search-results button').first().click({force:true}); assert.ok((await page.locator('#details').textContent()).includes('점검 필요 · 오류 확정 아님')); await page.locator('#audit-unplaced').click({force:true}); for(const id of ['OUT_PUMP','C_OUT_PUMP','UHR1-01411'])assert.ok((await page.locator('#audit-list').textContent()).includes(id)); await page.locator('#audit-dialog .close-dialog').click({force:true}); await page.locator('#reset').click({force:true});assert.equal((await state()).enabled,false); await page.emulateMedia({reducedMotion:'reduce'});await page.locator('#audit-highlight').check({force:true}); const reduced=await state();assert.equal(reduced.timer,false);assert.equal(reduced.phase,1); assert.equal(await page.locator('#audit-steady').isDisabled(),true); await page.clock.resume();await page.waitForTimeout(150); await page.locator('#audit-highlight').scrollIntoViewIfNeeded(); await page.screenshot({path:path.join(out,'audit-fixed.png'),fullPage:true}); assert.deepEqual(errors,[]);Object.assign(report,{status:'passed',target_conduits:expected.length, red_pixels_on:on.red,red_pixels_off:normal.red,original_colors_restored:true, checks:['target IDs','red pixels','1 second phases','exact original pixel restoration','timer cancellation', 'steady mode','layer visibility','selection reasons','3 unplaced facilities','reset','reduced motion','no page errors']}); }catch(e){report.error=e.stack;process.exitCode=1;await page.screenshot({path:path.join(out,'audit-failure.png'),fullPage:true}).catch(()=>{});} finally{await fs.writeFile(path.join(out,'audit-highlight-validation.json'),JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2));await browser.close();} })().catch(e=>{console.error(e);process.exitCode=1;});