2026-05-02 08:44:12 +08:00
|
|
|
|
import { expect, test } from './fixtures';
|
|
|
|
|
|
|
|
|
|
|
|
const moicenUnionid = process.env.MOICEN_E2E_UNIONID?.trim();
|
|
|
|
|
|
test.skip(!moicenUnionid, 'MOICEN_E2E_UNIONID 未设置');
|
|
|
|
|
|
|
|
|
|
|
|
async function loginAsTeacher(page: any) {
|
|
|
|
|
|
const q = new URLSearchParams({ unionid: moicenUnionid!, status: '2' });
|
2026-05-03 19:14:36 +08:00
|
|
|
|
await page.goto(`/?${q.toString()}`, { waitUntil: 'domcontentloaded', timeout: 120_000 });
|
2026-05-02 08:44:12 +08:00
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
|
|
|
2026-05-03 21:35:31 +08:00
|
|
|
|
// 逐层处理选择页:机构选择 → 角色选择(可能有 0~n 层)
|
2026-05-03 21:45:07 +08:00
|
|
|
|
let roleSelected = false;
|
|
|
|
|
|
for (let i = 0; i < 5 && !roleSelected; i++) {
|
2026-05-03 21:35:31 +08:00
|
|
|
|
const path = await page.evaluate(() => window.location.pathname);
|
|
|
|
|
|
|
|
|
|
|
|
if (path === '/org/select') {
|
|
|
|
|
|
await page.locator('.van-cell').first().click();
|
|
|
|
|
|
await page.waitForTimeout(3_000);
|
|
|
|
|
|
} else if (path === '/') {
|
|
|
|
|
|
const rs = page.getByText('请选择您的登录身份');
|
|
|
|
|
|
try {
|
|
|
|
|
|
await rs.waitFor({ state: 'visible', timeout: 10_000 });
|
2026-05-03 21:45:07 +08:00
|
|
|
|
const items = page.locator('.van-grid-item');
|
|
|
|
|
|
const count = await items.count();
|
|
|
|
|
|
if (count >= 3) {
|
|
|
|
|
|
await items.nth(2).click(); // TEACHER role
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await items.first().click();
|
|
|
|
|
|
}
|
2026-05-03 21:35:31 +08:00
|
|
|
|
await page.waitForTimeout(3_000);
|
2026-05-03 21:45:07 +08:00
|
|
|
|
roleSelected = true;
|
2026-05-03 21:35:31 +08:00
|
|
|
|
} catch {
|
2026-05-03 21:45:07 +08:00
|
|
|
|
// 无角色选择器 → 角色已自动选中
|
2026-05-03 21:35:31 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-05-02 08:44:12 +08:00
|
|
|
|
} else {
|
2026-05-03 21:35:31 +08:00
|
|
|
|
// 已在目标页面
|
|
|
|
|
|
break;
|
2026-05-02 08:44:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 21:35:31 +08:00
|
|
|
|
// CI 环境下 /clazz 页加载较慢,给予更宽松的超时
|
|
|
|
|
|
test.setTimeout(240_000);
|
|
|
|
|
|
|
2026-05-02 08:44:12 +08:00
|
|
|
|
test.describe('排课双视图切换(阿难账号)', () => {
|
|
|
|
|
|
|
|
|
|
|
|
test('教师端排课页显示视图切换按钮', async ({ page }) => {
|
|
|
|
|
|
await loginAsTeacher(page);
|
|
|
|
|
|
await page.goto('/clazz', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
|
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
|
|
await page.waitForTimeout(3000);
|
|
|
|
|
|
|
|
|
|
|
|
// Check toggle buttons exist
|
|
|
|
|
|
const toggleBar = page.locator('.view-toolbar');
|
|
|
|
|
|
await expect(toggleBar).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
|
|
|
|
|
const calBtn = toggleBar.locator('button', { hasText: '日历' });
|
|
|
|
|
|
const matrixBtn = toggleBar.locator('button', { hasText: '矩阵' });
|
|
|
|
|
|
await expect(calBtn).toBeVisible();
|
|
|
|
|
|
await expect(matrixBtn).toBeVisible();
|
|
|
|
|
|
|
|
|
|
|
|
// Default is calendar — 日历 should be primary
|
|
|
|
|
|
await expect(calBtn).toHaveClass(/van-button--primary/);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-02 09:17:34 +08:00
|
|
|
|
test('切换到矩阵视图后显示时段列和日期行', async ({ page }) => {
|
2026-05-02 08:44:12 +08:00
|
|
|
|
await loginAsTeacher(page);
|
|
|
|
|
|
await page.goto('/clazz', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
|
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
|
|
await page.waitForTimeout(3000);
|
|
|
|
|
|
|
|
|
|
|
|
// Switch to matrix view
|
|
|
|
|
|
const matrixBtn = page.locator('.view-toolbar button', { hasText: '矩阵' });
|
|
|
|
|
|
await matrixBtn.click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
|
|
|
|
|
|
// Matrix container should be visible
|
|
|
|
|
|
const matrixContainer = page.locator('.matrix-container');
|
|
|
|
|
|
await expect(matrixContainer).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
2026-05-02 09:17:34 +08:00
|
|
|
|
// Header should show time slot labels (第一节 ~ 第四节)
|
|
|
|
|
|
const headerCells = page.locator('.header-cell');
|
|
|
|
|
|
await expect(headerCells.first()).toBeVisible();
|
|
|
|
|
|
const count = await headerCells.count();
|
|
|
|
|
|
await expect(count).toBeGreaterThanOrEqual(4);
|
|
|
|
|
|
|
|
|
|
|
|
// Each header cell should have slot label + time
|
|
|
|
|
|
const firstHeader = headerCells.first();
|
|
|
|
|
|
await expect(firstHeader.locator('.header-label')).toBeVisible();
|
|
|
|
|
|
await expect(firstHeader.locator('.header-time')).toBeVisible();
|
|
|
|
|
|
|
|
|
|
|
|
// Day sidebar should show weekday labels (周一 ~ 周日)
|
2026-05-02 11:37:51 +08:00
|
|
|
|
const dayCells = page.locator('.sidebar-row');
|
2026-05-02 09:17:34 +08:00
|
|
|
|
await expect(dayCells.first()).toBeVisible();
|
|
|
|
|
|
expect(await dayCells.count()).toBe(7);
|
|
|
|
|
|
|
|
|
|
|
|
// Each day cell should have day name + date
|
|
|
|
|
|
const firstDay = dayCells.first();
|
|
|
|
|
|
await expect(firstDay.locator('.day-name')).toBeVisible();
|
|
|
|
|
|
await expect(firstDay.locator('.day-date')).toBeVisible();
|
2026-05-02 08:44:12 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('矩阵视图导航栏可操作(上一周/本周/下一周)', async ({ page }) => {
|
|
|
|
|
|
await loginAsTeacher(page);
|
|
|
|
|
|
await page.goto('/clazz', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
|
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
|
|
await page.waitForTimeout(3000);
|
|
|
|
|
|
|
|
|
|
|
|
// Switch to matrix
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '矩阵' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
|
|
|
|
|
|
// Nav buttons should exist
|
2026-05-03 17:04:14 +08:00
|
|
|
|
const nav = page.locator('.view-toolbar__nav');
|
2026-05-02 08:44:12 +08:00
|
|
|
|
await expect(nav).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
|
|
|
|
|
// Click "本周" to reset to current week
|
|
|
|
|
|
await nav.locator('button', { hasText: '本周' }).click();
|
|
|
|
|
|
await page.waitForTimeout(2000);
|
|
|
|
|
|
|
|
|
|
|
|
// Should show date range
|
2026-05-03 17:04:14 +08:00
|
|
|
|
const range = page.locator('.view-toolbar__range');
|
2026-05-02 08:44:12 +08:00
|
|
|
|
await expect(range).toBeVisible();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-02 09:07:27 +08:00
|
|
|
|
test('矩阵视图课块显示课程名、老师、学生信息', async ({ page }) => {
|
2026-05-02 08:44:12 +08:00
|
|
|
|
await loginAsTeacher(page);
|
|
|
|
|
|
await page.goto('/clazz', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
|
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
|
|
await page.waitForTimeout(3000);
|
|
|
|
|
|
|
|
|
|
|
|
// Switch to matrix
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '矩阵' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
|
2026-05-02 09:17:34 +08:00
|
|
|
|
// Check event blocks exist in the grid
|
|
|
|
|
|
const eventBlocks = page.locator('.event-block');
|
2026-05-02 09:07:27 +08:00
|
|
|
|
const count = await eventBlocks.count();
|
2026-05-02 08:44:12 +08:00
|
|
|
|
|
2026-05-02 09:07:27 +08:00
|
|
|
|
if (count === 0) {
|
|
|
|
|
|
test.skip(true, '无排课数据');
|
|
|
|
|
|
return;
|
2026-05-02 08:44:12 +08:00
|
|
|
|
}
|
2026-05-02 09:07:27 +08:00
|
|
|
|
|
2026-05-02 09:17:34 +08:00
|
|
|
|
// First block should show course name
|
2026-05-02 09:07:27 +08:00
|
|
|
|
const first = eventBlocks.first();
|
|
|
|
|
|
await expect(first.locator('.ev-title')).toBeVisible();
|
|
|
|
|
|
|
|
|
|
|
|
// At least one block should have teacher name
|
|
|
|
|
|
const hasTeacher = await page.locator('.ev-teacher').count();
|
|
|
|
|
|
expect(hasTeacher).toBeGreaterThanOrEqual(0);
|
|
|
|
|
|
|
|
|
|
|
|
// At least one block should have student names
|
|
|
|
|
|
const hasStudents = await page.locator('.ev-students').count();
|
|
|
|
|
|
expect(hasStudents).toBeGreaterThanOrEqual(0);
|
2026-05-02 08:44:12 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-02 11:37:51 +08:00
|
|
|
|
test('矩阵视图同一时段多节课横向排列', async ({ page }) => {
|
|
|
|
|
|
await loginAsTeacher(page);
|
|
|
|
|
|
await page.goto('/clazz', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
|
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
|
|
await page.waitForTimeout(3000);
|
|
|
|
|
|
|
|
|
|
|
|
// Switch to matrix
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '矩阵' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
|
|
|
|
|
|
// Find a cell with 2+ event blocks and verify it uses flex row
|
|
|
|
|
|
const isHorizontal = await page.evaluate(() => {
|
|
|
|
|
|
const cells = document.querySelectorAll('.event-cell');
|
|
|
|
|
|
for (const cell of cells) {
|
|
|
|
|
|
const blocks = cell.querySelectorAll('.event-block');
|
|
|
|
|
|
if (blocks.length >= 2) {
|
|
|
|
|
|
const style = window.getComputedStyle(cell);
|
|
|
|
|
|
return style.display === 'flex' && style.flexDirection === 'row';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!isHorizontal) {
|
|
|
|
|
|
const hasMulti = await page.evaluate(() => {
|
|
|
|
|
|
const cells = document.querySelectorAll('.event-cell');
|
|
|
|
|
|
return Array.from(cells).some(c => c.querySelectorAll('.event-block').length >= 2);
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!hasMulti) {
|
|
|
|
|
|
test.skip(true, '无同一时段多节课数据');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
expect(isHorizontal).toBe(true);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-02 08:44:12 +08:00
|
|
|
|
test('从矩阵视图切换回日历视图后日历正常显示', async ({ page }) => {
|
|
|
|
|
|
await loginAsTeacher(page);
|
|
|
|
|
|
await page.goto('/clazz', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
|
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
|
|
await page.waitForTimeout(3000);
|
|
|
|
|
|
|
|
|
|
|
|
// Switch to matrix
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '矩阵' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
await expect(page.locator('.matrix-container')).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
|
|
|
|
|
// Switch back to calendar
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '日历' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
|
|
|
|
|
|
// Calendar (FullCalendar) should be visible
|
|
|
|
|
|
await expect(page.locator('.fc')).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
});
|
2026-05-03 08:54:33 +08:00
|
|
|
|
|
|
|
|
|
|
test('日历与矩阵视图起始日一致(周一)', async ({ page }) => {
|
|
|
|
|
|
await loginAsTeacher(page);
|
|
|
|
|
|
await page.goto('/clazz', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
|
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
|
|
await page.waitForTimeout(3000);
|
|
|
|
|
|
|
|
|
|
|
|
// 切换到矩阵
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '矩阵' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
await expect(page.locator('.matrix-container')).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
|
|
|
|
|
// 矩阵的日期范围应以周一开头
|
2026-05-03 17:04:14 +08:00
|
|
|
|
const rangeText = await page.locator('.view-toolbar__range').textContent();
|
2026-05-03 08:54:33 +08:00
|
|
|
|
expect(rangeText).toBeTruthy();
|
|
|
|
|
|
const startDate = rangeText?.split('~')[0]?.trim();
|
|
|
|
|
|
expect(startDate).toBeTruthy();
|
|
|
|
|
|
if (startDate) {
|
|
|
|
|
|
const day = new Date(startDate).getDay();
|
|
|
|
|
|
expect(day).toBe(1); // Monday = 1
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('来回切换视图后数据仍正常加载', async ({ page }) => {
|
|
|
|
|
|
await loginAsTeacher(page);
|
|
|
|
|
|
await page.goto('/clazz', { waitUntil: 'domcontentloaded', timeout: 60_000 });
|
|
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
|
|
await page.waitForTimeout(3000);
|
|
|
|
|
|
|
2026-05-03 17:04:14 +08:00
|
|
|
|
// 无排课数据则跳过
|
|
|
|
|
|
const hasEvents = await page.locator('.fc-event').first().isVisible().catch(() => false);
|
|
|
|
|
|
if (!hasEvents) {
|
|
|
|
|
|
test.skip(true, '当前账号无可视事件');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-05-03 08:54:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 切换到矩阵
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '矩阵' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
await expect(page.locator('.matrix-container')).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
|
|
|
|
|
// 矩阵应有事件
|
|
|
|
|
|
const matrixFirst = await page.locator('.event-block').count();
|
|
|
|
|
|
|
|
|
|
|
|
// 切回日历
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '日历' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
await expect(page.locator('.fc')).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
|
|
|
|
|
// 日历仍有事件(说明数据加载正常)
|
2026-05-03 20:04:57 +08:00
|
|
|
|
await expect(async () => {
|
|
|
|
|
|
const calEvents = await page.locator('.fc-event').count();
|
|
|
|
|
|
expect(calEvents).toBeGreaterThan(0);
|
|
|
|
|
|
}).toPass({ timeout: 10_000 });
|
2026-05-03 08:54:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 再次切到矩阵
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '矩阵' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
await expect(page.locator('.matrix-container')).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
|
|
|
|
|
// 矩阵仍有事件
|
|
|
|
|
|
const matrixSecond = await page.locator('.event-block').count();
|
|
|
|
|
|
expect(matrixSecond).toBeGreaterThanOrEqual(0);
|
|
|
|
|
|
|
|
|
|
|
|
// 导航到下一周再回来
|
2026-05-03 17:04:14 +08:00
|
|
|
|
await page.locator('.view-toolbar__nav button', { hasText: '›' }).click();
|
2026-05-03 08:54:33 +08:00
|
|
|
|
await page.waitForTimeout(2000);
|
|
|
|
|
|
|
|
|
|
|
|
// 切回日历
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '日历' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
|
|
|
|
|
|
|
|
// 再切回矩阵,回到本周
|
|
|
|
|
|
await page.locator('.view-toolbar button', { hasText: '矩阵' }).click();
|
|
|
|
|
|
await page.waitForTimeout(1500);
|
2026-05-03 17:04:14 +08:00
|
|
|
|
await page.locator('.view-toolbar__nav button', { hasText: '本周' }).click();
|
2026-05-03 08:54:33 +08:00
|
|
|
|
await page.waitForTimeout(2000);
|
|
|
|
|
|
|
|
|
|
|
|
// 回到本周后应有事件
|
|
|
|
|
|
const currentWeekEvents = await page.locator('.event-block').count();
|
|
|
|
|
|
expect(currentWeekEvents).toBeGreaterThanOrEqual(0);
|
|
|
|
|
|
});
|
2026-05-02 08:44:12 +08:00
|
|
|
|
});
|