diff --git a/test-browser-validation.js b/test-browser-validation.js deleted file mode 100644 index f121f01..0000000 --- a/test-browser-validation.js +++ /dev/null @@ -1,303 +0,0 @@ -const { chromium } = require('playwright'); -const path = require('path'); -const fs = require('fs'); - -const SCREENSHOT_DIR = path.join(__dirname, 'test-screenshots'); -const FRONTEND_URL = 'http://localhost:5173'; -const API_BASE = 'http://localhost:3001'; - -// 确保截图目录存在 -if (!fs.existsSync(SCREENSHOT_DIR)) { - fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); -} - -async function apiGet(urlPath) { - const http = require('http'); - return new Promise((resolve, reject) => { - http.get(API_BASE + urlPath, res => { - let data = ''; - res.on('data', c => data += c); - res.on('end', () => resolve(JSON.parse(data))); - }).on('error', reject); - }); -} - -async function main() { - console.log('[BROWSER TEST] 启动浏览器自动化测试...'); - console.log(`[BROWSER TEST] 截图目录: ${SCREENSHOT_DIR}\n`); - - const browser = await chromium.launch({ headless: true }); - const context = await browser.newContext({ viewport: { width: 1440, height: 900 } }); - const page = await context.newPage(); - - // 截图计数器 - let screenshotNum = 0; - async function screenshot(name) { - screenshotNum++; - const fileName = `${String(screenshotNum).padStart(2, '0')}-${name}.png`; - const filePath = path.join(SCREENSHOT_DIR, fileName); - await page.screenshot({ path: filePath, fullPage: true }); - console.log(`[SCREENSHOT] ${fileName}`); - return filePath; - } - - const results = { - tests: [], - pages: [] - }; - - function recordTest(page, name, status, detail) { - results.tests.push({ page, name, status, detail }); - const icon = status === 'PASS' ? '✓' : status === 'FAIL' ? '✗' : '!'; - console.log(` [${icon}] ${name}${detail ? ' - ' + detail : ''}`); - } - - try { - // ========== 1. 首页 Dashboard ========== - console.log('\n========== 页面1: 首页 Dashboard =========='); - results.pages.push('Dashboard'); - - await page.goto(FRONTEND_URL, { waitUntil: 'networkidle', timeout: 15000 }); - await page.waitForTimeout(2000); - await screenshot('01-dashboard'); - - // 验证余额卡片 - const balanceText = await page.locator('.balance-amount').first().innerText().catch(() => null); - recordTest('Dashboard', balanceText && balanceText.includes('¥') ? 'PASS' : 'FAIL', - '余额显示', balanceText); - - // 验证本月收入/支出显示 - const incomeText = await page.locator('.stat-value.income').first().innerText().catch(() => null); - recordTest('Dashboard', incomeText && incomeText.length > 0 ? 'PASS' : 'FAIL', - '本月收入显示', incomeText); - - const expenseText = await page.locator('.stat-value.expense').first().innerText().catch(() => null); - recordTest('Dashboard', expenseText && expenseText.length > 0 ? 'PASS' : 'FAIL', - '本月支出显示', expenseText); - - // 验证快捷操作按钮 - const quickActions = await page.locator('.quick-actions .btn').count().catch(() => 0); - recordTest('Dashboard', quickActions >= 2 ? 'PASS' : 'FAIL', - '快捷操作按钮', `${quickActions}个按钮`); - - // 验证最近记录 - const recentRecords = await page.locator('.records-card .record-item').count().catch(() => 0); - recordTest('Dashboard', recentRecords > 0 ? 'PASS' : 'WARN', - '最近记录', `${recentRecords}条记录`); - - // 验证预算进度区域 - const budgetSection = await page.locator('.budget-card').count().catch(() => 0); - recordTest('Dashboard', budgetSection > 0 ? 'PASS' : 'WARN', - '预算进度区域', budgetSection > 0 ? '存在' : '不存在或无预算数据'); - - // ========== 2. 账单明细页 ========== - console.log('\n========== 页面2: 账单明细 Record =========='); - results.pages.push('Record'); - - // 点击导航栏的"账单"链接 - await page.locator('a[href="/record"], a:has-text("账单")').first().click().catch(async () => { - // 如果找不到导航,直接导航到URL - await page.goto(`${FRONTEND_URL}/record`, { waitUntil: 'networkidle', timeout: 15000 }); - }); - await page.waitForTimeout(2000); - await screenshot('02-record-all'); - - // 验证页面标题 - const pageTitle = await page.locator('.page-title').innerText().catch(() => ''); - recordTest('Record', pageTitle.includes('明细') || pageTitle.includes('账单') ? 'PASS' : 'FAIL', - '页面标题', pageTitle); - - // 验证筛选按钮 - const filterBtns = await page.locator('.filter-btn').count().catch(() => 0); - recordTest('Record', filterBtns >= 3 ? 'PASS' : 'FAIL', - '筛选按钮', `${filterBtns}个筛选按钮`); - - // 验证记录列表 - const recordItems = await page.locator('.record-item').count().catch(() => 0); - recordTest('Record', recordItems > 0 ? 'PASS' : 'FAIL', - '记录列表', `${recordItems}条记录`); - - // 验证支出记录(红色) - const expenseRecords = await page.locator('.record-amount.expense').count().catch(() => 0); - recordTest('Record', expenseRecords > 0 ? 'PASS' : 'WARN', - '支出记录样式(红色)', `${expenseRecords}条`); - - // 验证收入记录(绿色) - const incomeRecords = await page.locator('.record-amount.income').count().catch(() => 0); - recordTest('Record', incomeRecords > 0 ? 'PASS' : 'WARN', - '收入记录样式(绿色)', `${incomeRecords}条`); - - // 测试支出筛选 - await page.locator('.filter-btn[data-filter="expense"]').click().catch(() => {}); - await page.waitForTimeout(1000); - await screenshot('03-record-expense'); - const filteredExpenses = await page.locator('.record-item').count().catch(() => 0); - recordTest('Record', filteredExpenses > 0 ? 'PASS' : 'FAIL', - '支出筛选结果', `${filteredExpenses}条`); - - // 测试收入筛选 - await page.locator('.filter-btn[data-filter="income"]').click().catch(() => {}); - await page.waitForTimeout(1000); - await screenshot('04-record-income'); - const filteredIncomes = await page.locator('.record-item').count().catch(() => 0); - recordTest('Record', filteredIncomes > 0 ? 'PASS' : 'FAIL', - '收入筛选结果', `${filteredIncomes}条`); - - // 回到全部 - await page.locator('.filter-btn[data-filter="all"]').click().catch(() => {}); - await page.waitForTimeout(1000); - - // 验证新增按钮 - const fabBtn = await page.locator('.fab').count().catch(() => 0); - recordTest('Record', fabBtn > 0 ? 'PASS' : 'FAIL', - '新增浮动按钮', fabBtn > 0 ? '存在' : '不存在'); - - // ========== 3. 预算管理页 ========== - console.log('\n========== 页面3: 预算管理 Budget =========='); - results.pages.push('Budget'); - - await page.locator('a[href="/budget"], a:has-text("预算")').first().click().catch(async () => { - await page.goto(`${FRONTEND_URL}/budget`, { waitUntil: 'networkidle', timeout: 15000 }); - }); - await page.waitForTimeout(2000); - await screenshot('05-budget'); - - // 验证页面标题 - const budgetTitle = await page.locator('.page-title').innerText().catch(() => ''); - recordTest('Budget', budgetTitle.includes('预算') ? 'PASS' : 'FAIL', - '页面标题', budgetTitle); - - // 验证预算概览 - const budgetSummary = await page.locator('.budget-summary').count().catch(() => 0); - recordTest('Budget', budgetSummary > 0 ? 'PASS' : 'FAIL', - '预算概览区域', budgetSummary > 0 ? '存在' : '不存在'); - - // 验证分类卡片数量 - const categoryCards = await page.locator('.category-card').count().catch(() => 0); - recordTest('Budget', categoryCards >= 6 ? 'PASS' : 'WARN', - '分类预算卡片', `${categoryCards}个 (预期6个)`); - - // 验证设置预算按钮 - const setBudgetBtn = await page.locator('.btn-primary:has-text("设置预算")').count().catch(() => 0); - recordTest('Budget', setBudgetBtn > 0 ? 'PASS' : 'FAIL', - '设置预算按钮', setBudgetBtn > 0 ? '存在' : '不存在'); - - // ========== 4. 统计报表页 ========== - console.log('\n========== 页面4: 统计报表 Statistics =========='); - results.pages.push('Statistics'); - - await page.locator('a[href="/statistics"], a:has-text("统计")').first().click().catch(async () => { - await page.goto(`${FRONTEND_URL}/statistics`, { waitUntil: 'networkidle', timeout: 15000 }); - }); - await page.waitForTimeout(3000); // ECharts需要更多渲染时间 - await screenshot('06-statistics-line'); - - // 验证页面标题 - const statsTitle = await page.locator('.page-title').innerText().catch(() => ''); - recordTest('Statistics', statsTitle.includes('统计') ? 'PASS' : 'FAIL', - '页面标题', statsTitle); - - // 验证图表类型切换 - const chartTypeBtns = await page.locator('.chart-type-btn').count().catch(() => 0); - recordTest('Statistics', chartTypeBtns >= 3 ? 'PASS' : 'FAIL', - '图表类型切换按钮', `${chartTypeBtns}个 (折线/柱状/饼图)`); - - // 验证趋势图表容器 - const trendChart = await page.locator('.echarts-container').first().count().catch(() => 0); - recordTest('Statistics', trendChart > 0 ? 'PASS' : 'FAIL', - '趋势图表', trendChart > 0 ? '已渲染' : '未渲染'); - - // 切换到饼图 - await page.locator('.chart-type-btn[data-type="pie"]').click().catch(() => {}); - await page.waitForTimeout(2000); - await screenshot('07-statistics-pie'); - - // 验证饼图渲染 - const pieChart = await page.locator('.pie-chart-container').count().catch(() => 0); - recordTest('Statistics', pieChart > 0 ? 'PASS' : 'FAIL', - '饼图渲染', pieChart > 0 ? '已渲染' : '未渲染'); - - // 验证图例 - const pieLegend = await page.locator('.legend-item').count().catch(() => 0); - recordTest('Statistics', pieLegend > 0 ? 'PASS' : 'WARN', - '饼图图例', `${pieLegend}个分类`); - - // 验证支出构成金额显示 - const pieCenterText = await page.locator('.pie-chart-wrapper').innerText().catch(() => ''); - recordTest('Statistics', pieCenterText.includes('总支出') ? 'PASS' : 'WARN', - '饼图中心文本', pieCenterText.includes('总支出') ? '显示总支出' : '未显示总支出'); - - // 验证月度对比图表 - await page.locator('.chart-type-btn[data-type="line"]').click().catch(() => {}); - await page.waitForTimeout(1000); - - const compareChart = await page.locator('.echarts-container').count().catch(() => 0); - recordTest('Statistics', compareChart >= 2 ? 'PASS' : 'FAIL', - '月度对比图表', `${compareChart}个图表容器`); - - // ========== 5. API数据一致性验证 ========== - console.log('\n========== 数据一致性验证 =========='); - - // 获取API数据 - const recordsAPI = await apiGet('/api/records?userId=1'); - const dashboardAPI = await apiGet('/api/dashboard/summary?userId=1'); - const statsAPI = await apiGet('/api/statistics/monthly?userId=1&month=2026-04'); - - if (recordsAPI.success && dashboardAPI.success && statsAPI.success) { - // 验证记录总数 - const totalRecords = recordsAPI.data.length; - recordTest('API', totalRecords > 0 ? 'PASS' : 'FAIL', - 'API记录总数', `${totalRecords}条`); - - // 验证支出类别覆盖 - const expenseCategories = [...new Set(recordsAPI.data.filter(r => r.type === 'expense').map(r => r.category))]; - const expectedExpenseCategories = ['餐饮', '交通', '购物', '娱乐', '医疗', '其他']; - const missingExpCats = expectedExpenseCategories.filter(c => !expenseCategories.includes(c)); - recordTest('API', missingExpCats.length === 0 ? 'PASS' : 'WARN', - '支出类别覆盖', missingExpCats.length === 0 ? '全部6个类别都有数据' : `缺少: ${missingExpCats.join(', ')}`); - - // 验证收入类别覆盖 - const incomeCategories = [...new Set(recordsAPI.data.filter(r => r.type === 'income').map(r => r.category))]; - const expectedIncomeCategories = ['工资', '奖金', '投资', '兼职', '理财', '其他']; - const missingIncCats = expectedIncomeCategories.filter(c => !incomeCategories.includes(c)); - recordTest('API', missingIncCats.length === 0 ? 'PASS' : 'WARN', - '收入类别覆盖', missingIncCats.length === 0 ? '全部6个类别都有数据' : `缺少: ${missingIncCats.join(', ')}`); - - // 验证Dashboard与Stats一致性 - const dashIncome = dashboardAPI.data.monthIncome; - const statsIncome = statsAPI.data.totalIncome; - recordTest('API', Math.abs(dashIncome - statsIncome) < 0.01 ? 'PASS' : 'FAIL', - 'Dashboard/Stat收入一致性', `Dashboard: ¥${dashIncome}, Stats: ¥${statsIncome}`); - - const dashExpense = dashboardAPI.data.monthExpense; - const statsExpense = statsAPI.data.totalExpense; - recordTest('API', Math.abs(dashExpense - statsExpense) < 0.01 ? 'PASS' : 'FAIL', - 'Dashboard/Stat支出一致性', `Dashboard: ¥${dashExpense}, Stats: ¥${statsExpense}`); - } - - // ========== 生成汇总 ========== - console.log('\n==========================================='); - const totalTests = results.tests.length; - const passedTests = results.tests.filter(t => t.status === 'PASS').length; - const failedTests = results.tests.filter(t => t.status === 'FAIL').length; - const warnTests = results.tests.filter(t => t.status === 'WARN').length; - - console.log(`页面验证测试: ${totalTests}项 | 通过: ${passedTests} | 失败: ${failedTests} | 警告: ${warnTests}`); - console.log(`截图数量: ${screenshotNum}张`); - console.log(`截图目录: ${SCREENSHOT_DIR}`); - console.log('==========================================='); - - // 保存JSON结果 - const jsonPath = path.join(SCREENSHOT_DIR, 'browser-test-results.json'); - fs.writeFileSync(jsonPath, JSON.stringify(results, null, 2)); - console.log(`\n结果保存至: ${jsonPath}`); - - } catch (err) { - console.error('[BROWSER TEST ERROR]', err.message); - await screenshot('error-state'); - } finally { - await browser.close(); - } -} - -main();