#!/usr/bin/env node /** * UI 视觉验收截图工具(H5 端渲染,uni-app 同源码同样式) * 用法: node scripts/ui-screenshots.mjs [--base ] [--out ] * 默认 base: http://localhost:5175 out: ../docs/reports/screenshots * * 对每个核心页面截图并检查关键 UI 元素,输出 UI 验收结果(JSON + 摘要)。 */ import { chromium } from 'playwright' import { mkdirSync, writeFileSync, existsSync } from 'node:fs' import { join, resolve } from 'node:path' const args = process.argv.slice(2) const base = args.includes('--base') ? args[args.indexOf('--base') + 1] : 'http://localhost:5175' const outDir = resolve(args.includes('--out') ? args[args.indexOf('--out') + 1] : '../docs/reports/screenshots') // 页面定义: 路径 + 关键 UI 元素检查(body 关键词,语言兼容)+ class 检查 const pages = [ { name: '01-almanac-search', path: '/pages/almanac-search/index', keywords: [/今日黄历|Today/, /宜|Suitable/, /忌|Unsuitable/, /搜索天数|Search Days/, /搜索模板|Template/], classes: ['.today-almanac', '.template-panel', '.search-condition-panel', '.bottom-navigation'], }, { name: '02-ziwei', path: '/pages/ziwei/index', keywords: [/紫微斗数|Zi Wei Dou Shu/, /出生信息|Birth Info/, /排盘|Chart/], classes: ['.bottom-navigation'], }, { name: '03-fortune', path: '/pages/fortune/index', keywords: [/运势|Fortune/], classes: ['.bottom-navigation'], }, ] mkdirSync(outDir, { recursive: true }) const browser = await chromium.launch() const report = [] let passAll = true for (const p of pages) { const page = await browser.newPage({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2 }) const shotPath = join(outDir, `${p.name}.png`) const checks = [] try { await page.goto(`${base}/#${p.path}`, { waitUntil: 'networkidle', timeout: 30000 }) await page.waitForTimeout(1500) // 关键词检查(body 内容,语言兼容) const bodyText = (await page.textContent('body')) || '' for (const kw of p.keywords) { const pass = kw.test(bodyText) checks.push({ label: `关键词 ${kw}`, pass }) if (!pass) passAll = false } // class 检查 for (const sel of p.classes || []) { const cnt = await page.locator(sel).count() const pass = cnt > 0 checks.push({ label: `class ${sel}`, pass }) if (!pass) passAll = false } await page.screenshot({ path: shotPath, fullPage: false }) report.push({ page: p.name, path: p.path, checks, screenshot: shotPath }) console.log(`✅ ${p.name}: 截图成功,${checks.filter((c) => c.pass).length}/${checks.length} 元素通过`) } catch (e) { passAll = false report.push({ page: p.name, path: p.path, checks, error: String(e).slice(0, 120) }) console.log(`❌ ${p.name}: 加载失败 ${String(e).slice(0, 100)}`) } finally { await page.close() } } await browser.close() // 输出 JSON 报告 const jsonPath = join(outDir, 'ui-verification.json') writeFileSync(jsonPath, JSON.stringify({ base, generatedAt: new Date().toISOString(), passAll, report }, null, 2)) console.log(`\n${passAll ? '✅ 全部页面 UI 元素通过' : '❌ 存在未通过元素'}`) console.log(`截图目录: ${outDir}`) process.exit(passAll ? 0 : 1)