운영 화면과 회기 리뷰 UI 갱신

This commit is contained in:
Yun Chan 2026-06-28 20:13:21 +09:00
parent e7ebb38177
commit 3a9f70a97b
18 changed files with 2210 additions and 137 deletions

View file

@ -4,7 +4,19 @@ const sessionId = "33333333-3333-4333-8333-333333333333";
const learnerText = "요즘 많이 힘들었겠어요. 어떤 마음이 가장 크게 남아 있나요?";
const clientReply = "괜찮아요. 천천히 말해볼게요.";
function deferred() {
let resolve!: () => void;
const promise = new Promise<void>((next) => {
resolve = next;
});
return { promise, resolve };
}
async function routeMvpApi(page: Page) {
const sessionStartRequests: unknown[] = [];
const streamSeen = deferred();
const streamGate = deferred();
await page.route("**/api/auth/me", async (route) => {
await route.fulfill({
status: 200,
@ -49,6 +61,7 @@ async function routeMvpApi(page: Page) {
await route.fallback();
return;
}
sessionStartRequests.push(route.request().postDataJSON());
await route.fulfill({
status: 201,
contentType: "application/json",
@ -76,7 +89,7 @@ async function routeMvpApi(page: Page) {
session_no: 1,
status: "active",
stage: "라포",
theory_mode: "humanistic",
theory_mode: "cbt",
effective_openness: 0.21,
started_at: new Date().toISOString(),
ended_at: null,
@ -87,6 +100,8 @@ async function routeMvpApi(page: Page) {
});
await page.route(`**/api/sessions/${sessionId}/stream`, async (route) => {
streamSeen.resolve();
await streamGate.promise;
await route.fulfill({
status: 200,
contentType: "text/event-stream",
@ -176,6 +191,8 @@ async function routeMvpApi(page: Page) {
}),
});
});
return { sessionStartRequests, streamGate, streamSeen };
}
test.describe("P1 MVP core loop", () => {
@ -187,21 +204,32 @@ test.describe("P1 MVP core loop", () => {
page.on("console", (message) => {
if (message.type() === "error") diagnostics.push(`console: ${message.text()}`);
});
await routeMvpApi(page);
const api = await routeMvpApi(page);
await page.goto("/learn/session/P1");
await expect(
page.getByRole("button", { name: "회기 시작" }),
diagnostics.join("\n") || (await page.locator("#root").innerText().catch(() => "")),
).toBeVisible();
await page.getByRole("button", { name: /CBT/ }).click();
await page.getByRole("button", { name: "회기 시작" }).click();
await expect(page.locator(".sx-page.sx-page--active")).toBeVisible();
expect(api.sessionStartRequests[0]).toMatchObject({
persona_code: "P1",
theory_mode: "cbt",
});
await page.getByLabel("학습자 발화 입력").fill(learnerText);
await page.getByRole("button", { name: "보내기" }).click();
await api.streamSeen.promise;
await expect(page.locator(".sx-utt").filter({ hasText: learnerText })).toBeVisible();
await expect(page.locator(".sx-utt.is-thinking").filter({ hasText: "답변을 준비 중입니다." })).toBeVisible();
await expect(page.getByLabel("학습자 발화 입력")).toBeDisabled();
api.streamGate.resolve();
await expect(page.locator(".sx-utt").filter({ hasText: clientReply })).toBeVisible();
await expect(page.getByLabel("학습자 발화 입력")).toBeEnabled();
await page.getByRole("button", { name: "회기 종료" }).click();
await page.getByRole("button", { name: "종료하고 리뷰 보기" }).click();