const {chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright'); const assert=require('node:assert/strict');const fs=require('fs'); (async()=>{ const browser=await chromium.launch({headless:true,args:['--no-sandbox'],env:process.env});const page=await browser.newPage();const writes=[],errors=[],passed=[]; page.on('pageerror',e=>errors.push(e.message)); await page.route('**/api/firewall/**',async route=>{if(route.request().method()==='GET')return route.continue();writes.push({path:new URL(route.request().url()).pathname,body:route.request().postDataJSON()});await route.fulfill({json:{success:true,id:route.request().postDataJSON()?.id||'filter-preview'}})}); try{ await page.goto(process.env.DEBTIK_TEST_URL||'http://127.0.0.1:8080/',{waitUntil:'domcontentloaded'});await page.locator('#login-username').fill(process.env.DEBTIK_TEST_USER || 'admin');await page.locator('#login-password').fill(process.env.DEBTIK_TEST_PASSWORD || 'admin');await page.locator('#login-submit-btn').click();await page.waitForFunction(()=>state.currentUser!==null); await page.evaluate(async()=>{navigate('firewall');await loadFirewall(false)}); for(const prefix of ['fw','nat','mangle'])for(const key of ['layer7','connbytes','connrate','pcc','tlshost','psd','ipv4options','dstlimit','ingresspriority','priority','hotspot'])assert.equal(await page.locator('#'+prefix+'-field-'+key).count(),0); for(const [type,bad]of [['filter',['tarpit','fasttrack-connection']],['nat',['same','socksify','endpoint-independent-nat']],['mangle',['route','sniff-pc','sniff-tzsp','clear-df','strip-ipv4-options','fasttrack-connection']]])for(const action of bad)assert.equal(await page.locator('#'+type+'-modal-action option[value="'+action+'"]').count(),0); passed.push('Unsupported fields and actions physically removed'); for(const [type,open,apply]of [['filter','openEditFilterModal','applyFilterRule'],['nat','openEditNATModal','applyNATRule'],['mangle','openEditMangleModal','applyMangleRule']]){ const chain=await page.evaluate(async({type,open,apply})=>{const key=type==='filter'?'filterRules':type==='nat'?'natRules':'mangleRules';state[key]=[{id:type+'-preview',chain:'CUSTOM_TEST',action:'accept',disabled:false,passthrough:true}];window[open](type+'-preview');const value=document.getElementById(type+'-modal-chain').value;await window[apply](true);return value},{type,open,apply}); assert.equal(chain,'CUSTOM_TEST');assert.equal(writes.at(-1).body.chain,'CUSTOM_TEST'); }passed.push('Custom chain preserved in all three forms'); for(const [type,prefix,open,apply]of [['filter','fw','openAddFilterModal','applyFilterRule'],['nat','nat','openAddNATModal','applyNATRule']]){ await page.evaluate(async({type,prefix,open,apply})=>{window[open]();window[prefix==='fw'?'toggleFwField':'toggleNATField']('days',true);for(const d of ['sun','mon','tue','wed','thu','fri','sat'])document.getElementById(prefix+'-day-'+d).checked=d==='fri';await window[apply](true)},{type,prefix,open,apply});assert.equal(writes.at(-1).body.days,'fri'); }passed.push('Weekdays serialized for Filter and NAT'); await page.evaluate(()=>{state.filterRules=[{id:'filter-xss',chain:'input',action:'accept',comment:''}];renderFilterRules()});await page.waitForTimeout(250);assert.equal(await page.evaluate(()=>!!window.__fwAuditXss),false);assert.equal(await page.locator('#filter-table-body img').count(),0);passed.push('Stored firewall comment escaped'); await page.evaluate(()=>showToast('Error','','error')); await page.waitForTimeout(50); assert.equal(await page.evaluate(()=>!!window.__toastXss),false); assert.equal(await page.locator('#toast-container img').count(),0); passed.push('Firewall error messages escaped in notifications'); await page.evaluate(()=>{state.addressLists=[{id:'dynamic-test',name:'',address:'127.0.0.1',dynamic:true,timeout:30}];renderAddressLists()});assert.equal(await page.locator('#addrlist-table-body img').count(),0);assert.equal(await page.locator('#addrlist-table-body').getByText('Edit',{exact:true}).count(),0);passed.push('Dynamic entries escaped and read-only except Delete'); await page.evaluate(()=>{state.filterRules=[{id:'filter-reset',chain:'input',action:'accept'}];openEditFilterModal('filter-reset')});await page.evaluate(()=>resetCurrentRuleCounters());assert.equal(writes.at(-1).path,'/api/firewall/filter/filter-reset/reset-counters');passed.push('Single-rule reset calls backend'); await page.evaluate(()=>{state.filterRules=[{id:'filter-old',chain:'forward',action:'accept',tos:'min-delay'}];openEditFilterModal('filter-old');openAddFilterModal()});assert.equal(await page.locator('#fw-val-tos').inputValue(),'');passed.push('New rule does not inherit old TOS'); await page.evaluate(()=>handleLogoutClick());assert.deepEqual(errors,[]);console.log(JSON.stringify({passed,errors,mockedWrites:writes.length},null,2)); }finally{await browser.close()} })().catch(e=>{console.error(e);process.exitCode=1});