// Public-site error recovery and real model loading. Analysis requests are intercepted; // no paid upstream call or report is created. A separate empty browser identity is used. import assert from 'node:assert/strict'; import fs from 'node:fs'; import { 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 base = process.env.H5_BASE_URL || 'http://127.0.0.1:8080/'; const samplePath = fileURLToPath(new URL('../../reference/facepp-skinanalyze/examples/images/C3_180.jpg', import.meta.url)); const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 375, height: 812 } }); page.setDefaultTimeout(60000); const errors = []; page.on('pageerror', e => errors.push(e.message)); await page.addInitScript(() => { navigator.mediaDevices.getUserMedia = () => Promise.reject(new DOMException('test denial', 'NotAllowedError')); const original = window.setTimeout; window.setTimeout = function (fn, delay, ...args) { // Accelerate only the actual analyze deadline for the timeout branch. return original(fn, window.__fastAnalyzeTimeout && delay === 150000 ? 150 : delay, ...args); }; }); try { await page.goto(base, { waitUntil: 'domcontentloaded' }); await page.addScriptTag({ url: base + 'vendor/faceapi/face-api.js' }); // Real models from production paths, actual landmark inference on the sample image. const detected = await page.evaluate(async data => { await Promise.all([ faceapi.nets.tinyFaceDetector.loadFromUri('vendor/faceapi/models'), faceapi.nets.faceLandmark68Net.loadFromUri('vendor/faceapi/models') ]); const img = await faceapi.fetchImage(data); const d = await faceapi.detectSingleFace(img, new faceapi.TinyFaceDetectorOptions({ inputSize: 416, scoreThreshold: 0.3 })).withFaceLandmarks(); return d ? { score: d.detection.score, landmarks: d.landmarks.positions.length } : null; }, 'data:image/jpeg;base64,' + fs.readFileSync(samplePath).toString('base64')); assert(detected && detected.landmarks === 68 && detected.score > 0.3); console.log('PASS: real production models load and detect sample face with 68 landmarks'); // Keep subsequent error-flow uploads independent of angle validation. await page.route('**/vendor/faceapi/face-api.js', route => route.abort()); await page.evaluate(() => { window.faceapi = undefined; }); await page.waitForFunction(() => !document.querySelector('#login-message').textContent.includes('检查')); await page.check('#login-consent'); await page.click('[data-action="guest-experience"]'); await page.waitForSelector('#s-splash.active'); await page.click('[data-action="open-privacy"]'); await page.click('#privacy-check'); await page.click('[data-action="agree-continue"]'); for (const selector of ['#pick-birth', '#pick-gender', '#pick-region']) { await page.click(selector); await page.click('.ms-mobile-select.ms-show .ms-ensure'); await page.waitForTimeout(400); } const cases = [ ['business', '未检测到人脸', route => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ ok: false, message: '未检测到人脸' }) })], ['network', '网络异常', route => route.abort('failed')], ['timeout', '检测服务开小差', async route => { await new Promise(resolve => setTimeout(resolve, 500)); await route.abort().catch(() => {}); }] ]; for (const [name, message, handler] of cases) { await page.route('**/api/skin-analyze', handler); await page.evaluate(fast => { window.__fastAnalyzeTimeout = fast; }, name === 'timeout'); await page.click('[data-action="submit-profile"]'); await page.click('[data-action="start-camera"]'); for (let i = 1; i <= 3; i++) { await page.waitForFunction(n => document.querySelector('#cam-step').textContent.includes(n + '/3'), i); await page.waitForTimeout(1600); await page.setInputFiles('#cam-file', samplePath); } await page.waitForSelector('#s-profile.active'); assert((await page.locator('#toast').textContent()).includes(message), name); assert.equal(await page.locator('#p-analyzing.show').count(), 0); assert(await page.locator('#f-birth').inputValue()); await page.unroute('**/api/skin-analyze', handler); console.log('PASS: ' + name + ' error → dismiss loading → retain profile → allow retry'); } const reports = await (await page.context().request.get(base + 'api/reports')).json(); assert.equal(reports.total, 0); assert.deepEqual(errors, []); console.log('PASS: no spurious reports and no JS errors'); } finally { await browser.close(); }