301 lines
26 KiB
JavaScript
Raw Permalink 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.

// 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<b>&"\\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','<img src=x onerror=window.__xss=1>');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='<div class="asset-n">'+nm+'</div>';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='<div class="asset-n">'+escapeHtml(nm)+'</div>';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); });