190 lines
5.6 KiB
TypeScript
190 lines
5.6 KiB
TypeScript
/**
|
||
* 教师端 E2E 测试 - 通用工具函数
|
||
*/
|
||
|
||
import { Page, expect } from '@playwright/test';
|
||
import { TEACHER_CONFIG } from './fixtures';
|
||
|
||
/**
|
||
* 使用教师端账号登录
|
||
*/
|
||
export async function loginAsTeacher(page: Page) {
|
||
await page.goto('/login');
|
||
|
||
// 点击教师角色按钮
|
||
await page.locator('.role-btn').filter({ hasText: '教师' }).first().click();
|
||
|
||
// 输入账号密码
|
||
await page.getByPlaceholder('请输入账号').fill(TEACHER_CONFIG.account);
|
||
await page.getByPlaceholder('请输入密码').fill(TEACHER_CONFIG.password);
|
||
|
||
// 点击登录按钮
|
||
await page.locator('.login-btn').click();
|
||
|
||
// 等待登录按钮消失(表示登录请求完成)
|
||
await page.locator('.login-btn').waitFor({ state: 'hidden', timeout: 10000 });
|
||
|
||
// 等待页面加载
|
||
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
||
|
||
// 等待 URL 包含 teacher(使用正则表达式)
|
||
await page.waitForURL(/teacher/, { timeout: 5000 }).catch(() => {});
|
||
}
|
||
|
||
/**
|
||
* 点击二级菜单项
|
||
* @param page 页面对象
|
||
* @param parentMenu 一级菜单文本
|
||
* @param childMenu 二级菜单文本
|
||
*/
|
||
export async function clickSubMenu(page: Page, parentMenu: string, childMenu: string) {
|
||
// 等待页面加载
|
||
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
||
await page.waitForTimeout(2000);
|
||
|
||
// 检查侧边栏是否折叠,如果折叠则展开
|
||
const isCollapsed = await page.locator('.ant-layout-sider-collapsed').count() > 0;
|
||
if (isCollapsed) {
|
||
const collapseButton = page.locator('.trigger').first();
|
||
await collapseButton.click();
|
||
await page.waitForTimeout(1000);
|
||
}
|
||
|
||
// 点击一级菜单展开
|
||
const parentMenuItem = page.locator('.ant-menu-submenu-title:has-text("' + parentMenu + '")').first();
|
||
await parentMenuItem.click();
|
||
|
||
// 等待二级菜单 DOM 出现
|
||
await page.waitForSelector('.ant-menu-submenu-open', { timeout: 5000 }).catch(() => {});
|
||
await page.waitForTimeout(500);
|
||
|
||
// 使用 evaluate 在浏览器上下文中点击,绕过可见性检查
|
||
await page.evaluate((menuText) => {
|
||
const items = Array.from(document.querySelectorAll('.ant-menu-item'));
|
||
const target = items.find(item => item.textContent?.includes(menuText));
|
||
if (target) {
|
||
(target as HTMLElement).click();
|
||
}
|
||
}, childMenu);
|
||
|
||
await page.waitForTimeout(1500);
|
||
}
|
||
|
||
/**
|
||
* 退出登录
|
||
*/
|
||
export async function logout(page: Page) {
|
||
// 尝试多种方式找到退出登录按钮
|
||
|
||
// 方式 1:查找退出登录按钮
|
||
const logoutBtn1 = page.getByText(/退出登录 | 退出|logout/i).first();
|
||
if (await logoutBtn1.count() > 0) {
|
||
try {
|
||
await logoutBtn1.click({ timeout: 3000 });
|
||
await page.waitForURL(/.*\/login.*/, { timeout: 10000 }).catch(() => {});
|
||
return;
|
||
} catch (e) {
|
||
// 如果点击失败,继续尝试其他方式
|
||
}
|
||
}
|
||
|
||
// 方式 2:查找用户头像/菜单按钮并点击
|
||
const userMenuBtn = page.locator('.ant-dropdown-trigger, .user-menu, [class*="user"]').first();
|
||
if (await userMenuBtn.count() > 0) {
|
||
try {
|
||
await userMenuBtn.click({ timeout: 3000 });
|
||
await page.waitForTimeout(500);
|
||
const logoutInMenu = page.getByText(/退出登录 | 退出|logout/i).first();
|
||
if (await logoutInMenu.count() > 0) {
|
||
await logoutInMenu.click({ timeout: 3000 });
|
||
await page.waitForURL(/.*\/login.*/, { timeout: 10000 }).catch(() => {});
|
||
return;
|
||
}
|
||
} catch (e) {
|
||
// 如果点击失败,继续尝试其他方式
|
||
}
|
||
}
|
||
|
||
// 方式 3:尝试清空 localStorage 和 sessionStorage 并跳转到登录页
|
||
await page.evaluate(() => {
|
||
localStorage.clear();
|
||
sessionStorage.clear();
|
||
});
|
||
await page.goto('/login');
|
||
await page.waitForURL(/.*\/login.*/, { timeout: 10000 });
|
||
}
|
||
|
||
/**
|
||
* 等待表格加载完成
|
||
*/
|
||
export async function waitForTable(page: Page, timeout = 10000) {
|
||
await page.waitForSelector('table, .ant-table', { timeout });
|
||
}
|
||
|
||
/**
|
||
* 等待弹窗显示
|
||
*/
|
||
export async function waitForModal(page: Page, title?: string, timeout = 5000) {
|
||
if (title) {
|
||
await page.getByText(title).waitFor({ timeout });
|
||
} else {
|
||
await page.waitForSelector('.ant-modal', { timeout });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 等待成功提示
|
||
*/
|
||
export async function waitForSuccess(page: Page, message?: string, timeout = 5000) {
|
||
if (message) {
|
||
await page.getByText(message).waitFor({ timeout });
|
||
} else {
|
||
await page.waitForSelector('.ant-message-success', { timeout });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 等待错误提示
|
||
*/
|
||
export async function waitForError(page: Page, message?: string, timeout = 5000) {
|
||
if (message) {
|
||
await page.getByText(message).waitFor({ timeout });
|
||
} else {
|
||
await page.waitForSelector('.ant-message-error', { timeout });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 在表格中查找并点击操作按钮
|
||
*/
|
||
export async function clickRowAction(page: Page, rowName: string, action: string) {
|
||
const row = page.getByRole('row').filter({ hasText: rowName });
|
||
await row.getByRole('button', { name: action }).click();
|
||
}
|
||
|
||
/**
|
||
* 关闭弹窗
|
||
*/
|
||
export async function closeModal(page: Page) {
|
||
await page.keyboard.press('Escape');
|
||
// 或者点击关闭按钮
|
||
const closeBtn = page.locator('.ant-modal-close');
|
||
if (await closeBtn.count() > 0) {
|
||
await closeBtn.click();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 等待页面加载完成
|
||
*/
|
||
export async function waitForPageLoad(page: Page, timeout = 10000) {
|
||
await page.waitForLoadState('networkidle', { timeout });
|
||
}
|
||
|
||
/**
|
||
* 等待 API 请求完成
|
||
*/
|
||
export async function waitForAPI(page: Page, urlPattern: string | RegExp, timeout = 10000) {
|
||
await page.waitForResponse(urlPattern, { timeout }).catch(() => {});
|
||
}
|