332 lines
11 KiB
TypeScript
332 lines
11 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
|
|
test("서로 다른 유저는 /projects 에서 자신의 프로젝트만 볼 수 있어야 한다", async ({ page }) => {
|
|
type Project = {
|
|
id: string;
|
|
title: string;
|
|
slug: string;
|
|
status: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
};
|
|
|
|
let currentUser: "A" | "B" = "A";
|
|
const projectsA: Project[] = [];
|
|
const projectsB: Project[] = [];
|
|
let nextId = 1;
|
|
|
|
const nowIso = () => new Date().toISOString();
|
|
|
|
await page.route("**/api/auth/me", async (route) => {
|
|
const email = currentUser === "A" ? "user-a@example.com" : "user-b@example.com";
|
|
const id = currentUser === "A" ? "user-a" : "user-b";
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ id, email, tokenVersion: 1 }),
|
|
});
|
|
});
|
|
|
|
await page.route("**/api/projects*", async (route) => {
|
|
const request = route.request();
|
|
const url = new URL(request.url());
|
|
const method = request.method();
|
|
|
|
const ownerProjects = currentUser === "A" ? projectsA : projectsB;
|
|
const otherProjects = currentUser === "A" ? projectsB : projectsA;
|
|
|
|
if (url.pathname === "/api/projects" && method === "GET") {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(ownerProjects),
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (url.pathname === "/api/projects" && method === "POST") {
|
|
const bodyText = request.postData() ?? "{}";
|
|
const body = JSON.parse(bodyText) as Partial<Project> & { title?: string; slug?: string };
|
|
const slug = body.slug ?? "";
|
|
const title = body.title ?? "";
|
|
|
|
const conflict = otherProjects.find((p) => p.slug === slug);
|
|
if (conflict) {
|
|
await route.fulfill({
|
|
status: 409,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ message: "이미 다른 사용자가 사용 중인 프로젝트 주소입니다." }),
|
|
});
|
|
return;
|
|
}
|
|
|
|
const existingIndex = ownerProjects.findIndex((p) => p.slug === slug);
|
|
const now = nowIso();
|
|
|
|
let project: Project;
|
|
if (existingIndex >= 0) {
|
|
const existing = ownerProjects[existingIndex];
|
|
project = { ...existing, title: title || existing.title, updatedAt: now };
|
|
ownerProjects[existingIndex] = project;
|
|
} else {
|
|
project = {
|
|
id: String(nextId++),
|
|
title: title || "제목 없음",
|
|
slug,
|
|
status: "DRAFT",
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
ownerProjects.push(project);
|
|
}
|
|
|
|
await route.fulfill({
|
|
status: 201,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(project),
|
|
});
|
|
return;
|
|
}
|
|
|
|
await route.fulfill({
|
|
status: 404,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ message: "Not implemented in E2E mock" }),
|
|
});
|
|
});
|
|
|
|
// 1) 유저 A: 에디터에서 프로젝트를 저장하고, /projects 에서 자신의 프로젝트만 보여야 한다.
|
|
await page.goto("/editor");
|
|
|
|
const propertiesSidebarA = page.getByTestId("properties-sidebar");
|
|
await propertiesSidebarA.getByLabel("Project title").fill("유저 A 프로젝트");
|
|
await propertiesSidebarA.getByLabel("Project slug").fill("user-a-project");
|
|
|
|
await page.getByRole("button", { name: "Menu" }).click();
|
|
await page.getByRole("button", { name: "Save / load project" }).click();
|
|
await page.getByRole("button", { name: "Save (local + server)" }).click();
|
|
|
|
await expect(page).toHaveURL(/\/_?projects/);
|
|
await expect(page.getByText("user-a-project")).toBeVisible();
|
|
await expect(page.getByText("user-b-project")).toHaveCount(0);
|
|
|
|
// 2) 유저 B: currentUser 전환 후 별도 프로젝트를 저장하고, /projects 에서 자신의 것만 보여야 한다.
|
|
currentUser = "B";
|
|
|
|
await page.goto("/editor");
|
|
|
|
const propertiesSidebarB = page.getByTestId("properties-sidebar");
|
|
await propertiesSidebarB.getByLabel("Project title").fill("유저 B 프로젝트");
|
|
await propertiesSidebarB.getByLabel("Project slug").fill("user-b-project");
|
|
|
|
await page.getByRole("button", { name: "Menu" }).click();
|
|
await page.getByRole("button", { name: "Save / load project" }).click();
|
|
await page.getByRole("button", { name: "Save (local + server)" }).click();
|
|
|
|
await expect(page).toHaveURL(/\/_?projects/);
|
|
await expect(page.getByText("user-b-project")).toBeVisible();
|
|
await expect(page.getByText("user-a-project")).toHaveCount(0);
|
|
|
|
// 3) 다시 유저 A 로 전환했을 때, /projects 에서는 유저 A 의 프로젝트만 보여야 한다.
|
|
currentUser = "A";
|
|
|
|
await page.goto("/projects");
|
|
|
|
const toolbar = page.getByTestId("projects-toolbar");
|
|
await expect(toolbar).toBeVisible();
|
|
|
|
await expect(page.getByText("user-a-project")).toBeVisible();
|
|
await expect(page.getByText("user-b-project")).toHaveCount(0);
|
|
});
|
|
|
|
test("프로젝트 목록에서 '폼 제출 내역' 링크를 클릭하면 해당 프로젝트의 제출 내역 페이지로 이동해야 한다", async ({ page }) => {
|
|
// 로그인된 사용자 시나리오를 가정한다.
|
|
await page.route("**/api/auth/me", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ id: "user-e2e", email: "e2e@example.com", tokenVersion: 1 }),
|
|
});
|
|
});
|
|
|
|
// /api/projects 응답을 목킹해 목록 페이지에 하나의 프로젝트가 보이도록 한다.
|
|
await page.route("**/api/projects", async (route) => {
|
|
const request = route.request();
|
|
const url = new URL(request.url());
|
|
const method = request.method();
|
|
|
|
if (url.pathname === "/api/projects" && method === "GET") {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([
|
|
{
|
|
id: "1",
|
|
title: "테스트 프로젝트 A",
|
|
slug: "test-project-a",
|
|
status: "DRAFT",
|
|
createdAt: new Date("2025-01-01T00:00:00.000Z").toISOString(),
|
|
updatedAt: new Date("2025-01-01T00:00:00.000Z").toISOString(),
|
|
},
|
|
]),
|
|
});
|
|
return;
|
|
}
|
|
|
|
await route.fulfill({
|
|
status: 404,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ message: "Not implemented in E2E mock" }),
|
|
});
|
|
});
|
|
|
|
// 선택한 프로젝트의 폼 제출 내역 API 응답도 목킹해, 제출 데이터가 테이블에 렌더링되는지 검증한다.
|
|
await page.route("**/api/projects/test-project-a/submissions", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([
|
|
{
|
|
id: "sub-1",
|
|
projectId: "1",
|
|
userId: "user-e2e",
|
|
createdAt: new Date("2025-01-02T12:34:56.000Z").toISOString(),
|
|
payload: {
|
|
name: "홍길동",
|
|
email: "user@example.com",
|
|
phone: "010-1234-5678",
|
|
birthdate: "1990-01-01",
|
|
message: "안녕하세요",
|
|
},
|
|
},
|
|
]),
|
|
});
|
|
});
|
|
|
|
// 프로젝트 목록 페이지로 이동한다.
|
|
await page.goto("/projects");
|
|
|
|
// 목록에 테스트 프로젝트가 보여야 한다.
|
|
await expect(page.getByText("테스트 프로젝트 A")).toBeVisible();
|
|
await expect(page.getByText("test-project-a")).toBeVisible();
|
|
|
|
// 액션 영역의 "폼 제출 내역" 링크를 클릭하면 제출 내역 페이지로 이동해야 한다.
|
|
await page.getByRole("link", { name: "Form submissions" }).click();
|
|
|
|
await expect(page).toHaveURL(/\/projects\/test-project-a\/submissions/);
|
|
|
|
// 제출 내역 페이지의 헤더와 slug, 제출 데이터가 렌더링되어야 한다.
|
|
await expect(page.getByRole("heading", { name: "All form submissions" })).toBeVisible();
|
|
await expect(page.getByText("test-project-a")).toBeVisible();
|
|
|
|
await expect(page.getByText("홍길동")).toBeVisible();
|
|
await expect(page.getByText("user@example.com")).toBeVisible();
|
|
await expect(page.getByText("message: 안녕하세요")).toBeVisible();
|
|
await expect(page.getByText("1990-01-01")).toBeVisible();
|
|
await expect(page.getByText("message: 안녕하세요")).toBeVisible();
|
|
});
|
|
|
|
test("프로젝트 목록 헤더에서 대시보드로 이동할 수 있어야 한다", async ({ page }) => {
|
|
await page.route("**/api/auth/me", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ id: "user-e2e", email: "e2e@example.com", tokenVersion: 1 }),
|
|
});
|
|
});
|
|
|
|
await page.route("**/api/projects", async (route) => {
|
|
const request = route.request();
|
|
const url = new URL(request.url());
|
|
const method = request.method();
|
|
|
|
if (url.pathname === "/api/projects" && method === "GET") {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([
|
|
{
|
|
id: "1",
|
|
title: "테스트 프로젝트 A",
|
|
slug: "test-project-a",
|
|
status: "DRAFT",
|
|
createdAt: new Date("2025-01-01T00:00:00.000Z").toISOString(),
|
|
updatedAt: new Date("2025-01-01T00:00:00.000Z").toISOString(),
|
|
},
|
|
]),
|
|
});
|
|
return;
|
|
}
|
|
|
|
await route.fulfill({
|
|
status: 404,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ message: "Not implemented in E2E mock" }),
|
|
});
|
|
});
|
|
|
|
await page.route("**/api/dashboard/overview", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
summaryStats: {
|
|
totalProjects: 1,
|
|
totalSubmissions: 0,
|
|
todaySubmissions: 0,
|
|
last7DaysSubmissions: 0,
|
|
},
|
|
projectStats: [],
|
|
dailySubmissions: [],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto("/projects");
|
|
|
|
await expect(page.getByText("테스트 프로젝트 A")).toBeVisible();
|
|
|
|
await page.getByRole("link", { name: "Dashboard" }).click();
|
|
|
|
await expect(page).toHaveURL(/\/dashboard/);
|
|
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
|
|
});
|
|
|
|
test("전체 제출 내역 페이지에서 공통 GNB와 메뉴가 보여야 한다", async ({ page }) => {
|
|
await page.route("**/api/projects/submissions", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([]),
|
|
});
|
|
});
|
|
|
|
await page.route("**/api/auth/logout", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ message: "ok" }),
|
|
});
|
|
});
|
|
|
|
await page.goto("/projects/submissions");
|
|
|
|
await expect(page.getByRole("heading", { name: "All form submissions" })).toBeVisible();
|
|
|
|
const dashboardLink = page.getByRole("link", { name: "Dashboard" });
|
|
await expect(dashboardLink).toBeVisible();
|
|
|
|
const projectsLink = page.getByRole("link", { name: "Projects" });
|
|
await expect(projectsLink).toBeVisible();
|
|
|
|
const submissionsLink = page.getByRole("link", { name: "All submissions" });
|
|
await expect(submissionsLink).toBeVisible();
|
|
|
|
const menuButton = page.getByRole("button", { name: "Menu" });
|
|
await expect(menuButton).toBeVisible();
|
|
|
|
await menuButton.click();
|
|
await expect(page.getByRole("button", { name: "Log out" })).toBeVisible();
|
|
});
|