kindergarten_java/lesingle-edu-reading-platform-frontend/tests/e2e/post-class-flow/post-class.spec.ts

475 lines
16 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 }) => {
test.slow();
// 1. 进入上课记录
await page.click('text=上课记录');
await page.waitForTimeout(2000);
// 2. 点击第一个记录的"课后记录"或"填写记录"按钮
const postClassButton = page.locator('button:has-text("课后记录")').or(page.locator('button:has-text("填写记录")')).or(page.locator('button:has-text("记录")'));
const buttonExists = await postClassButton.count() > 0;
if (buttonExists) {
await postClassButton.first().click();
await page.waitForTimeout(2000);
// 3. 验证进入课后记录页面
const url = page.url();
const isRecordPage = url.includes('record') || url.includes('feedback') || url.includes('post');
test.info().annotations.push({
type: 'info',
description: isRecordPage ? `进入课后记录页: ${url}` : `当前URL: ${url}`,
});
// 4. 验证页面结构
const form = page.locator('form, [class*="form"], [class*="record"]');
expect(await form.count()).toBeGreaterThan(0);
} else {
// 如果没有按钮,尝试直接点击记录项
const recordItems = page.locator('[class*="record"], [class*="lesson"]');
if (await recordItems.count() > 0) {
await recordItems.first().click();
await page.waitForTimeout(2000);
// 查找填写记录按钮
const fillButton = page.locator('button:has-text("填写")').or(page.locator('button:has-text("记录")'));
if (await fillButton.count() > 0) {
await fillButton.first().click();
await page.waitForTimeout(2000);
}
}
}
await page.screenshot({ path: 'test-results/post-class-enter.png' });
});
test('测试2: 基本信息填写', async ({ page }) => {
test.slow();
// 进入课后记录页面
await page.click('text=上课记录');
await page.waitForTimeout(2000);
const recordItems = page.locator('[class*="record"], [class*="lesson"]');
if (await recordItems.count() > 0) {
await recordItems.first().click();
await page.waitForTimeout(2000);
const fillButton = page.locator('button:has-text("填写")').or(page.locator('button:has-text("记录")'));
if (await fillButton.count() > 0) {
await fillButton.first().click();
await page.waitForTimeout(2000);
}
}
await page.waitForTimeout(1000);
// 1. 验证日期字段
const dateField = page.locator('[class*="date"]').or(page.locator('input[type="date"]'));
const hasDateField = await dateField.count() > 0;
// 2. 验证班级选择
const classField = page.locator('[class*="class"]').or(page.locator('select'));
const hasClassField = await classField.count() > 0;
// 3. 验证课程信息显示
const courseInfo = page.locator('[class*="course"], [class*="info"]');
const hasCourseInfo = await courseInfo.count() > 0;
test.info().annotations.push({
type: 'info',
description: `日期字段: ${hasDateField}, 班级字段: ${hasClassField}, 课程信息: ${hasCourseInfo}`,
});
await page.screenshot({ path: 'test-results/post-class-basic-info.png' });
});
test('测试3: 教学反思填写', async ({ page }) => {
test.slow();
// 进入课后记录页面
await page.click('text=上课记录');
await page.waitForTimeout(2000);
const recordItems = page.locator('[class*="record"], [class*="lesson"]');
if (await recordItems.count() > 0) {
await recordItems.first().click();
await page.waitForTimeout(2000);
const fillButton = page.locator('button:has-text("填写")').or(page.locator('button:has-text("记录")'));
if (await fillButton.count() > 0) {
await fillButton.first().click();
await page.waitForTimeout(2000);
}
}
await page.waitForTimeout(1000);
// 1. 查找教学反思输入框
const reflectionField = page.locator('textarea, [contenteditable="true"], [class*="reflection"]').or(page.locator('text=教学反思'));
const reflectionCount = await reflectionField.count();
if (reflectionCount > 0) {
// 2. 填写教学反思
const textarea = reflectionField.first().locator('textarea').or(page.locator('[contenteditable="true"]'));
const editableCount = await textarea.count();
if (editableCount > 0) {
await textarea.first().fill('这是一次成功的授课,学生参与度很高,互动良好。');
await page.waitForTimeout(1000);
test.info().annotations.push({
type: 'success',
description: '教学反思填写成功',
});
}
}
await page.screenshot({ path: 'test-results/post-class-reflection.png' });
});
test('测试4: 学生表现记录', async ({ page }) => {
test.slow();
// 进入课后记录页面
await page.click('text=上课记录');
await page.waitForTimeout(2000);
const recordItems = page.locator('[class*="record"], [class*="lesson"]');
if (await recordItems.count() > 0) {
await recordItems.first().click();
await page.waitForTimeout(2000);
const fillButton = page.locator('button:has-text("填写")').or(page.locator('button:has-text("记录")'));
if (await fillButton.count() > 0) {
await fillButton.first().click();
await page.waitForTimeout(2000);
}
}
await page.waitForTimeout(1000);
// 1. 查找学生表现区域
const performanceSection = page.locator('text=学生表现').or(page.locator('[class*="student"], [class*="performance"]'));
const hasPerformanceSection = await performanceSection.count() > 0;
// 2. 查找评分或评价控件
const ratingControl = page.locator('[class*="rate"], [class*="score"], .ant-rate');
const hasRating = await ratingControl.count() > 0;
// 3. 查找学生列表
const studentList = page.locator('[class*="student"], [class*="child"]');
const studentCount = await studentList.count();
test.info().annotations.push({
type: 'info',
description: `学生表现区域: ${hasPerformanceSection}, 评分控件: ${hasRating}, 学生数量: ${studentCount}`,
});
await page.screenshot({ path: 'test-results/post-class-performance.png' });
});
test('测试5: 教学效果评估', async ({ page }) => {
test.slow();
// 进入课后记录页面
await page.click('text=上课记录');
await page.waitForTimeout(2000);
const recordItems = page.locator('[class*="record"], [class*="lesson"]');
if (await recordItems.count() > 0) {
await recordItems.first().click();
await page.waitForTimeout(2000);
const fillButton = page.locator('button:has-text("填写")').or(page.locator('button:has-text("记录")'));
if (await fillButton.count() > 0) {
await fillButton.first().click();
await page.waitForTimeout(2000);
}
}
await page.waitForTimeout(1000);
// 1. 查找教学效果评估区域
const effectSection = page.locator('text=教学效果').or(page.locator('text=效果评估'));
const hasEffectSection = await effectSection.count() > 0;
// 2. 查找目标达成度评估
const goalSection = page.locator('text=目标').or(page.locator('text=达成'));
const hasGoalSection = await goalSection.count() > 0;
test.info().annotations.push({
type: 'info',
description: `效果评估: ${hasEffectSection}, 目标达成度: ${hasGoalSection}`,
});
await page.screenshot({ path: 'test-results/post-class-effect.png' });
});
test('测试6: 照片/视频上传', async ({ page }) => {
test.slow();
// 进入课后记录页面
await page.click('text=上课记录');
await page.waitForTimeout(2000);
const recordItems = page.locator('[class*="record"], [class*="lesson"]');
if (await recordItems.count() > 0) {
await recordItems.first().click();
await page.waitForTimeout(2000);
const fillButton = page.locator('button:has-text("填写")').or(page.locator('button:has-text("记录")'));
if (await fillButton.count() > 0) {
await fillButton.first().click();
await page.waitForTimeout(2000);
}
}
await page.waitForTimeout(1000);
// 1. 查找上传按钮
const uploadButton = page.locator('button:has-text("上传")').or(page.locator('[class*="upload"]'));
const uploadCount = await uploadButton.count();
// 2. 查找文件输入控件
const fileInput = page.locator('input[type="file"]');
const fileCount = await fileInput.count();
test.info().annotations.push({
type: 'info',
description: `上传按钮: ${uploadCount}, 文件输入: ${fileCount}`,
});
await page.screenshot({ path: 'test-results/post-class-upload.png' });
});
test('测试7: 保存课后记录', async ({ page }) => {
test.slow();
// 进入课后记录页面
await page.click('text=上课记录');
await page.waitForTimeout(2000);
const recordItems = page.locator('[class*="record"], [class*="lesson"]');
if (await recordItems.count() > 0) {
await recordItems.first().click();
await page.waitForTimeout(2000);
const fillButton = page.locator('button:has-text("填写")').or(page.locator('button:has-text("记录")'));
if (await fillButton.count() > 0) {
await fillButton.first().click();
await page.waitForTimeout(2000);
}
}
await page.waitForTimeout(1000);
// 1. 填写一些内容
const reflectionField = page.locator('textarea, [contenteditable="true"]');
if (await reflectionField.count() > 0) {
await reflectionField.first().fill('测试课后记录内容');
await page.waitForTimeout(500);
}
// 2. 查找保存按钮
const saveButton = page.locator('button:has-text("保存")').or(page.locator('button:has-text("提交")'));
const saveCount = await saveButton.count();
if (saveCount > 0) {
// 3. 点击保存
await saveButton.first().click();
await page.waitForTimeout(2000);
// 4. 检查是否有成功提示
const successMessage = page.locator('text=成功').or(page.locator('text=保存'));
const hasSuccessMessage = await successMessage.count() > 0;
test.info().annotations.push({
type: 'info',
description: hasSuccessMessage ? '保存成功提示已显示' : '未检测到成功提示',
});
}
await page.screenshot({ path: 'test-results/post-class-save.png' });
});
test('测试8: 布置阅读任务', async ({ page }) => {
test.slow();
// 进入课后记录页面
await page.click('text=上课记录');
await page.waitForTimeout(2000);
const recordItems = page.locator('[class*="record"], [class*="lesson"]');
if (await recordItems.count() > 0) {
await recordItems.first().click();
await page.waitForTimeout(2000);
const fillButton = page.locator('button:has-text("填写")').or(page.locator('button:has-text("记录")'));
if (await fillButton.count() > 0) {
await fillButton.first().click();
await page.waitForTimeout(2000);
}
}
await page.waitForTimeout(1000);
// 1. 查找布置任务按钮或Tab
const taskButton = page.locator('button:has-text("布置任务")').or(page.locator('text=布置任务'));
const hasTaskButton = await taskButton.count() > 0;
if (hasTaskButton) {
// 2. 点击布置任务
await taskButton.first().click();
await page.waitForTimeout(1000);
// 3. 验证任务布置界面
const taskModal = page.locator('[class*="modal"]').or(page.locator('[class*="task"]'));
const hasTaskModal = await taskModal.count() > 0;
test.info().annotations.push({
type: 'info',
description: hasTaskModal ? '任务布置界面已显示' : '未找到任务布置界面',
});
}
await page.screenshot({ path: 'test-results/post-class-task.png' });
});
test('测试9: 查看历史记录', async ({ page }) => {
test.slow();
// 1. 进入上课记录
await page.click('text=上课记录');
await page.waitForTimeout(2000);
// 2. 查找已完成记录的标记
const completedRecords = page.locator('text=已完成').or(page.locator('[class*="completed"]'));
const completedCount = await completedRecords.count();
// 3. 查看历史记录筛选
const filterControls = page.locator('.ant-select, [class*="filter"]');
const filterCount = await filterControls.count();
// 4. 点击一个已完成的记录查看详情
if (completedCount > 0) {
await completedRecords.first().click();
await page.waitForTimeout(2000);
// 5. 验证详情显示
const detailContent = page.locator('[class*="detail"], [class*="content"]');
const hasDetail = await detailContent.count() > 0;
test.info().annotations.push({
type: 'info',
description: hasDetail ? '记录详情已显示' : '未找到详情内容',
});
}
test.info().annotations.push({
type: 'info',
description: `已完成记录: ${completedCount}, 筛选控件: ${filterCount}`,
});
await page.screenshot({ path: 'test-results/post-class-history.png' });
});
test('测试10: 编辑和删除记录', async ({ page }) => {
test.slow();
// 1. 进入上课记录
await page.click('text=上课记录');
await page.waitForTimeout(2000);
// 2. 查找编辑按钮
const editButton = page.locator('button:has-text("编辑")');
const editCount = await editButton.count();
// 3. 查找删除按钮
const deleteButton = page.locator('button:has-text("删除")');
const deleteCount = await deleteButton.count();
test.info().annotations.push({
type: 'info',
description: `编辑按钮: ${editCount}, 删除按钮: ${deleteCount}`,
});
// 注意:不实际执行删除操作,避免影响数据
await page.screenshot({ path: 'test-results/post-class-edit-delete.png' });
});
test('测试11: 课程反馈提交', async ({ page }) => {
test.slow();
// 1. 进入课程反馈页面
await page.click('text=课程反馈');
await page.waitForTimeout(2000);
// 2. 验证反馈列表
const feedbackList = page.locator('[class*="feedback"], [class*="list"]');
const listCount = await feedbackList.count();
// 3. 查找填写反馈按钮
const createButton = page.locator('button:has-text("填写")').or(page.locator('button:has-text("添加")'));
const createCount = await createButton.count();
test.info().annotations.push({
type: 'info',
description: `反馈列表: ${listCount}, 创建按钮: ${createCount}`,
});
await page.screenshot({ path: 'test-results/feedback-list.png' });
});
test('测试12: 记录统计和导出', async ({ page }) => {
test.slow();
// 1. 进入上课记录
await page.click('text=上课记录');
await page.waitForTimeout(2000);
// 2. 查找统计信息
const statCards = page.locator('[class*="stat"], [class*="summary"]');
const statCount = await statCards.count();
// 3. 查找导出按钮
const exportButton = page.locator('button:has-text("导出")');
const exportCount = await exportButton.count();
test.info().annotations.push({
type: 'info',
description: `统计卡片: ${statCount}, 导出按钮: ${exportCount}`,
});
await page.screenshot({ path: 'test-results/post-class-stats.png' });
});
});