chore: remove verify-data-consistency.js
This commit is contained in:
@@ -1,416 +0,0 @@
|
||||
/**
|
||||
* 前后端数据一致性验证测试
|
||||
*
|
||||
* 验证内容:
|
||||
* 1. 首页 - Dashboard Summary
|
||||
* 2. 记账页面 - Records
|
||||
* 3. 预算页面 - Budgets
|
||||
* 4. 统计页面 - Statistics
|
||||
*
|
||||
* 验证标准:
|
||||
* - 金额误差:0.01
|
||||
* - 百分比误差:0.1%
|
||||
* - 计数误差:0
|
||||
*/
|
||||
|
||||
const { chromium } = require('playwright');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 配置
|
||||
const CONFIG = {
|
||||
frontendUrl: 'http://localhost:5173',
|
||||
backendUrl: 'http://localhost:3001',
|
||||
userId: 6,
|
||||
tolerance: {
|
||||
amount: 0.01,
|
||||
percentage: 0.1,
|
||||
count: 0
|
||||
}
|
||||
};
|
||||
|
||||
// 测试结果
|
||||
const testResults = {
|
||||
timestamp: new Date().toISOString(),
|
||||
summary: {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0
|
||||
},
|
||||
pages: []
|
||||
};
|
||||
|
||||
// API 数据获取
|
||||
async function fetchApiData(endpoint) {
|
||||
const response = await fetch(`${CONFIG.backendUrl}${endpoint}`);
|
||||
const data = await response.json();
|
||||
return data.data;
|
||||
}
|
||||
|
||||
// 比较数值(考虑误差)
|
||||
function compareNumbers(actual, expected, tolerance = CONFIG.tolerance.amount) {
|
||||
const diff = Math.abs(actual - expected);
|
||||
return {
|
||||
match: diff <= tolerance,
|
||||
actual,
|
||||
expected,
|
||||
diff
|
||||
};
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runTests() {
|
||||
console.log('========================================');
|
||||
console.log('前后端数据一致性验证测试');
|
||||
console.log('========================================\n');
|
||||
|
||||
const browser = await chromium.launch({ headless: false });
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
// ========================================
|
||||
// 1. 首页验证
|
||||
// ========================================
|
||||
console.log('[1/4] 验证首页数据...');
|
||||
testResults.summary.total++;
|
||||
|
||||
const dashboardApiData = await fetchApiData(`/api/dashboard/summary?userId=${CONFIG.userId}`);
|
||||
|
||||
await page.goto(CONFIG.frontendUrl);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 截图
|
||||
const dashboardScreenshot = path.join(__dirname, 'test-screenshots', 'verify-dashboard.png');
|
||||
await page.screenshot({ path: dashboardScreenshot, fullPage: true });
|
||||
|
||||
// 提取页面数据
|
||||
const pageData = await page.evaluate(() => {
|
||||
const getText = (selector) => {
|
||||
const el = document.querySelector(selector);
|
||||
return el ? el.textContent.trim() : null;
|
||||
};
|
||||
|
||||
const parseAmount = (text) => {
|
||||
if (!text) return null;
|
||||
const match = text.match(/[\d,]+\.?\d*/);
|
||||
return match ? parseFloat(match[0].replace(/,/g, '')) : null;
|
||||
};
|
||||
|
||||
// 尝试多种选择器
|
||||
const balanceText = getText('.balance-amount, [data-testid="balance"], .total-balance, h2');
|
||||
const incomeText = getText('.income-amount, [data-testid="income"], .month-income');
|
||||
const expenseText = getText('.expense-amount, [data-testid="expense"], .month-expense');
|
||||
|
||||
return {
|
||||
balance: parseAmount(balanceText),
|
||||
income: parseAmount(incomeText),
|
||||
expense: parseAmount(expenseText),
|
||||
balanceText,
|
||||
incomeText,
|
||||
expenseText
|
||||
};
|
||||
});
|
||||
|
||||
// 比较
|
||||
const dashboardResult = {
|
||||
page: '首页 (Dashboard)',
|
||||
url: CONFIG.frontendUrl,
|
||||
screenshot: dashboardScreenshot,
|
||||
apiData: {
|
||||
totalBalance: dashboardApiData.totalBalance,
|
||||
monthIncome: dashboardApiData.monthIncome,
|
||||
monthExpense: dashboardApiData.monthExpense
|
||||
},
|
||||
pageData: pageData,
|
||||
comparisons: {
|
||||
totalBalance: compareNumbers(pageData.balance || dashboardApiData.totalBalance, dashboardApiData.totalBalance),
|
||||
monthIncome: compareNumbers(pageData.income || dashboardApiData.monthIncome, dashboardApiData.monthIncome),
|
||||
monthExpense: compareNumbers(pageData.expense || dashboardApiData.monthExpense, dashboardApiData.monthExpense)
|
||||
},
|
||||
passed: true,
|
||||
issues: []
|
||||
};
|
||||
|
||||
// 检查账户数量 - 首页没有账户列表卡片,跳过此检查
|
||||
// 检查预算进度数量
|
||||
const budgetProgressCount = await page.locator('.budget-progress-item').count();
|
||||
if (budgetProgressCount !== dashboardApiData.budgetUsage.length) {
|
||||
dashboardResult.issues.push(`预算进度数量不一致: 页面显示 ${budgetProgressCount} 个, API返回 ${dashboardApiData.budgetUsage.length} 个`);
|
||||
}
|
||||
|
||||
if (dashboardResult.issues.length > 0) {
|
||||
dashboardResult.passed = false;
|
||||
}
|
||||
|
||||
if (dashboardResult.passed) {
|
||||
testResults.summary.passed++;
|
||||
console.log(' [PASS] 首页数据验证通过');
|
||||
} else {
|
||||
testResults.summary.failed++;
|
||||
console.log(' [FAIL] 首页数据验证失败');
|
||||
dashboardResult.issues.forEach(issue => console.log(` - ${issue}`));
|
||||
}
|
||||
|
||||
testResults.pages.push(dashboardResult);
|
||||
|
||||
// ========================================
|
||||
// 2. 记账页面验证
|
||||
// ========================================
|
||||
console.log('\n[2/4] 验证记账页面数据...');
|
||||
testResults.summary.total++;
|
||||
|
||||
const recordsApiData = await fetchApiData(`/api/records?userId=${CONFIG.userId}`);
|
||||
|
||||
// 导航到记账页面
|
||||
await page.click('a[href="/record"], button:has-text("记账"), nav a:has-text("记账")').catch(() => {
|
||||
return page.goto(`${CONFIG.frontendUrl}/record`);
|
||||
});
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const recordScreenshot = path.join(__dirname, 'test-screenshots', 'verify-record.png');
|
||||
await page.screenshot({ path: recordScreenshot, fullPage: true });
|
||||
|
||||
// 提取页面记录数量
|
||||
const recordPageData = await page.evaluate(() => {
|
||||
const records = document.querySelectorAll('.record-item, [data-testid="record"], tr[data-id]');
|
||||
return {
|
||||
recordCount: records.length
|
||||
};
|
||||
});
|
||||
|
||||
const recordResult = {
|
||||
page: '记账页面 (Records)',
|
||||
url: `${CONFIG.frontendUrl}/record`,
|
||||
screenshot: recordScreenshot,
|
||||
apiData: {
|
||||
recordCount: recordsApiData.length,
|
||||
records: recordsApiData.slice(0, 5) // 只保存前5条
|
||||
},
|
||||
pageData: recordPageData,
|
||||
comparisons: {
|
||||
recordCount: {
|
||||
match: recordPageData.recordCount === recordsApiData.length,
|
||||
actual: recordPageData.recordCount,
|
||||
expected: recordsApiData.length,
|
||||
diff: Math.abs(recordPageData.recordCount - recordsApiData.length)
|
||||
}
|
||||
},
|
||||
passed: recordPageData.recordCount === recordsApiData.length,
|
||||
issues: []
|
||||
};
|
||||
|
||||
if (!recordResult.passed) {
|
||||
recordResult.issues.push(`记录数量不一致: 页面显示 ${recordPageData.recordCount} 条, API返回 ${recordsApiData.length} 条`);
|
||||
}
|
||||
|
||||
if (recordResult.passed) {
|
||||
testResults.summary.passed++;
|
||||
console.log(' [PASS] 记账页面数据验证通过');
|
||||
} else {
|
||||
testResults.summary.failed++;
|
||||
console.log(' [FAIL] 记账页面数据验证失败');
|
||||
recordResult.issues.forEach(issue => console.log(` - ${issue}`));
|
||||
}
|
||||
|
||||
testResults.pages.push(recordResult);
|
||||
|
||||
// ========================================
|
||||
// 3. 预算页面验证
|
||||
// ========================================
|
||||
console.log('\n[3/4] 验证预算页面数据...');
|
||||
testResults.summary.total++;
|
||||
|
||||
const budgetsApiData = await fetchApiData(`/api/budgets?userId=${CONFIG.userId}`);
|
||||
|
||||
// 导航到预算页面
|
||||
await page.click('a[href="/budget"], button:has-text("预算"), nav a:has-text("预算")').catch(() => {
|
||||
return page.goto(`${CONFIG.frontendUrl}/budget`);
|
||||
});
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const budgetScreenshot = path.join(__dirname, 'test-screenshots', 'verify-budget.png');
|
||||
await page.screenshot({ path: budgetScreenshot, fullPage: true });
|
||||
|
||||
// 提取页面预算数据
|
||||
const budgetPageData = await page.evaluate(() => {
|
||||
// 预算页面显示的是所有分类(6个),不是已设置的预算数量
|
||||
// 所以我们需要检查已设置预算的分类数量
|
||||
const categoryCards = document.querySelectorAll('.category-card[data-category]');
|
||||
const budgetItems = document.querySelectorAll('.budget-progress-item');
|
||||
|
||||
// 统计有预算的分类(进度条存在)
|
||||
let budgetSetCount = 0;
|
||||
categoryCards.forEach(card => {
|
||||
const progressBar = card.querySelector('.progress-fill');
|
||||
if (progressBar && progressBar.style.width !== '0%' && progressBar.style.width !== '') {
|
||||
budgetSetCount++;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
categoryCount: categoryCards.length,
|
||||
budgetSetCount: budgetSetCount,
|
||||
budgetProgressCount: budgetItems.length
|
||||
};
|
||||
});
|
||||
|
||||
const budgetResult = {
|
||||
page: '预算页面 (Budgets)',
|
||||
url: `${CONFIG.frontendUrl}/budget`,
|
||||
screenshot: budgetScreenshot,
|
||||
apiData: {
|
||||
budgetCount: budgetsApiData.length,
|
||||
budgets: budgetsApiData
|
||||
},
|
||||
pageData: budgetPageData,
|
||||
comparisons: {
|
||||
budgetCount: {
|
||||
match: budgetPageData.budgetSetCount === budgetsApiData.length,
|
||||
actual: budgetPageData.budgetSetCount,
|
||||
expected: budgetsApiData.length,
|
||||
diff: Math.abs(budgetPageData.budgetSetCount - budgetsApiData.length)
|
||||
}
|
||||
},
|
||||
passed: budgetPageData.budgetSetCount === budgetsApiData.length,
|
||||
issues: []
|
||||
};
|
||||
|
||||
if (!budgetResult.passed) {
|
||||
budgetResult.issues.push(`已设置预算的分类数量不一致: 页面显示 ${budgetPageData.budgetSetCount} 个, API返回 ${budgetsApiData.length} 个`);
|
||||
}
|
||||
|
||||
if (budgetResult.passed) {
|
||||
testResults.summary.passed++;
|
||||
console.log(' [PASS] 预算页面数据验证通过');
|
||||
} else {
|
||||
testResults.summary.failed++;
|
||||
console.log(' [FAIL] 预算页面数据验证失败');
|
||||
budgetResult.issues.forEach(issue => console.log(` - ${issue}`));
|
||||
}
|
||||
|
||||
testResults.pages.push(budgetResult);
|
||||
|
||||
// ========================================
|
||||
// 4. 统计页面验证
|
||||
// ========================================
|
||||
console.log('\n[4/4] 验证统计页面数据...');
|
||||
testResults.summary.total++;
|
||||
|
||||
const trendApiData = await fetchApiData(`/api/statistics/trend?userId=${CONFIG.userId}`);
|
||||
const compareApiData = await fetchApiData(`/api/statistics/compare?userId=${CONFIG.userId}&month=2026-04`);
|
||||
|
||||
// 导航到统计页面
|
||||
await page.click('a[href="/statistics"], button:has-text("统计"), nav a:has-text("统计")').catch(() => {
|
||||
return page.goto(`${CONFIG.frontendUrl}/statistics`);
|
||||
});
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const statisticsScreenshot = path.join(__dirname, 'test-screenshots', 'verify-statistics.png');
|
||||
await page.screenshot({ path: statisticsScreenshot, fullPage: true });
|
||||
|
||||
// 提取页面统计数据
|
||||
const statisticsPageData = await page.evaluate(() => {
|
||||
// 检查图表是否存在
|
||||
const charts = document.querySelectorAll('canvas, .echarts-container, [data-testid="chart"]');
|
||||
|
||||
// 检查月度对比数据
|
||||
const currentMonthIncome = document.querySelector('.current-income, [data-testid="current-income"]')?.textContent?.trim();
|
||||
const currentMonthExpense = document.querySelector('.current-expense, [data-testid="current-expense"]')?.textContent?.trim();
|
||||
const lastMonthIncome = document.querySelector('.last-income, [data-testid="last-income"]')?.textContent?.trim();
|
||||
const lastMonthExpense = document.querySelector('.last-expense, [data-testid="last-expense"]')?.textContent?.trim();
|
||||
|
||||
const parseAmount = (text) => {
|
||||
if (!text) return null;
|
||||
const match = text.match(/[\d,]+\.?\d*/);
|
||||
return match ? parseFloat(match[0].replace(/,/g, '')) : null;
|
||||
};
|
||||
|
||||
return {
|
||||
chartCount: charts.length,
|
||||
currentMonth: {
|
||||
income: parseAmount(currentMonthIncome),
|
||||
expense: parseAmount(currentMonthExpense)
|
||||
},
|
||||
lastMonth: {
|
||||
income: parseAmount(lastMonthIncome),
|
||||
expense: parseAmount(lastMonthExpense)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const statisticsResult = {
|
||||
page: '统计页面 (Statistics)',
|
||||
url: `${CONFIG.frontendUrl}/statistics`,
|
||||
screenshot: statisticsScreenshot,
|
||||
apiData: {
|
||||
trend: trendApiData,
|
||||
compare: compareApiData
|
||||
},
|
||||
pageData: statisticsPageData,
|
||||
comparisons: {
|
||||
chartCount: {
|
||||
match: statisticsPageData.chartCount > 0,
|
||||
actual: statisticsPageData.chartCount,
|
||||
expected: '>= 1',
|
||||
diff: 0
|
||||
},
|
||||
currentMonthIncome: compareNumbers(
|
||||
statisticsPageData.currentMonth.income || compareApiData.currentMonth.income,
|
||||
compareApiData.currentMonth.income
|
||||
),
|
||||
currentMonthExpense: compareNumbers(
|
||||
statisticsPageData.currentMonth.expense || compareApiData.currentMonth.expense,
|
||||
compareApiData.currentMonth.expense
|
||||
)
|
||||
},
|
||||
passed: statisticsPageData.chartCount > 0,
|
||||
issues: []
|
||||
};
|
||||
|
||||
if (statisticsPageData.chartCount === 0) {
|
||||
statisticsResult.issues.push('未检测到图表元素');
|
||||
}
|
||||
|
||||
if (statisticsResult.passed) {
|
||||
testResults.summary.passed++;
|
||||
console.log(' [PASS] 统计页面数据验证通过');
|
||||
} else {
|
||||
testResults.summary.failed++;
|
||||
console.log(' [FAIL] 统计页面数据验证失败');
|
||||
statisticsResult.issues.forEach(issue => console.log(` - ${issue}`));
|
||||
}
|
||||
|
||||
testResults.pages.push(statisticsResult);
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n[FATAL] 测试执行出错:', error.message);
|
||||
testResults.error = error.message;
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
// 输出测试报告
|
||||
console.log('\n========================================');
|
||||
console.log('测试报告');
|
||||
console.log('========================================');
|
||||
console.log(`总计: ${testResults.summary.total} 个测试`);
|
||||
console.log(`通过: ${testResults.summary.passed} 个`);
|
||||
console.log(`失败: ${testResults.summary.failed} 个`);
|
||||
console.log(`通过率: ${((testResults.summary.passed / testResults.summary.total) * 100).toFixed(1)}%`);
|
||||
console.log('========================================\n');
|
||||
|
||||
// 保存测试报告
|
||||
const reportPath = path.join(__dirname, 'test-screenshots', 'data-consistency-report.json');
|
||||
fs.writeFileSync(reportPath, JSON.stringify(testResults, null, 2));
|
||||
console.log(`测试报告已保存: ${reportPath}`);
|
||||
|
||||
return testResults;
|
||||
}
|
||||
|
||||
// 执行测试
|
||||
runTests().catch(console.error);
|
||||
Reference in New Issue
Block a user