6c9a463301
music-room Playwright (Gitea Actions) / playwright (push) Failing after 1h5m50s
- Increase page.goto timeout from 60s to 120s in all login helpers (9 CI timeout failures were all login page.goto timeouts) - Make clazz-supervisor-matrix switchToRole gracefully skip when role switcher icon is not available (no multi-role user) - Update clazz-scheduling tests to match production UI (no .fc-createClazz-button, use .view-toolbar instead) - Update clazz-ui .fc-toolbar → .view-toolbar selector - Update department test to handle single-dept optional CurrentDepartmentId (multi-org user may not auto-select) - Update teacher-switching tests to match student profile UI (no .current-teacher section) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
357 lines
12 KiB
TypeScript
357 lines
12 KiB
TypeScript
import { expect, test } from './fixtures';
|
|
|
|
const moicenUnionid = process.env.MOICEN_E2E_UNIONID?.trim();
|
|
|
|
test.describe('学生老师切换', () => {
|
|
|
|
async function loginAsStudent(page: import('@playwright/test').Page) {
|
|
const q = new URLSearchParams({ unionid: moicenUnionid!, status: '2' });
|
|
await page.goto(`/?${q.toString()}`, {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 120_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
// Wait for SPA to settle and login/role resolution to complete
|
|
await page.waitForTimeout(5000);
|
|
|
|
// Pick STUDENT role explicitly
|
|
const roleSelect = page.getByText('请选择您的登录身份');
|
|
if (await roleSelect.isVisible().catch(() => false)) {
|
|
const studentRole = page.locator('.van-grid-item').filter({ hasText: '学生' });
|
|
if (await studentRole.isVisible().catch(() => false)) {
|
|
await studentRole.click();
|
|
await page.waitForTimeout(5000);
|
|
}
|
|
}
|
|
|
|
// Handle org select if needed
|
|
if (page.url().includes('/org/select')) {
|
|
const guest = page.getByText('请返回微信小程序完成登录');
|
|
if (await guest.isVisible().catch(() => false)) {
|
|
test.skip(true, '会话不可用');
|
|
return;
|
|
}
|
|
const orgCells = page.locator('#app .van-cell-group .van-cell');
|
|
const n = await orgCells.count();
|
|
expect(n).toBeGreaterThan(0);
|
|
await orgCells.first().click();
|
|
await page.waitForTimeout(5000);
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
|
|
if (await guest.isVisible().catch(() => false)) {
|
|
test.skip(true, 'org switch 后会话不可用');
|
|
return;
|
|
}
|
|
|
|
await page.goto('/student/profile', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 60_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
}
|
|
|
|
// Ensure we're on a student page
|
|
const currentPath = new URL(page.url()).pathname;
|
|
if (!currentPath.startsWith('/student/')) {
|
|
await page.goto('/student/profile', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 60_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
}
|
|
|
|
// Skip if session lost
|
|
if (
|
|
await page
|
|
.getByText('请返回微信小程序完成登录')
|
|
.isVisible()
|
|
.catch(() => false)
|
|
) {
|
|
test.skip(true, '无法以学生身份访问 profile');
|
|
return;
|
|
}
|
|
}
|
|
|
|
test('学生 profile 页加载无崩溃且可进入我的老师列表', async ({ page }) => {
|
|
test.setTimeout(180_000);
|
|
|
|
await loginAsStudent(page);
|
|
|
|
// Navigate to student profile
|
|
await page.goto('/student/profile', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 60_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Profile page should load without crashing (no teacher section for students anymore)
|
|
const profileHeader = page.locator('.info');
|
|
await expect(profileHeader).toBeVisible({ timeout: 15_000 });
|
|
|
|
// Students can navigate to teacher list from bottom tab or profile
|
|
await page.goto('/student/teachers', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 60_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
if (
|
|
await page
|
|
.getByText('请返回微信小程序完成登录')
|
|
.isVisible()
|
|
.catch(() => false)
|
|
) {
|
|
test.skip(true, '会话已过期');
|
|
return;
|
|
}
|
|
|
|
// Teacher list page should load (may be empty if no teachers)
|
|
const teacherCells = page.locator('.van-cell');
|
|
const count = await teacherCells.count();
|
|
// At minimum the page should render (even if empty state is shown)
|
|
expect(count).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
test('「我的老师」列表显示本机构老师', async ({ page }) => {
|
|
test.setTimeout(180_000);
|
|
|
|
await loginAsStudent(page);
|
|
|
|
// Navigate to my teachers page
|
|
await page.goto('/student/teachers', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 60_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
if (
|
|
await page
|
|
.getByText('请返回微信小程序完成登录')
|
|
.isVisible()
|
|
.catch(() => false)
|
|
) {
|
|
test.skip(true, '会话已过期');
|
|
return;
|
|
}
|
|
|
|
// Check teacher list — may be empty if student has no teachers in this org
|
|
const teacherCells = page.locator('.van-cell');
|
|
const count = await teacherCells.count();
|
|
if (count === 0) {
|
|
test.skip(true, '该学生名下没有关联老师');
|
|
return;
|
|
}
|
|
expect(count).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('角色切换后自动选择老师且不会回机构选择页', async ({ page }) => {
|
|
test.setTimeout(180_000);
|
|
|
|
// Login as student to establish session
|
|
await loginAsStudent(page);
|
|
|
|
// Navigate to profile
|
|
await page.goto('/student/profile', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 60_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Scroll to top so role-switcher icon is visible
|
|
await page.evaluate(() => window.scrollTo(0, 0));
|
|
await page.waitForTimeout(500);
|
|
|
|
const roleSwitcher = page.locator('.van-icon-exchange');
|
|
if (!(await roleSwitcher.isVisible().catch(() => false))) {
|
|
test.skip(true, '学生角色没有多角色切换权限');
|
|
return;
|
|
}
|
|
await roleSwitcher.click();
|
|
|
|
// Wait for action sheet to appear and settle
|
|
await page.locator('.van-action-sheet').waitFor({ state: 'visible', timeout: 10_000 });
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Select TEACHER role (index 2 in ["测试员","学生","教师","管理员","主管教师"])
|
|
const teacherRoleBtn = page.locator('.van-action-sheet__item').nth(2);
|
|
if (!(await teacherRoleBtn.isVisible().catch(() => false))) {
|
|
test.skip(true, '未找到教师角色选项');
|
|
return;
|
|
}
|
|
await teacherRoleBtn.click();
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Handle confirm dialog if shown
|
|
const confirmDialog = page.locator('.van-dialog__confirm');
|
|
if (await confirmDialog.isVisible().catch(() => false)) {
|
|
await confirmDialog.click();
|
|
await page.waitForTimeout(5000);
|
|
}
|
|
|
|
// Wait for role switch to complete
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Navigate to teacher profile to ensure we're in teacher context
|
|
await page.goto('/teacher/profile', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 60_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
if (await page.getByText('请返回微信小程序完成登录').isVisible().catch(() => false)) {
|
|
test.skip(true, '老师会话不可用');
|
|
return;
|
|
}
|
|
|
|
// Now switch back to STUDENT role
|
|
const roleSwitcher2 = page.locator('.van-icon-exchange');
|
|
await roleSwitcher2.click();
|
|
|
|
// Wait for action sheet to appear and settle
|
|
await page.locator('.van-action-sheet').waitFor({ state: 'visible', timeout: 10_000 });
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Select STUDENT role (index 1 in ["测试员","学生","教师","管理员","主管教师"])
|
|
const studentRoleBtn = page.locator('.van-action-sheet__item').nth(1);
|
|
if (!(await studentRoleBtn.isVisible().catch(() => false))) {
|
|
test.skip(true, '未找到学生角色选项');
|
|
return;
|
|
}
|
|
await studentRoleBtn.click();
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Handle confirm dialog
|
|
const confirmDialog2 = page.locator('.van-dialog__confirm');
|
|
if (await confirmDialog2.isVisible().catch(() => false)) {
|
|
await confirmDialog2.click();
|
|
await page.waitForTimeout(5000);
|
|
}
|
|
|
|
// Wait for role switch and auto-select to complete
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(5000);
|
|
|
|
// Auto-select should have picked a teacher and redirected to /student/home
|
|
const currentPath = new URL(page.url()).pathname;
|
|
// Verify we did NOT end up on org-select page
|
|
expect(currentPath).not.toBe('/org/select');
|
|
|
|
// Should be on a valid student page (not org-select)
|
|
const isStudentPage = currentPath.startsWith('/student/') || currentPath === '/';
|
|
expect(isStudentPage).toBe(true);
|
|
|
|
// Verify teacher is auto-selected: navigate to profile and check
|
|
await page.goto('/student/profile', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 60_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
const teacherSection = page.locator('.current-teacher');
|
|
if (await teacherSection.isVisible().catch(() => false)) {
|
|
const teacherText = await teacherSection.locator('span').first().textContent();
|
|
// Teacher should be selected (not "未选择老师")
|
|
expect(teacherText).not.toContain('未选择老师');
|
|
}
|
|
});
|
|
|
|
test('老师选择后不会回到机构选择页面', async ({ page }) => {
|
|
test.setTimeout(180_000);
|
|
|
|
// Login as student
|
|
await loginAsStudent(page);
|
|
|
|
// Navigate to teacher list page directly
|
|
await page.goto('/student/teachers', {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 60_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(3000);
|
|
|
|
if (
|
|
await page
|
|
.getByText('请返回微信小程序完成登录')
|
|
.isVisible()
|
|
.catch(() => false)
|
|
) {
|
|
test.skip(true, '会话已过期');
|
|
return;
|
|
}
|
|
|
|
// Teacher list should show
|
|
const teacherCells = page.locator('.van-cell');
|
|
const teacherCount = await teacherCells.count();
|
|
if (teacherCount === 0) {
|
|
test.skip(true, '该学生名下没有关联老师');
|
|
return;
|
|
}
|
|
expect(teacherCount).toBeGreaterThan(0);
|
|
|
|
// Select a teacher
|
|
await teacherCells.first().click();
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Verify we didn't end up on org-select
|
|
const afterUrl = page.url();
|
|
expect(afterUrl).not.toContain('/org/select');
|
|
|
|
// Should still be on a valid student page
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
});
|
|
|
|
test('无老师学生自动跳转到选择老师页面', async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
|
|
// Intercept the teachers API to return empty (simulating student with no claimed teachers)
|
|
await page.route('**/find_all_teachers_by_student_id/**', async route => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ r: true, d: [], e: null })
|
|
});
|
|
});
|
|
|
|
const q = new URLSearchParams({ unionid: moicenUnionid!, status: '2' });
|
|
await page.goto(`/?${q.toString()}`, {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 120_000,
|
|
});
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
|
|
await page.waitForTimeout(5000);
|
|
|
|
// Pick STUDENT role explicitly
|
|
const roleSelect = page.getByText('请选择您的登录身份');
|
|
if (await roleSelect.isVisible().catch(() => false)) {
|
|
const studentRole = page.locator('.van-grid-item').filter({ hasText: '学生' });
|
|
if (await studentRole.isVisible().catch(() => false)) {
|
|
await studentRole.click();
|
|
await page.waitForTimeout(5000);
|
|
}
|
|
}
|
|
|
|
// Handle org select if needed
|
|
if (page.url().includes('/org/select')) {
|
|
const orgCells = page.locator('#app .van-cell-group .van-cell');
|
|
const n = await orgCells.count();
|
|
expect(n).toBeGreaterThan(0);
|
|
await orgCells.first().click();
|
|
await page.waitForTimeout(5000);
|
|
}
|
|
|
|
// Student with no teachers should be redirected to teacher-select page
|
|
// The redirect happens asynchronously via watchEffect
|
|
await expect(async () => {
|
|
const url = page.url();
|
|
expect(url).toContain('/student/teacher-select');
|
|
}).toPass({ timeout: 30_000 });
|
|
});
|
|
});
|