/** * 全流程端到端测试 - 后台管理系统 UI 层 * * 使用 Playwright 驱动浏览器操作 Element Plus 后台管理页面: * 1. 利用auth.setup已有的登录态直接进入 * 2. 导航到团课管理 * 3. 创建新团课(无封面、张教练负责) * 4. 验证创建成功 * 5. 保存课程ID供后续流程使用 */ import { test, expect } from '@playwright/test'; import { GroupCourseManagementPage } from '../pages/GroupCourseManagementPage'; import { LoginPage } from '../pages/LoginPage'; import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 共享状态文件路径(供后续API测试读取) const STATE_FILE = path.resolve(__dirname, '../../..', 'test-context-ui.json'); test.describe.serial('全流程 - 后台管理UI', () => { let coursePage: GroupCourseManagementPage; const timestamp = Date.now(); const courseName = `UI全流程_${timestamp.toString(36)}`; const startDate = new Date(Date.now() + 5 * 60 * 60 * 1000); const endDate = new Date(startDate.getTime() + 60 * 60 * 1000); const pad = (n: number) => String(n).padStart(2, '0'); const startTimeStr = `${startDate.getFullYear()}-${pad(startDate.getMonth()+1)}-${pad(startDate.getDate())} ${pad(startDate.getHours())}:${pad(startDate.getMinutes())}:00`; const endTimeStr = `${endDate.getFullYear()}-${pad(endDate.getMonth()+1)}-${pad(endDate.getDate())} ${pad(endDate.getHours())}:${pad(endDate.getMinutes())}:00`; function writeState(data: Record) { try { fs.writeFileSync(STATE_FILE, JSON.stringify(data, null, 2), 'utf-8'); console.log('[UI] 状态已写入:', STATE_FILE); } catch (e) { console.error('[UI] 写入状态文件失败:', e); } } test.beforeEach(async ({ page }) => { coursePage = new GroupCourseManagementPage(page); }); test('TC-UI-001: 验证已登录状态并导航到仪表盘', async ({ page }) => { await test.step('导航到首页(利用auth.setup的登录态)', async () => { await page.goto('/'); await page.waitForLoadState('networkidle'); await page.waitForTimeout(2000); const currentUrl = page.url(); console.log('[UI] 当前URL:', currentUrl); // 如果跳转到登录页,手动登录 if (currentUrl.includes('/login')) { console.log('[UI] 需要手动登录...'); const loginPage = new LoginPage(page); await loginPage.usernameInput.fill('admin'); await loginPage.passwordInput.fill('Test@123'); await loginPage.loginButton.click(); await page.waitForURL(/\/(dashboard|\/)$/, { timeout: 30000 }); console.log('[UI] 手动登录完成'); } else { console.log('[UI] 已处于登录状态'); } await page.waitForLoadState('networkidle'); }); await test.step('保存admin token到共享状态', async () => { const token = await page.evaluate(() => localStorage.getItem('token') || ''); writeState({ adminToken: token, courseName: courseName, timestamp: timestamp, baseUrl: 'http://192.168.110.64:8084', }); console.log('[UI] Token已保存, 长度:', token.length); }); }); test('TC-UI-002: 导航到团课管理页面', async ({ page }) => { await test.step('进入团课管理', async () => { // 点击侧边栏 const sidebarLink = page.locator('.el-menu-item').filter({ hasText: '团课管理' }) .or(page.locator('span').filter({ hasText: '团课管理' })); const count = await sidebarLink.count(); if (count > 0) { await sidebarLink.first().click(); } else { // 检查是否有子菜单 const subMenu = page.locator('.el-sub-menu__title').filter({ hasText: '课程' }) .or(page.locator('.el-sub-menu__title').filter({ hasText: '团课' })); if (await subMenu.count() > 0) { await subMenu.first().click(); await page.waitForTimeout(500); const menuItem = page.locator('.el-menu-item').filter({ hasText: '团课管理' }); if (await menuItem.count() > 0) { await menuItem.first().click(); } } } console.log('[UI] 已点击团课管理导航'); }); await test.step('等待页面加载', async () => { await page.waitForLoadState('networkidle'); await page.waitForTimeout(1500); console.log('[UI] 当前URL:', page.url()); // 截图验证 await page.screenshot({ path: `test-results/course-page-${timestamp}.png` }); }); }); test('TC-UI-003: 创建团课 - 打开弹窗→填写表单→提交', async ({ page }) => { const dialog = page.locator('.el-dialog'); await test.step('3a. 确保在团课管理页面', async () => { if (!page.url().includes('course')) { await page.goto('/courses'); await page.waitForLoadState('networkidle'); await page.waitForTimeout(1000); } }); await test.step('3b. 点击新增团课按钮打开弹窗', async () => { let clicked = false; const btn1 = page.getByRole('button', { name: '新增团课' }); if (await btn1.count() > 0) { await btn1.first().click(); clicked = true; } if (!clicked) { const btn2 = page.locator('button:has-text("新增团课")'); if (await btn2.count() > 0) { await btn2.first().click(); clicked = true; } } if (!clicked) { const btn3 = page.locator('button:has-text("新增")').first(); if (await btn3.count() > 0) { await btn3.click(); clicked = true; } } if (!clicked) throw new Error('未找到新增团课按钮'); await page.waitForTimeout(800); const isVisible = await dialog.isVisible().catch(() => false); console.log('[UI] 弹窗可见:', isVisible); await page.screenshot({ path: `test-results/dialog-${timestamp}.png` }); }); await test.step('3c. 填写课程名称', async () => { const nameInput = dialog.locator('.el-form-item').filter({ hasText: '课程名称' }).locator('input'); if (await nameInput.count() > 0) { await nameInput.fill(courseName); } else { await dialog.locator('input').first().fill(courseName); } console.log('[UI] 课程名称:', courseName); }); await test.step('3d. 选择教练 - 张教练', async () => { const coachSelect = dialog.locator('.el-form-item').filter({ hasText: '教练' }).locator('.el-select'); if (await coachSelect.count() > 0) { await coachSelect.click(); await page.waitForTimeout(600); const options = page.locator('.el-select-dropdown__item').locator('visible=true'); const optCount = await options.count(); let found = false; for (let i = 0; i < optCount; i++) { const text = await options.nth(i).textContent(); if (text && text.includes('张')) { await options.nth(i).click(); console.log('[UI] 已选张教练:', text.trim()); found = true; break; } } if (!found && optCount > 0) { await options.first().click(); } await page.waitForTimeout(300); } }); await test.step('3e. 填写开始/结束时间', async () => { const startInput = dialog.locator('.el-form-item').filter({ hasText: '开始时间' }).locator('input'); if (await startInput.count() > 0) { await startInput.click(); await page.waitForTimeout(300); await startInput.fill(startTimeStr); } const endInput = dialog.locator('.el-form-item').filter({ hasText: '结束时间' }).locator('input'); if (await endInput.count() > 0) { await endInput.click(); await page.waitForTimeout(300); await endInput.fill(endTimeStr); } console.log('[UI] 时间:', startTimeStr, '~', endTimeStr); }); await test.step('3f. 填写地点(无封面)', async () => { const locInput = dialog.locator('.el-form-item').filter({ hasText: '地点' }).locator('input'); if (await locInput.count() > 0) { await locInput.fill('UI自动化测试场地'); } console.log('[UI] 封面留空 — 无封面'); }); await test.step('3g. 点击确定提交', async () => { const submitBtn = dialog.getByRole('button', { name: '确定' }) .or(dialog.locator('button:has-text("确定")')) .or(dialog.locator('.el-button--primary').last()); await submitBtn.click(); console.log('[UI] 已提交'); }); await test.step('3h. 等待响应并截图', async () => { await page.waitForTimeout(2000); await page.screenshot({ path: `test-results/after-submit-${timestamp}.png` }); const successMsg = page.locator('.el-message--success'); if (await successMsg.count() > 0) { console.log('[UI] 成功:', await successMsg.first().textContent()); } }); await test.step('3h5. 处理时间冲突警告弹窗(如有)', async () => { // 检查是否有 "时间冲突警告" 弹窗 const conflictDialog = page.locator('.el-message-box').filter({ hasText: '时间冲突警告' }); const warningDialog = page.locator('.el-overlay-message-box').filter({ hasText: '时间冲突' }); if ((await conflictDialog.count() > 0) || (await warningDialog.count() > 0)) { console.log('[UI] 检测到时间冲突警告弹窗,尝试关闭...'); await page.screenshot({ path: `test-results/time-conflict-dialog-${timestamp}.png` }); // 点击弹窗中的确定按钮 const confirmBtn = page.locator('.el-message-box__btns button').first() .or(page.locator('.el-overlay-message-box button:has-text("确定")')) .or(page.locator('.el-overlay-message-box button:has-text("确认")')) .or(page.locator('.el-message-box button:has-text("确定")')); if (await confirmBtn.count() > 0) { await confirmBtn.click(); console.log('[UI] 已关闭时间冲突警告弹窗'); } else { await page.keyboard.press('Escape'); console.log('[UI] 通过Escape关闭弹窗'); } await page.waitForTimeout(1000); // 按 Escape 关闭可能残留的其他遮罩(如 select 下拉、modal 遮罩) await page.keyboard.press('Escape'); await page.waitForTimeout(500); console.log('[UI] 已按Escape清除残留遮罩'); } else { console.log('[UI] 无时间冲突警告弹窗'); // 仍然按 Escape 以防有其他遮罩 await page.keyboard.press('Escape'); await page.waitForTimeout(300); } }); await test.step('3i. 搜索验证课程', async () => { const searchInput = page.locator('input[placeholder*="搜索"]').first(); if (await searchInput.count() > 0) { await searchInput.fill(courseName); const searchBtn = page.locator('button:has-text("搜索")').first(); if (await searchBtn.count() > 0) await searchBtn.click(); await page.waitForTimeout(2000); await page.screenshot({ path: `test-results/search-result-${timestamp}.png` }); console.log('[UI] 已搜索:', courseName); } }); console.log('[UI] ====== 后台管理UI测试完成 ======'); }); });