// Solid-color canvas fixtures verify actual blended pixels, not only CSS flags. import assert from 'node:assert/strict';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 browser=await chromium.launch(); try{ const page=await browser.newPage({viewport:{width:375,height:726}});const errors=[];let mode='map'; page.on('pageerror',e=>errors.push(e.message)); await page.route('**/*',r=>{assert.equal(r.request().method(),'GET');return r.continue();}); await page.route('**/assets/mock/report-fixture.js',r=>r.fulfill({contentType:'text/javascript',body:` function solid(color){var c=document.createElement('canvas');c.width=100;c.height=100;var x=c.getContext('2d');x.fillStyle=color;x.fillRect(0,0,100,100);return c.toDataURL('image/png');} window.MOCK_FIXTURE={result:{score_info:{melanin_score:90,water_score:80,dark_circle_score:50,wrinkle_score:65},melasma:{value:1},freckle:{value:0},face_maps:${mode==='map'?"{brown_area:solid('#000')}" : '{}'}},side:{},photos:[solid('rgb(200,100,50)')]}; `})); const base=process.env.H5_BASE_URL||'http://127.0.0.1:8080/'; const pixel=async()=>page.locator('#viz-tone').evaluate(async i=>{await i.decode();const c=document.createElement('canvas');c.width=i.naturalWidth;c.height=i.naturalHeight;const x=c.getContext('2d');x.drawImage(i,0,0);return [...x.getImageData(c.width/2,c.height/2,1,1).data].slice(0,3);}); await page.goto(base+'?mock=1&screen=report'); await page.waitForFunction(()=>{const i=document.querySelector('#viz-tone');return !i.hidden&&i.naturalWidth&&i.dataset.overlayAlpha==='0.3'}); const actual=await pixel();[140,70,35].forEach((v,i)=>assert(Math.abs(actual[i]-v)<=4,actual+' != 30% overlay')); assert.equal(await page.locator('#viz-tone').evaluate(i=>getComputedStyle(i).opacity),'1','face image not globally faded'); assert.equal(await page.locator('#tone-detections').count(),0,'no detection status overlay'); assert.equal(await page.locator('#tone-map-note').count(),0,'no technical caption below color copy'); assert(!(await page.locator('#tx-tone').textContent()).includes('占肤色项20%')); mode='missing';await page.reload();await page.waitForFunction(()=>{const i=document.querySelector('#viz-tone');return !i.hidden&&i.naturalWidth}); const plain=await pixel();[200,100,50].forEach((v,i)=>assert(Math.abs(plain[i]-v)<=4)); assert.equal(await page.locator('#tone-map-note').count(),0,'missing map does not add technical caption'); assert.deepEqual(errors,[]);console.log('PASS tone overlay: actual 30% pixel blending, original photo opaque, true flags/no invented boxes, missing map photo fallback, new dark-circle copy, no writes'); }finally{await browser.close()}