import assert from 'node:assert/strict' import fs from 'node:fs/promises' import path from 'node:path' import crypto from 'node:crypto' import { tmpdir } from 'node:os' import { pathToFileURL } from 'node:url' const runtime=path.resolve('.tmp/rea-css-runtime/dist') const load=relative=>import(pathToFileURL(path.join(runtime,relative))) const {captureElectronMediaAssets}=await load('browser/CdpElectronMediaAssets.js') const {electronVisualProbeInputSchema,electronMediaAssetsSchema}=await load('domain/javascript/ElectronVisualProbeSchema.js') const {inspectElectronPageInputSchema,electronPageInspectionSchema}=await load('domain/javascript/electronObservation.js') const {inspectCdpElectronPage}=await load('browser/CdpElectronInspection.js') const {createElectronEvidence}=await load('application/javascript/ElectronEvidence.js') const temporary=await fs.mkdtemp(path.join(tmpdir(),'rea-img-resource-')) try { const html=path.join(temporary,'index.html');await fs.writeFile(html,'') const page=pathToFileURL(html).href,resourceUrl=pathToFileURL(path.join(temporary,'actual.png')).href const main={frame_id:'main',file_path:await fs.realpath(html)},frame={id:'main',url:page} const png='iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jKXcAAAAASUVORK5CYII=' const bytes=Buffer.from(png,'base64'),digest=crypto.createHash('sha256').update(bytes).digest('hex') const makeSnapshot=()=>({strings:[page,'main','HTML','IMG','class','selected','src','fallback.png',resourceUrl,'contenteditable','true','14px'],documents:[{ documentURL:0,frameId:1,scrollOffsetX:0,scrollOffsetY:0, nodes:{nodeName:[2,3],parentIndex:[-1,0],backendNodeId:[10,20],attributes:[[],[4,5,6,7]],currentSourceURL:{index:[1],value:[8]}}, layout:{nodeIndex:[0,1],bounds:[[0,0,100,80],[10,10,40,50]],styles:[]} }]}) let mode='ok',snapshot=makeSnapshot(),calls=[],listener,selectedResourceUrl=resourceUrl const send=async(method,params={})=>{ calls.push({method,params}) assert.ok(!['Runtime.evaluate','Runtime.callFunctionOn','Runtime.getProperties','Network.getCookies','Network.loadNetworkResource','Page.navigate'].includes(method)) if(method==='Page.getFrameTree')return {frameTree:{frame:mode==='navigation-after-content'&&calls.some(c=>c.method==='Page.getResourceContent')?{...frame,id:'changed'}:frame}} if(method==='DOM.getDocument')return {root:{nodeId:1}} if(method==='DOM.querySelectorAll')return {nodeIds:mode==='absent'?[]:mode==='ambiguous'?[2,3]:[2]} if(method==='DOM.describeNode')return {node:{nodeName:mode==='not-img'?'DIV':'IMG',backendNodeId:mode==='foreign-backend'?999:20}} if(method==='Page.getLayoutMetrics')return {cssLayoutViewport:{clientWidth:100,clientHeight:80,pageX:0,pageY:0}} if(method==='DOM.getNodeForLocation')return {backendNodeId:mode==='covered'?999:mode==='partly-covered'&¶ms.y>20?999:20} if(method==='Page.getResourceTree')return {frameTree:{frame,resources:mode==='not-loaded'?[]:[{url:selectedResourceUrl,type:'Image',mimeType:mode==='svg'?'image/svg+xml':'image/png',...(mode==='declared-oversize'?{contentSize:2097153}:{})}],childFrames:[{frame:{id:'foreign',url:'https://foreign.invalid'},resources:[{url:resourceUrl,type:'Image',mimeType:'image/png'}]}]}} if(method==='Page.getResourceContent'){ if(mode==='content-unavailable')throw Error('private-url-or-cookie-must-not-leak') return {base64Encoded:mode!=='not-binary',content:mode==='invalid-base64'?'not-base64':mode==='signature'?'YWJj':mode==='oversize'?Buffer.alloc(2097153).toString('base64'):png} } if(method==='DOMSnapshot.captureSnapshot'){const copy=structuredClone(snapshot);for(const doc of copy.documents)doc.layout.styles=doc.layout.nodeIndex.map(()=>params.computedStyles.map(()=>11));return copy} if(method==='Page.captureScreenshot')return {data:png} if(method==='Target.getTargets')return {targetInfos:[]} return {} } assert.deepEqual(electronVisualProbeInputSchema.parse({}).media_selectors,[]) assert.equal(electronVisualProbeInputSchema.safeParse({media_selectors:Array.from({length:5},(_,i)=>'#img'+i)}).success,false) assert.equal(electronVisualProbeInputSchema.safeParse({media_selectors:['#same','#same']}).success,false) assert.equal(await captureElectronMediaAssets(snapshot,[main],[],send),undefined) assert.equal(calls.length,0,'default off must perform no resource reads') const capture=async()=>{calls=[];return electronMediaAssetsSchema.parse(await captureElectronMediaAssets(snapshot,[main],['.selected'],send))} let result=await capture() assert.equal(result.items[0].status,'exported') assert.equal(result.items[0].source.selection,'current_source_url') assert.equal(result.items[0].artifact.sha256,digest) assert.equal(result.items[0].artifact.data_base64,png) assert.equal(result.total_bytes,bytes.length) assert.equal(calls.find(c=>c.method==='Page.getResourceContent').params.url,resourceUrl,'currentSrc wins over the fallback src attribute') assert.equal(result.items[0].backend_node_id,20) mode='covered';result=await capture();assert.equal(result.items[0].status,'exported');assert.equal(result.items[0].visibility.status,'covered');assert.equal(result.items[0].selected_node.current_source_url,resourceUrl); mode='partly-covered';assert.equal((await capture()).items[0].status,'exported','partial occlusion may still export actual loaded full image bytes') for(const [test,reason] of [['absent','selector_absent'],['ambiguous','selector_ambiguous'],['not-img','selected_node_not_img'],['foreign-backend','snapshot_identity_unavailable'],['not-loaded','selected_source_not_loaded_in_main_frame'],['svg','unsupported_image_type'],['declared-oversize','asset_byte_budget_exceeded'],['oversize','asset_byte_budget_exceeded'],['signature','image_signature_mismatch'],['invalid-base64','invalid_base64'],['not-binary','binary_content_unavailable'],['content-unavailable','protocol_resource_unavailable'],['navigation-after-content','frame_changed']]){ mode=test;result=await capture();assert.equal(result.items[0].status,'unknown',test);assert.equal(result.items[0].reason,reason,test);assert.equal(result.total_bytes,0,test) assert.equal(result.items[0].artifact,undefined,test) if(['not-loaded','svg','declared-oversize'].includes(test))assert.equal(calls.some(c=>c.method==='Page.getResourceContent'),false,test+' must reject before the read') assert.equal(JSON.stringify(result).includes('private-url-or-cookie'),false) } mode='ok';snapshot=makeSnapshot();snapshot.documents[0].nodes.attributes[0]=[9,10] assert.equal((await capture()).items[0].reason,'editable_subtree_excluded') snapshot=makeSnapshot();snapshot.documents[0].layout.bounds[1]=[200,200,40,50] assert.equal((await capture()).items[0].visibility.reason,'outside_visible_viewport') snapshot=makeSnapshot();snapshot.documents[0].layout.nodeIndex=[0] assert.equal((await capture()).items[0].visibility.reason,'no_visible_layout') snapshot=makeSnapshot();delete snapshot.documents[0].nodes.currentSourceURL;snapshot.strings[7]=resourceUrl assert.equal((await capture()).items[0].source.selection,'src') snapshot=makeSnapshot();selectedResourceUrl='https://account:private-password@assets.invalid/img.png?token=private-query' snapshot.strings[8]=selectedResourceUrl+'#fragment' result=await capture() assert.equal(result.items[0].status,'exported') assert.equal(result.items[0].source.url,'https://assets.invalid/img.png') assert.equal(result.items[0].selected_node.current_source_url,'https://assets.invalid/img.png') assert.equal(result.items[0].source.url_sha256,crypto.createHash('sha256').update(snapshot.strings[8]).digest('hex')) assert.equal(JSON.stringify(result).includes('private-'),false,'source metadata omits URL credentials and queries') selectedResourceUrl=resourceUrl snapshot=makeSnapshot() // Exercise the public inspection normalization and Evidence path, not only the helper. const input=inspectElectronPageInputSchema.parse({cdp_endpoint:'http://127.0.0.1:9223',target_id:'fixture',observation_ms:0,include_visuals:true,visual_probe:{media_selectors:['.selected']}}) const context={connection:{send,onEvent(fn){listener=fn;return()=>{listener=undefined}}},sessionId:'selected-session',input,target:{id:'fixture',type:'page',title:'Own fixture',attached:false},discovery:{version:{product:'Fixture/1',protocol_version:'1.3',revision:'fixture',user_agent:'fixture',js_version:'fixture'}}} const inspection=electronPageInspectionSchema.parse(await inspectCdpElectronPage(context)) assert.equal(inspection.visuals.media_assets.items[0].artifact.sha256,digest) assert.deepEqual(inspection.visuals.layout.documents[0].nodes[1].attributes,{class:'selected'}) const evidence=createElectronEvidence('inspect_electron_page',input,inspection,{id:'rea-cdp-electron',name:'Local selected resource fixture',version:'1+visual-local.3'}) assert.deepEqual(evidence.parameters.visual_probe.media_selectors,['.selected']) assert.equal(evidence.normalized_result.visuals.media_assets.items[0].artifact.data_base64,png) assert.match(evidence.evidence_id,/^ev_[a-f0-9]{64}$/) const optedOut=electronPageInspectionSchema.parse(await inspectCdpElectronPage({...context,input:{...input,visual_probe:undefined}})) assert.equal(optedOut.visuals.media_assets,undefined) assert.equal(JSON.stringify(optedOut.visuals).includes('fallback.png'),false) assert.equal(listener,undefined) console.log('REA selected IMG resource regression passed: opt-in/schema/Evidence, snapshot currentSrc/backend attribution, covered bytes with separate visibility, scope, binary budgets/signatures, URL credential redaction, unknowns and default URL omission') }finally{await fs.rm(temporary,{recursive:true,force:true})}