kindergarten_java/reading-platform-frontend/tests/e2e/prepare-mode-flow/prepare-mode.spec.ts

531 lines
18 KiB
TypeScript
Raw Permalink Normal View History

refactor: 完成代码重构规范化 - 2026-03-12 后端重构: - 添加统一响应格式 ResultDto<T> 和 PageResultDto<T> - 添加分页查询 DTO 基类 PageQueryDto - 添加响应转换拦截器 TransformInterceptor - 添加公共工具函数(JSON 解析、分页计算) - 配置 Swagger/OpenAPI 文档(访问路径:/api-docs) - Tenant 模块 DTO 规范化示例(添加 @ApiProperty 装饰器) - CourseLesson 控制器重构 - 移除类级路径参数,修复 Orval 验证错误 - 后端 DTO 规范化 - 为 Course、Lesson、TeacherCourse、SchoolCourse 控制器添加完整的 Swagger 装饰器 前端重构: - 配置 Orval 从后端 OpenAPI 自动生成 API 客户端 - 生成 API 客户端代码(带完整参数定义) - 创建 API 客户端统一入口 (src/api/client.ts) - 创建 API 适配层 (src/api/teacher.adapter.ts) - 配置文件路由 (unplugin-vue-router) - 课程模块迁移到新 API 客户端 - 修复 PrepareModeView.vue API 调用错误 - 教师模块迁移到新 API 客户端 - 修复 school-course.ts 类型错误 - 清理 teacher.adapter.ts 未使用导入 - 修复 client.ts API 客户端结构 - 创建文件路由目录结构 Bug 修复: - 修复路由配置问题 - 移除 top-level await,改用手动路由配置 - 修复响应拦截器 - 正确解包 { code, message, data } 格式的响应 - 清理 teacher.adapter.ts 未使用导入 - 修复 client.ts API 客户端结构 - 创建文件路由目录结构 系统测试: - 后端 API 测试通过 (7/7) - 前端路由测试通过 (4/4) - 数据库完整性验证通过 - Orval API 客户端验证通过 - 超管端功能测试通过 (97.8% 通过率) 新增文件: - reading-platform-backend/src/common/dto/result.dto.ts - reading-platform-backend/src/common/dto/page-query.dto.ts - reading-platform-backend/src/common/interceptors/transform.interceptor.ts - reading-platform-backend/src/common/utils/json.util.ts - reading-platform-backend/src/common/utils/pagination.util.ts - reading-platform-frontend/orval.config.ts - reading-platform-frontend/src/api/generated/mutator.ts - reading-platform-frontend/src/api/client.ts - reading-platform-frontend/src/api/teacher.adapter.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:27:13 +08:00
import { test, expect } from '@playwright/test';
test.describe('备课模式完整流程', () => {
let baseURL = 'http://localhost:5173';
test.beforeEach(async ({ page }) => {
await page.goto(baseURL);
await page.waitForLoadState('networkidle');
await page.click('text=教师');
await page.waitForTimeout(500);
const accountInput = page.locator('input[placeholder*="账号"]').or(page.locator('input[name="account"]'));
await accountInput.fill('teacher1');
const passwordInput = page.locator('input[placeholder*="密码"]').or(page.locator('input[type="password"]'));
await passwordInput.fill('123456');
const loginButton = page.locator('button[type="submit"]').or(page.locator('.login-btn')).or(page.locator('button:has-text("登录")'));
await loginButton.click();
await page.waitForURL('**/dashboard', { timeout: 10000 }).catch(() => {
return page.waitForURL('**/courses', { timeout: 5000 });
});
await page.waitForTimeout(1000);
});
test('测试1: 进入备课模式', async ({ page }) => {
// 1. 进入课程中心
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
// 2. 选择第一个课程
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
// 3. 点击开始备课
const prepareButton = page.locator('button:has-text("开始备课")');
await prepareButton.click();
// 4. 验证跳转
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 5. 验证备课模式结构
await expect(page.locator('aside, [class*="navigation"], [class*="sidebar"]').first()).toBeVisible();
await expect(page.locator('[class*="preview"], [class*="content"]').first()).toBeVisible();
test.info().annotations.push({
type: 'success',
description: '成功进入备课模式',
});
await page.screenshot({ path: 'test-results/prepare-mode-layout.png' });
});
test('测试2: 左侧导航 - 课程包概览', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 点击课程包概览
const overviewNav = page.locator('text=课程包概览').or(page.locator('[class*="overview"]'));
if (await overviewNav.count() > 0) {
await overviewNav.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证基本信息展示
const basicInfo = page.locator('[class*="basic"], [class*="info"]');
if (await basicInfo.count() > 0) {
await expect(basicInfo.first()).toBeVisible();
}
// 3. 验证统计数据
const stats = page.locator('[class*="stat"], [class*="count"]');
const statCount = await stats.count();
test.info().annotations.push({
type: 'info',
description: `统计项数量: ${statCount}`,
});
await page.screenshot({ path: 'test-results/prepare-overview.png' });
});
test('测试3: 左侧导航 - 包含课程', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 点击包含课程
const lessonsNav = page.locator('text=包含课程').or(page.locator('[class*="lessons"]'));
if (await lessonsNav.count() > 0) {
await lessonsNav.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证课程列表
const lessonList = page.locator('[class*="lesson"], [class*="course-item"]');
const lessonCount = await lessonList.count();
test.info().annotations.push({
type: 'info',
description: `课程数量: ${lessonCount}`,
});
expect(lessonCount).toBeGreaterThan(0);
// 3. 点击第一个课程
if (lessonCount > 0) {
await lessonList.first().click();
await page.waitForTimeout(1000);
}
await page.screenshot({ path: 'test-results/prepare-lessons.png' });
});
test('测试4: 右侧内容预览 - 课程介绍', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 点击课程介绍Tab
const introTab = page.locator('text=课程介绍').or(page.locator('[class*="intro"]'));
if (await introTab.count() > 0) {
await introTab.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证介绍内容展示
const introContent = page.locator('[class*="intro"], [class*="content"]');
expect(await introContent.count()).toBeGreaterThan(0);
// 3. 验证8个介绍字段
const introFields = ['简介', '亮点', '目标', '安排', '重难点', '方法', '评价', '注意事项'];
let visibleFieldCount = 0;
for (const field of introFields) {
const fieldLocator = page.locator(`text=${field}`);
if (await fieldLocator.count() > 0) {
visibleFieldCount++;
}
}
test.info().annotations.push({
type: 'info',
description: `介绍字段: ${visibleFieldCount}/${introFields.length}`,
});
await page.screenshot({ path: 'test-results/prepare-intro-content.png', fullPage: true });
});
test('测试5: 右侧内容预览 - 排课参考', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 点击排课参考Tab
const scheduleTab = page.locator('text=排课参考').or(page.locator('[class*="schedule"]'));
if (await scheduleTab.count() > 0) {
await scheduleTab.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证排课表格或内容
const scheduleTable = page.locator('table').or(page.locator('[class*="table"]'));
const scheduleContent = page.locator('[class*="schedule-content"]');
const hasTable = await scheduleTable.count() > 0;
const hasContent = await scheduleContent.count() > 0;
test.info().annotations.push({
type: 'info',
description: hasTable ? '显示排课表格' : (hasContent ? '显示排课内容' : '未找到排课参考'),
});
await page.screenshot({ path: 'test-results/prepare-schedule.png' });
});
test('测试6: 右侧内容预览 - 环创建设', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 点击环创建设Tab
const envTab = page.locator('text=环创建设').or(page.locator('[class*="environment"]'));
if (await envTab.count() > 0) {
await envTab.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证环创建设内容
const envContent = page.locator('text=主题环境').or(page.locator('text=区域活动')).or(page.locator('[class*="environment"]'));
const hasContent = await envContent.count() > 0;
test.info().annotations.push({
type: 'info',
description: hasContent ? '显示环创建设内容' : '未找到环创建设',
});
await page.screenshot({ path: 'test-results/prepare-environment.png' });
});
test('测试7: 教学目标展示', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 选择导入课
const introLesson = page.locator('text=导入课').or(page.locator('[class*="introduction"]'));
if (await introLesson.count() > 0) {
await introLesson.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证教学目标展示
const objectives = page.locator('text=教学目标').or(page.locator('[class*="objective"]'));
if (await objectives.count() > 0) {
await expect(objectives.first()).toBeVisible();
}
// 3. 验证教学准备展示
const preparation = page.locator('text=教学准备').or(page.locator('[class*="preparation"]'));
if (await preparation.count() > 0) {
await expect(preparation.first()).toBeVisible();
}
await page.screenshot({ path: 'test-results/prepare-objectives.png' });
});
test('测试8: 教学过程展示', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 选择导入课
const introLesson = page.locator('text=导入课').or(page.locator('[class*="introduction"]'));
if (await introLesson.count() > 0) {
await introLesson.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证教学过程展示
const process = page.locator('text=教学过程').or(page.locator('[class*="process"], [class*="steps"]'));
if (await process.count() > 0) {
await expect(process.first()).toBeVisible();
}
// 3. 验证环节列表
const steps = page.locator('[class*="step"], [class*="stage"]');
const stepCount = await steps.count();
test.info().annotations.push({
type: 'info',
description: `教学环节数量: ${stepCount}`,
});
// 4. 展开第一个环节查看详情
if (stepCount > 0) {
await steps.first().click();
await page.waitForTimeout(500);
}
await page.screenshot({ path: 'test-results/prepare-process.png', fullPage: true });
});
test('测试9: 核心资源展示', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 选择导入课
const introLesson = page.locator('text=导入课').or(page.locator('[class*="introduction"]'));
if (await introLesson.count() > 0) {
await introLesson.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证核心资源展示
const resources = page.locator('text=核心资源').or(page.locator('[class*="resource"]'));
const resourceCount = await resources.count();
test.info().annotations.push({
type: 'info',
description: resourceCount > 0 ? `找到 ${resourceCount} 个资源区域` : '未找到核心资源区域',
});
// 3. 验证资源类型视频、PPT、PDF等
const resourceTypes = ['视频', 'PPT', 'PDF', '音频', '图片'];
const foundTypes: string[] = [];
for (const type of resourceTypes) {
const typeLocator = page.locator(`text=${type}`);
if (await typeLocator.count() > 0) {
foundTypes.push(type);
}
}
test.info().annotations.push({
type: 'info',
description: `资源类型: ${foundTypes.join(', ') || '无'}`,
});
await page.screenshot({ path: 'test-results/prepare-resources.png' });
});
test('测试10: 教学延伸和反思', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 选择导入课
const introLesson = page.locator('text=导入课').or(page.locator('[class*="introduction"]'));
if (await introLesson.count() > 0) {
await introLesson.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证教学延伸
const extension = page.locator('text=教学延伸').or(page.locator('[class*="extension"]'));
if (await extension.count() > 0) {
await expect(extension.first()).toBeVisible();
}
// 3. 验证教学反思
const reflection = page.locator('text=教学反思').or(page.locator('[class*="reflection"]'));
if (await reflection.count() > 0) {
await expect(reflection.first()).toBeVisible();
}
await page.screenshot({ path: 'test-results/prepare-extension.png' });
});
test('测试11: 备课笔记功能', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 点击备课笔记Tab
const notesTab = page.locator('text=备课笔记').or(page.locator('[class*="notes"]'));
if (await notesTab.count() > 0) {
await notesTab.first().click();
await page.waitForTimeout(1000);
}
// 2. 验证笔记编辑器
const editor = page.locator('[contenteditable="true"], .ant-input, textarea');
const hasEditor = await editor.count() > 0;
if (hasEditor) {
// 3. 尝试输入笔记内容
await editor.first().fill('这是测试笔记内容');
await page.waitForTimeout(1000);
test.info().annotations.push({
type: 'success',
description: '备课笔记功能测试完成',
});
} else {
test.info().annotations.push({
type: 'warning',
description: '未找到笔记编辑器',
});
}
await page.screenshot({ path: 'test-results/prepare-notes.png' });
});
test('测试12: 从备课模式进入上课', async ({ page }) => {
test.slow();
// 进入备课模式
await page.click('text=课程中心');
await page.waitForURL('**/courses', { timeout: 10000 });
await page.waitForTimeout(1000);
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
await firstCourseCard.click();
await page.waitForTimeout(2000);
await page.click('button:has-text("开始备课")');
await page.waitForURL('**/prepare', { timeout: 10000 });
await page.waitForTimeout(2000);
// 1. 查找开始上课按钮
const startClassButton = page.locator('button:has-text("开始上课")').or(page.locator('button:has-text("进入上课")'));
const buttonExists = await startClassButton.count() > 0;
if (buttonExists) {
await startClassButton.first().click();
await page.waitForTimeout(2000);
// 2. 验证跳转到上课模式或课程选择弹窗
const url = page.url();
test.info().annotations.push({
type: 'info',
description: `点击后URL: ${url}`,
});
// 3. 如果显示课程选择弹窗,选择所有课程
const selectModal = page.locator('[class*="modal"], [class*="select"]');
if (await selectModal.count() > 0) {
const confirmButton = page.locator('button:has-text("确定")').or(page.locator('button:has-text("开始上课")'));
if (await confirmButton.count() > 0) {
await confirmButton.first().click();
await page.waitForTimeout(2000);
}
}
} else {
test.info().annotations.push({
type: 'warning',
description: '未找到"开始上课"按钮',
});
}
await page.screenshot({ path: 'test-results/prepare-to-class.png' });
});
});