Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
384
frontend-vben/apps/web-antd/tests/menu-comparison.test.ts
Normal file
384
frontend-vben/apps/web-antd/tests/menu-comparison.test.ts
Normal file
@@ -0,0 +1,384 @@
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
|
||||
interface MenuStats {
|
||||
totalCount: number;
|
||||
menuItems: string[];
|
||||
categories: string[];
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface MenuComparison {
|
||||
backend: MenuStats;
|
||||
mock: MenuStats;
|
||||
differences: {
|
||||
missingInMock: string[];
|
||||
onlyInMock: string[];
|
||||
totalDifference: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单对比测试
|
||||
* 测试前后端菜单显示差异,找出不一致的地方
|
||||
*/
|
||||
test.describe('菜单显示对比测试', () => {
|
||||
let comparison: MenuComparison;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
console.log('🚀 开始菜单对比测试');
|
||||
});
|
||||
|
||||
test('1️⃣ 后端启动状态下的菜单测试', async ({ page }) => {
|
||||
console.log('📊 测试后端启动状态下的菜单');
|
||||
|
||||
// 访问应用
|
||||
await page.goto('http://localhost:5174');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 检查是否在登录页面
|
||||
const isLoginPage = await page
|
||||
.locator('input[placeholder*="用户名"], input[placeholder*="admin"]')
|
||||
.isVisible();
|
||||
|
||||
if (isLoginPage) {
|
||||
console.log('🔐 检测到登录页面,开始登录');
|
||||
|
||||
// 填写登录信息
|
||||
await page.fill(
|
||||
'input[placeholder*="用户名"], input[placeholder*="admin"]',
|
||||
'admin',
|
||||
);
|
||||
await page.fill(
|
||||
'input[placeholder*="密码"], input[type="password"]',
|
||||
'111111',
|
||||
);
|
||||
|
||||
// 点击登录按钮
|
||||
await page.click('button[type="submit"], button:has-text("登录")');
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
// 等待菜单加载
|
||||
await page.waitForSelector(
|
||||
'aside[class*="sidebar"], nav[class*="menu"], .ant-menu',
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 获取所有菜单项
|
||||
const menuStats = await extractMenuItems(page);
|
||||
|
||||
// 截图保存
|
||||
await page.screenshot({
|
||||
path: 'test-results/screenshots/backend-menu-full.png',
|
||||
fullPage: true,
|
||||
});
|
||||
|
||||
console.log(`✅ 后端模式菜单统计:`);
|
||||
console.log(` - 总菜单项: ${menuStats.totalCount}`);
|
||||
console.log(` - 主要分类: ${menuStats.categories.length}`);
|
||||
console.log(
|
||||
` - 菜单列表: ${menuStats.menuItems.slice(0, 10).join(', ')}${menuStats.menuItems.length > 10 ? '...' : ''}`,
|
||||
);
|
||||
|
||||
comparison = {
|
||||
backend: menuStats,
|
||||
mock: { totalCount: 0, menuItems: [], categories: [], timestamp: '' },
|
||||
differences: { missingInMock: [], onlyInMock: [], totalDifference: 0 },
|
||||
};
|
||||
});
|
||||
|
||||
test('2️⃣ Mock模式下的菜单测试', async ({ page, browser }) => {
|
||||
console.log('🎭 测试Mock模式下的菜单');
|
||||
|
||||
// 创建新的浏览器上下文来模拟无后端状态
|
||||
const context = await browser.newContext();
|
||||
const mockPage = await context.newPage();
|
||||
|
||||
// 拦截所有API请求,模拟后端不可用
|
||||
await mockPage.route('**/api/**', (route) => {
|
||||
route.abort('failed');
|
||||
});
|
||||
|
||||
await mockPage.route('**/auth/**', (route) => {
|
||||
route.abort('failed');
|
||||
});
|
||||
|
||||
await mockPage.route('**/system/**', (route) => {
|
||||
route.abort('failed');
|
||||
});
|
||||
|
||||
// 访问应用
|
||||
await mockPage.goto('http://localhost:5174');
|
||||
await mockPage.waitForTimeout(3000);
|
||||
|
||||
// 尝试登录(使用Mock数据)
|
||||
try {
|
||||
const isLoginPage = await mockPage
|
||||
.locator('input[placeholder*="用户名"], input[placeholder*="admin"]')
|
||||
.isVisible();
|
||||
|
||||
if (isLoginPage) {
|
||||
console.log('🔐 Mock模式登录测试');
|
||||
|
||||
// 使用Mock用户登录
|
||||
await mockPage.fill(
|
||||
'input[placeholder*="用户名"], input[placeholder*="admin"]',
|
||||
'admin',
|
||||
);
|
||||
await mockPage.fill(
|
||||
'input[placeholder*="密码"], input[type="password"]',
|
||||
'123456',
|
||||
);
|
||||
|
||||
await mockPage.click('button[type="submit"], button:has-text("登录")');
|
||||
await mockPage.waitForTimeout(3000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ Mock模式登录失败,尝试直接访问主页');
|
||||
await mockPage.goto('http://localhost:5174/dashboard');
|
||||
await mockPage.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
// 等待菜单加载或超时
|
||||
try {
|
||||
await mockPage.waitForSelector(
|
||||
'aside[class*="sidebar"], nav[class*="menu"], .ant-menu',
|
||||
{ timeout: 8000 },
|
||||
);
|
||||
await mockPage.waitForTimeout(2000);
|
||||
} catch (error) {
|
||||
console.log('⚠️ Mock模式菜单加载超时');
|
||||
}
|
||||
|
||||
// 获取Mock模式菜单项
|
||||
const mockMenuStats = await extractMenuItems(mockPage);
|
||||
|
||||
// 截图保存
|
||||
await mockPage.screenshot({
|
||||
path: 'test-results/screenshots/mock-menu-full.png',
|
||||
fullPage: true,
|
||||
});
|
||||
|
||||
console.log(`🎭 Mock模式菜单统计:`);
|
||||
console.log(` - 总菜单项: ${mockMenuStats.totalCount}`);
|
||||
console.log(` - 主要分类: ${mockMenuStats.categories.length}`);
|
||||
console.log(
|
||||
` - 菜单列表: ${mockMenuStats.menuItems.slice(0, 10).join(', ')}${mockMenuStats.menuItems.length > 10 ? '...' : ''}`,
|
||||
);
|
||||
|
||||
// 更新对比数据
|
||||
comparison.mock = mockMenuStats;
|
||||
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test('3️⃣ 生成菜单对比报告', async () => {
|
||||
console.log('📋 生成详细对比报告');
|
||||
|
||||
// 计算差异
|
||||
const backendMenus = new Set(comparison.backend.menuItems);
|
||||
const mockMenus = new Set(comparison.mock.menuItems);
|
||||
|
||||
const missingInMock = Array.from(backendMenus).filter(
|
||||
(item) => !mockMenus.has(item),
|
||||
);
|
||||
const onlyInMock = Array.from(mockMenus).filter(
|
||||
(item) => !backendMenus.has(item),
|
||||
);
|
||||
|
||||
comparison.differences = {
|
||||
missingInMock,
|
||||
onlyInMock,
|
||||
totalDifference: Math.abs(
|
||||
comparison.backend.totalCount - comparison.mock.totalCount,
|
||||
),
|
||||
};
|
||||
|
||||
// 生成报告
|
||||
const report = generateComparisonReport(comparison);
|
||||
|
||||
// 保存报告到文件
|
||||
const fs = await import('fs');
|
||||
await fs.promises.writeFile(
|
||||
'test-results/menu-comparison-report.json',
|
||||
JSON.stringify(comparison, null, 2),
|
||||
);
|
||||
|
||||
// 输出控制台报告
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('🔍 菜单对比分析报告');
|
||||
console.log('='.repeat(80));
|
||||
console.log(report);
|
||||
console.log('='.repeat(80));
|
||||
|
||||
// 断言检查
|
||||
expect(comparison.backend.totalCount).toBeGreaterThan(0);
|
||||
// 注意:Mock模式可能完全没有菜单,这是正常的测试结果
|
||||
console.log(
|
||||
`⚠️ 发现重大差异: Mock模式缺失 ${comparison.differences.totalDifference} 个菜单项`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 提取页面菜单项信息
|
||||
*/
|
||||
async function extractMenuItems(page: Page): Promise<MenuStats> {
|
||||
// 等待菜单容器加载
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
try {
|
||||
// 尝试多种菜单选择器
|
||||
const menuSelectors = [
|
||||
'.ant-menu-item',
|
||||
'.ant-menu-submenu-title',
|
||||
'[role="menuitem"]',
|
||||
'li[class*="menu"]',
|
||||
'a[class*="menu"]',
|
||||
'.sidebar-menu-item',
|
||||
'.nav-item',
|
||||
];
|
||||
|
||||
let allMenuItems: string[] = [];
|
||||
let categories: string[] = [];
|
||||
|
||||
for (const selector of menuSelectors) {
|
||||
try {
|
||||
const elements = await page.locator(selector).all();
|
||||
|
||||
for (const element of elements) {
|
||||
const text = await element.textContent();
|
||||
if (text && text.trim()) {
|
||||
const cleanText = text.trim();
|
||||
if (!allMenuItems.includes(cleanText)) {
|
||||
allMenuItems.push(cleanText);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略选择器错误,继续下一个
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试获取主要分类
|
||||
try {
|
||||
const categorySelectors = [
|
||||
'.ant-menu-submenu-title',
|
||||
'.menu-group-title',
|
||||
'[class*="category"]',
|
||||
'.sidebar-title',
|
||||
];
|
||||
|
||||
for (const selector of categorySelectors) {
|
||||
try {
|
||||
const elements = await page.locator(selector).all();
|
||||
for (const element of elements) {
|
||||
const text = await element.textContent();
|
||||
if (text && text.trim()) {
|
||||
const cleanText = text.trim();
|
||||
if (!categories.includes(cleanText)) {
|
||||
categories.push(cleanText);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略选择器错误
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ 获取分类失败:', error.message);
|
||||
}
|
||||
|
||||
// 如果没有找到菜单项,尝试其他方法
|
||||
if (allMenuItems.length === 0) {
|
||||
console.log('⚠️ 使用备用方法获取菜单');
|
||||
|
||||
const fallbackSelectors = [
|
||||
'span:has-text("仪表板")',
|
||||
'span:has-text("账号管理")',
|
||||
'span:has-text("群组管理")',
|
||||
'span:has-text("私信群发")',
|
||||
'span:has-text("系统管理")',
|
||||
'a[href*="/dashboard"]',
|
||||
'a[href*="/account"]',
|
||||
'a[href*="/system"]',
|
||||
];
|
||||
|
||||
for (const selector of fallbackSelectors) {
|
||||
try {
|
||||
const elements = await page.locator(selector).all();
|
||||
for (const element of elements) {
|
||||
const text = await element.textContent();
|
||||
if (text && text.trim()) {
|
||||
const cleanText = text.trim();
|
||||
if (!allMenuItems.includes(cleanText)) {
|
||||
allMenuItems.push(cleanText);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalCount: allMenuItems.length,
|
||||
menuItems: allMenuItems,
|
||||
categories: categories,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
console.log('❌ 菜单提取失败:', error.message);
|
||||
return {
|
||||
totalCount: 0,
|
||||
menuItems: [],
|
||||
categories: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成对比报告
|
||||
*/
|
||||
function generateComparisonReport(comparison: MenuComparison): string {
|
||||
const { backend, mock, differences } = comparison;
|
||||
|
||||
let report = '';
|
||||
|
||||
report += `📊 菜单统计对比:\n`;
|
||||
report += ` 后端模式: ${backend.totalCount} 个菜单项\n`;
|
||||
report += ` Mock模式: ${mock.totalCount} 个菜单项\n`;
|
||||
report += ` 差异数量: ${differences.totalDifference} 个\n\n`;
|
||||
|
||||
if (differences.missingInMock.length > 0) {
|
||||
report += `❌ Mock模式中缺失的菜单项 (${differences.missingInMock.length}个):\n`;
|
||||
differences.missingInMock.forEach((item) => {
|
||||
report += ` - ${item}\n`;
|
||||
});
|
||||
report += '\n';
|
||||
}
|
||||
|
||||
if (differences.onlyInMock.length > 0) {
|
||||
report += `➕ 仅在Mock模式中存在的菜单项 (${differences.onlyInMock.length}个):\n`;
|
||||
differences.onlyInMock.forEach((item) => {
|
||||
report += ` - ${item}\n`;
|
||||
});
|
||||
report += '\n';
|
||||
}
|
||||
|
||||
report += `🔧 建议修复方案:\n`;
|
||||
if (differences.missingInMock.length > 0) {
|
||||
report += ` 1. 在Mock数据中添加缺失的 ${differences.missingInMock.length} 个菜单项\n`;
|
||||
report += ` 2. 检查菜单权限配置是否一致\n`;
|
||||
report += ` 3. 验证路由配置完整性\n`;
|
||||
}
|
||||
|
||||
if (differences.totalDifference === 0) {
|
||||
report += `✅ 前后端菜单完全一致,无需修复\n`;
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
Reference in New Issue
Block a user