import { test, expect } from "@playwright/test"; // 최초에는 실패하는 테스트: /editor 페이지와 헤더 텍스트가 아직 없음 test("/editor 페이지가 존재하고 Page Editor 헤더를 보여줘야 한다", async ({ page }) => { await page.goto("/editor"); await expect(page.getByRole("heading", { name: "Page Editor" })).toBeVisible(); }); test("텍스트 블록 추가 버튼을 누르면 캔버스에 '새 텍스트' 블록이 보여야 한다", async ({ page }) => { await page.goto("/editor"); await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); const canvas = page.getByTestId("editor-canvas"); await expect(canvas.getByText("새 텍스트")).toBeVisible(); }); test("텍스트 블록을 더블클릭해서 내용을 수정할 수 있어야 한다", async ({ page }) => { await page.goto("/editor"); // 텍스트 블록을 하나 추가한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); // 기존 텍스트 블록을 더블클릭해서 편집 모드로 전환한다. const canvas = page.getByTestId("editor-canvas"); const block = canvas.getByText("새 텍스트"); await block.dblclick(); // 편집 모드에서 텍스트를 변경한다. const editor = page.getByRole("textbox", { name: "텍스트 블록 내용 편집" }); await editor.fill("수정된 텍스트"); await editor.press("Enter"); // 변경된 텍스트가 캔버스에 반영되어야 한다. const canvasAfterEdit = page.getByTestId("editor-canvas"); await expect(canvasAfterEdit.getByText("수정된 텍스트")).toBeVisible(); }); test("속성 패널에서 선택된 텍스트 블록 내용을 수정하면 캔버스에 반영되어야 한다", async ({ page }) => { await page.goto("/editor"); // 텍스트 블록을 추가한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); // 캔버스 블록을 클릭해서 선택한다. const canvas = page.getByTestId("editor-canvas"); await canvas.getByText("새 텍스트").click(); // 우측 속성 패널에서 텍스트를 수정한다. const sidebarEditor = page.getByRole("textbox", { name: "선택한 텍스트 블록 내용" }); await sidebarEditor.fill("사이드바에서 수정된 텍스트"); // 캔버스에 수정된 텍스트가 보여야 한다. const canvasAfterSidebarEdit = page.getByTestId("editor-canvas"); await expect(canvasAfterSidebarEdit.getByText("사이드바에서 수정된 텍스트")).toBeVisible(); }); test("여러 텍스트 블록 중 선택을 전환하면 속성 패널 내용과 선택 하이라이트가 바뀌어야 한다", async ({ page }) => { await page.goto("/editor"); // 텍스트 블록 두 개를 추가한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); const canvas = page.getByTestId("editor-canvas"); const blocks = canvas.getByTestId("editor-block"); const firstBlock = blocks.nth(0); const secondBlock = blocks.nth(1); // 첫 번째 블록을 선택하고, 속성 패널에서 텍스트를 변경한다. await firstBlock.click(); const sidebarEditor = page.getByRole("textbox", { name: "선택한 텍스트 블록 내용" }); await sidebarEditor.fill("첫 번째 블록"); // 두 번째 블록을 선택하고, 다시 속성 패널에서 텍스트를 변경한다. await secondBlock.click(); await sidebarEditor.fill("두 번째 블록"); // 캔버스에는 두 블록의 텍스트가 모두 보여야 한다. await expect(canvas.getByText("첫 번째 블록")).toBeVisible(); await expect(canvas.getByText("두 번째 블록")).toBeVisible(); // 현재 선택된 블록은 두 번째 블록이어야 한다. await expect(firstBlock).toHaveAttribute("data-selected", "false"); await expect(secondBlock).toHaveAttribute("data-selected", "true"); // 속성 패널 textarea에도 두 번째 블록 텍스트가 표시되어야 한다. await expect(sidebarEditor).toHaveValue("두 번째 블록"); }); test("텍스트 블록의 정렬과 글자 크기를 속성 패널에서 변경하면 캔버스 스타일이 바뀌어야 한다", async ({ page }) => { await page.goto("/editor"); // 텍스트 블록을 하나 추가하고 선택한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); const canvas = page.getByTestId("editor-canvas"); const block = canvas.getByTestId("editor-block").nth(0); await block.click(); // 속성 패널에서 정렬을 가운데로 변경한다. const alignSelect = page.getByRole("combobox", { name: "정렬" }); await alignSelect.selectOption("center"); // 속성 패널에서 글자 크기를 크게로 변경한다. const sizeSelect = page.getByRole("combobox", { name: "글자 크기" }); await sizeSelect.selectOption("lg"); // 캔버스 블록에 text-center와 text-lg 클래스가 적용되어야 한다. await expect(block).toHaveClass(/text-center/); await expect(block).toHaveClass(/text-lg/); }); test("에디터 상태를 JSON으로 내보내고 다시 불러오면 동일한 블록 상태가 복원되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 블록 두 개를 추가하고 각각 다른 내용/스타일을 설정한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); const blocks = canvas.getByTestId("editor-block"); const firstBlock = blocks.nth(0); const secondBlock = blocks.nth(1); // 첫 번째 블록: 텍스트/정렬/크기 설정 await firstBlock.click(); const sidebarEditor = page.getByRole("textbox", { name: "선택한 텍스트 블록 내용" }); await sidebarEditor.fill("첫 번째 JSON 블록"); const alignSelect = page.getByRole("combobox", { name: "정렬" }); await alignSelect.selectOption("left"); const sizeSelect = page.getByRole("combobox", { name: "글자 크기" }); await sizeSelect.selectOption("sm"); // 두 번째 블록: 텍스트/정렬/크기 설정 await secondBlock.click(); await sidebarEditor.fill("두 번째 JSON 블록"); await alignSelect.selectOption("right"); await sizeSelect.selectOption("lg"); // 현재 상태를 JSON으로 내보낸다. await page.getByRole("button", { name: "JSON 내보내기" }).click(); const exportArea = page.getByRole("textbox", { name: "에디터 상태 JSON" }); const exportedJson = await exportArea.inputValue(); // 캔버스를 초기화한다. await page.getByRole("button", { name: "캔버스 초기화" }).click(); await expect(canvas.getByTestId("editor-block")).toHaveCount(0); // JSON을 다시 입력하고 불러온다. const importArea = page.getByRole("textbox", { name: "JSON 불러오기" }); await importArea.fill(exportedJson); await page.getByRole("button", { name: "JSON 적용" }).click(); // 복원된 캔버스에 두 블록이 모두 존재해야 한다. const restoredBlocks = canvas.getByTestId("editor-block"); await expect(restoredBlocks).toHaveCount(2); // 각 블록의 텍스트와 스타일이 원상 복구되었는지 확인한다. const restoredFirst = restoredBlocks.nth(0); const restoredSecond = restoredBlocks.nth(1); await expect(restoredFirst).toContainText("첫 번째 JSON 블록"); await expect(restoredFirst).toHaveClass(/text-left/); await expect(restoredFirst).toHaveClass(/text-sm/); await expect(restoredSecond).toContainText("두 번째 JSON 블록"); await expect(restoredSecond).toHaveClass(/text-right/); await expect(restoredSecond).toHaveClass(/text-lg/); }); test("각 캔버스 블록에 드래그 핸들 UI가 표시되고 선택 상태가 aria-selected로 노출되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 블록 두 개를 추가한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); const blocks = canvas.getByTestId("editor-block"); const firstBlock = blocks.nth(0); const secondBlock = blocks.nth(1); // 각 블록 안에 드래그 핸들이 존재해야 한다. await expect(firstBlock.getByRole("button", { name: "블록 드래그 핸들" })).toBeVisible(); await expect(secondBlock.getByRole("button", { name: "블록 드래그 핸들" })).toBeVisible(); // 초기에는 마지막으로 추가된 두 번째 블록이 선택되어 있어야 한다. await expect(firstBlock).toHaveAttribute("aria-selected", "false"); await expect(secondBlock).toHaveAttribute("aria-selected", "true"); // 첫 번째 블록을 클릭하면 선택 상태가 전환되어야 한다. await firstBlock.click(); await expect(firstBlock).toHaveAttribute("aria-selected", "true"); await expect(secondBlock).toHaveAttribute("aria-selected", "false"); }); test("블록 드래그앤드롭으로 순서를 변경할 수 있어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 블록 두 개를 추가하고 서로 다른 텍스트로 구분한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); const blocks = canvas.getByTestId("editor-block"); const firstBlock = blocks.nth(0); const secondBlock = blocks.nth(1); // 텍스트를 A/B로 설정해서 순서를 식별한다. await firstBlock.click(); const sidebarEditor = page.getByRole("textbox", { name: "선택한 텍스트 블록 내용" }); await sidebarEditor.fill("블록 A"); await secondBlock.click(); await sidebarEditor.fill("블록 B"); // 두 번째 블록의 드래그 핸들을 첫 번째 블록 위치로 드래그앤드롭 한다. const secondHandle = secondBlock.getByRole("button", { name: "블록 드래그 핸들" }); const firstHandle = firstBlock.getByRole("button", { name: "블록 드래그 핸들" }); await secondHandle.dragTo(firstHandle); // 드래그 후에는 순서가 [블록 B, 블록 A]가 되어야 한다. const reorderedBlocks = canvas.getByTestId("editor-block"); await expect(reorderedBlocks.nth(0)).toContainText("블록 B"); await expect(reorderedBlocks.nth(1)).toContainText("블록 A"); }); test("버튼 블록을 추가하고 라벨과 링크를 수정할 수 있어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 버튼 블록을 추가한다. await page.getByRole("button", { name: "버튼 블록 추가" }).click(); // 캔버스에 기본 버튼이 렌더되어야 한다. const button = canvas.getByRole("button", { name: "버튼" }); await expect(button).toBeVisible(); // 속성 패널에서 버튼 텍스트와 링크를 수정한다. const labelInput = page.getByRole("textbox", { name: "버튼 텍스트" }); const hrefInput = page.getByRole("textbox", { name: "버튼 링크" }); await labelInput.fill("자세히 보기"); await hrefInput.fill("https://example.com"); // 캔버스의 버튼 텍스트가 변경되어야 한다. const updatedButton = canvas.getByRole("button", { name: "자세히 보기" }); await expect(updatedButton).toBeVisible(); }); test("2컬럼 섹션에서 블록을 다른 컬럼 영역으로 드래그하면 컬럼이 변경되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 섹션을 하나 추가하고 레이아웃을 2컬럼으로 변경한다. await page.getByRole("button", { name: "섹션 블록 추가" }).click(); // 섹션을 선택한다 (캔버스 첫 블록). const sectionBlock = canvas.getByTestId("editor-block").nth(0); await sectionBlock.click(); const layoutSelect = page.getByRole("combobox", { name: "섹션 레이아웃" }); await layoutSelect.selectOption("2col"); // 왼쪽 컬럼에 텍스트 블록을 하나 추가한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); const leftColumn = canvas.locator('[data-section-id][data-column-id]').nth(0); const rightColumn = canvas.locator('[data-section-id][data-column-id]').nth(1); // 왼쪽 컬럼 안에 텍스트 블록이 존재하는지 확인한다. const leftBlocks = leftColumn.getByTestId("editor-block"); await expect(leftBlocks).toHaveCount(1); // 왼쪽 컬럼의 첫 블록을 오른쪽 컬럼 droppable 영역으로 드래그한다. const leftBlock = leftBlocks.nth(0); const handle = leftBlock.getByRole("button", { name: "블록 드래그 핸들" }); await handle.dragTo(rightColumn); // 드래그 이후, 오른쪽 컬럼 안에 블록이 존재해야 하고 왼쪽 컬럼은 비어 있어야 한다. const rightBlocks = rightColumn.getByTestId("editor-block"); await expect(rightBlocks).toHaveCount(1); // 왼쪽 컬럼에는 더 이상 블록이 없어야 한다. await expect(leftColumn.getByTestId("editor-block")).toHaveCount(0); });