Files
telegram-management-system/frontend-vben/apps/web-antd/tests/menu-comparison.test.ts
你的用户名 237c7802e5
Some checks failed
Deploy / deploy (push) Has been cancelled
Initial commit: Telegram Management System
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>
2025-11-04 15:37:50 +08:00

385 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}