// Isolated server/browser; detection is an in-memory fixture, never a paid call. import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import {spawn,execSync} from 'node:child_process'; import {fileURLToPath,pathToFileURL} from 'node:url'; const {chromium}=await import(pathToFileURL(execSync('npm root -g',{encoding:'utf8'}).trim()+'/playwright/index.mjs').href); const h5=fileURLToPath(new URL('../',import.meta.url)); const root=fs.mkdtempSync(path.join(os.tmpdir(),'juzi-funnel-http-')); const child=spawn(process.execPath,['server.js'],{cwd:h5,env:{...process.env,PORT:'18138',HOST:'127.0.0.1',JUZI_DATA_ROOT:root, SKIN_API_KEY:'fixture',SKIN_API_SECRET:'fixture',PUBLIC_BASE_URL:'',WECHAT_APP_ID:'',WECHAT_APP_SECRET:''},stdio:['ignore','pipe','pipe']}); let browser; try { await new Promise((resolve,reject)=>{const t=setTimeout(()=>reject(Error('startup timeout')),10000);child.stdout.on('data',b=>{if(b.toString().includes('已启动')){clearTimeout(t);resolve();}});child.once('exit',()=>{clearTimeout(t);reject(Error('server exit'));});}); const base='http://127.0.0.1:18138/'; assert.equal((await fetch(base+'api/capture-funnel',{method:'POST',body:'{}'})).status,403); browser=await chromium.launch({headless:true});const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message)); const source=fs.readFileSync(path.join(h5,'js/app.js'),'utf8'); await page.route('**/js/app.js*',r=>r.fulfill({contentType:'text/javascript',body:source.replace(/\}\)\(\);\s*$/,` window.__journeyTest = { run: function(fail) { beginCaptureJourney(); ['front','right','left'].forEach(trackCapture); photos=window.MOCK_FIXTURE.photos.slice();showScreen('s-cam'); requestAnalyze=function(ph,seq,p,done){done(null,window.MOCK_FIXTURE);}; if(fail)renderReport=function(){throw Error('fixture render failure');}; submitPhotos(camSeq);stopScanCycle(); }, history: function(){showScreen('s-report');} }; })();`)})); await page.goto(base); await page.evaluate(async()=>{await fetch('api/auth/browser',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({consent:true})});}); await page.reload();await page.waitForFunction(()=>AccountClient.user()); await page.addScriptTag({url:base+'assets/mock/report-fixture.js'}); await page.evaluate(()=>window.__journeyTest.run(false)); await page.waitForSelector('#s-report.active'); await page.waitForFunction(async()=>{const d=await(await fetch('api/admin/stats')).json();return d.capture_funnel.total.counts.report===1;}); await page.evaluate(()=>window.__journeyTest.history()); await page.evaluate(()=>window.__journeyTest.run(true)); await page.waitForFunction(async()=>{const d=await(await fetch('api/admin/stats')).json();return d.capture_funnel.total.counts.failed===1;}); const stats=await(await fetch(base+'api/admin/stats')).json(); assert.equal(stats.capture_funnel.total.counts.started,2);assert.equal(stats.capture_funnel.total.counts.report,1); assert.equal(stats.capture_funnel.total.response_without_report,1);assert.equal(stats.capture_funnel.total.rate,50); assert.equal((await fetch(base+'data/capture-funnel.jsonl')).status,403); assert.equal(await page.evaluate(async()=> (await fetch('api/capture-funnel',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'})).status),400); await page.goto(base+'admin.html');await page.locator('[data-tab="stats"]').click(); await page.waitForFunction(()=>document.querySelector('#capture-funnel').textContent.includes('50%')); assert((await page.locator('#capture-funnel').textContent()).includes('成功结果但未进入 1')); assert.deepEqual(errors,[]);console.log('PASS: session-gated ingestion, actual report activation vs render failure, history excluded, admin funnel render, private storage; no paid API'); } finally {if(browser)await browser.close();child.kill();}