/** Real public-source/local private-counterpart acceptance, not a unit fixture. */ import {chromium,expect} from '@playwright/test'; import {readFile,writeFile,mkdir} from 'node:fs/promises'; import {execFile} from 'node:child_process'; import {promisify} from 'node:util'; import path from 'node:path'; import {createHash} from 'node:crypto'; const run=promisify(execFile),root=process.cwd(); const output=path.join(root,'.local/repository-pilot','ui-acceptance-'+Date.now()); await mkdir(output); const frozen=JSON.parse(await readFile('.local/repository-pilot/calculator/frozen.json','utf8')); const source=frozen.repository.source+'/tree/'+frozen.repository.commit_sha; const contract=JSON.parse(await readFile('docs/contracts/calculator-repair-suite.json','utf8'))[0]; await writeFile(path.join(output,'contract.json'),JSON.stringify(contract)); const browser=await chromium.launch(); let page; try{ const context=await browser.newContext();page=await context.newPage(); page.setDefaultTimeout(15000); const errors=[];page.on('pageerror',e=>errors.push(e.message)); await page.goto('http://127.0.0.1:8120/'); await expect(page.getByText('YOUR PRIVATE GUEST SESSION')).toBeVisible(); const auth=await context.request.post('http://127.0.0.1:8120/api/v1/auth/guest',{headers:{Origin:'http://127.0.0.1:8120'}}); const owner=(await auth.json()).owner_id; const csrf=(await context.cookies()).find(c=>c.name==='chameleon_csrf').value; const response=await context.request.post('http://127.0.0.1:8120/api/v1/adaptations',{headers:{Origin:'http://127.0.0.1:8120','X-CSRF-Token':csrf,'Idempotency-Key':crypto.randomUUID()},data:{source,source_type:'repository',build_counterpart:true}}); if(response.status()!==202)throw Error('Intake rejected: '+await response.text()); let job=await response.json(); const deadline=Date.now()+60000; while(job.counterpart?.status!=='READY'&&Date.now()setTimeout(resolve,500)); job=await (await context.request.get(`http://127.0.0.1:8120/api/v1/adaptations/${job.id}`)).json(); } if(job.counterpart?.status!=='READY')throw Error('Capture timeout'); const twin=job.counterpart.twin_id; const base=['scripts/verify_saved_repository.py','--owner',owner,'--twin',twin,'--contract',path.join(output,'contract.json')]; const original=await run('.venv/Scripts/python.exe',[...base,'--output',path.join(output,'original')],{cwd:root,timeout:90000,windowsHide:true}); await run('node',['scripts/build_calculator_overlay.mjs',path.join(output,'original/package.json'),path.join(output,'overlay.json')],{cwd:root,timeout:10000,windowsHide:true}); const repaired=await run('.venv/Scripts/python.exe',[...base,'--output',path.join(output,'repaired'),'--overlay',path.join(output,'overlay.json')],{cwd:root,timeout:90000,windowsHide:true}); await page.reload(); // API-created jobs do not update the browser's remembered conversation. // Reopen through the actual saved-conversation control, as a returning user does. await page.locator('.saved-menu summary').click(); await page.locator('.saved-conversations button').filter({hasText:source.replace(/^https?:\/\//,'')}).click(); const saved=await (await context.request.get('http://127.0.0.1:8120/api/v1/twins')).json(); const artifact=saved.find(item=>item.id===twin); if(!artifact)throw Error('Saved counterpart missing from owner list'); await page.getByRole('button',{name:artifact.source+' / open source package',exact:true}).click(); const panel=page.getByRole('region',{name:'Recorded behavior checks'}); await expect(panel.getByText('Contract passed',{exact:true})).toBeVisible(); await expect(panel.getByText('Contract failed',{exact:true})).toBeVisible(); await expect(panel.getByText('Repaired copy',{exact:true})).toBeVisible(); await expect(panel.getByText('Original source',{exact:true})).toBeVisible(); const downloadEvent=page.waitForEvent('download'); await panel.getByRole('link',{name:'Download tested counterpart ZIP'}).click(); const download=await downloadEvent; await download.saveAs(path.join(output,'downloaded-counterpart.zip')); const downloadHash=createHash('sha256').update(await readFile(path.join(output,'downloaded-counterpart.zip'))).digest('hex'); expect(downloadHash).toBe(JSON.parse(repaired.stdout).counterpart.sha256); await panel.getByRole('button',{name:'Open repaired counterpart',exact:true}).click(); const app=page.frameLocator('iframe[title="Your isolated repaired counterpart"]'); for(const key of ['2','+','3','='])await app.getByText(key,{exact:true}).click(); await expect(app.locator('#display')).toHaveText('5'); const isolated=await app.locator('body').evaluate(async()=>{ let parentDenied=false,fetchDenied=false; try{void parent.document.body}catch{parentDenied=true} try{await fetch('/api/v1/session')}catch{fetchDenied=true} return {parentDenied,fetchDenied}; }); expect(isolated).toEqual({parentDenied:true,fetchDenied:true}); await page.screenshot({path:path.join(output,'desktop.png'),fullPage:true}); await page.setViewportSize({width:390,height:844}); await page.screenshot({path:path.join(output,'mobile.png'),fullPage:true}); expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true); expect(errors).toEqual([]); const report={status:'PASS',source,job:job.id,twin,original:JSON.parse(original.stdout),repaired:JSON.parse(repaired.stdout),download_sha256:downloadHash,interactive:{addition:'PASS',...isolated},source_writes:false,public_deployment:false}; await writeFile(path.join(output,'report.json'),JSON.stringify(report,null,2)); console.log(JSON.stringify({status:'PASS',output})); }catch(error){ await writeFile(path.join(output,'failure.json'),JSON.stringify({status:'FAIL',message:String(error),public_deployment:false},null,2)); if(page)await page.screenshot({path:path.join(output,'failure.png'),fullPage:true}).catch(()=>{}); throw error; }finally{await browser.close();}