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 }); }); });