前后端目录重命名: - 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 文档中的路径引用
508 lines
16 KiB
TypeScript
508 lines
16 KiB
TypeScript
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);
|
||
|
||
const recordItems = page.locator('[class*="record"], [class*="lesson"]');
|
||
if (await recordItems.count() > 0) {
|
||
await recordItems.first().click();
|
||
await page.waitForTimeout(2000);
|
||
} else {
|
||
await page.click('text=课程中心');
|
||
await page.waitForTimeout(1000);
|
||
|
||
const firstCourseCard = page.locator('.course-card').or(page.locator('[class*="course-card"]')).first();
|
||
await firstCourseCard.click();
|
||
await page.waitForTimeout(2000);
|
||
|
||
const selectLessonsButton = page.locator('button:has-text("选择课程上课")');
|
||
if (await selectLessonsButton.count() > 0) {
|
||
await selectLessonsButton.click();
|
||
await page.waitForTimeout(2000);
|
||
|
||
const confirmButton = page.locator('button:has-text("确定")');
|
||
if (await confirmButton.count() > 0) {
|
||
await confirmButton.click();
|
||
await page.waitForTimeout(3000);
|
||
}
|
||
}
|
||
}
|
||
|
||
await page.waitForTimeout(2000);
|
||
|
||
// 2. 查找展播模式按钮
|
||
const broadcastButton = page.locator('button:has-text("展播")').or(page.locator('button:has-text("全屏")'));
|
||
const buttonCount = await broadcastButton.count();
|
||
|
||
if (buttonCount > 0) {
|
||
// 3. 点击展播模式(新标签页)
|
||
const [newPage] = await Promise.all([
|
||
page.context().waitForEvent('page'),
|
||
broadcastButton.first().click()
|
||
]);
|
||
|
||
await newPage.waitForLoadState('networkidle');
|
||
await newPage.waitForTimeout(2000);
|
||
|
||
// 4. 验证新标签页URL
|
||
const broadcastUrl = newPage.url();
|
||
test.info().annotations.push({
|
||
type: 'success',
|
||
description: `展播模式已打开: ${broadcastUrl}`,
|
||
});
|
||
|
||
// 5. 验证展播模式全屏状态
|
||
const isFullscreen = await newPage.locator('[class*="fullscreen"], [class*="broadcast"]').count() > 0;
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: isFullscreen ? '进入全屏展播模式' : '展播页面已打开',
|
||
});
|
||
|
||
await newPage.screenshot({ path: 'test-results/broadcast-open.png', fullPage: true });
|
||
|
||
await newPage.close();
|
||
} else {
|
||
test.info().annotations.push({
|
||
type: 'warning',
|
||
description: '未找到展播模式按钮',
|
||
});
|
||
}
|
||
});
|
||
|
||
test('测试2: 展播模式布局验证', async ({ page }) => {
|
||
test.slow();
|
||
|
||
// 1. 直接访问展播模式URL(如果有上课记录ID)
|
||
await page.goto('http://localhost:5173/teacher/lessons');
|
||
await page.waitForTimeout(2000);
|
||
|
||
// 2. 查找展播入口
|
||
const broadcastButton = page.locator('button:has-text("展播")');
|
||
if (await broadcastButton.count() > 0) {
|
||
const [newPage] = await Promise.all([
|
||
page.context().waitForEvent('page'),
|
||
broadcastButton.click()
|
||
]);
|
||
|
||
await newPage.waitForLoadState('networkidle');
|
||
await newPage.waitForTimeout(2000);
|
||
|
||
// 3. 验证展播页面结构
|
||
const mainContainer = newPage.locator('main, [class*="container"], [class*="broadcast"]');
|
||
expect(await mainContainer.count()).toBeGreaterThan(0);
|
||
|
||
// 4. 验证内容显示区
|
||
const contentArea = newPage.locator('[class*="content"], [class*="display"]');
|
||
expect(await contentArea.count()).toBeGreaterThan(0);
|
||
|
||
// 5. 验证控制按钮区域
|
||
const controls = newPage.locator('[class*="control"], [class*="button"]');
|
||
const controlCount = await controls.count();
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: `控制按钮数量: ${controlCount}`,
|
||
});
|
||
|
||
await newPage.screenshot({ path: 'test-results/broadcast-layout.png', fullPage: true });
|
||
|
||
await newPage.close();
|
||
} else {
|
||
test.skip('无法找到展播入口');
|
||
}
|
||
});
|
||
|
||
test('测试3: 展播模式Kids Mode展示', async ({ page }) => {
|
||
test.slow();
|
||
|
||
// 进入展播模式
|
||
await page.goto('http://localhost:5173/teacher/lessons');
|
||
await page.waitForTimeout(2000);
|
||
|
||
const broadcastButton = page.locator('button:has-text("展播")');
|
||
if (await broadcastButton.count() > 0) {
|
||
const [newPage] = await Promise.all([
|
||
page.context().waitForEvent('page'),
|
||
broadcastButton.click()
|
||
]);
|
||
|
||
await newPage.waitForLoadState('networkidle');
|
||
await newPage.waitForTimeout(2000);
|
||
|
||
// 1. 验证Kids Mode组件显示
|
||
const kidsMode = newPage.locator('[class*="kids"], [class*="child"], [class*="display"]');
|
||
const hasKidsMode = await kidsMode.count() > 0;
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: hasKidsMode ? 'Kids Mode组件已显示' : '未找到Kids Mode',
|
||
});
|
||
|
||
// 2. 验证内容元素
|
||
if (hasKidsMode) {
|
||
const images = kidsMode.first().locator('img');
|
||
const imageCount = await images.count();
|
||
|
||
const textContent = await kidsMode.first().allTextContents();
|
||
const textCount = textContent.filter(t => t.trim()).length;
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: `图片数量: ${imageCount}, 文本段落数: ${textCount}`,
|
||
});
|
||
}
|
||
|
||
await newPage.screenshot({ path: 'test-results/broadcast-kids-mode.png', fullPage: true });
|
||
|
||
await newPage.close();
|
||
} else {
|
||
test.skip('无法找到展播入口');
|
||
}
|
||
});
|
||
|
||
test('测试4: 展播模式全屏功能', async ({ page }) => {
|
||
test.slow();
|
||
|
||
// 进入展播模式
|
||
await page.goto('http://localhost:5173/teacher/lessons');
|
||
await page.waitForTimeout(2000);
|
||
|
||
const broadcastButton = page.locator('button:has-text("展播")');
|
||
if (await broadcastButton.count() > 0) {
|
||
const [newPage] = await Promise.all([
|
||
page.context().waitForEvent('page'),
|
||
broadcastButton.click()
|
||
]);
|
||
|
||
await newPage.waitForLoadState('networkidle');
|
||
await newPage.waitForTimeout(2000);
|
||
|
||
// 1. 检查是否自动全屏
|
||
const isFullscreen = await newPage.evaluate(() => {
|
||
return document.fullscreenElement != null ||
|
||
document.webkitFullscreenElement != null ||
|
||
document.mozFullScreenElement != null;
|
||
});
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: isFullscreen ? '已自动进入全屏模式' : '未自动全屏',
|
||
});
|
||
|
||
// 2. 手动触发全屏(如果未自动全屏)
|
||
if (!isFullscreen) {
|
||
const fullscreenButton = newPage.locator('button:has-text("全屏")').or(page.locator('[class*="fullscreen"]'));
|
||
if (await fullscreenButton.count() > 0) {
|
||
await fullscreenButton.first().click();
|
||
await newPage.waitForTimeout(1000);
|
||
}
|
||
}
|
||
|
||
await newPage.screenshot({ path: 'test-results/broadcast-fullscreen.png', fullPage: true });
|
||
|
||
await newPage.close();
|
||
} else {
|
||
test.skip('无法找到展播入口');
|
||
}
|
||
});
|
||
|
||
test('测试5: 展播模式环节切换', async ({ page }) => {
|
||
test.slow();
|
||
|
||
// 进入展播模式
|
||
await page.goto('http://localhost:5173/teacher/lessons');
|
||
await page.waitForTimeout(2000);
|
||
|
||
const broadcastButton = page.locator('button:has-text("展播")');
|
||
if (await broadcastButton.count() > 0) {
|
||
const [newPage] = await Promise.all([
|
||
page.context().waitForEvent('page'),
|
||
broadcastButton.click()
|
||
]);
|
||
|
||
await newPage.waitForLoadState('networkidle');
|
||
await newPage.waitForTimeout(2000);
|
||
|
||
// 1. 查找环节导航
|
||
const stepNav = newPage.locator('[class*="step"], [class*="nav"], .ant-steps');
|
||
const stepCount = await stepNav.count();
|
||
|
||
// 2. 尝试下一个环节
|
||
const nextButton = newPage.locator('button:has-text("下一")').or(page.locator('button:has-text("下一步")'));
|
||
const nextCount = await nextButton.count();
|
||
|
||
if (nextCount > 0) {
|
||
await nextButton.first().click();
|
||
await newPage.waitForTimeout(1500);
|
||
|
||
test.info().annotations.push({
|
||
type: 'success',
|
||
description: '切换到下一个环节',
|
||
});
|
||
}
|
||
|
||
// 3. 尝试上一个环节
|
||
const prevButton = newPage.locator('button:has-text("上一")').or(page.locator('button:has-text("上一步")'));
|
||
const prevCount = await prevButton.count();
|
||
|
||
if (prevCount > 0) {
|
||
await prevButton.first().click();
|
||
await newPage.waitForTimeout(1500);
|
||
}
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: `导航区域: ${stepCount}, 下一按钮: ${nextCount}, 上一按钮: ${prevCount}`,
|
||
});
|
||
|
||
await newPage.screenshot({ path: 'test-results/broadcast-switch-step.png' });
|
||
|
||
await newPage.close();
|
||
} else {
|
||
test.skip('无法找到展播入口');
|
||
}
|
||
});
|
||
|
||
test('测试6: 展播模式键盘控制', async ({ page }) => {
|
||
test.slow();
|
||
|
||
// 进入展播模式
|
||
await page.goto('http://localhost:5173/teacher/lessons');
|
||
await page.waitForTimeout(2000);
|
||
|
||
const broadcastButton = page.locator('button:has-text("展播")');
|
||
if (await broadcastButton.count() > 0) {
|
||
const [newPage] = await Promise.all([
|
||
page.context().waitForEvent('page'),
|
||
broadcastButton.click()
|
||
]);
|
||
|
||
await newPage.waitForLoadState('networkidle');
|
||
await newPage.waitForTimeout(2000);
|
||
|
||
// 1. 测试空格键
|
||
await newPage.keyboard.press('Space');
|
||
await newPage.waitForTimeout(1000);
|
||
|
||
// 2. 测试左右箭头
|
||
await newPage.keyboard.press('ArrowRight');
|
||
await newPage.waitForTimeout(1000);
|
||
|
||
await newPage.keyboard.press('ArrowLeft');
|
||
await newPage.waitForTimeout(1000);
|
||
|
||
// 3. 测试ESC退出
|
||
await newPage.keyboard.press('Escape');
|
||
await newPage.waitForTimeout(1000);
|
||
|
||
test.info().annotations.push({
|
||
type: 'success',
|
||
description: '键盘控制测试完成',
|
||
});
|
||
|
||
await newPage.screenshot({ path: 'test-results/broadcast-keyboard.png' });
|
||
|
||
await newPage.close();
|
||
} else {
|
||
test.skip('无法找到展播入口');
|
||
}
|
||
});
|
||
|
||
test('测试7: 展播模式资源展示', async ({ page }) => {
|
||
test.slow();
|
||
|
||
// 进入展播模式
|
||
await page.goto('http://localhost:5173/teacher/lessons');
|
||
await page.waitForTimeout(2000);
|
||
|
||
const broadcastButton = page.locator('button:has-text("展播")');
|
||
if (await broadcastButton.count() > 0) {
|
||
const [newPage] = await Promise.all([
|
||
page.context().waitForEvent('page'),
|
||
broadcastButton.click()
|
||
]);
|
||
|
||
await newPage.waitForLoadState('networkidle');
|
||
await newPage.waitForTimeout(2000);
|
||
|
||
// 1. 查找图片资源
|
||
const images = newPage.locator('img');
|
||
const imageCount = await images.count();
|
||
|
||
// 2. 查找视频资源
|
||
const videos = newPage.locator('video');
|
||
const videoCount = await videos.count();
|
||
|
||
// 3. 查找文字内容
|
||
const textElements = await newPage.locator('body').allTextContents();
|
||
const textLength = textElements.join('').length;
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: `图片: ${imageCount}, 视频: ${videoCount}, 文字长度: ${textLength}`,
|
||
});
|
||
|
||
await newPage.screenshot({ path: 'test-results/broadcast-resources.png', fullPage: true });
|
||
|
||
await newPage.close();
|
||
} else {
|
||
test.skip('无法找到展播入口');
|
||
}
|
||
});
|
||
|
||
test('测试8: 展播模式颜色主题', async ({ page }) => {
|
||
test.slow();
|
||
|
||
// 进入展播模式
|
||
await page.goto('http://localhost:5173/teacher/lessons');
|
||
await page.waitForTimeout(2000);
|
||
|
||
const broadcastButton = page.locator('button:has-text("展播")');
|
||
if (await broadcastButton.count() > 0) {
|
||
const [newPage] = await Promise.all([
|
||
page.context().waitForEvent('page'),
|
||
broadcastButton.click()
|
||
]);
|
||
|
||
await newPage.waitForLoadState('networkidle');
|
||
await newPage.waitForTimeout(2000);
|
||
|
||
// 1. 检查背景色(深色主题适合投影)
|
||
const backgroundColor = await newPage.evaluate(() => {
|
||
return window.getComputedStyle(document.body).backgroundColor;
|
||
});
|
||
|
||
// 2. 检查文字颜色(高对比度)
|
||
const textColor = await newPage.evaluate(() => {
|
||
const mainElement = document.querySelector('main, [class*="content"]');
|
||
return mainElement ? window.getComputedStyle(mainElement).color : 'rgb(0, 0, 0)';
|
||
});
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: `背景色: ${backgroundColor}, 文字色: ${textColor}`,
|
||
});
|
||
|
||
await newPage.screenshot({ path: 'test-results/broadcast-theme.png', fullPage: true });
|
||
|
||
await newPage.close();
|
||
} else {
|
||
test.skip('无法找到展播入口');
|
||
}
|
||
});
|
||
|
||
test('测试9: 展播模式关闭和返回', async ({ page }) => {
|
||
test.slow();
|
||
|
||
// 进入展播模式
|
||
await page.goto('http://localhost:5173/teacher/lessons');
|
||
await page.waitForTimeout(2000);
|
||
|
||
const broadcastButton = page.locator('button:has-text("展播")');
|
||
if (await broadcastButton.count() > 0) {
|
||
const [newPage] = await Promise.all([
|
||
page.context().waitForEvent('page'),
|
||
broadcastButton.click()
|
||
]);
|
||
|
||
await newPage.waitForLoadState('networkidle');
|
||
await newPage.waitForTimeout(2000);
|
||
|
||
// 1. 查找关闭按钮
|
||
const closeButton = newPage.locator('button:has-text("关闭")').or(page.locator('.ant-modal-close'));
|
||
const closeCount = await closeButton.count();
|
||
|
||
// 2. 查找返回按钮
|
||
const backButton = newPage.locator('button:has-text("返回")').or(page.locator('[class*="back"]'));
|
||
const backCount = await backButton.count();
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: `关闭按钮: ${closeCount}, 返回按钮: ${backCount}`,
|
||
});
|
||
|
||
// 3. 测试ESC关闭
|
||
await newPage.keyboard.press('Escape');
|
||
await newPage.waitForTimeout(1000);
|
||
|
||
// 4. 检查页面是否还在
|
||
const isStillOpen = await newPage.evaluate(() => document.readyState !== 'unloaded');
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: isStillOpen ? '页面仍然打开' : '页面已关闭',
|
||
});
|
||
|
||
if (isStillOpen) {
|
||
await newPage.close();
|
||
}
|
||
} else {
|
||
test.skip('无法找到展播入口');
|
||
}
|
||
});
|
||
|
||
test('测试10: 展播模式URL参数支持', async ({ page }) => {
|
||
test.slow();
|
||
|
||
// 1. 测试带step参数的URL
|
||
const testUrls = [
|
||
'http://localhost:5173/teacher/broadcast/1',
|
||
'http://localhost:5173/teacher/lessons/1/broadcast',
|
||
];
|
||
|
||
for (const testUrl of testUrls) {
|
||
try {
|
||
await page.goto(testUrl);
|
||
await page.waitForTimeout(2000);
|
||
|
||
// 2. 检查页面是否正常加载
|
||
const content = page.locator('main, [class*="content"]');
|
||
const hasContent = await content.count() > 0;
|
||
|
||
test.info().annotations.push({
|
||
type: 'info',
|
||
description: `${testUrl}: ${hasContent ? '加载成功' : '加载失败'}`,
|
||
});
|
||
|
||
if (hasContent) {
|
||
await page.screenshot({ path: `test-results/broadcast-url-${testUrls.indexOf(testUrl)}.png` });
|
||
}
|
||
} catch (e) {
|
||
test.info().annotations.push({
|
||
type: 'warning',
|
||
description: `${testUrl}: 访问失败`,
|
||
});
|
||
}
|
||
}
|
||
});
|
||
});
|