323 lines
9.8 KiB
TypeScript
323 lines
9.8 KiB
TypeScript
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
|
|
});
|