import { expect, test, type Page, type Response } from "@playwright/test"; import { expectNoHorizontalOverflow, fetchAvailablePersona, signInAsLearner, signInAsTeacher, } from "./support"; interface SessionStartResponse { session_id: string; } async function expectResponseOk(response: { ok: () => boolean; text: () => Promise }) { if (!response.ok()) { expect(response.ok(), await response.text()).toBeTruthy(); } } function isTeacherDashboardResponse(response: Response) { const url = new URL(response.url()); return response.request().method() === "GET" && url.pathname.endsWith("/teacher/dashboard"); } async function createEndedLearnerSession(page: Page) { await signInAsLearner(page); const persona = await fetchAvailablePersona(page); const start = await page.request.post("/api/sessions", { data: { persona_code: persona.code, theory_mode: "humanistic", }, }); await expectResponseOk(start); const session = (await start.json()) as SessionStartResponse; const ended = await page.request.post(`/api/sessions/${session.session_id}/end`); await expectResponseOk(ended); return session.session_id; } test.describe("teacher console", () => { test("renders real server sessions from server-owned rows", async ({ page }) => { const sessionId = await createEndedLearnerSession(page); await signInAsTeacher(page); const dashboardResponsePromise = page.waitForResponse(isTeacherDashboardResponse); await page.goto("/teach"); const dashboardResponse = await dashboardResponsePromise; await expectResponseOk(dashboardResponse); const dashboard = await dashboardResponse.json(); expect(dashboard.recent_sessions.some((session: { session_id: string }) => session.session_id === sessionId)).toBe( true, ); await expect(page.locator("code").filter({ hasText: sessionId }).first()).toBeVisible(); await expect(page.getByRole("heading", { name: /\d+건의 리뷰가 대기 중입니다\./ })).toBeVisible(); await expect(page.getByText("3명에게 개입")).toHaveCount(0); await expect(page.getByText("김상담")).toHaveCount(0); await expectNoHorizontalOverflow(page); }); test("keeps long teacher lists in bounded panels", async ({ page }) => { await createEndedLearnerSession(page); await signInAsTeacher(page); await page.goto("/teach"); await expect(page.locator(".pf-list")).toBeVisible(); await expect(page.locator(".pf-tablewrap")).toBeVisible(); const metrics = await page.evaluate(() => { const list = document.querySelector(".pf-list"); const table = document.querySelector(".pf-tablewrap"); const header = document.querySelector(".pf-table th"); if (!list || !table || !header) { throw new Error("teacher list panels were not rendered"); } const listStyle = window.getComputedStyle(list); const tableStyle = window.getComputedStyle(table); const headerStyle = window.getComputedStyle(header); const doc = document.documentElement; return { docHeight: doc.scrollHeight, viewportHeight: doc.clientHeight, listMaxHeight: listStyle.maxHeight, listOverflowY: listStyle.overflowY, tableMaxHeight: tableStyle.maxHeight, tableOverflowY: tableStyle.overflowY, headerPosition: headerStyle.position, }; }); expect(metrics.listMaxHeight).not.toBe("none"); expect(metrics.tableMaxHeight).not.toBe("none"); expect(["auto", "scroll"]).toContain(metrics.listOverflowY); expect(["auto", "scroll"]).toContain(metrics.tableOverflowY); expect(metrics.headerPosition).toBe("sticky"); expect(metrics.docHeight - metrics.viewportHeight).toBeLessThanOrEqual(2200); await expectNoHorizontalOverflow(page); }); test("denies learner access to the teacher dashboard API and UI", async ({ page }) => { await signInAsLearner(page); const denied = await page.request.get("/api/teacher/dashboard"); expect(denied.status(), await denied.text()).toBe(403); await page.goto("/teach"); await expect(page).toHaveURL(/\/learn$/); await expect(page.locator(".pf-root")).toHaveCount(0); }); });