import { test, expect } from "@playwright/test"; // 프리뷰/뷰 모드 TDD: 먼저 실패하는 E2E부터 작성한다. test("/preview 페이지는 에디터 크롬 없이 컨텐츠만 보여야 한다", async ({ page }) => { // 프리뷰 페이지로 이동한다. await page.goto("/preview"); // 프리뷰 전용 헤더가 있어야 한다. await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible(); // 에디터 좌측 패널용 버튼들은 나타나지 않아야 한다. await expect(page.getByRole("button", { name: "텍스트 블록 추가" })).toHaveCount(0); await expect(page.getByRole("button", { name: "Hero 템플릿 추가" })).toHaveCount(0); }); test("에디터에서 Hero 템플릿을 추가하면 프리뷰에서 동일한 Hero 콘텐츠가 보여야 한다", async ({ page }) => { // 에디터에서 Hero 템플릿을 추가한다. await page.goto("/editor"); await page.getByRole("button", { name: "Hero 템플릿 추가" }).click(); // 헤드라인과 CTA 버튼이 에디터에서 생성되었는지 한 번 확인한다. const editorCanvas = page.getByTestId("editor-canvas"); await expect(editorCanvas.getByText("Hero 제목을 여기에 입력하세요")).toBeVisible(); await expect(editorCanvas.getByRole("button", { name: "지금 시작하기" })).toBeVisible(); // 헤더의 프리뷰 열기 링크를 통해 /preview 로 이동한다 (클라이언트 내 내비게이션 유지). await page.getByRole("link", { name: "프리뷰 열기" }).click(); // 프리뷰 헤더가 보여야 한다. await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible(); // 프리뷰에서도 Hero 헤드라인과 CTA 버튼이 그대로 보여야 한다. await expect(page.getByText("Hero 제목을 여기에 입력하세요")).toBeVisible(); // 프리뷰에서는 CTA를 실제 링크()로 렌더링하므로 텍스트 기준으로만 검증한다. await expect(page.getByText("지금 시작하기")).toBeVisible(); // 프리뷰 화면에는 에디터용 "텍스트 블록 추가" 버튼이 없어야 한다. await expect(page.getByRole("button", { name: "텍스트 블록 추가" })).toHaveCount(0); }); test("에디터에서 Features 템플릿을 추가하면 프리뷰에서 3개의 Feature 섹션이 보여야 한다", async ({ page }) => { await page.goto("/editor"); await page.getByRole("button", { name: "Features 템플릿 추가" }).click(); const editorCanvas = page.getByTestId("editor-canvas"); await expect(editorCanvas.getByText("Feature 1 제목")).toBeVisible(); await expect(editorCanvas.getByText("Feature 2 제목")).toBeVisible(); await expect(editorCanvas.getByText("Feature 3 제목")).toBeVisible(); await page.getByRole("link", { name: "프리뷰 열기" }).click(); await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible(); await expect(page.getByText("Feature 1 제목")).toBeVisible(); await expect(page.getByText("Feature 2 제목")).toBeVisible(); await expect(page.getByText("Feature 3 제목")).toBeVisible(); }); test("에디터에서 CTA 템플릿을 추가하면 프리뷰에서 CTA 텍스트와 버튼이 보여야 한다", async ({ page }) => { await page.goto("/editor"); await page.getByRole("button", { name: "CTA 템플릿 추가" }).click(); const editorCanvas = page.getByTestId("editor-canvas"); await expect(editorCanvas.getByText("지금 바로 행동을 유도하는 CTA 텍스트를 입력하세요.")).toBeVisible(); await expect(editorCanvas.getByRole("button", { name: "CTA 버튼" })).toBeVisible(); await page.getByRole("link", { name: "프리뷰 열기" }).click(); await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible(); await expect(page.getByText("지금 바로 행동을 유도하는 CTA 텍스트를 입력하세요.")).toBeVisible(); await expect(page.getByText("CTA 버튼")).toBeVisible(); }); test("모바일 뷰에서도 프리뷰 페이지가 깨지지 않고 주요 템플릿 컨텐츠를 보여줘야 한다", async ({ page }) => { // 에디터에서 여러 템플릿을 추가한다. await page.goto("/editor"); await page.getByRole("button", { name: "Hero 템플릿 추가" }).click(); await page.getByRole("button", { name: "Features 템플릿 추가" }).click(); await page.getByRole("button", { name: "Pricing 템플릿 추가" }).click(); await page.getByRole("button", { name: "Testimonials 템플릿 추가" }).click(); // 모바일 뷰포트로 전환한다. await page.setViewportSize({ width: 390, height: 844 }); // 프리뷰로 이동한다. await page.getByRole("link", { name: "프리뷰 열기" }).click(); // 프리뷰 헤더와 주요 템플릿 텍스트들이 모두 보여야 한다. await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible(); // Hero 헤드라인 await expect(page.getByText("Hero 제목을 여기에 입력하세요")).toBeVisible(); // Features 제목 일부 await expect(page.getByText("Feature 1 제목")).toBeVisible(); // Pricing 플랜 이름/가격 일부 await expect(page.getByText("Basic")).toBeVisible(); await expect(page.getByText("₩9,900/월")).toBeVisible(); // Testimonials 본문 일부 await expect(page.getByText("이 서비스 덕분에 랜딩 페이지 제작 시간이 크게 줄었습니다.")).toBeVisible(); }); test("폼 컨트롤러에 매핑한 폼 요소와 버튼이 프리뷰에서 함께 동작해야 한다", async ({ page }) => { // 1) 에디터에서 폼 요소, 버튼, 폼 블록을 추가한다. await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 폼 입력 요소와 버튼, 폼 블록을 순서대로 추가한다. await page.getByRole("button", { name: "폼 입력 추가" }).click(); await page.getByRole("button", { name: "버튼 블록 추가" }).click(); await page.getByRole("button", { name: "폼 블록 추가" }).click(); // 방금 추가된 폼 블록을 선택한다. const blocks = canvas.getByTestId("editor-block"); const formBlock = blocks.nth((await blocks.count()) - 1); await formBlock.click({ force: true }); // 2) 우측 속성 패널에서 폼 컨트롤러 매핑을 설정한다. const fieldMappingGroup = page.getByRole("group", { name: "폼 필드 매핑" }); const fieldCheckboxes = fieldMappingGroup.getByRole("checkbox"); // 첫 번째 폼 요소를 이 폼의 fieldIds 에 매핑한다. await fieldCheckboxes.nth(0).check(); // Submit 버튼 셀렉트 박스에서 방금 추가한 버튼 블록을 선택한다. const submitSelect = page.getByRole("combobox", { name: "Submit 버튼" }); // 첫 번째 옵션은 "(선택 안 함)" 이므로, 그 이후 옵션 중 하나를 선택한다. await submitSelect.selectOption({ index: 1 }); // 3) 프리뷰 페이지로 이동한다. await page.getByRole("link", { name: "프리뷰 열기" }).click(); await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible(); // 기대: 폼 컨트롤러에 매핑한 폼 입력 요소가 프리뷰에서 필드로 렌더링되어야 한다. // (구체적인 라벨 텍스트는 구현에 따라 달라질 수 있어 우선 textbox 중 하나가 보이는지만 확인한다.) const input = page.getByRole("textbox").first(); await expect(input).toBeVisible(); // 값을 입력한 뒤, 매핑된 버튼(기본 라벨 "버튼")을 클릭하면 폼이 전송되고 성공 메시지가 보여야 한다. await input.fill("테스트 값"); await page.getByRole("button", { name: "버튼" }).click(); await expect(page.getByText("성공적으로 전송되었습니다.")).toBeVisible(); }); test("여러 폼 블록과 여러 버튼이 있을 때 각 폼이 독립적으로 제출되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); const blocks = canvas.getByTestId("editor-block"); // Form A: 입력 필드 + 버튼 + 폼 블록을 추가한 뒤, 즉시 컨트롤러를 매핑한다. await page.getByRole("button", { name: "폼 입력 추가" }).click(); await page.getByRole("button", { name: "버튼 블록 추가" }).click(); await page.getByRole("button", { name: "폼 블록 추가" }).click(); let count = await blocks.count(); const formBlockA = blocks.nth(count - 1); await formBlockA.click({ force: true }); let fieldMappingGroup = page.getByRole("group", { name: "폼 필드 매핑" }); let fieldCheckboxes = fieldMappingGroup.getByRole("checkbox"); // 현재 존재하는 폼 요소 중 첫 번째를 A 폼에 매핑한다. await fieldCheckboxes.nth(0).check(); let submitSelect = page.getByRole("combobox", { name: "Submit 버튼" }); // 첫 번째 버튼을 A 폼의 submit 버튼으로 매핑 (index 1: 첫 번째 실제 버튼) await submitSelect.selectOption({ index: 1 }); // Form B: 입력 필드 + 버튼 + 폼 블록을 다시 추가한 뒤, 즉시 컨트롤러를 매핑한다. await page.getByRole("button", { name: "폼 입력 추가" }).click(); await page.getByRole("button", { name: "버튼 블록 추가" }).click(); await page.getByRole("button", { name: "폼 블록 추가" }).click(); count = await blocks.count(); const formBlockB = blocks.nth(count - 1); await formBlockB.click({ force: true }); fieldMappingGroup = page.getByRole("group", { name: "폼 필드 매핑" }); fieldCheckboxes = fieldMappingGroup.getByRole("checkbox"); // 현재 폼 요소 중 마지막 것을 B 폼에 매핑 (두 번째 입력 필드라고 가정) const lastCheckboxIndex = (await fieldCheckboxes.count()) - 1; await fieldCheckboxes.nth(lastCheckboxIndex).check(); submitSelect = page.getByRole("combobox", { name: "Submit 버튼" }); // 두 번째 버튼을 B 폼의 submit 버튼으로 매핑 (index 2: 두 번째 실제 버튼) await submitSelect.selectOption({ index: 2 }); // 프리뷰로 이동한다. await page.getByRole("link", { name: "프리뷰 열기" }).click(); await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible(); const textboxes = page.getByRole("textbox"); const textboxCount = await textboxes.count(); // 최소 2개의 텍스트 입력이 있어야 한다. expect(textboxCount).toBeGreaterThanOrEqual(2); const firstInput = textboxes.nth(0); const secondInput = textboxes.nth(1); // Form A 입력값을 채우고 첫 번째 버튼 클릭 → 성공 메시지 기대 await firstInput.fill("A 폼 값"); await page.getByRole("button", { name: "버튼" }).first().click(); // 첫 번째 폼에 대한 성공 메시지가 최소 하나는 보여야 한다. await expect(page.getByText("성공적으로 전송되었습니다.").first()).toBeVisible(); // Form B 입력값을 채우고 두 번째 버튼 클릭 → 다시 성공 메시지 기대 await secondInput.fill("B 폼 값"); await page.getByRole("button", { name: "버튼" }).nth(1).click(); // 두 번째 폼에 대한 성공 메시지도 두 번째 요소로 표시되었는지 확인한다. const successMessages = page.getByText("성공적으로 전송되었습니다."); await expect(successMessages.nth(1)).toBeVisible(); });