feat: 个人记账与预算管理系统 MVP 初始版本
This commit is contained in:
@@ -0,0 +1,589 @@
|
||||
/**
|
||||
* 前端页面数据验证测试脚本 - 修正版
|
||||
* 验证 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);
|
||||
Reference in New Issue
Block a user