// End-to-end runner for Add New Asset — drives the real page over CDP. const fs = require('fs'); const PAGE_URL = 'file://' + process.cwd() + '/asset-create.html'; async function cdp() { const list = await (await fetch('http://127.0.0.1:9222/json/list')).json(); let target = list.find(t => t.type === 'page'); if (!target) { target = await (await fetch('http://127.0.0.1:9222/json/new')).json(); } const ws = new WebSocket(target.webSocketDebuggerUrl); await new Promise(r => ws.addEventListener('open', r, { once: true })); let id = 0; const pend = new Map(); const evWaiters = []; ws.addEventListener('message', e => { const m = JSON.parse(e.data); if (m.id && pend.has(m.id)) { pend.get(m.id)(m); pend.delete(m.id); } else if (m.method) { for (const w of evWaiters.slice()) if (w.method === m.method) { evWaiters.splice(evWaiters.indexOf(w),1); w.resolve(m.params); } } }); const send = (method, params={}) => new Promise(res => { const i=++id; pend.set(i,res); ws.send(JSON.stringify({id:i,method,params})); }); const waitEvent = (method, ms=8000) => new Promise((resolve,reject)=>{ const w={method,resolve}; evWaiters.push(w); setTimeout(()=>{const x=evWaiters.indexOf(w); if(x>=0){evWaiters.splice(x,1); reject(new Error('timeout '+method));}},ms); }); await send('Page.enable'); await send('Runtime.enable'); return { send, waitEvent, ws }; } async function main() { const { send, waitEvent } = await cdp(); // eval expression in page, return value async function ev(expr) { const m = await send('Runtime.evaluate', { expression: `(function(){${expr}})()`, returnByValue: true, awaitPromise: true }); const res = m.result || {}; if (res.exceptionDetails) { const ex = res.exceptionDetails; throw new Error((ex.exception && (ex.exception.description || ex.exception.value)) || ex.text || JSON.stringify(ex)); } return res.result ? res.result.value : undefined; } async function navigate() { const loaded = waitEvent('Page.loadEventFired'); await send('Page.navigate', { url: PAGE_URL }); await loaded; // wait for app ready for (let i=0;i<50;i++){ const ok = await ev('return !!(window.AMS && window.submitAsset && document.getElementById("assetName"))'); if (ok) break; await new Promise(r=>setTimeout(r,100)); } // inject helpers await ev(`window.__t={ setVal(id,v){const el=document.getElementById(id);if(!el)return false;el.value=v;el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));return true;}, toasts(){return [...document.querySelectorAll('#toastContainer .toast')].map(t=>t.textContent.replace(/×$/,'').trim());}, last(){const a=this.toasts();return a[a.length-1]||'';}, clear(){const c=document.getElementById('toastContainer');if(c)c.innerHTML='';}, count(){return AMS.assets.length;}, focused(){return document.activeElement?document.activeElement.id:'';}, vis(id){const el=document.getElementById(id);if(!el)return false;return getComputedStyle(el).display!=='none';} };return true;`); } const results = []; async function run(tcid, name, fn) { try { const out = await fn(); results.push({ tcid, name, status: out.pass ? 'Pass' : 'Fail', actual: out.actual }); } catch (e) { results.push({ tcid, name, status: 'Fail', actual: 'ERROR: ' + e.message }); } } await navigate(); // helper to set the three core valid fields // Fills ALL now-required fields with valid values (post-fix) const fillValid = `__t.setVal('assetType','physical');handleTypeChange();__t.setVal('assetName','E2E Asset');__t.setVal('assetCategory','Laptops');__t.setVal('purchaseCost','85000');__t.setVal('assetVendor','Dell India Pvt. Ltd.');__t.setVal('purchaseDate','2025-05-28');__t.setVal('depRate','20');__t.setVal('assetDept','IT');`; // TC004 ID preview await run('TC_AddAsset_004','Asset ID preview updates on name entry', async()=>{ const v = await ev(`__t.setVal('assetName','PreviewTest');updatePreview();return document.getElementById('previewId').textContent;`); return { pass: /^AST-2025-\d{3}$/.test(v), actual: 'previewId='+v }; }); // TC005 ID uniqueness max+1 await run('TC_AddAsset_005','Asset ID = max existing +1 (no collision)', async()=>{ const v = await ev(`var max=AMS.assets.reduce((m,a)=>{var n=parseInt(String(a.id).split('-').pop(),10);return isNaN(n)?m:Math.max(m,n);},0);var nid=nextAssetId();var exp='AST-2025-'+String(max+1).padStart(3,'0');var dup=AMS.assets.some(a=>a.id===nid);return JSON.stringify({nid:nid,exp:exp,dup:dup});`); const o = JSON.parse(v); return { pass: o.nid===o.exp && !o.dup, actual:`next=${o.nid} expected=${o.exp} duplicate=${o.dup}` }; }); // TC006 default physical + visibility await run('TC_AddAsset_006','Default type Physical; serial/assignee/location visible', async()=>{ const v = await ev(`__t.setVal('assetType','physical');handleTypeChange();return JSON.stringify({type:document.getElementById('assetType').value,phys:__t.vis('physicalFieldsRow'),dig:__t.vis('digitalFieldsRow'),assignee:__t.vis('assigneeGroup')});`); const o=JSON.parse(v); return { pass:o.type==='physical'&&o.phys&&!o.dig&&o.assignee, actual:v }; }); // TC007 digital fields await run('TC_AddAsset_007','Digital type shows license/seats, hides serial/location', async()=>{ const v = await ev(`__t.setVal('assetType','digital');handleTypeChange();return JSON.stringify({dig:__t.vis('digitalFieldsRow'),phys:__t.vis('physicalFieldsRow'),proj:__t.vis('projectGroup'),assignee:__t.vis('assigneeGroup'),cats:[...document.getElementById('assetCategory').options].map(o=>o.text).join(',')});`); const o=JSON.parse(v); return { pass:o.dig&&!o.phys&&o.proj&&!o.assignee&&/Software Licenses/.test(o.cats), actual:v }; }); // TC008 other await run('TC_AddAsset_008','Other type category list + fields', async()=>{ const v=await ev(`__t.setVal('assetType','other');handleTypeChange();return JSON.stringify({cats:[...document.getElementById('assetCategory').options].map(o=>o.text).join(','),phys:__t.vis('physicalFieldsRow'),proj:__t.vis('projectGroup')});`); const o=JSON.parse(v); return { pass:/Leases/.test(o.cats)&&!o.phys&&o.proj, actual:v }; }); // TC009 category resets on type change await run('TC_AddAsset_009','Category resets when type changes', async()=>{ const v=await ev(`__t.setVal('assetType','physical');handleTypeChange();__t.setVal('assetCategory','Laptops');var before=document.getElementById('assetCategory').value;__t.setVal('assetType','digital');handleTypeChange();var after=document.getElementById('assetCategory').value;return JSON.stringify({before:before,after:after});`); const o=JSON.parse(v); return { pass:o.before==='Laptops'&&o.after!=='Laptops', actual:v }; }); // TC010/011 name required await run('TC_AddAsset_011','Asset Name mandatory -> error + focus', async()=>{ const v=await ev(`__t.clear();__t.setVal('assetType','physical');handleTypeChange();__t.setVal('assetName','');__t.setVal('assetCategory','Laptops');__t.setVal('purchaseCost','5000');var c0=__t.count();submitAsset();return JSON.stringify({toast:__t.last(),focus:__t.focused(),delta:__t.count()-c0});`); const o=JSON.parse(v); return { pass:/asset name/i.test(o.toast)&&o.focus==='assetName'&&o.delta===0, actual:v }; }); // TC012 category required await run('TC_AddAsset_012','Category mandatory -> error + focus', async()=>{ const v=await ev(`__t.clear();__t.setVal('assetName','HasName');__t.setVal('assetCategory','');__t.setVal('purchaseCost','5000');var c0=__t.count();submitAsset();return JSON.stringify({toast:__t.last(),focus:__t.focused(),delta:__t.count()-c0});`); const o=JSON.parse(v); return { pass:/category/i.test(o.toast)&&o.focus==='assetCategory'&&o.delta===0, actual:v }; }); // TC013 cost required await run('TC_AddAsset_013','Purchase Cost mandatory -> error + focus', async()=>{ const v=await ev(`__t.clear();__t.setVal('assetName','HasName');__t.setVal('assetCategory','Laptops');__t.setVal('purchaseCost','');var c0=__t.count();submitAsset();return JSON.stringify({toast:__t.last(),focus:__t.focused(),delta:__t.count()-c0});`); const o=JSON.parse(v); return { pass:/cost/i.test(o.toast)&&o.focus==='purchaseCost'&&o.delta===0, actual:v }; }); // TC014 validation order await run('TC_AddAsset_014','Validation order Name->Category->Cost', async()=>{ const t1=await ev(`__t.clear();__t.setVal('assetName','');__t.setVal('assetCategory','');__t.setVal('purchaseCost','');submitAsset();return __t.last();`); const t2=await ev(`__t.clear();__t.setVal('assetName','N');__t.setVal('assetCategory','');__t.setVal('purchaseCost','');submitAsset();return __t.last();`); const t3=await ev(`__t.clear();__t.setVal('assetName','N');__t.setVal('assetCategory','Laptops');__t.setVal('purchaseCost','');submitAsset();return __t.last();`); return { pass:/name/i.test(t1)&&/category/i.test(t2)&&/cost/i.test(t3), actual:`[1]${t1} | [2]${t2} | [3]${t3}` }; }); // TC015 vendor now enforced (DEF-03 fixed) await run('TC_AddAsset_015','Vendor required is enforced', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('assetVendor','');var c0=__t.count();submitAsset();return JSON.stringify({delta:__t.count()-c0,toast:__t.last(),focus:__t.focused()});`); const o=JSON.parse(v); return { pass:o.delta===0&&/vendor/i.test(o.toast)&&o.focus==='assetVendor', actual:'FIXED -> '+JSON.stringify(o) }; }); // TC019 cost 0 rejected await run('TC_AddAsset_019','Purchase Cost = 0 rejected', async()=>{ const v=await ev(`__t.clear();__t.setVal('assetName','ZeroCost');__t.setVal('assetCategory','Laptops');__t.setVal('purchaseCost','0');var c0=__t.count();submitAsset();return JSON.stringify({toast:__t.last(),delta:__t.count()-c0});`); const o=JSON.parse(v); return { pass:/cost/i.test(o.toast)&&o.delta===0, actual:v }; }); // TC020 negative cost now rejected (DEF-02 fixed) await run('TC_AddAsset_020','Negative Purchase Cost rejected', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('purchaseCost','-5000');var c0=__t.count();submitAsset();return JSON.stringify({delta:__t.count()-c0,toast:__t.last(),focus:__t.focused()});`); const o=JSON.parse(v); return { pass:o.delta===0&&/cost/i.test(o.toast)&&o.focus==='purchaseCost', actual:'FIXED -> '+JSON.stringify(o) }; }); // TC021 large cost await run('TC_AddAsset_021','Very large cost saved', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('purchaseCost','99999999999');var c0=__t.count();submitAsset();var s=AMS.assets[AMS.assets.length-1];return JSON.stringify({delta:__t.count()-c0,cost:s.cost});`); const o=JSON.parse(v); return { pass:o.delta===1&&o.cost===99999999999, actual:v }; }); // TC022 decimal cost await run('TC_AddAsset_022','Decimal cost accepted', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('purchaseCost','85000.50');var c0=__t.count();submitAsset();var s=AMS.assets[AMS.assets.length-1];return JSON.stringify({delta:__t.count()-c0,cost:s.cost});`); const o=JSON.parse(v); return { pass:o.delta===1&&o.cost===85000.5, actual:v }; }); // TC024 dep rate min/max attributes await run('TC_AddAsset_024','Depreciation Rate has min=1 max=100', async()=>{ const v=await ev(`var el=document.getElementById('depRate');return JSON.stringify({min:el.min,max:el.max});`); const o=JSON.parse(v); return { pass:o.min==='1'&&o.max==='100', actual:v }; }); // TC025 allocated>total now blocked (DEF-07 fixed) await run('TC_AddAsset_025','Allocated Seats > Total blocked', async()=>{ const v=await ev(`__t.clear();__t.setVal('assetType','digital');handleTypeChange();__t.setVal('assetName','SeatTest');__t.setVal('assetCategory','Software Licenses');__t.setVal('purchaseCost','1000');__t.setVal('assetVendor','Dell India Pvt. Ltd.');__t.setVal('purchaseDate','2025-05-28');__t.setVal('depRate','20');__t.setVal('assetDept','IT');__t.setVal('seatsTotal','5');__t.setVal('seatsAlloc','10');var c0=__t.count();submitAsset();return JSON.stringify({delta:__t.count()-c0,toast:__t.last(),focus:__t.focused()});`); const o=JSON.parse(v); return { pass:o.delta===0&&/exceed/i.test(o.toast), actual:'FIXED -> '+JSON.stringify(o) }; }); // TC026 long/special name + check storage await run('TC_AddAsset_026','Long/special-char name accepted & stored', async()=>{ const v=await ev(`__t.clear();__t.setVal('assetType','physical');handleTypeChange();var nm='Z&"\\u00e9\\u2764'+'x'.repeat(250);__t.setVal('assetName',nm);__t.setVal('assetCategory','Laptops');__t.setVal('purchaseCost','1000');var c0=__t.count();submitAsset();var s=AMS.assets[AMS.assets.length-1];return JSON.stringify({delta:__t.count()-c0,len:s.name.length,starts:s.name.slice(0,3)});`); const o=JSON.parse(v); return { pass:o.delta===1&&o.len>250, actual:v }; }); // TC027 serial/model optional await run('TC_AddAsset_027','Serial/Model optional -> serial defaults N/A', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('serialNo','');__t.setVal('modelNo','');var c0=__t.count();submitAsset();var s=AMS.assets[AMS.assets.length-1];return JSON.stringify({delta:__t.count()-c0,serial:s.serial});`); const o=JSON.parse(v); return { pass:o.delta===1&&o.serial==='N/A', actual:v }; }); // TC028 license optional await run('TC_AddAsset_028','Digital: license optional, defaults applied', async()=>{ const v=await ev(`__t.clear();__t.setVal('assetType','digital');handleTypeChange();__t.setVal('assetName','DigA');__t.setVal('assetCategory','Software Licenses');__t.setVal('purchaseCost','1000');__t.setVal('assetVendor','Dell India Pvt. Ltd.');__t.setVal('purchaseDate','2025-05-28');__t.setVal('depRate','20');__t.setVal('assetDept','IT');__t.setVal('seatsTotal','5');__t.setVal('seatsAlloc','3');__t.setVal('licenseKey','');__t.clear();var c0=__t.count();submitAsset();closeModal('successModal');var s=AMS.assets[AMS.assets.length-1];return JSON.stringify({delta:__t.count()-c0,toast:__t.last(),lic:s.licenseKey,serial:s.serial,loc:s.loc});`); const o=JSON.parse(v); return { pass:o.delta===1&&o.lic==='N/A'&&o.serial==='Digital-License'&&o.loc==='Cloud Environment', actual:v }; }); // TC029 status default active await run('TC_AddAsset_029','Status defaults to Active', async()=>{ const v=await ev(`__t.clear();${fillValid}var c0=__t.count();submitAsset();var s=AMS.assets[AMS.assets.length-1];return JSON.stringify({delta:__t.count()-c0,status:s.status});`); const o=JSON.parse(v); return { pass:o.delta===1&&o.status==='Active', actual:v }; }); // TC030 assignee persists await run('TC_AddAsset_030','Assignee selection persists', async()=>{ const v=await ev(`__t.clear();${fillValid}var opt=[...document.getElementById('assetAssignee').options].find(o=>o.value&&o.value!=='');__t.setVal('assetAssignee',opt.value);var c0=__t.count();submitAsset();var s=AMS.assets[AMS.assets.length-1];return JSON.stringify({delta:__t.count()-c0,assignee:s.assignee,picked:opt.value});`); const o=JSON.parse(v); return { pass:o.delta===1&&o.assignee===o.picked, actual:v }; }); // TC032 SLM preview await run('TC_AddAsset_032','SLM depreciation preview (flat)', async()=>{ const v=await ev(`__t.setVal('purchaseCost','100000');__t.setVal('depMethod','SLM');__t.setVal('depRate','20');calcDepreciation();var shown=__t.vis('depPreview');var txt=document.getElementById('depPreviewContent').textContent;return JSON.stringify({shown:shown,hasDep:/20/.test(txt),txt:txt.replace(/\\s+/g,' ').slice(0,120)});`); const o=JSON.parse(v); return { pass:o.shown&&o.hasDep, actual:o.txt }; }); // TC033 WDV preview await run('TC_AddAsset_033','WDV depreciation preview (reducing)', async()=>{ const v=await ev(`__t.setVal('purchaseCost','100000');__t.setVal('depMethod','WDV');__t.setVal('depRate','20');calcDepreciation();var txt=document.getElementById('depPreviewContent').textContent.replace(/\\s+/g,' ');return JSON.stringify({shown:__t.vis('depPreview'),txt:txt.slice(0,140)});`); const o=JSON.parse(v); return { pass:o.shown, actual:o.txt }; }); // TC034 preview hidden when cost empty await run('TC_AddAsset_034','Preview hidden when cost empty', async()=>{ const v=await ev(`__t.setVal('purchaseCost','');calcDepreciation();return JSON.stringify({shown:__t.vis('depPreview')});`); const o=JSON.parse(v); return { pass:o.shown===false, actual:v }; }); // TC036 happy path success modal await run('TC_AddAsset_036','Happy path -> success modal + persisted', async()=>{ const v=await ev(`__t.clear();${fillValid}var c0=__t.count();submitAsset();var modal=document.getElementById('successModal');var open=modal&&modal.classList.contains('open');var txt=modal?modal.textContent:'';return JSON.stringify({delta:__t.count()-c0,open:open,created:/Asset Created/.test(txt)});`); const o=JSON.parse(v); await ev(`closeModal('successModal');return 1;`); return { pass:o.delta===1&&o.open&&o.created, actual:v }; }); // TC038 detail id retrievable await run('TC_AddAsset_038','New asset retrievable by ID', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('assetName','DetailCheck');submitAsset();closeModal('successModal');var s=AMS.assets[AMS.assets.length-1];var found=AMS.assets.find(a=>a.id===s.id);return JSON.stringify({id:s.id,found:!!found,name:found?found.name:null});`); const o=JSON.parse(v); return { pass:o.found&&o.name==='DetailCheck', actual:v }; }); // TC039 activity feed await run('TC_AddAsset_039','Activity feed records creation', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('assetName','FeedCheck');submitAsset();closeModal('successModal');var f=AMS.activityFeed[0];return JSON.stringify({action:f.action,target:f.target});`); const o=JSON.parse(v); return { pass:/created asset/.test(o.action)&&o.target==='FeedCheck', actual:v }; }); // TC040 stats update await run('TC_AddAsset_040','Stats update on creation', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('assetStatus','Active');var t0=AMS.stats.total,a0=AMS.stats.active,v0=AMS.stats.totalValue;submitAsset();closeModal('successModal');return JSON.stringify({dt:AMS.stats.total-t0,da:AMS.stats.active-a0,dv:AMS.stats.totalValue-v0});`); const o=JSON.parse(v); return { pass:o.dt===1&&o.da===1&&o.dv===85000, actual:v }; }); // TC042 icon mapping await run('TC_AddAsset_042','Icon assigned by type/category', async()=>{ const v=await ev(`function mk(type,cat,setup){__t.clear();__t.setVal('assetType',type);handleTypeChange();__t.setVal('assetName','IconT');__t.setVal('assetCategory',cat);__t.setVal('purchaseCost','1000');__t.setVal('assetVendor','Dell India Pvt. Ltd.');__t.setVal('purchaseDate','2025-05-28');__t.setVal('depRate','20');__t.setVal('assetDept','IT');__t.setVal('seatsTotal','5');__t.setVal('seatsAlloc','1');submitAsset();closeModal('successModal');return AMS.assets[AMS.assets.length-1].icon;} var sw=mk('digital','Software Licenses');var cl=mk('digital','Cloud Subscriptions');__t.setVal('assetType','physical');handleTypeChange();var ve=mk('physical','Vehicles');var df=mk('physical','Laptops');return JSON.stringify({sw:sw,cl:cl,ve:ve,df:df});`); const o=JSON.parse(v); return { pass:o.sw==='🔑'&&o.cl==='☁️'&&o.ve==='🚗'&&o.df==='💻', actual:v }; }); // TC043 draft not persisted await run('TC_AddAsset_043','Save Draft shows toast, not persisted', async()=>{ const v=await ev(`__t.clear();__t.setVal('assetName','DraftX');var c0=__t.count();saveDraft();return JSON.stringify({toast:__t.last(),delta:__t.count()-c0});`); const o=JSON.parse(v); return { pass:/draft/i.test(o.toast)&&o.delta===0, actual:v }; }); // TC041 persistence after reload await run('TC_AddAsset_041','Created asset persists after reload', async()=>{ const id = await ev(`__t.clear();${fillValid}__t.setVal('assetName','PersistMe');submitAsset();closeModal('successModal');return AMS.assets[AMS.assets.length-1].id;`); await navigate(); // reload page (reads from localStorage) const found = await ev(`return JSON.stringify({found:!!AMS.assets.find(a=>a.id===${JSON.stringify(id)})});`); return { pass: JSON.parse(found).found, actual:`reloaded; id ${id} present=${JSON.parse(found).found}` }; }); // RBAC TC002: Employee blocked await run('TC_AddAsset_002','Employee role blocked (Access Denied)', async()=>{ await ev(`var u=AMS.users.find(x=>x.role==='Employee')||{id:'emp',name:'Emp',role:'Employee',avatar:'E'};AMS.currentUser=u;AMS.save();return 1;`); await navigate(); const denied = await ev(`return /Access Denied/.test(document.body.innerText);`); // restore admin await ev(`AMS.currentUser=AMS.users.find(x=>x.role==='Asset Manager')||AMS.users[0];AMS.save();return 1;`); await navigate(); return { pass: denied, actual: 'Access Denied shown = '+denied }; }); await navigate(); // fresh state for remaining tests // TC016 purchase date now enforced (DEF-04 fixed) await run('TC_AddAsset_016','Purchase Date required is enforced', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('purchaseDate','');var c0=__t.count();submitAsset();return JSON.stringify({delta:__t.count()-c0,toast:__t.last(),focus:__t.focused()});`); const o=JSON.parse(v); return { pass:o.delta===0&&/date/i.test(o.toast)&&o.focus==='purchaseDate', actual:'FIXED -> '+JSON.stringify(o) }; }); // TC017 department now enforced (DEF-05 fixed) await run('TC_AddAsset_017','Department required is enforced', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('assetDept','');var c0=__t.count();submitAsset();return JSON.stringify({delta:__t.count()-c0,toast:__t.last(),focus:__t.focused()});`); const o=JSON.parse(v); return { pass:o.delta===0&&/department/i.test(o.toast)&&o.focus==='assetDept', actual:'FIXED -> '+JSON.stringify(o) }; }); // TC018 dep rate now enforced (DEF-06 fixed) await run('TC_AddAsset_018','Depreciation Rate required/validated', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('depRate','');var c0=__t.count();submitAsset();return JSON.stringify({delta:__t.count()-c0,toast:__t.last(),focus:__t.focused()});`); const o=JSON.parse(v); return { pass:o.delta===0&&/depreciation rate/i.test(o.toast)&&o.focus==='depRate', actual:'FIXED -> '+JSON.stringify(o) }; }); // TC031 project (digital) await run('TC_AddAsset_031','Project selection persists (Digital)', async()=>{ const v=await ev(`__t.clear();__t.setVal('assetType','digital');handleTypeChange();__t.setVal('assetName','ProjAsset');__t.setVal('assetCategory','Software Licenses');__t.setVal('purchaseCost','1000');__t.setVal('assetVendor','Dell India Pvt. Ltd.');__t.setVal('purchaseDate','2025-05-28');__t.setVal('depRate','20');__t.setVal('assetDept','IT');__t.setVal('seatsTotal','5');__t.setVal('seatsAlloc','1');var opt=[...document.getElementById('assetProject').options].find(o=>o.value);__t.setVal('assetProject',opt.value);submitAsset();var s=AMS.assets[AMS.assets.length-1];closeModal('successModal');return JSON.stringify({project:s.project,picked:opt.value,assignee:s.assignee});`); const o=JSON.parse(v); return { pass:o.project===o.picked&&o.assignee==='IT Team', actual:v }; }); // TC035 custom fields appear (Laptops has defs) await run('TC_AddAsset_035','Category-specific custom fields render', async()=>{ const v=await ev(`__t.setVal('assetType','physical');handleTypeChange();__t.setVal('assetCategory','Laptops');renderCustomFields();var vis=__t.vis('customFieldsSection');var count=document.getElementById('customFieldsGrid').children.length;__t.setVal('assetCategory','Storage');renderCustomFields();var visNone=__t.vis('customFieldsSection');return JSON.stringify({vis:vis,count:count,hiddenForNoDefs:!visNone});`); const o=JSON.parse(v); return { pass:o.vis===true && o.count>0 && o.hiddenForNoDefs, actual:v }; }); // TC037 appears in All Assets data set (count increased & findable) await run('TC_AddAsset_037','Created asset present in assets dataset for list', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('assetName','ListVisible');var c0=AMS.assets.length;submitAsset();closeModal('successModal');var present=AMS.assets.some(a=>a.name==='ListVisible');return JSON.stringify({delta:AMS.assets.length-c0,present:present});`); const o=JSON.parse(v); return { pass:o.delta===1&&o.present, actual:v }; }); // Security: XSS payload stored; check how it's rendered on assets list await run('TC_AddAsset_SEC','XSS payload in name stored; rendered via innerHTML (review)', async()=>{ const v=await ev(`__t.clear();${fillValid}__t.setVal('assetName','');submitAsset();closeModal('successModal');var s=AMS.assets[AMS.assets.length-1];return JSON.stringify({stored:s.name});`); const o=JSON.parse(v); // load assets page and check whether the payload executes const loaded = waitEvent('Page.loadEventFired'); await send('Page.navigate', { url: 'file://'+process.cwd()+'/assets.html' }); await loaded; await new Promise(r=>setTimeout(r,400)); // Reproduce the exact render path: assets list builds rows with `${a.name}` into innerHTML await ev(`window.__xss=0;var nm=AMS.assets.find(a=>/onerror/.test(a.name)).name;var d=document.createElement('div');d.innerHTML='
'+nm+'
';document.body.appendChild(d);return 1;`); await new Promise(r=>setTimeout(r,400)); // Reproduce the ACTUAL render path used by renderTable (escapeHtml applied) const escFired = JSON.parse(await ev(`window.__xss=0;var nm=AMS.assets.find(a=>/onerror/.test(a.name)).name;var d=document.createElement('div');d.innerHTML='
'+escapeHtml(nm)+'
';document.body.appendChild(d);return JSON.stringify({fired:window.__xss===1});`)); return { pass: escFired.fired===false, actual: `payload stored='${o.stored}'. After fix, escapeHtml() output does NOT execute onerror -> ${escFired.fired?'STILL VULNERABLE':'SAFE (escaped)'}` }; }); await navigate(); fs.writeFileSync('/tmp/e2e_results.json', JSON.stringify(results, null, 2)); const pass = results.filter(r=>r.status==='Pass').length; console.log(`\n=== E2E Add New Asset: ${pass}/${results.length} passed ===\n`); for (const r of results) console.log(`${r.status==='Pass'?'✅':'❌'} ${r.tcid} ${r.name}\n ${r.actual}`); process.exit(0); } main().catch(e=>{ console.error('FATAL', e); process.exit(1); });