- 移除微信订阅推送功能(云函数/订阅管理页),回归纯客户端零后端架构 - 新增今日黄历默认展示卡片 TodayAlmanacCard(9 语言翻译) - i18n 修复:启用 globalInjection 修复 $t 未注入,插值消息全部改为 Messages Functions 适配小程序端(63 处) - 修复底部导航切换失效与运势页数据不刷新 - 统一设计令牌(深褐主色 + 印红强调色),组件 UI 规范重构 - 图标 iconfont 化(FontAwesome 子集化,修复 Android 豆腐块) - 新增 postcss-px2rpx 机型自适应(仅 mp-weixin 构建生效) - 新增 5 个用户旅程 E2E 测试(31 用例)与验收报告 - 补充微信小程序项目配置(appid)
86 lines
3.3 KiB
JavaScript
86 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* UI 视觉验收截图工具(H5 端渲染,uni-app 同源码同样式)
|
|
* 用法: node scripts/ui-screenshots.mjs [--base <url>] [--out <dir>]
|
|
* 默认 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)
|