kindergarten_java/lesingle-edu-reading-platform-frontend/tests/e2e/admin/course-package-comprehensive.spec.ts
En 40589f59e7 chore: 重命名项目目录
前后端目录重命名:
- reading-platform-java/ → lesingle-edu-reading-platform-backend/
- reading-platform-frontend/ → lesingle-edu-reading-platform-frontend/

更新相关文件:
- 所有 shell 脚本中的目录引用
- pom.xml 和 application.yml 中的项目名称
- package.json 中的项目名称
- .claude/CLAUDE.md 中的路径引用
- README 文档中的路径引用
2026-03-26 11:31:47 +08:00

527 lines
21 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 测试
*
* 测试场景:
* 1. 完整流程测试 - 填写所有 7 个步骤,创建完整的课程包
* 2. 最小化测试 - 只填写必填项(步骤 1+ 步骤 5快速创建课程包
* 3. 跳步测试 - 跳过可选步骤(导入课/领域课/环创)
* 4. 数据验证测试 - 验证必填项验证逻辑
*/
import { test, expect } from '@playwright/test';
import { loginAsAdmin, waitForTable, waitForSuccess } from './helpers';
// 测试数据
const TEST_COURSE_COMPLETE = {
name: '完整测试课程包-' + Date.now(),
theme: '社会认知',
grades: ['小班'],
bookName: '折耳兔奇奇',
coreContent: '通过折耳兔奇奇的故事,帮助孩子理解友谊和分享的重要性,培养社交能力。',
domainTags: ['倾听与表达'],
// 课程介绍
introSummary: '这是一门专为小班幼儿设计的课程,通过有趣的故事情节,培养孩子的语言表达能力和社交技能。',
introHighlights: '1. 故事情节生动有趣\n2. 互动性强\n3. 培养分享意识',
introGoals: '1. 理解故事内容\n2. 学会分享\n3. 培养语言表达能力',
// 排课参考
totalHours: '8',
hourDuration: '30',
scheduleAdvice: '建议每周 1-2 课时,可根据实际情况调整',
// 集体课
collectiveName: '集体课——折耳兔奇奇绘本阅读',
collectiveDuration: 25,
collectiveObjectives: '1. 认知:完整观看绘本动画,理解故事主要情节\n2. 情感:感受故事的趣味性,产生阅读兴趣',
collectivePreparation: '1. 绘本 PPT\n2. 相关图片\n3. 教学道具',
// 环创建设
environmentConstruction: '1. 夸夸卡展示区\n2. "我的特别之处"展示墙\n3. 主题墙布置',
};
const TEST_COURSE_MINIMAL = {
name: '最小化测试课程包-' + Date.now(),
theme: '社会认知',
grades: ['中班'],
coreContent: '最小化测试课程,仅包含必填项。',
domainTags: ['人际交往'],
};
test.describe('课程包创建 - 完整流程测试', () => {
test('完整流程:填写所有 7 个步骤创建课程包', async ({ page }) => {
test.setTimeout(180000);
const courseName = TEST_COURSE_COMPLETE.name;
// 步骤 1: 登录
await loginAsAdmin(page);
await page.waitForTimeout(1000);
// 步骤 2: 进入课程管理页面
await page.goto('/admin/courses', { timeout: 30000, waitUntil: 'commit' });
// 等待页面加载完成
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
await page.waitForTimeout(3000);
// 步骤 3: 点击新建课程包按钮
const createButton = page.getByRole('button', { name: '新建课程包' });
await createButton.click();
await page.waitForTimeout(1000);
// 验证进入创建页面
await expect(page.getByText('创建课程包')).toBeVisible({ timeout: 5000 });
// ==================== 步骤 1: 基本信息 ====================
await page.getByPlaceholder('请输入课程包名称').fill(courseName);
// 选择关联主题
await page.locator('.ant-select-selector').first().click();
await page.waitForTimeout(500);
await page.getByText(TEST_COURSE_COMPLETE.theme, { exact: true }).first().click();
await page.waitForTimeout(300);
// 选择适用年级
await page.getByRole('checkbox', { name: TEST_COURSE_COMPLETE.grades[0] }).click();
await page.waitForTimeout(300);
// 填写核心内容
await page.getByPlaceholder('请输入课程包核心内容').fill(TEST_COURSE_COMPLETE.coreContent);
// 填写绘本名称
await page.getByPlaceholder('请输入关联绘本名称(可选)').fill(TEST_COURSE_COMPLETE.bookName);
// 选择领域标签
const domainSelector = page.locator('.ant-select-selector').last();
await domainSelector.click();
await page.waitForTimeout(800);
for (const tag of TEST_COURSE_COMPLETE.domainTags) {
await page.getByText(tag, { exact: true }).first().click({ force: true });
await page.waitForTimeout(300);
}
await page.keyboard.press('Escape');
await page.waitForTimeout(300);
// 点击下一步
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 2: 课程介绍 ====================
// 课程简介 - 点击标签页并填写
await page.getByText('课程简介').click();
await page.waitForTimeout(500);
await page.locator('textarea[placeholder*="课程整体简介"]').first().fill(TEST_COURSE_COMPLETE.introSummary);
await page.waitForTimeout(300);
// 课程亮点
await page.getByText('课程亮点').click();
await page.waitForTimeout(500);
await page.locator('textarea[placeholder*="课程特色亮点"]').first().fill(TEST_COURSE_COMPLETE.introHighlights);
await page.waitForTimeout(300);
// 课程总目标
await page.getByText('课程总目标').click();
await page.waitForTimeout(500);
await page.locator('textarea[placeholder*="课程总体目标"]').first().fill(TEST_COURSE_COMPLETE.introGoals);
await page.waitForTimeout(300);
// 点击下一步
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 3: 排课参考 ====================
// 步骤 3 使用表格形式,直接跳过(可选步骤)
console.log(' - 跳过排课参考(可选)');
// 点击下一步
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 4: 导入课 ====================
// 跳过导入课配置(可选步骤)
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 5: 集体课 ====================
// 创建集体课
const createCollectiveBtn = page.getByRole('button', { name: '创建集体课' });
await createCollectiveBtn.click();
// 等待 Vue 组件渲染完成(响应式更新需要时间)
await page.waitForTimeout(2000);
// 等待课程名称输入框可见并填写
const nameInput = page.locator('input[placeholder="请输入课程名称"]').first();
await nameInput.waitFor({ state: 'visible', timeout: 10000 });
await nameInput.scrollIntoViewIfNeeded();
await nameInput.fill(TEST_COURSE_COMPLETE.collectiveName);
await page.waitForTimeout(300);
// 填写课程时长 - 使用 a-input-number 的选择器(等待组件完全渲染)
await page.waitForTimeout(1000);
// 使用键盘导航来聚焦输入框
await page.keyboard.press('Tab');
await page.waitForTimeout(300);
// 直接填写数值
await page.keyboard.type(TEST_COURSE_COMPLETE.collectiveDuration.toString());
await page.waitForTimeout(300);
// 填写教学目标
const objectivesTextarea = page.locator('textarea[placeholder*="教学目标"]').first();
await objectivesTextarea.scrollIntoViewIfNeeded();
await objectivesTextarea.waitFor({ state: 'visible', timeout: 5000 });
await objectivesTextarea.fill(TEST_COURSE_COMPLETE.collectiveObjectives);
await page.waitForTimeout(300);
// 填写教学准备
const preparationTextarea = page.locator('textarea[placeholder*="教学准备"]').first();
await preparationTextarea.scrollIntoViewIfNeeded();
await preparationTextarea.waitFor({ state: 'visible', timeout: 5000 });
await preparationTextarea.fill(TEST_COURSE_COMPLETE.collectivePreparation);
await page.waitForTimeout(300);
// 点击下一步
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 6: 领域课 ====================
// 跳过领域课配置(可选步骤)
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 7: 环创建设 ====================
// 等待元素可见并滚动到视图中
await page.waitForTimeout(500);
const envTextarea = page.locator('textarea[placeholder*="环创"]').first();
await envTextarea.scrollIntoViewIfNeeded();
await envTextarea.waitFor({ state: 'visible', timeout: 10000 }).catch(async () => {
// 如果找不到,尝试其他选择器
await page.locator('textarea[placeholder*="主题墙"]').first().scrollIntoViewIfNeeded();
});
await envTextarea.fill(TEST_COURSE_COMPLETE.environmentConstruction);
await page.waitForTimeout(300);
// 点击创建按钮
await page.getByRole('button', { name: '创建' }).click();
// ==================== 验证创建成功 ====================
// 等待成功提示或页面跳转
try {
await page.waitForSelector('.ant-message-success', { timeout: 5000 });
} catch {
// 如果没有成功提示,可能是页面直接跳转了
console.log(' - 未检测到成功提示,检查页面跳转...');
}
// 等待跳转到列表页
await page.waitForURL('**/admin/courses**', { timeout: 15000 });
await page.waitForTimeout(1000);
// 验证新课程包在列表中显示
await waitForTable(page, 15000);
const table = page.locator('.ant-table').first();
const courseRow = table.getByText(courseName);
// 如果找不到,刷新页面后再试一次
try {
await expect(courseRow).toBeVisible({ timeout: 10000 });
} catch {
console.log(' - 第一次尝试未找到课程包,刷新页面...');
await page.goto('/admin/courses', { waitUntil: 'networkidle' });
await page.waitForTimeout(5000);
await waitForTable(page, 15000);
await expect(table.getByText(courseName)).toBeVisible({ timeout: 10000 });
}
console.log(`✅ 完整流程测试通过 - 课程包 "${courseName}" 创建成功`);
});
});
test.describe('课程包创建 - 最小化测试', () => {
test('最小化流程:只填写必填项创建课程包', async ({ page }) => {
test.setTimeout(120000);
const courseName = TEST_COURSE_MINIMAL.name;
// 步骤 1: 登录
await loginAsAdmin(page);
await page.waitForTimeout(1000);
// 步骤 2: 进入课程管理页面
await page.goto('/admin/courses', { timeout: 30000, waitUntil: 'commit' });
// 等待页面加载完成
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
await page.waitForTimeout(3000);
// 步骤 3: 点击新建课程包按钮
const createButton = page.getByRole('button', { name: '新建课程包' });
await createButton.click();
await page.waitForTimeout(1000);
// 验证进入创建页面
await expect(page.getByText('创建课程包')).toBeVisible({ timeout: 5000 });
// ==================== 步骤 1: 基本信息(只填必填项)====================
await page.getByPlaceholder('请输入课程包名称').fill(courseName);
// 选择关联主题
await page.locator('.ant-select-selector').first().click();
await page.waitForTimeout(500);
await page.getByText(TEST_COURSE_MINIMAL.theme, { exact: true }).first().click();
await page.waitForTimeout(300);
// 选择适用年级
await page.getByRole('checkbox', { name: TEST_COURSE_MINIMAL.grades[0] }).click();
await page.waitForTimeout(300);
// 填写核心内容
await page.getByPlaceholder('请输入课程包核心内容').fill(TEST_COURSE_MINIMAL.coreContent);
// 点击下一步
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 2-6: 全部跳过 ====================
// 步骤 2: 课程介绍(跳过)
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(500);
// 步骤 3: 排课参考(跳过)
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(500);
// 步骤 4: 导入课(跳过)
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(500);
// 步骤 5: 集体课(跳过)
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(500);
// 步骤 6: 领域课(跳过)
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(500);
// ==================== 步骤 7: 环创建设(跳过)====================
// 直接点击创建按钮
await page.getByRole('button', { name: '创建' }).click();
// ==================== 验证创建成功 ====================
// 等待成功提示或页面跳转
try {
await page.waitForSelector('.ant-message-success', { timeout: 5000 });
} catch {
// 如果没有成功提示,可能是页面直接跳转了
console.log(' - 未检测到成功提示,检查页面跳转...');
}
// 等待跳转到列表页
await page.waitForURL('**/admin/courses**', { timeout: 15000 });
// 等待网络请求完成
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
// 额外等待确保数据加载完成
await page.waitForTimeout(5000);
// 验证新课程包在列表中显示
// 使用 page.getByText 而不是 table.getByText 来增加查找范围
const courseRow = page.getByText(courseName).first();
// 增加等待时间,允许数据加载
try {
await expect(courseRow).toBeVisible({ timeout: 20000 });
} catch {
// 如果找不到,尝试刷新页面
console.log('未找到课程包,尝试刷新页面...');
await page.goto('/admin/courses', { waitUntil: 'networkidle' });
await page.waitForTimeout(5000);
await expect(courseRow).toBeVisible({ timeout: 10000 });
}
console.log(`✅ 最小化测试通过 - 课程包 "${courseName}" 创建成功`);
});
});
test.describe('课程包创建 - 验证逻辑测试', () => {
test('必填项验证:未填必填项时无法进入下一步', async ({ page }) => {
test.setTimeout(60000);
// 步骤 1: 登录
await loginAsAdmin(page);
await page.waitForTimeout(1000);
// 步骤 2: 进入课程管理页面
await page.goto('/admin/courses', { timeout: 30000, waitUntil: 'commit' });
// 等待页面加载完成
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
await page.waitForTimeout(3000);
// 步骤 3: 点击新建课程包按钮
const createButton = page.getByRole('button', { name: '新建课程包' });
await createButton.click();
await page.waitForTimeout(1000);
// 验证进入创建页面
await expect(page.getByText('创建课程包')).toBeVisible({ timeout: 5000 });
// ==================== 测试必填项验证 ====================
// 不填写任何内容,直接点击下一步
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// 验证应该显示错误提示(通过表单验证样式判断)
// 课程包名称必填项应该显示红色边框
const nameInput = page.getByPlaceholder('请输入课程包名称');
await expect(nameInput).toBeVisible();
// 验证仍停留在步骤 1
await expect(page.getByText('基本信息')).toBeVisible();
await expect(page.getByRole('button', { name: '下一步' })).toBeVisible();
console.log('✅ 必填项验证测试通过 - 未填必填项时无法进入下一步');
});
test('部分填写验证:只填部分必填项时无法进入下一步', async ({ page }) => {
test.setTimeout(60000);
const partialCourseName = '部分填写测试-' + Date.now();
// 步骤 1: 登录
await loginAsAdmin(page);
await page.waitForTimeout(1000);
// 步骤 2: 进入课程管理页面
await page.goto('/admin/courses', { timeout: 30000, waitUntil: 'commit' });
// 等待页面加载完成
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
await page.waitForTimeout(3000);
// 步骤 3: 点击新建课程包按钮
const createButton = page.getByRole('button', { name: '新建课程包' });
await createButton.click();
await page.waitForTimeout(1000);
// 只填写课程名称,其他必填项不填
await page.getByPlaceholder('请输入课程包名称').fill(partialCourseName);
// 直接点击下一步
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// 验证应该仍停留在步骤 1因为主题和年级未选
await expect(page.getByText('基本信息')).toBeVisible();
console.log('✅ 部分填写验证测试通过 - 只填部分必填项时无法进入下一步');
});
});
test.describe('课程包创建 - 数据持久化测试', () => {
test('数据验证:创建后查看详情数据完整', async ({ page }) => {
test.setTimeout(180000);
const courseName = '数据验证测试-' + Date.now();
// 步骤 1: 登录
await loginAsAdmin(page);
await page.waitForTimeout(1000);
// 步骤 2: 进入课程管理页面
await page.goto('/admin/courses', { timeout: 30000, waitUntil: 'commit' });
// 等待页面加载完成
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
await page.waitForTimeout(3000);
// 步骤 3: 点击新建课程包按钮
const createButton = page.getByRole('button', { name: '新建课程包' });
await createButton.click();
await page.waitForTimeout(1000);
// ==================== 步骤 1: 基本信息 ====================
await page.getByPlaceholder('请输入课程包名称').fill(courseName);
await page.locator('.ant-select-selector').first().click();
await page.waitForTimeout(500);
await page.getByText('社会认知', { exact: true }).first().click();
await page.waitForTimeout(300);
await page.getByRole('checkbox', { name: '大班' }).click();
await page.waitForTimeout(300);
await page.getByPlaceholder('请输入课程包核心内容').fill('数据持久化测试内容');
await page.getByPlaceholder('请输入关联绘本名称(可选)').fill('测试绘本');
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 2: 课程介绍 ====================
await page.getByText('课程简介').click();
await page.waitForTimeout(500);
await page.locator('textarea[placeholder*="课程整体简介"]').first().fill('测试课程简介');
await page.waitForTimeout(300);
// 步骤 3 排课参考使用表格,直接跳过
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 4: 导入课 ====================
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 5: 集体课 ====================
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 6: 领域课 ====================
await page.getByRole('button', { name: '下一步' }).click();
await page.waitForTimeout(1000);
// ==================== 步骤 7: 环创建设 ====================
// 等待步骤面板稳定
await page.waitForTimeout(2000);
// 使用 force 选项直接填写,忽略 visible 检查
const envTextarea = page.locator('textarea[placeholder*="环创建设内容"]').first();
await envTextarea.fill('测试环创内容', { force: true });
await page.waitForTimeout(300);
// 点击创建按钮
await page.getByRole('button', { name: '创建' }).click();
// ==================== 验证创建成功 ====================
// 等待成功提示或页面跳转
try {
await page.waitForSelector('.ant-message-success', { timeout: 5000 });
} catch {
console.log(' - 未检测到成功提示,检查页面跳转...');
}
// 等待跳转到列表页
await page.waitForURL('**/admin/courses**', { timeout: 15000 });
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
await page.waitForTimeout(2000);
// ==================== 查看课程包详情 ====================
// 找到新课程包所在行 - 使用更宽松的等待
const table = page.locator('.ant-table').first();
await table.waitFor({ state: 'visible', timeout: 30000 });
// 等待表格内容加载
await page.waitForFunction(async () => {
const tableElement = document.querySelector('.ant-table');
if (!tableElement) return false;
const rows = tableElement.querySelectorAll('tbody tr[data-row-key]');
return rows.length > 0;
}, { timeout: 15000 }).catch(() => {});
const courseRow = table.locator('tr').filter({ hasText: courseName });
// 点击"查看"或"详情"按钮
await courseRow.getByRole('button', { name: /查看 | 详情/ }).click();
await page.waitForTimeout(2000);
// 验证详情页数据
await expect(page.getByText(courseName)).toBeVisible({ timeout: 5000 });
// 验证基本信息
await expect(page.getByText('社会认知')).toBeVisible();
await expect(page.getByText('大班')).toBeVisible();
await expect(page.getByText('测试绘本')).toBeVisible();
console.log(`✅ 数据持久化测试通过 - 课程包 "${courseName}" 数据完整`);
});
});