/** * 前端页面数据验证测试脚本 - 修正版 * 验证 4 个页面的数据是否正确显示 * * 测试环境: * - 前端地址: http://localhost:5173 * - 数据库用户 ID: 6 */ const { chromium } = require('@playwright/test'); const path = require('path'); const fs = require('fs'); // 配置 const BASE_URL = 'http://localhost:5173'; const SCREENSHOT_DIR = path.join(__dirname, 'test-screenshots'); // 测试结果 const testResults = { summary: { total: 0, passed: 0, failed: 0, warnings: 0 }, pages: {} }; // 确保截图目录存在 if (!fs.existsSync(SCREENSHOT_DIR)) { fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); } /** * 延迟函数 */ function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 测试首页 (/) */ async function testDashboard(page) { console.log('\n========== 测试首页 (/) =========='); const result = { url: `${BASE_URL}/`, checks: [], issues: [], screenshot: null }; try { // 导航到首页 await page.goto(BASE_URL, { waitUntil: 'networkidle' }); await delay(2000); // 等待数据加载 // 截图 const screenshotPath = path.join(SCREENSHOT_DIR, '01-dashboard.png'); await page.screenshot({ path: screenshotPath, fullPage: true }); result.screenshot = screenshotPath; console.log(`截图已保存: ${screenshotPath}`); // 获取页面文本 const pageText = await page.textContent('body'); console.log('\n页面文本片段:', pageText.substring(0, 500)); // 检查总余额显示 console.log('\n--- 检查总余额 ---'); const hasBalance = pageText.includes('当前余额') || pageText.includes('余额'); const hasAmount = /¥\s*[\d,]+\.?\d*/.test(pageText) || /\d{4,}/.test(pageText); result.checks.push({ item: '总余额标题', expected: '当前余额', found: hasBalance, status: hasBalance ? 'PASS' : 'FAIL' }); result.checks.push({ item: '金额显示', expected: '¥ 格式金额', found: hasAmount, status: hasAmount ? 'PASS' : 'FAIL' }); if (hasBalance && hasAmount) { console.log('[PASS] 总余额显示正确'); } else { console.log('[FAIL] 总余额未正确显示'); result.issues.push('总余额未正确显示'); } // 检查收支统计 console.log('\n--- 检查收支统计 ---'); const hasIncome = pageText.includes('本月收入') || pageText.includes('收入'); const hasExpense = pageText.includes('本月支出') || pageText.includes('支出'); result.checks.push({ item: '本月收入', found: hasIncome, status: hasIncome ? 'PASS' : 'FAIL' }); result.checks.push({ item: '本月支出', found: hasExpense, status: hasExpense ? 'PASS' : 'FAIL' }); if (hasIncome) console.log('[PASS] 本月收入显示'); if (hasExpense) console.log('[PASS] 本月支出显示'); // 检查预算进度 console.log('\n--- 检查预算进度 ---'); const hasBudget = pageText.includes('预算进度') || pageText.includes('预算'); const hasCategory = pageText.includes('餐饮') || pageText.includes('交通'); result.checks.push({ item: '预算进度区域', found: hasBudget, status: hasBudget ? 'PASS' : 'WARN' }); result.checks.push({ item: '预算分类', found: hasCategory, status: hasCategory ? 'PASS' : 'WARN' }); if (hasBudget) console.log('[PASS] 预算进度区域显示'); if (hasCategory) console.log('[PASS] 预算分类显示'); // 检查最近记录 console.log('\n--- 检查最近记录 ---'); const hasRecords = pageText.includes('最近记录') || pageText.includes('记录'); const hasRecordData = pageText.includes('午餐') || pageText.includes('地铁') || pageText.includes('工资') || pageText.includes('购物'); result.checks.push({ item: '最近记录区域', found: hasRecords, status: hasRecords ? 'PASS' : 'WARN' }); result.checks.push({ item: '记录数据', found: hasRecordData, status: hasRecordData ? 'PASS' : 'WARN' }); if (hasRecords) console.log('[PASS] 最近记录区域显示'); if (hasRecordData) console.log('[PASS] 记录数据显示'); } catch (error) { result.issues.push(`测试异常: ${error.message}`); console.error(`[ERROR] 首页测试失败: ${error.message}`); } return result; } /** * 测试记账页面 (/record) */ async function testRecord(page) { console.log('\n========== 测试记账页面 (/record) =========='); const result = { url: `${BASE_URL}/record`, checks: [], issues: [], screenshot: null }; try { // 导航到记账页面 await page.goto(`${BASE_URL}/record`, { waitUntil: 'networkidle' }); await delay(2000); // 截图 const screenshotPath = path.join(SCREENSHOT_DIR, '02-record.png'); await page.screenshot({ path: screenshotPath, fullPage: true }); result.screenshot = screenshotPath; console.log(`截图已保存: ${screenshotPath}`); // 获取页面文本 const pageText = await page.textContent('body'); // 检查交易记录列表 console.log('\n--- 检查交易记录列表 ---'); // 预期记录数据 const expectedRecords = ['午餐', '地铁', '工资', '购物', '电影', '房租', '还款']; let foundCount = 0; for (const record of expectedRecords) { if (pageText.includes(record)) { foundCount++; console.log(`[PASS] 找到记录: ${record}`); } } result.checks.push({ item: '交易记录数据', expected: `至少 5 条记录`, found: foundCount >= 5, status: foundCount >= 5 ? 'PASS' : 'FAIL' }); if (foundCount < 5) { result.issues.push(`只找到 ${foundCount} 条记录`); } // 检查记录类型(支出/收入) console.log('\n--- 检查记录类型 ---'); const hasExpense = pageText.includes('支出'); const hasIncome = pageText.includes('收入'); result.checks.push({ item: '记录类型 - 支出', found: hasExpense, status: hasExpense ? 'PASS' : 'WARN' }); result.checks.push({ item: '记录类型 - 收入', found: hasIncome, status: hasIncome ? 'PASS' : 'WARN' }); if (hasExpense) console.log('[PASS] 支出类型显示'); if (hasIncome) console.log('[PASS] 收入类型显示'); // 检查金额显示 const hasAmount = /\d+\.?\d*/.test(pageText); result.checks.push({ item: '金额显示', found: hasAmount, status: hasAmount ? 'PASS' : 'FAIL' }); if (hasAmount) { console.log('[PASS] 金额数据存在'); } else { console.log('[FAIL] 未找到金额数据'); result.issues.push('金额数据未显示'); } } catch (error) { result.issues.push(`测试异常: ${error.message}`); console.error(`[ERROR] 记账页面测试失败: ${error.message}`); } return result; } /** * 测试预算页面 (/budget) */ async function testBudget(page) { console.log('\n========== 测试预算页面 (/budget) =========='); const result = { url: `${BASE_URL}/budget`, checks: [], issues: [], screenshot: null }; try { // 导航到预算页面 await page.goto(`${BASE_URL}/budget`, { waitUntil: 'networkidle' }); await delay(2000); // 截图 const screenshotPath = path.join(SCREENSHOT_DIR, '03-budget.png'); await page.screenshot({ path: screenshotPath, fullPage: true }); result.screenshot = screenshotPath; console.log(`截图已保存: ${screenshotPath}`); // 获取页面文本 const pageText = await page.textContent('body'); // 检查预算数据 console.log('\n--- 检查预算数据 ---'); // 预期预算类别 const expectedCategories = ['餐饮', '交通', '购物', '娱乐']; for (const category of expectedCategories) { const hasCategory = pageText.includes(category); result.checks.push({ item: `预算类别 - ${category}`, found: hasCategory, status: hasCategory ? 'PASS' : 'WARN' }); if (hasCategory) { console.log(`[PASS] 预算类别 "${category}" 显示`); } else { console.log(`[WARN] 预算类别 "${category}" 未找到`); } } // 检查预算进度 console.log('\n--- 检查预算进度 ---'); // 查找进度条元素 const progressBars = await page.locator('[class*="progress"], [role="progressbar"]').all(); console.log(`找到 ${progressBars.length} 个进度条元素`); const hasProgress = progressBars.length > 0 || pageText.includes('%') || pageText.includes('进度'); result.checks.push({ item: '预算进度显示', found: hasProgress, status: hasProgress ? 'PASS' : 'WARN' }); if (hasProgress) { console.log('[PASS] 预算进度显示'); } else { console.log('[WARN] 预算进度可能未正确显示'); result.issues.push('预算进度显示可能有问题'); } // 检查预算金额 const hasBudgetAmount = /\d+/.test(pageText); result.checks.push({ item: '预算金额显示', found: hasBudgetAmount, status: hasBudgetAmount ? 'PASS' : 'FAIL' }); if (hasBudgetAmount) { console.log('[PASS] 预算金额数据存在'); } else { console.log('[FAIL] 未找到预算金额数据'); result.issues.push('预算金额数据未显示'); } } catch (error) { result.issues.push(`测试异常: ${error.message}`); console.error(`[ERROR] 预算页面测试失败: ${error.message}`); } return result; } /** * 测试统计页面 (/statistics) */ async function testStatistics(page) { console.log('\n========== 测试统计页面 (/statistics) =========='); const result = { url: `${BASE_URL}/statistics`, checks: [], issues: [], screenshot: null }; try { // 导航到统计页面 await page.goto(`${BASE_URL}/statistics`, { waitUntil: 'networkidle' }); await delay(3000); // 图表加载需要更多时间 // 截图 const screenshotPath = path.join(SCREENSHOT_DIR, '04-statistics.png'); await page.screenshot({ path: screenshotPath, fullPage: true }); result.screenshot = screenshotPath; console.log(`截图已保存: ${screenshotPath}`); // 获取页面文本 const pageText = await page.textContent('body'); // 检查图表显示 console.log('\n--- 检查图表显示 ---'); // 查找图表容器 const chartContainers = await page.locator('[class*="chart"], [id*="chart"], canvas').all(); console.log(`找到 ${chartContainers.length} 个图表元素`); const hasChart = chartContainers.length > 0; result.checks.push({ item: '图表容器', expected: '至少 1 个图表', found: hasChart, status: hasChart ? 'PASS' : 'FAIL' }); if (hasChart) { console.log('[PASS] 图表容器存在'); } else { console.log('[FAIL] 未找到图表容器'); result.issues.push('图表未正确渲染'); } // 检查图表切换按钮 console.log('\n--- 检查图表切换功能 ---'); // 查找切换按钮 const switchButtons = await page.locator('button').all(); let foundButtons = []; for (const button of switchButtons) { const text = await button.textContent(); if (text && (text.includes('饼图') || text.includes('折线') || text.includes('柱状'))) { foundButtons.push(text.trim()); } } console.log(`找到切换按钮: ${foundButtons.join(', ')}`); result.checks.push({ item: '图表切换按钮', expected: '饼图、折线图、柱状图', found: foundButtons.length >= 3, status: foundButtons.length >= 3 ? 'PASS' : 'WARN' }); if (foundButtons.length >= 3) { console.log('[PASS] 图表切换按钮存在'); // 测试切换功能 console.log('\n--- 测试图表切换 ---'); // 尝试点击饼图按钮 const pieButton = await page.locator('button:has-text("饼图")').first(); if (await pieButton.isVisible()) { await pieButton.click(); await delay(1000); console.log('[INFO] 点击了饼图按钮'); // 截图 const pieScreenshot = path.join(SCREENSHOT_DIR, '04-statistics-pie.png'); await page.screenshot({ path: pieScreenshot, fullPage: true }); } // 尝试点击折线图按钮 const lineButton = await page.locator('button:has-text("折线")').first(); if (await lineButton.isVisible()) { await lineButton.click(); await delay(1000); console.log('[INFO] 点击了折线图按钮'); // 截图 const lineScreenshot = path.join(SCREENSHOT_DIR, '04-statistics-line.png'); await page.screenshot({ path: lineScreenshot, fullPage: true }); } // 尝试点击柱状图按钮 const barButton = await page.locator('button:has-text("柱状")').first(); if (await barButton.isVisible()) { await barButton.click(); await delay(1000); console.log('[INFO] 点击了柱状图按钮'); // 截图 const barScreenshot = path.join(SCREENSHOT_DIR, '04-statistics-bar.png'); await page.screenshot({ path: barScreenshot, fullPage: true }); } result.checks.push({ item: '图表切换功能', found: true, status: 'PASS' }); console.log('[PASS] 图表切换功能正常'); } else { console.log('[WARN] 未找到完整的图表切换按钮'); result.issues.push('图表切换按钮不完整'); } // 检查是否有数据标签 const hasDataLabels = pageText.includes('餐饮') || pageText.includes('交通') || pageText.includes('购物') || pageText.includes('娱乐') || pageText.includes('支出') || pageText.includes('收入'); result.checks.push({ item: '数据标签', found: hasDataLabels, status: hasDataLabels ? 'PASS' : 'WARN' }); if (hasDataLabels) { console.log('[PASS] 数据标签存在'); } else { console.log('[WARN] 数据标签可能未正确显示'); } } catch (error) { result.issues.push(`测试异常: ${error.message}`); console.error(`[ERROR] 统计页面测试失败: ${error.message}`); } return result; } /** * 主测试函数 */ async function runTests() { console.log('========================================'); console.log(' 前端页面数据验证测试 - 修正版'); console.log(' 测试时间:', new Date().toLocaleString()); console.log(' 前端地址:', BASE_URL); console.log('========================================'); // 启动浏览器 const browser = await chromium.launch({ headless: false, // 可视化模式,方便观察 slowMo: 100 }); const context = await browser.newContext({ viewport: { width: 1280, height: 800 } }); const page = await context.newPage(); try { // 测试首页 testResults.pages.dashboard = await testDashboard(page); // 测试记账页面 testResults.pages.record = await testRecord(page); // 测试预算页面 testResults.pages.budget = await testBudget(page); // 测试统计页面 testResults.pages.statistics = await testStatistics(page); } finally { await browser.close(); } // 统计结果 console.log('\n========================================'); console.log(' 测试结果汇总'); console.log('========================================'); for (const [pageName, result] of Object.entries(testResults.pages)) { console.log(`\n【${pageName.toUpperCase()}】`); console.log(` URL: ${result.url}`); console.log(` 截图: ${result.screenshot || '无'}`); const passed = result.checks.filter(c => c.status === 'PASS').length; const failed = result.checks.filter(c => c.status === 'FAIL').length; const warned = result.checks.filter(c => c.status === 'WARN').length; console.log(` 检查项: ${passed} 通过, ${failed} 失败, ${warned} 警告`); if (result.issues.length > 0) { console.log(` 问题列表:`); result.issues.forEach(issue => console.log(` - ${issue}`)); } testResults.summary.total += result.checks.length; testResults.summary.passed += passed; testResults.summary.failed += failed; testResults.summary.warnings += warned; } console.log('\n----------------------------------------'); console.log(`总计: ${testResults.summary.passed}/${testResults.summary.total} 通过`); console.log(`失败: ${testResults.summary.failed}`); console.log(`警告: ${testResults.summary.warnings}`); console.log('----------------------------------------'); // 保存测试报告 const reportPath = path.join(SCREENSHOT_DIR, 'test-report.json'); fs.writeFileSync(reportPath, JSON.stringify(testResults, null, 2)); console.log(`\n测试报告已保存: ${reportPath}`); return testResults; } // 执行测试 runTests().catch(console.error);