95 lines
3.4 KiB
TypeScript
95 lines
3.4 KiB
TypeScript
/**
|
|
* Teacher portal smoke + happy-path coverage.
|
|
*
|
|
* Routes verified against client/src/App.tsx (teacher role block).
|
|
*/
|
|
import { test, expect } from '@playwright/test';
|
|
import { loginAs } from '../helpers/auth';
|
|
import { ROUTES_PER_ROLE, ROLE_LANDING } from '../helpers/nav';
|
|
import {
|
|
captureConsoleErrors,
|
|
expectNoConsoleErrors,
|
|
expectPageMounted,
|
|
} from '../helpers/assertions';
|
|
|
|
const BASE = 'http://localhost:3000';
|
|
const TEACHER_CHROME = ['Africa Alert', 'Teacher', 'Dashboard'];
|
|
|
|
test.describe('Teacher portal — smoke @smoke', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginAs(page, 'teacher');
|
|
});
|
|
|
|
for (const route of ROUTES_PER_ROLE.teacher) {
|
|
test(`mounts ${route}`, async ({ page }) => {
|
|
const cap = captureConsoleErrors(page);
|
|
try {
|
|
await page.goto(`${BASE}${route}`);
|
|
await expectPageMounted(page, route, [route.split('/').pop() || route, ...TEACHER_CHROME]);
|
|
} finally {
|
|
cap.dispose();
|
|
expectNoConsoleErrors(cap.errors);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
test.describe('Teacher portal — happy path @flow', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await loginAs(page, 'teacher');
|
|
});
|
|
|
|
test('lands on the teacher dashboard after login', async ({ page }) => {
|
|
await page.goto(`${BASE}/`);
|
|
await expect(page).toHaveURL(new RegExp(`${ROLE_LANDING.teacher.replace(/\//g, '\\/')}$`));
|
|
});
|
|
|
|
test('my-courses: course list page renders', async ({ page }) => {
|
|
await page.goto(`${BASE}/my-courses`);
|
|
await expect(page.locator('h1, h2').first()).toBeVisible();
|
|
// The course list either has a list of course cards or an empty state.
|
|
const list = page
|
|
.locator('article, .course, [data-testid="course"]')
|
|
.or(page.getByText(/no courses|no classes|empty/i));
|
|
await expect(list.first()).toBeVisible({ timeout: 5_000 });
|
|
});
|
|
|
|
test('attendance/take: page renders an attendance surface', async ({ page }) => {
|
|
await page.goto(`${BASE}/attendance/take`);
|
|
await expect(page.locator('h1, h2').first()).toBeVisible();
|
|
// Should have a class picker, a date picker, or a roster.
|
|
const attendanceSurface = page
|
|
.locator('select, input[type="date"]')
|
|
.or(page.getByText(/attendance|present|absent/i));
|
|
await expect(attendanceSurface.first()).toBeVisible({ timeout: 5_000 });
|
|
});
|
|
|
|
test('assignments: list page is reachable', async ({ page }) => {
|
|
await page.goto(`${BASE}/assignments`);
|
|
await expect(page.locator('h1, h2').first()).toBeVisible();
|
|
});
|
|
|
|
test('students: roster page renders', async ({ page }) => {
|
|
await page.goto(`${BASE}/students`);
|
|
await expect(page.locator('h1, h2').first()).toBeVisible();
|
|
});
|
|
|
|
test('class-room: course scheme page renders', async ({ page }) => {
|
|
await page.goto(`${BASE}/class-room`);
|
|
await expect(page.locator('h1, h2').first()).toBeVisible();
|
|
});
|
|
|
|
// Exam-review workflow (added 2026-07-21). The exam registry at
|
|
// /exams/manage should mount for teachers and show either the
|
|
// table (with or without a Submit button on draft/rejected rows)
|
|
// or an empty state.
|
|
test('exams: registry page renders the exam table or empty state', async ({ page }) => {
|
|
await page.goto(`${BASE}/exams/manage`);
|
|
await expect(page.locator('h1, h2').first()).toBeVisible();
|
|
const tableOrEmpty = page
|
|
.locator('table')
|
|
.or(page.getByText(/no.*exam.*module|empty|no active/i));
|
|
await expect(tableOrEmpty.first()).toBeVisible({ timeout: 5_000 });
|
|
});
|
|
});
|