ether-admin/test-project-e2e.cjs

700 lines
22 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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.

const { chromium } = require('playwright');
// 测试配置
const CONFIG = {
baseUrl: 'http://localhost:5175',
apiUrl: 'http://localhost:8080',
credentials: {
username: 'admin',
password: 'Admin@123'
},
timeout: 30000,
screenshotDir: '/tmp/e2e-project-test'
};
// 测试结果记录
const testResults = {
passed: [],
failed: [],
warnings: []
};
// 工具函数
const log = {
info: (msg) => console.log(`[INFO] ${msg}`),
success: (msg) => console.log(`\x1b[32m[SUCCESS]\x1b[0m ${msg}`),
error: (msg) => console.log(`\x1b[31m[ERROR]\x1b[0m ${msg}`),
warning: (msg) => console.log(`\x1b[33m[WARNING]\x1b[0m ${msg}`),
test: (name, status) => {
const icon = status === 'PASS' ? '✓' : '✗';
const color = status === 'PASS' ? '\x1b[32m' : '\x1b[31m';
console.log(` ${color}${icon}\x1b[0m ${name}`);
if (status === 'PASS') {
testResults.passed.push(name);
} else {
testResults.failed.push(name);
}
}
};
// 等待应用初始化
async function waitForAppInit(page, timeout = 30000) {
// 等待页面完全加载
await page.waitForLoadState('networkidle');
// 等待关键元素出现
try {
await page.waitForSelector('.ant-layout', { timeout: 5000 });
await page.waitForTimeout(1000); // 额外等待确保稳定
return true;
} catch (e) {
// 如果没有 layout可能是登录页
await page.waitForTimeout(1000);
return true;
}
}
// 登录函数
async function login(page) {
log.info('执行登录...');
await page.goto(`${CONFIG.baseUrl}/login`, { waitUntil: 'networkidle' });
await waitForAppInit(page);
// 等待登录表单加载
await page.waitForSelector('.login-form', { timeout: 10000 });
// 填写用户名
const usernameInput = await page.$('.login-form input[type="text"]');
if (usernameInput) {
await usernameInput.fill(CONFIG.credentials.username);
} else {
// 尝试其他选择器
const inputs = await page.$$('.login-form input');
if (inputs.length > 0) {
await inputs[0].fill(CONFIG.credentials.username);
}
}
// 填写密码
const passwordInput = await page.$('.login-form input[type="password"]');
if (passwordInput) {
await passwordInput.fill(CONFIG.credentials.password);
}
// 点击登录按钮
const loginButton = await page.$('.login-btn');
if (loginButton) {
await loginButton.click();
} else {
// 尝试其他选择器
const buttons = await page.$$('.login-form button');
if (buttons.length > 0) {
await buttons[0].click();
}
}
// 等待登录成功
await page.waitForURL('**/dashboard', { timeout: 10000 });
const url = page.url();
if (url.includes('/login')) {
throw new Error('登录失败,仍在登录页');
}
log.success('登录成功');
await page.screenshot({ path: `${CONFIG.screenshotDir}/01-login-success.png` });
}
// 测试1: 项目列表页面功能
async function testProjectList(page) {
log.info('测试项目列表页面功能...');
// 导航到项目列表
await page.goto(`${CONFIG.baseUrl}/project/list`, { waitUntil: 'networkidle' });
await waitForAppInit(page);
await page.waitForTimeout(3000); // 等待数据加载
await page.screenshot({ path: `${CONFIG.screenshotDir}/02-project-list.png` });
// 测试1.1: 验证页面标题
try {
const pageTitle = await page.textContent('.page-title');
if (pageTitle && pageTitle.includes('项目管理')) {
log.test('项目列表 - 页面标题显示正确', 'PASS');
} else {
log.test('项目列表 - 页面标题显示正确', 'FAIL');
testResults.warnings.push('页面标题不匹配');
}
} catch (e) {
log.test('项目列表 - 页面标题显示正确', 'FAIL');
testResults.warnings.push('无法找到页面标题元素');
}
// 测试1.2: 验证搜索功能
try {
const searchInput = await page.$('input[placeholder*="项目名称"]');
if (searchInput) {
await searchInput.fill('测试项目');
await page.waitForTimeout(500);
// 点击查询按钮
const searchButton = await page.$('button:has-text("查询")');
if (searchButton) {
await searchButton.click();
await page.waitForTimeout(1000);
}
log.test('项目列表 - 搜索功能可用', 'PASS');
} else {
log.test('项目列表 - 搜索功能可用', 'FAIL');
}
} catch (e) {
log.test('项目列表 - 搜索功能可用', 'FAIL');
testResults.warnings.push(`搜索功能异常: ${e.message}`);
}
// 测试1.3: 验证状态筛选功能
try {
// 重置搜索条件
const resetButton = await page.$('button:has-text("重置")');
if (resetButton) {
await resetButton.click();
await page.waitForTimeout(1000);
}
// 查找状态下拉框
const statusSelect = await page.$('.ant-select');
if (statusSelect) {
await statusSelect.click();
await page.waitForTimeout(500);
// 检查下拉选项
const options = await page.$$('.ant-select-item');
if (options.length > 0) {
log.test('项目列表 - 状态筛选功能可用', 'PASS');
// 关闭下拉框
await page.keyboard.press('Escape');
await page.waitForTimeout(300);
} else {
log.test('项目列表 - 状态筛选功能可用', 'FAIL');
}
} else {
log.test('项目列表 - 状态筛选功能可用', 'FAIL');
}
} catch (e) {
log.test('项目列表 - 状态筛选功能可用', 'FAIL');
testResults.warnings.push(`状态筛选异常: ${e.message}`);
}
// 测试1.4: 验证表格显示
try {
const table = await page.$('.ant-table');
if (table) {
// 等待表格数据加载
await page.waitForTimeout(2000);
const rows = await page.$$('.ant-table-tbody tr');
const rowCount = rows.length;
// 检查是否有空数据提示
const emptyText = await page.textContent('.ant-empty-description').catch(() => null);
if (emptyText && emptyText.includes('No data')) {
log.test('项目列表 - 表格显示正常 (无数据)', 'PASS');
testResults.warnings.push('项目列表无数据可能需要检查API或数据');
} else if (rowCount > 0) {
// 验证表格内容
const firstRowText = await page.textContent('.ant-table-tbody tr:first-child').catch(() => '');
if (firstRowText && firstRowText.includes('PRJ')) {
log.test(`项目列表 - 表格显示正常 (共${rowCount}条记录)`, 'PASS');
} else {
log.test(`项目列表 - 表格显示正常 (共${rowCount}条记录)`, 'PASS');
}
} else {
log.test('项目列表 - 表格显示正常', 'FAIL');
}
} else {
log.test('项目列表 - 表格显示正常', 'FAIL');
}
} catch (e) {
log.test('项目列表 - 表格显示正常', 'FAIL');
testResults.warnings.push(`表格显示异常: ${e.message}`);
}
// 测试1.5: 验证分页功能(只有数据超过一页时才显示)
try {
const pagination = await page.$('.ant-pagination');
if (pagination) {
log.test('项目列表 - 分页组件显示正常', 'PASS');
} else {
// 检查是否因为数据太少而不显示分页
const rows = await page.$$('.ant-table-tbody tr');
if (rows.length <= 10) {
log.test('项目列表 - 分页组件显示正常 (数据少于10条不显示分页)', 'PASS');
} else {
log.test('项目列表 - 分页组件显示正常', 'FAIL');
}
}
} catch (e) {
log.test('项目列表 - 分页组件显示正常', 'FAIL');
}
await page.screenshot({ path: `${CONFIG.screenshotDir}/03-project-list-features.png` });
}
// 测试2: 项目详情页面功能
async function testProjectDetail(page) {
log.info('测试项目详情页面功能...');
// 返回项目列表
await page.goto(`${CONFIG.baseUrl}/project/list`, { waitUntil: 'networkidle' });
await waitForAppInit(page);
// 等待表格加载
await page.waitForTimeout(2000);
// 查找第一个项目的详情按钮
try {
// 先检查是否有数据
const rows = await page.$$('.ant-table-tbody tr');
if (rows.length === 0) {
log.warning('项目列表为空,跳过详情测试');
testResults.warnings.push('项目列表为空,无法测试详情页');
return;
}
// 尝试多种方式查找详情按钮
let detailButton = await page.$('button:has-text("详情")');
if (!detailButton) {
// 尝试在操作列中查找
detailButton = await page.$('.ant-table-tbody tr:first-child button:has-text("详情")');
}
if (!detailButton) {
// 尝试查找包含"详情"文本的按钮
const allButtons = await page.$$('.ant-table-tbody tr:first-child button');
for (const btn of allButtons) {
const text = await btn.textContent();
if (text && text.includes('详情')) {
detailButton = btn;
break;
}
}
}
if (detailButton) {
await detailButton.click();
await page.waitForTimeout(2000);
} else {
log.warning('未找到详情按钮尝试从表格获取项目ID');
// 从表格获取项目ID
const firstRow = await page.$('.ant-table-tbody tr:first-child');
if (firstRow) {
const cells = await firstRow.$$('td');
if (cells.length > 0) {
const projectCode = await cells[0].textContent();
log.info(`找到项目编码: ${projectCode}`);
}
}
// 跳过详情测试
log.warning('无法访问详情页,跳过详情测试');
testResults.warnings.push('无法访问项目详情页');
return;
}
await page.screenshot({ path: `${CONFIG.screenshotDir}/04-project-detail.png` });
// 测试2.1: 验证统计卡片显示
try {
const statCards = await page.$$('.ant-statistic');
if (statCards.length >= 6) {
log.test('项目详情 - 统计卡片显示正常', 'PASS');
} else {
log.test('项目详情 - 统计卡片显示正常', 'FAIL');
testResults.warnings.push(`统计卡片数量不足: ${statCards.length}`);
}
} catch (e) {
log.test('项目详情 - 统计卡片显示正常', 'FAIL');
}
// 测试2.2: 验证Tab页切换
try {
const tabs = await page.$$('.ant-tabs-tab');
if (tabs.length >= 4) {
log.test('项目详情 - Tab页显示正常', 'PASS');
// 测试切换到成员管理
for (const tab of tabs) {
const text = await tab.textContent();
if (text && text.includes('成员管理')) {
await tab.click();
await page.waitForTimeout(1000);
await page.screenshot({ path: `${CONFIG.screenshotDir}/05-member-tab.png` });
break;
}
}
} else {
log.test('项目详情 - Tab页显示正常', 'FAIL');
}
} catch (e) {
log.test('项目详情 - Tab页显示正常', 'FAIL');
testResults.warnings.push(`Tab页异常: ${e.message}`);
}
// 测试2.3: 验证基本信息显示
try {
// 等待数据加载
await page.waitForTimeout(1000);
// 尝试多种选择器
let found = false;
// 方式1: 检查 descriptions-item
const descriptions = await page.$$('.ant-descriptions-item');
if (descriptions.length > 0) {
found = true;
}
// 方式2: 检查内容是否包含项目信息
if (!found) {
const infoContent = await page.textContent('.ant-tabs-tabpane-active').catch(() => '');
if (infoContent && (infoContent.includes('项目编码') || infoContent.includes('PRJ') || infoContent.includes('项目名称'))) {
found = true;
}
}
// 方式3: 检查是否有描述列表
if (!found) {
const descList = await page.$('.ant-descriptions');
if (descList) {
found = true;
}
}
log.test('项目详情 - 基本信息显示正常', found ? 'PASS' : 'FAIL');
} catch (e) {
log.test('项目详情 - 基本信息显示正常', 'FAIL');
}
} catch (e) {
log.error(`项目详情测试失败: ${e.message}`);
testResults.warnings.push(`项目详情测试异常: ${e.message}`);
}
}
// 测试3: 项目成员管理
async function testProjectMember(page) {
log.info('测试项目成员管理功能...');
try {
// 确保在成员管理Tab
const memberTab = await page.$('.ant-tabs-tab:has-text("成员管理")');
if (!memberTab) {
// 尝试点击成员管理Tab
const tabs = await page.$$('.ant-tabs-tab');
for (const tab of tabs) {
const text = await tab.textContent();
if (text && text.includes('成员管理')) {
await tab.click();
await page.waitForTimeout(1000);
break;
}
}
}
// 测试3.1: 验证成员列表显示
try {
const memberTable = await page.$('.ant-tabs-tabpane-active .ant-table');
if (memberTable) {
const rows = await page.$$('.ant-tabs-tabpane-active .ant-table-tbody tr');
log.test('成员管理 - 成员列表显示正常', 'PASS');
} else {
log.test('成员管理 - 成员列表显示正常', 'FAIL');
}
} catch (e) {
log.test('成员管理 - 成员列表显示正常', 'FAIL');
}
// 测试3.2: 验证添加成员按钮
try {
const addButton = await page.$('button:has-text("添加成员")');
if (addButton) {
log.test('成员管理 - 添加成员按钮存在', 'PASS');
// 点击添加成员按钮
await addButton.click();
await page.waitForTimeout(500);
// 检查弹窗是否显示
const modal = await page.$('.ant-modal');
if (modal) {
log.test('成员管理 - 添加成员弹窗显示正常', 'PASS');
await page.screenshot({ path: `${CONFIG.screenshotDir}/06-add-member-modal.png` });
// 关闭弹窗
const cancelButton = await page.$('.ant-modal button:has-text("取消")');
if (cancelButton) {
await cancelButton.click();
await page.waitForTimeout(300);
}
} else {
log.test('成员管理 - 添加成员弹窗显示正常', 'FAIL');
}
} else {
log.test('成员管理 - 添加成员按钮存在', 'FAIL');
}
} catch (e) {
log.test('成员管理 - 添加成员功能测试', 'FAIL');
testResults.warnings.push(`添加成员异常: ${e.message}`);
}
} catch (e) {
log.error(`成员管理测试失败: ${e.message}`);
testResults.warnings.push(`成员管理测试异常: ${e.message}`);
}
}
// 测试4: 项目状态管理
async function testProjectStatus(page) {
log.info('测试项目状态管理功能...');
// 返回项目列表
await page.goto(`${CONFIG.baseUrl}/project/list`, { waitUntil: 'networkidle' });
await waitForAppInit(page);
await page.waitForTimeout(2000);
try {
// 检查是否有数据
const rows = await page.$$('.ant-table-tbody tr');
if (rows.length === 0) {
log.warning('项目列表为空,跳过状态测试');
testResults.warnings.push('项目列表为空,无法测试状态管理');
return;
}
// 测试4.1: 验证状态标签显示
try {
const statusTags = await page.$$('.ant-table-tbody .ant-tag');
if (statusTags.length > 0) {
log.test('项目状态 - 状态标签显示正常', 'PASS');
} else {
log.test('项目状态 - 状态标签显示正常', 'FAIL');
}
} catch (e) {
log.test('项目状态 - 状态标签显示正常', 'FAIL');
}
// 测试4.2: 验证状态切换按钮
try {
const firstRowButtons = await page.$$('.ant-table-tbody tr:first-child button');
let hasToggle = false;
for (const button of firstRowButtons) {
const text = await button.textContent();
if (text && (text.includes('禁用') || text.includes('启用'))) {
hasToggle = true;
break;
}
}
log.test('项目状态 - 状态切换按钮存在', hasToggle ? 'PASS' : 'FAIL');
} catch (e) {
log.test('项目状态 - 状态切换按钮存在', 'FAIL');
}
await page.screenshot({ path: `${CONFIG.screenshotDir}/07-project-status.png` });
} catch (e) {
log.error(`项目状态测试失败: ${e.message}`);
testResults.warnings.push(`项目状态测试异常: ${e.message}`);
}
}
// 测试5: 项目配置管理
async function testProjectConfig(page) {
log.info('测试项目配置管理功能...');
// 导航到项目详情
await page.goto(`${CONFIG.baseUrl}/project/list`, { waitUntil: 'networkidle' });
await waitForAppInit(page);
await page.waitForTimeout(2000);
try {
// 检查是否有数据
const rows = await page.$$('.ant-table-tbody tr');
if (rows.length === 0) {
log.warning('项目列表为空,跳过配置测试');
testResults.warnings.push('项目列表为空,无法测试项目配置');
return;
}
// 点击第一个项目的详情按钮
let detailButton = await page.$('.ant-table-tbody tr:first-child button:has-text("详情")');
if (!detailButton) {
const allButtons = await page.$$('.ant-table-tbody tr:first-child button');
for (const btn of allButtons) {
const text = await btn.textContent();
if (text && text.includes('详情')) {
detailButton = btn;
break;
}
}
}
if (!detailButton) {
log.warning('未找到详情按钮,跳过配置测试');
testResults.warnings.push('无法访问项目配置页');
return;
}
await detailButton.click();
await page.waitForTimeout(2000);
// 切换到配置Tab
const tabs = await page.$$('.ant-tabs-tab');
for (const tab of tabs) {
const text = await tab.textContent();
if (text && text.includes('项目配置')) {
await tab.click();
await page.waitForTimeout(1000);
break;
}
}
await page.screenshot({ path: `${CONFIG.screenshotDir}/08-project-config.png` });
// 测试5.1: 验证配置项显示
try {
const switches = await page.$$('.ant-switch');
if (switches.length >= 8) {
log.test('项目配置 - 配置项显示正常', 'PASS');
} else {
log.test('项目配置 - 配置项显示正常', 'FAIL');
testResults.warnings.push(`配置项数量不足: ${switches.length}`);
}
} catch (e) {
log.test('项目配置 - 配置项显示正常', 'FAIL');
}
// 测试5.2: 验证保存按钮
try {
const saveButton = await page.$('button:has-text("保存配置")');
if (saveButton) {
log.test('项目配置 - 保存按钮存在', 'PASS');
} else {
log.test('项目配置 - 保存按钮存在', 'FAIL');
}
} catch (e) {
log.test('项目配置 - 保存按钮存在', 'FAIL');
}
} catch (e) {
log.error(`项目配置测试失败: ${e.message}`);
testResults.warnings.push(`项目配置测试异常: ${e.message}`);
}
}
// 主测试流程
async function runTests() {
log.info('========================================');
log.info('Ether 项目管理功能 E2E 测试');
log.info('========================================');
log.info(`测试时间: ${new Date().toLocaleString('zh-CN')}`);
log.info(`前端地址: ${CONFIG.baseUrl}`);
log.info(`后端地址: ${CONFIG.apiUrl}`);
log.info('========================================\n');
const browser = await chromium.launch({
headless: true
});
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 }
});
const page = await context.newPage();
// 监听控制台输出
page.on('console', msg => {
const text = msg.text();
if (msg.type() === 'error') {
testResults.warnings.push(`浏览器控制台错误: ${text}`);
} else if (text.includes('API') || text.includes('请求') || text.includes('失败') || text.includes('登录')) {
log.info(`控制台: ${text}`);
}
});
// 监听网络请求
page.on('response', async (response) => {
const url = response.url();
if (url.includes('/api/mdm/projects')) {
try {
const status = response.status();
log.info(`API请求: ${url} - 状态: ${status}`);
if (status === 200) {
const body = await response.json();
log.info(`API响应数据: ${JSON.stringify(body).substring(0, 200)}...`);
}
} catch (e) {
// 忽略解析错误
}
}
});
page.on('pageerror', error => {
testResults.warnings.push(`页面错误: ${error.message}`);
});
try {
// 登录
await login(page);
// 执行测试
await testProjectList(page);
await testProjectDetail(page);
await testProjectMember(page);
await testProjectStatus(page);
await testProjectConfig(page);
} catch (error) {
log.error(`测试执行失败: ${error.message}`);
await page.screenshot({ path: `${CONFIG.screenshotDir}/error.png` });
} finally {
await browser.close();
}
// 输出测试报告
log.info('\n========================================');
log.info('测试报告');
log.info('========================================');
log.info(`通过: ${testResults.passed.length}`);
log.info(`失败: ${testResults.failed.length}`);
log.info(`警告: ${testResults.warnings.length}`);
if (testResults.failed.length > 0) {
log.error('\n失败的测试项:');
testResults.failed.forEach(item => log.error(` - ${item}`));
}
if (testResults.warnings.length > 0) {
log.warning('\n警告信息:');
testResults.warnings.forEach(item => log.warning(` - ${item}`));
}
log.info('\n========================================');
log.info(`测试完成! 截图保存在: ${CONFIG.screenshotDir}`);
log.info('========================================\n');
// 返回退出码
process.exit(testResults.failed.length > 0 ? 1 : 0);
}
// 执行测试
runTests().catch(error => {
log.error(`测试执行异常: ${error.message}`);
process.exit(1);
});