import { test, expect, Page } from '@playwright/test'; /** * 学校端排课功能完整测试 * 测试范围:Tab导航、列表视图、课表视图、日历视图、4步向导创建排课 */ const BASE_URL = 'http://localhost:5174'; const LOGIN_CREDS = { username: 'school1', password: '123456' }; test.describe('学校端排课功能测试', () => { let page: Page; let authToken: string; test.beforeAll(async ({ browser }) => { page = await browser.newPage(); // 登录获取 token const loginResponse = await page.request.post(`${BASE_URL}/api/v1/auth/login`, { data: { username: LOGIN_CREDS.username, password: LOGIN_CREDS.password } }); const loginData = await loginResponse.json(); expect(loginData.code).toBe(200); authToken = loginData.data.token; // 设置 localStorage await page.goto(BASE_URL); await page.evaluate(([token, user]) => { localStorage.setItem('token', token); localStorage.setItem('user', JSON.stringify(user)); }, [authToken, loginData.data]); }); test.afterAll(async () => { await page.close(); }); test.describe('Tab 导航切换', () => { test('应该能访问排课页面', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 验证页面标题 const title = await page.textContent('.page-header h2, h1'); expect(title).toContain('课程排期'); }); test('应该显示三个Tab导航', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 验证Tab存在 const tabs = await page.locator('a[role="tab"], .ant-tabs-tab').all(); expect(tabs.length).toBeGreaterThanOrEqual(3); const tabTexts = await Promise.all(tabs.map(tab => tab.textContent())); console.log('✅ 找到Tab:', tabTexts); }); test('应该能切换到列表视图', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 点击列表视图Tab const listTab = page.locator('a[role="tab"], .ant-tabs-tab').filter({ hasText: '列表' }).first(); await listTab.click(); await page.waitForTimeout(500); // 验证列表视图内容 const table = page.locator('.ant-table'); await expect(table.first()).toBeVisible(); }); test('应该能切换到课表视图', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 点击课表视图Tab const timetableTab = page.locator('a[role="tab"], .ant-tabs-tab').filter({ hasText: '课表' }).first(); await timetableTab.click(); await page.waitForTimeout(500); // 验证课表视图内容 const timetableHeader = page.locator('.timetable-header, .day-header'); await expect(timetableHeader.first()).toBeVisible(); }); test('应该能切换到日历视图', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 点击日历视图Tab const calendarTab = page.locator('a[role="tab"], .ant-tabs-tab').filter({ hasText: '日历' }).first(); await calendarTab.click(); await page.waitForTimeout(500); // 验证日历视图内容 const calendarView = page.locator('.ant-picker-calendar'); await expect(calendarView.first()).toBeVisible(); }); }); test.describe('列表视图功能', () => { test('应该显示排课列表数据', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 确保在列表视图 const listTab = page.locator('a[role="tab"], .ant-tabs-tab').filter({ hasText: '列表' }).first(); await listTab.click(); await page.waitForTimeout(1000); // 检查表格是否存在 const table = page.locator('.ant-table'); await expect(table.first()).toBeVisible(); // 尝试获取排课数据 const rows = await table.locator('.ant-table-tbody tr').all(); console.log(`✅ 列表视图找到 ${rows.length} 行数据`); }); test('应该有筛选功能', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 检查筛选器 const classSelect = page.locator('select[placeholder*="班级"], .ant-select').first(); const teacherSelect = page.locator('.filter-section .ant-select').nth(1); // 筛选器可能存在,记录日志 const hasClassFilter = await classSelect.count() > 0; const hasTeacherFilter = await teacherSelect.count() > 0; console.log(`✅ 班级筛选: ${hasClassFilter ? '存在' : '不存在'}`); console.log(`✅ 教师筛选: ${hasTeacherFilter ? '存在' : '不存在'}`); }); }); test.describe('课表视图功能', () => { test('应该显示周次导航', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 切换到课表视图 const timetableTab = page.locator('a[role="tab"], .ant-tabs-tab').filter({ hasText: '课表' }).first(); await timetableTab.click(); await page.waitForTimeout(1000); // 检查周次导航按钮 const prevWeekBtn = page.locator('button:has-text("上一周")'); const nextWeekBtn = page.locator('button:has-text("下一周")'); const currentWeekBtn = page.locator('button:has-text("本周")'); const hasPrevBtn = await prevWeekBtn.count() > 0; const hasNextBtn = await nextWeekBtn.count() > 0; const hasCurrentBtn = await currentWeekBtn.count() > 0; console.log(`✅ 上一周按钮: ${hasPrevBtn ? '存在' : '不存在'}`); console.log(`✅ 下一周按钮: ${hasNextBtn ? '存在' : '不存在'}`); console.log(`✅ 本周按钮: ${hasCurrentBtn ? '存在' : '不存在'}`); }); test('应该显示周一到周日的列', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); const timetableTab = page.locator('a[role="tab"], .ant-tabs-tab').filter({ hasText: '课表' }).first(); await timetableTab.click(); await page.waitForTimeout(1000); // 检查周次显示 const dayHeaders = page.locator('.day-header, .timetable-header > div'); const count = await dayHeaders.count(); console.log(`✅ 找到 ${count} 个日期列`); }); }); test.describe('新建排课功能', () => { test('应该能打开新建排课弹窗', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 点击新建排课按钮 const createBtn = page.locator('button:has-text("新建排课")'); const btnCount = await createBtn.count(); if (btnCount > 0) { await createBtn.first().click(); await page.waitForTimeout(1000); // 检查弹窗是否打开 const modal = page.locator('.ant-modal').filter({ hasText: /新建|创建|排课/ }); const isVisible = await modal.isVisible(); console.log(`✅ 新建排课弹窗: ${isVisible ? '已打开' : '未打开'}`); } else { console.log('⚠️ 未找到新建排课按钮'); } }); test('步骤1:应该能选择课程套餐', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 打开新建排课弹窗 const createBtn = page.locator('button:has-text("新建排课")'); if (await createBtn.count() > 0) { await createBtn.first().click(); await page.waitForTimeout(1000); // 检查课程套餐下拉框 const collectionSelect = page.locator('.ant-modal .ant-select').first(); const hasSelect = await collectionSelect.count() > 0; console.log(`✅ 课程套餐选择器: ${hasSelect ? '存在' : '不存在'}`); } }); test('步骤3:应该支持为每个班级分配教师', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); // 打开新建排课弹窗 const createBtn = page.locator('button:has-text("新建排课")'); if (await createBtn.count() > 0) { await createBtn.first().click(); await page.waitForTimeout(1000); // 尝试找到教师分配相关元素 const teacherLabel = page.locator('.ant-modal label:has-text("教师")'); const count = await teacherLabel.count(); console.log(`✅ 教师分配相关元素: 找到 ${count} 个`); } }); }); test.describe('API 后端测试', () => { test('应该能获取课程套餐列表', async () => { const response = await page.request.get(`${BASE_URL}/api/v1/school/packages`, { headers: { 'Authorization': `Bearer ${authToken}` } }); expect(response.status()).toBe(200); const data = await response.json(); expect(data.code).toBe(200); console.log(`✅ 获取课程套餐列表成功:`, data.data ? JSON.stringify(data.data).substring(0, 100) + '...' : '无数据'); }); test('应该能获取课表数据', async () => { const today = new Date().toISOString().split('T')[0]; const startDate = today; const endDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; const response = await page.request.get(`${BASE_URL}/api/v1/school/schedules/timetable?startDate=${startDate}&endDate=${endDate}`, { headers: { 'Authorization': `Bearer ${authToken}` } }); expect(response.status()).toBe(200); const data = await response.json(); expect(data.code).toBe(200); console.log(`✅ 获取课表数据成功:`, data.data ? '有数据' : '无数据'); }); test('应该能获取排课列表', async () => { const response = await page.request.get(`${BASE_URL}/api/v1/school/schedules?pageNum=1&pageSize=10`, { headers: { 'Authorization': `Bearer ${authToken}` } }); expect(response.status()).toBe(200); const data = await response.json(); expect(data.code).toBe(200); if (data.data && data.data.list) { console.log(`✅ 获取排课列表成功: ${data.data.list.length} 条记录`); } else { console.log(`✅ 获取排课列表成功: 无数据`); } }); test('应该能获取班级列表', async () => { const response = await page.request.get(`${BASE_URL}/api/v1/school/classes`, { headers: { 'Authorization': `Bearer ${authToken}` } }); expect(response.status()).toBe(200); const data = await response.json(); expect(data.code).toBe(200); console.log(`✅ 获取班级列表成功:`, Array.isArray(data.data) ? `${data.data.length} 个班级` : '数据格式正确'); }); test('应该能获取教师列表', async () => { const response = await page.request.get(`${BASE_URL}/api/v1/school/teachers?pageNum=1&pageSize=50`, { headers: { 'Authorization': `Bearer ${authToken}` } }); expect(response.status()).toBe(200); const data = await response.json(); expect(data.code).toBe(200); if (data.data && data.data.list) { console.log(`✅ 获取教师列表成功: ${data.data.list.length} 位教师`); } }); }); test.describe('创建排课时序测试(E2E)', () => { test('完整流程测试:从登录到创建排课', async () => { // 已登录,直接跳转到排课页面 await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); console.log('📍 当前页面:', page.url()); console.log('✅ 成功访问排课页面'); }); test('截图:当前排课页面状态', async () => { await page.goto(`${BASE_URL}/school/schedule`); await page.waitForLoadState('networkidle'); await page.waitForTimeout(2000); // 截图 await page.screenshot({ path: 'screenshots/schedule-page-current.png', fullPage: true }); console.log('✅ 已保存截图: screenshots/schedule-page-current.png'); }); }); });