import { test, expect } from "@playwright/test"; // 리포트 페이지(/reports) E2E // - 로그인 사용자는 GNB 의 Reports 탭을 통해 /reports 로 이동할 수 있어야 한다. // - /reports 에서는 필터/요약 카드/그래프/프로젝트별 테이블 컨테이너가 렌더링되어야 한다. test("로그인 사용자가 GNB Reports 탭을 통해 /reports 에 접근할 수 있어야 한다", async ({ page }) => { const email = `reports+${Date.now()}@example.com`; await page.route("**/api/auth/me", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: "user-reports", email, tokenVersion: 1, emailVerified: true }), }); }); await page.route("**/api/projects", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([ { id: "p1", title: "Project A", slug: "project-a", status: "PUBLISHED", createdAt: "2025-01-01T00:00:00.000Z", updatedAt: "2025-01-01T00:00:00.000Z", }, ]), }); }); await page.route("**/api/projects", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([ { id: "p1", title: "Project A", slug: "project-a", status: "PUBLISHED", createdAt: "2025-01-01T00:00:00.000Z", updatedAt: "2025-01-01T00:00:00.000Z", }, { id: "p2", title: "Project B", slug: "project-b", status: "DRAFT", createdAt: "2025-01-02T00:00:00.000Z", updatedAt: "2025-01-02T00:00:00.000Z", }, ]), }); }); // 대시보드 개요 API 를 목 처리해 /dashboard 진입 시 401/리다이렉트가 발생하지 않도록 한다. await page.route("**/api/dashboard/overview", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ summaryStats: { totalProjects: 0, totalSubmissions: 0, todaySubmissions: 0, last7DaysSubmissions: 0, }, projectStats: [], dailySubmissions: [], }), }); }); await page.route("**/api/projects", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([ { id: "p1", title: "Project A", slug: "project-a", status: "PUBLISHED", createdAt: "2025-01-01T00:00:00.000Z", updatedAt: "2025-01-01T00:00:00.000Z", }, { id: "p2", title: "Project B", slug: "project-b", status: "DRAFT", createdAt: "2025-01-02T00:00:00.000Z", updatedAt: "2025-01-02T00:00:00.000Z", }, ]), }); }); await page.route("**/api/reports/leads*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ total: 0, items: [] }), }); }); await page.route("**/api/reports/overview*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ summary: { totalSubmissions: 3, uniqueProjects: 2, avgPerDay: 1, }, daily: [ { date: "2025-01-01", count: 1 }, { date: "2025-01-02", count: 2 }, ], byProject: [ { projectId: "p1", title: "Project A", slug: "project-a", submissions: 2, ratio: 2 / 3 }, { projectId: "p2", title: "Project B", slug: "project-b", submissions: 1, ratio: 1 / 3 }, ], }), }); }); await page.goto("/dashboard"); const reportsTab = page.getByRole("link", { name: "Reports" }); await expect(reportsTab).toBeVisible(); await reportsTab.click(); await expect(page).toHaveURL(/\/reports/); await expect(page.getByRole("heading", { name: "Reports" })).toBeVisible(); await expect(page.getByTestId("reports-summary-total-submissions")).toContainText("3"); await expect(page.getByTestId("reports-daily-chart")).toBeVisible(); await expect(page.getByTestId("reports-by-project-table")).toBeVisible(); }); test("/reports 에서는 기간/프로젝트 필터와 기본 리포트 섹션이 렌더링되어야 한다", async ({ page }) => { const email = `reports2+${Date.now()}@example.com`; await page.route("**/api/auth/me", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: "user-reports2", email, tokenVersion: 1, emailVerified: true }), }); }); await page.route("**/api/reports/overview*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ summary: { totalSubmissions: 0, uniqueProjects: 0, avgPerDay: 0, }, daily: [], byProject: [], }), }); }); await page.goto("/reports"); await expect(page.getByRole("heading", { name: "Reports" })).toBeVisible(); // 기간/프로젝트 필터 컨트롤이 존재해야 한다. await expect(page.getByTestId("reports-filter-range-toggle")).toBeVisible(); await expect(page.getByTestId("reports-filter-project")).toBeVisible(); // 요약/그래프/테이블 컨테이너가 렌더링되어야 한다. await expect(page.getByTestId("reports-summary-total-submissions")).toBeVisible(); await expect(page.getByTestId("reports-daily-chart")).toBeVisible(); await expect(page.getByTestId("reports-by-project-table")).toBeVisible(); }); test("/reports 에서는 시간 패턴과 프로젝트 상태 요약 섹션도 함께 볼 수 있어야 한다", async ({ page }) => { const email = `reports3+${Date.now()}@example.com`; await page.route("**/api/auth/me", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: "user-reports3", email, tokenVersion: 1, emailVerified: true }), }); }); await page.route("**/api/reports/overview*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ summary: { totalSubmissions: 3, uniqueProjects: 2, avgPerDay: 1.5, }, daily: [ { date: "2025-01-01", count: 1 }, { date: "2025-01-02", count: 2 }, ], byProject: [ { projectId: "p1", title: "Project A", slug: "project-a", submissions: 2, ratio: 2 / 3 }, { projectId: "p2", title: "Project B", slug: "project-b", submissions: 1, ratio: 1 / 3 }, ], timePatterns: { byWeek: [{ week: "2025-W01", count: 3 }], byMonth: [{ month: "2025-01", count: 3 }], byWeekday: [ { weekday: 1, count: 1 }, { weekday: 2, count: 2 }, ], byHour: [ { hour: 9, count: 1 }, { hour: 15, count: 2 }, ], }, projectStatusSummary: { byStatus: [ { status: "PUBLISHED", projects: 1, submissions: 2 }, { status: "DRAFT", projects: 1, submissions: 1 }, ], }, }), }); }); await page.goto("/reports"); await expect(page.getByRole("heading", { name: "Reports" })).toBeVisible(); await expect(page.getByTestId("reports-time-patterns")).toBeVisible(); await expect(page.getByTestId("reports-project-status-summary")).toBeVisible(); }); test("/reports 에서는 Leads 요약과 리드 리스트 테이블이 렌더링되어야 한다", async ({ page }) => { const email = `reports-leads+${Date.now()}@example.com`; await page.route("**/api/auth/me", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: "user-reports-leads", email, tokenVersion: 1, emailVerified: true }), }); }); await page.route("**/api/reports/overview*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ summary: { totalSubmissions: 3, uniqueProjects: 2, avgPerDay: 1.5, }, daily: [], byProject: [], timePatterns: { byWeek: [], byMonth: [], byWeekday: [], byHour: [], }, projectStatusSummary: { byStatus: [] }, }), }); }); await page.route("**/api/reports/leads*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ total: 2, items: [ { id: "s1", projectId: "p1", projectSlug: "project-a", projectTitle: "Project A", createdAt: "2025-01-09T12:00:00.000Z", payload: { name: "Alice", email: "alice@example.com" }, }, { id: "s2", projectId: "p2", projectSlug: "project-b", projectTitle: "Project B", createdAt: "2025-01-08T09:00:00.000Z", payload: { name: "Bob" }, }, ], }), }); }); await page.goto("/reports"); await expect(page.getByRole("heading", { name: "Reports" })).toBeVisible(); const leadsSection = page.getByTestId("reports-leads-table"); await expect(leadsSection).toBeVisible(); // 요약 텍스트에 total 값이 반영되어야 한다. await expect(page.getByTestId("reports-leads-summary")).toContainText("2"); // 테이블 행이 두 개 렌더링되는지만 간단히 확인. await expect(leadsSection.getByRole("row")).toHaveCount(1 + 2); // 헤더 1 + 데이터 2 });