import { expect, test } from "@playwright/test"; import type { Page } from "@playwright/test"; type ApiRequest = { readonly method: string; readonly path: string; readonly search: string; }; async function loginThroughMockMode(page: Page): Promise { await page.goto("/login"); await page.getByLabel("이메일").fill("admin@test.com"); await page.getByLabel("비밀번호").fill("password"); await page.getByRole("button", { name: "로그인" }).click(); await expect(page).toHaveURL(/\/ai-characters$/); } function collectAdminApiRequests(page: Page): ApiRequest[] { const requests: ApiRequest[] = []; page.on("request", (request) => { const url = new URL(request.url()); if (url.pathname.startsWith("/api/v2/admin/")) { requests.push({ method: request.method(), path: url.pathname, search: url.search }); } }); return requests; } test("active mock resources complete the cross-domain journey without uncontracted requests", async ({ page }, testInfo) => { test.skip(testInfo.project.name.startsWith("mobile-"), "Desktop-only mutation journey."); // Given const requests = collectAdminApiRequests(page); await page.setViewportSize({ width: 1280, height: 900 }); await loginThroughMockMode(page); // When / Then await page.getByRole("link", { name: "루나 선택" }).click(); await expect(page.getByRole("heading", { name: "루나", exact: true })).toBeVisible(); await page.goto("/ai-characters/101/audio-contents"); await expect(page.getByRole("group", { name: "달빛 상담 오디오 오디오 플레이어" })).toBeVisible(); await page.getByRole("link", { name: "달빛 상담 오디오 상세 보기" }).click(); await expect(page.getByRole("heading", { name: "달빛 상담 오디오", exact: true })).toBeVisible(); await page.goto("/ai-characters/101/series/5001"); await expect(page.getByRole("heading", { name: "연결된 오디오", exact: true })).toBeVisible(); await page.getByRole("searchbox", { name: "검색어" }).fill("아침"); await page.getByRole("checkbox", { name: /아침 안내 오디오/ }).click(); await page.getByRole("button", { name: "선택한 오디오 연결" }).click(); await expect(page.getByRole("button", { name: "아침 안내 오디오 연결 해제" })).toBeVisible(); await page.getByRole("link", { name: "전체 시리즈 순서 관리" }).click(); await page.getByRole("button", { name: "아침 루틴 시리즈 위로" }).click(); await page.getByRole("button", { name: "순서 저장" }).click(); await expect(page.getByText("시리즈 순서를 저장했습니다")).toBeVisible(); await page.goto("/ai-characters/101/community-posts"); await page.getByRole("button", { name: "오늘의 상담 기록입니다. 게시글 열기" }).click(); const communityDialog = page.getByRole("dialog", { name: "커뮤니티 게시글" }); await communityDialog.getByRole("textbox", { name: "내용" }).fill("교차 회귀 수정 내용입니다."); await communityDialog.getByRole("button", { name: "수정 저장" }).click(); await expect.poll(() => requests.some((request) => request.method === "PUT" && request.path.includes("/community-posts/"))).toBe(true); await page.goto("/ai-characters/101/fan-talks"); await page.getByRole("button", { name: "첫 번째 응원입니다. 답변하기" }).click(); const fanTalkDialog = page.getByRole("dialog", { name: "FanTalk 답변" }); await fanTalkDialog.getByLabel("답변 내용").fill("교차 회귀 답변입니다."); await fanTalkDialog.getByRole("button", { name: "답변 등록" }).click(); await expect(fanTalkDialog.getByRole("status", { name: "답변 저장 성공" })).toBeVisible(); expect(requests.some((request) => /\/community-posts\/\d+$/.test(request.path) && request.method === "GET")).toBe(false); expect(requests.some((request) => request.path.includes("/fan-talks/7001") && request.method === "GET")).toBe(false); expect(requests.every((request) => !request.search.includes("status=") && !request.search.includes("sort="))).toBe(true); }); test("inactive character workspace blocks child mutation requests", async ({ page }) => { // Given const requests = collectAdminApiRequests(page); await page.setViewportSize({ width: 1280, height: 900 }); await loginThroughMockMode(page); // When / Then await page.goto("/ai-characters/202/community-posts/new"); await expect(page.getByRole("heading", { name: "커뮤니티 게시글 생성 차단" })).toBeVisible(); await expect(page.getByRole("button", { name: "생성" })).toBeHidden(); await page.goto("/ai-characters/202/fan-talks"); await expect(page.getByRole("heading", { name: "FanTalk", exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: /답변하기/ })).toBeHidden(); expect(requests.some((request) => request.method !== "GET" && request.path.includes("/ai-characters/202/"))).toBe(false); });