Files
gym-manage/gym-manage-web/e2e/journeys/full-flow-admin-ui.spec.ts
T

271 lines
11 KiB
TypeScript

/**
* 全流程端到端测试 - 后台管理系统 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<string, unknown>) {
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测试完成 ======');
});
});