// Deterministic API payloads; no network writes or upstream detection. import assert from 'node:assert/strict';import fs from 'node:fs'; import{execSync}from'node:child_process';import{pathToFileURL}from'node:url'; const{chromium}=await import(pathToFileURL(execSync('npm root -g',{encoding:'utf8'}).trim()+'/playwright/index.mjs').href); const b=await chromium.launch(); try{ const p=await b.newPage({viewport:{width:390,height:1100},isMobile:true,hasTouch:true});const errors=[];p.on('pageerror',e=>errors.push(e.message)); await p.route('**/api/**',r=>{assert.equal(r.request().method(),'GET');return r.fulfill({json:{ok:true,user:{kind:'browser'}}});}); const source=fs.readFileSync(new URL('../js/app.js',import.meta.url),'utf8'); await p.route('**/js/app.js*',r=>r.fulfill({contentType:'text/javascript',body:source.replace(/\}\)\(\);\s*$/,` window.__wrinkleTest=function(x){var fx=MOCK_FIXTURE,raw=JSON.parse(JSON.stringify(fx.result)); if(x.noFrontMap)delete raw.face_maps.texture_enhanced_lines; var vm=SkinScore.buildViewModel(raw,fx.side,{}); renderReport(vm,fx.photos[0],null,{result:raw,side:fx.side,wrinkle_views:x.views,face_rectangle:fx.face_rectangle},[fx.photos[0],x.left,x.right]); showScreen('s-report');setDetailTab(document.querySelector('.card-detail'),'1',true); };})();`)})); await p.goto((process.env.H5_BASE_URL||'http://127.0.0.1:8080/')+'?mock=1&screen=report'); const svg=body=>'data:image/svg+xml,'+encodeURIComponent(''+body+''); const left=svg(''),right=svg(''); const map=color=>svg(''); const entry=color=>({status:'ready',result:{face_maps:{texture_enhanced_lines:map(color)}}}); for(const width of[320,375,414,480]){ await p.setViewportSize({width,height:1100}); await p.evaluate(x=>window.__wrinkleTest(x),{left,right,views:{left:entry('red'),right:entry('blue')}}); await p.waitForFunction(()=>['wrinkle-left','wrinkle-right'].every(id=>{const i=document.getElementById(id);return i.naturalWidth>0&&!i.closest('figure').hidden&&i.dataset.hasAnnotation==='true';})); assert(await p.locator('#wrinkle-layer-note').evaluate(e=>e.hidden)); assert(await p.locator('#ditem-img2').evaluate(e=>e.dataset.hasAnnotation==='true')); const layout=await p.evaluate(()=>{ const styles=e=>{const s=getComputedStyle(e);return{width:s.width,height:s.height,marginBottom:s.marginBottom,transform:s.transform,objectFit:s.objectFit,objectPosition:s.objectPosition};}; return{contour:styles(document.querySelector('#ditem-img1').parentElement),wrinkle:styles(document.querySelector('#ditem-img2').parentElement),frontA:styles(document.querySelector('#ditem-img1')),frontB:styles(document.querySelector('#ditem-img2')),sideA:styles(document.querySelector('#contour-left')),sideB:styles(document.querySelector('#wrinkle-left')),offsetA:document.querySelector('#ditem-img1').dataset.contentOffset,offsetB:document.querySelector('#ditem-img2').dataset.contentOffset}; }); assert.deepEqual(layout.contour,layout.wrinkle,'same front frame and spacing'); assert.deepEqual(layout.frontA,layout.frontB,'same front image size'); assert.deepEqual(layout.sideA,layout.sideB,'same side image scaling and positioning'); assert.equal(layout.offsetA,layout.offsetB,'same face crop offset; overlay moves with photo'); const leftSrc=await p.locator('#wrinkle-left').getAttribute('src'),rightSrc=await p.locator('#wrinkle-right').getAttribute('src'); assert.notEqual(leftSrc,left);assert.notEqual(rightSrc,right);assert.notEqual(leftSrc,rightSrc); await p.getByRole('button',{name:'查看左脸皱纹检测大图'}).click();assert.notEqual(await p.locator('#zoom-img').getAttribute('src'),leftSrc);await p.locator('#p-zoom').click(); await p.getByRole('button',{name:'查看右脸皱纹检测大图'}).click();assert.notEqual(await p.locator('#zoom-img').getAttribute('src'),rightSrc);await p.locator('#p-zoom').click(); } await p.evaluate(x=>window.__wrinkleTest(x),{left,right,views:{}}); await p.waitForFunction(()=>!document.querySelector('#wrinkle-left').closest('figure').hidden&&!document.querySelector('#wrinkle-right').closest('figure').hidden); assert.equal(await p.locator('#wrinkle-left').getAttribute('src'),left,'no front-face map projected onto unannotated side'); assert(await p.locator('#wrinkle-layer-note').isVisible()); const marks={left_cheek_wrinkle_outline:[{x:10,y:10},{x:200,y:10},{x:200,y:80}]}; await p.evaluate(x=>window.__wrinkleTest(x),{left,right:null,noFrontMap:true,views:{left:{status:'ready',result:{wrinkle_mark:marks}}}}); await p.waitForFunction(()=>document.querySelector('#wrinkle-left').dataset.hasAnnotation==='true'&&document.querySelector('#ditem-img2').dataset.hasAnnotation==='true'); assert(await p.locator('#wrinkle-right').evaluate(e=>e.closest('figure').hidden&&!e.hasAttribute('src'))); await p.evaluate(x=>window.__wrinkleTest(x),{left:null,right:null,views:{}}); assert(await p.locator('#wrinkle-sides').evaluate(e=>e.hidden));assert(await p.locator('#wrinkle-layer-note').evaluate(e=>e.hidden)); assert.deepEqual(errors,[]); console.log('PASS wrinkle three views: 4 widths, own overlays, annotated zoom, front overlay preserved, missing layer/photo, real-coordinate fallback, no false projection and stale clearing'); }finally{await b.close();}