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