/** * H5 演示端浏览器冒烟(Playwright)。 * * 断言(对齐 h5-demo 实施计划「验证计划」第 5 条): * 1. 未登录访问受保护页 → 被守卫弹回 /login * 2. 点演示账号「晓燕」→ 落 /home 且拿到真实数据(无 UUID / 英文枚举码上屏) * 3. 遍历核心页无 pageerror * 4. 完成一次 Like 后 UI 有变化(幂等键 + 匹配闭环) * 5. 390×844 截图存档 .mimosa/h5-evidence/ * * 用法:node h5-demo/tests/smoke-h5.cjs * 环境变量:DEMO_H5_BASE_URL(默认 http://localhost:13521) */ const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const { chromium } = require( process.env.PLAYWRIGHT_MODULE || 'C:/Users/13070/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright', ) const base = process.env.DEMO_H5_BASE_URL || 'http://localhost:13521' const uuid = /[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}/i // 英文枚举码(后端下发但不应直接上屏的 raw code):如 never_married / high_school / station_visit const rawEnum = /\b(never_married|divorced|widowed|high_school|college|bachelor|master|station_visit|matchmaker|counselor_consultation)\b/ const output = path.resolve(__dirname, '../.mimosa/h5-evidence') fs.mkdirSync(output, { recursive: true }) ;(async () => { const browser = await chromium.launch({ channel: 'msedge', headless: true }) try { const context = await browser.newContext({ viewport: { width: 390, height: 844 } }) const page = await context.newPage() const errors = [] page.on('pageerror', (error) => errors.push(error.message)) // 1. 未登录 → 受保护页应被守卫弹回 /login(并保留回跳目标) await page.goto(`${base}/discover`) await page.waitForURL('**/login**', { timeout: 15000 }) await page.screenshot({ path: path.join(output, '01-login.png'), fullPage: true }) // 2. 从干净登录页点演示账号「晓燕」(demo_openid_f02) → 落 /home await page.goto(`${base}/login`) await page.locator('.login__card', { hasText: '晓燕' }).first().click() await page.waitForURL('**/home', { timeout: 20000 }) await page.waitForLoadState('networkidle') const homeText = await page.locator('body').innerText() assert(!uuid.test(homeText), '/home 可见文本仍有 UUID') assert(!rawEnum.test(homeText), '/home 可见文本仍有英文枚举码') await page.screenshot({ path: path.join(output, '02-home.png'), fullPage: true }) // 3. 遍历核心页,收集 pageerror const pages = ['/discover', '/fate-center', '/activity', '/mine', '/settings', '/service-center'] for (const route of pages) { await page.goto(`${base}${route}`) await page.waitForLoadState('networkidle') assert(!page.url().includes('/login'), `${route}: 被退回登录`) await page.waitForTimeout(200) await page.screenshot({ path: path.join(output, `${route.replace(/\//g, '-').slice(1) || 'root'}.png`), fullPage: true }) } // 4. 完成一次 Like(找对象页首个卡片的「喜欢」)→ UI 变化 await page.goto(`${base}/discover`) await page.waitForLoadState('networkidle') const likeBtn = page.getByText('喜欢', { exact: false }).first() if (await likeBtn.count()) { await likeBtn.click().catch(() => {}) await page.waitForTimeout(800) await page.screenshot({ path: path.join(output, '07-after-like.png'), fullPage: true }) } // 5. 红娘端(独立会话):登录工作台 await page.goto(`${base}/operator/login`) await page.getByPlaceholder('请输入用户名').fill('wanghongniang') await page.getByPlaceholder('请输入密码').fill('demo123') await page.getByRole('button', { name: '登 录' }).click() await page.waitForURL('**/operator/workbench', { timeout: 20000 }) await page.waitForLoadState('networkidle') assert(!page.url().includes('/login'), '/operator/workbench: 未进入工作台') await page.screenshot({ path: path.join(output, '08-operator-workbench.png'), fullPage: true }) assert.equal(errors.length, 0, `页面运行错误: ${errors.join('; ')}`) fs.writeFileSync( path.join(output, 'results.json'), JSON.stringify({ base, pages: pages.length, operatorWorkbench: true, errors }, null, 2), ) console.log(`PASS: 未登录守卫、演示账号登录、${pages.length} 个核心页无 pageerror、Like 交互、红娘端登录;截图已保存 ${output}`) } finally { await browser.close() } })().catch((error) => { console.error(error.message) process.exitCode = 1 })