/** * 前端页面数据验证测试脚本 * 验证 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}`); // 检查账户余额 console.log('\n--- 检查账户余额 ---'); const balanceCards = await page.locator('[class*="card"], [class*="balance"]').all(); console.log(`找到 ${balanceCards.length} 个卡片元素`); // 检查是否有金额显示 const pageText = await page.textContent('body'); // 预期数据 const expectedBalances = [ { name: '支付宝', amount: 5000 }, { name: '微信', amount: 3000 }, { name: '银行卡', amount: 10000 } ]; for (const balance of expectedBalances) { const hasName = pageText.includes(balance.name); const hasAmount = pageText.includes(balance.amount.toString()) || pageText.includes(balance.amount.toLocaleString()); const check = { item: `账户余额 - ${balance.name}`, expected: `${balance.name}: ${balance.amount}`, found: hasName && hasAmount, status: (hasName && hasAmount) ? 'PASS' : 'FAIL' }; result.checks.push(check); if (hasName && hasAmount) { console.log(`[PASS] ${balance.name} 余额显示正确`); } else { console.log(`[FAIL] ${balance.name} 余额未找到`); result.issues.push(`${balance.name} 余额未正确显示`); } } // 检查收支统计 console.log('\n--- 检查收支统计 ---'); const incomePattern = /收入|Income/i; const expensePattern = /支出|Expense/i; const hasIncome = incomePattern.test(pageText); const hasExpense = expensePattern.test(pageText); result.checks.push({ item: '收支统计 - 收入', found: hasIncome, status: hasIncome ? 'PASS' : 'WARN' }); result.checks.push({ item: '收支统计 - 支出', found: hasExpense, status: hasExpense ? 'PASS' : 'WARN' }); if (hasIncome) console.log('[PASS] 收入统计显示'); if (hasExpense) console.log('[PASS] 支出统计显示'); if (!hasIncome || !hasExpense) { result.issues.push('收支统计可能未正确显示'); } // 检查是否有数据加载错误 const hasError = pageText.includes('加载失败') || pageText.includes('错误') || pageText.includes('Error'); if (hasError) { result.issues.push('页面存在加载错误'); console.log('[FAIL] 页面存在加载错误'); } } 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}`); // 检查交易记录列表 console.log('\n--- 检查交易记录列表 ---'); // 查找记录元素 const recordItems = await page.locator('tr, [class*="record"], [class*="item"]').all(); console.log(`找到 ${recordItems.length} 个可能的记录元素`); // 获取页面文本 const pageText = await page.textContent('body'); // 预期有 7 条记录 const expectedRecordCount = 7; // 检查是否有记录数据显示 const hasRecords = pageText.includes('早餐') || pageText.includes('午餐') || pageText.includes('工资') || pageText.includes('地铁') || pageText.includes('购物') || pageText.includes('电影') || pageText.includes('晚餐'); result.checks.push({ item: '交易记录数据', expected: `至少 ${expectedRecordCount} 条记录`, found: hasRecords, status: hasRecords ? 'PASS' : 'FAIL' }); if (hasRecords) { console.log('[PASS] 交易记录数据存在'); } else { console.log('[FAIL] 未找到交易记录数据'); result.issues.push('交易记录列表无数据'); } // 检查记录类型(支出/收入) 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 hasSwitchButtons = false; for (const button of switchButtons) { const text = await button.textContent(); if (text && (text.includes('饼图') || text.includes('折线') || text.includes('柱状'))) { hasSwitchButtons = true; console.log(`找到切换按钮: ${text.trim()}`); } } result.checks.push({ item: '图表切换按钮', found: hasSwitchButtons, status: hasSwitchButtons ? 'PASS' : 'WARN' }); if (hasSwitchButtons) { 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 hasDataIndicators = pageText.includes('餐饮') || pageText.includes('交通') || pageText.includes('购物') || pageText.includes('娱乐'); result.checks.push({ item: '统计数据', found: hasDataIndicators, status: hasDataIndicators ? 'PASS' : 'WARN' }); if (hasDataIndicators) { 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);