kindergarten_java/reading-platform-frontend/tests/e2e/teacher/helpers.ts
Claude Opus 4.6 c90873bea9 Merge remote-tracking branch 'origin/master' and complete two-tier structure refactoring
合并同事的远程更新:
- 多地点登录支持功能
- 资源库管理优化
- 数据看板修复
- 视频预览功能
- KidsMode增强

两层结构重构完成:
- 数据库迁移 V28(course_collection、course_collection_package)
- 后端实体、Service、Controller实现
- 前端API类型和组件重构
- 修复冲突文件:CHANGELOG.md、components.d.ts、TeacherLessonController.java

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 16:59:06 +08:00

212 lines
6.3 KiB
TypeScript
Raw 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.

/**
* 教师端 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 directMenuItem = page.locator('[role="menuitem"]').filter({ hasText: childMenu }).first();
if (await directMenuItem.count() > 0) {
await directMenuItem.click();
await page.waitForTimeout(1500);
return;
}
// 点击一级菜单展开(父子菜单结构)
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);
}
/**
* 直接点击菜单项
* @param page 页面对象
* @param menuText 菜单文本
*/
export async function clickMenuItem(page: Page, menuText: string) {
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.waitForTimeout(1000);
const menuItem = page.locator('[role="menuitem"]').filter({ hasText: menuText }).first();
await menuItem.click();
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(() => {});
}