kindergarten_java/lesingle-edu-reading-platform-frontend/tests/e2e/broadcast-flow/broadcast.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

508 lines
16 KiB
TypeScript
Raw Permalink 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.

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}: 访问失败`,
});
}
}
});
});