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 }) => { test.skip(process.env.CI === "true"); 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.dblclick({ force: true }); // 편집 모드에서 텍스트를 변경한다. 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"); const firstBlock = canvas.getByTestId("editor-block").nth(0); await firstBlock.click({ force: true }); // 우측 속성 패널에서 텍스트를 수정한다. 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({ force: true }); const sidebarEditor = page.getByRole("textbox", { name: "선택한 텍스트 블록 내용" }); await sidebarEditor.fill("첫 번째 블록"); // 두 번째 블록을 선택하고, 다시 속성 패널에서 텍스트를 변경한다. await secondBlock.click({ force: true }); await sidebarEditor.fill("두 번째 블록"); // 캔버스에는 두 번째 블록의 텍스트가 보여야 한다. 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({ force: true }); // 속성 패널에서 정렬을 가운데로 변경한다. 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("텍스트 블록 인라인 툴바에서 정렬과 글자 크기를 변경할 수 있어야 한다", async ({ page }) => { test.skip(process.env.CI === "true"); 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.dblclick({ force: true }); // 인라인 툴바 버튼을 이용해 가운데 정렬 및 큰 글자 크기로 변경한다. const centerAlignButton = page.getByRole("button", { name: "가운데 정렬" }); const largeSizeButton = page.getByRole("button", { name: "글자 크게" }); await centerAlignButton.click(); await largeSizeButton.click(); // 편집을 종료하기 위해 블록 밖을 클릭한다. await canvas.click({ position: { x: 5, y: 5 } }); // 캔버스 블록에 text-center 와 text-lg 클래스가 적용되어 있어야 한다. await expect(block).toHaveClass(/text-center/); await expect(block).toHaveClass(/text-lg/); }); test("에디터 상태를 JSON으로 내보내고 다시 불러오면 동일한 블록 상태가 복원되어야 한다", async ({ page }) => { test.skip(process.env.CI === "true"); 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({ force: true }); 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({ force: true }); 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 }) => { test.skip(process.env.CI === "true"); 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({ force: true }); await expect(firstBlock).toHaveAttribute("aria-selected", "true"); await expect(secondBlock).toHaveAttribute("aria-selected", "false"); }); test("블록 드래그앤드롭으로 순서를 변경할 수 있어야 한다", async ({ page }) => { test.skip(process.env.CI === "true"); 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({ force: true }); 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 }) => { test.skip(process.env.CI === "true"); 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({ force: true }); 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); }); test("Hero 템플릿 버튼을 클릭하면 섹션과 기본 텍스트/버튼 블록이 캔버스에 생성되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // Hero 템플릿을 추가한다. await page.getByRole("button", { name: "Hero 템플릿 추가" }).click(); // 섹션/블록이 하나 이상 존재해야 한다. const blocks = canvas.getByTestId("editor-block"); await expect(blocks.first()).toBeVisible(); // Hero 헤드라인 텍스트가 포함된 블록이 보여야 한다. await expect(canvas.getByText("Hero 제목을 여기에 입력하세요")).toBeVisible(); // CTA 버튼도 렌더되어 있어야 한다. const ctaButton = canvas.getByRole("button", { name: "지금 시작하기" }); await expect(ctaButton).toBeVisible(); }); test("Features 템플릿 버튼을 클릭하면 3개의 Feature 항목(제목/설명)이 생성되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); await page.getByRole("button", { name: "Features 템플릿 추가" }).click(); // Feature 제목들이 렌더되어야 한다. await expect(canvas.getByText("Feature 1 제목")).toBeVisible(); await expect(canvas.getByText("Feature 2 제목")).toBeVisible(); await expect(canvas.getByText("Feature 3 제목")).toBeVisible(); }); test("CTA 템플릿 버튼을 클릭하면 CTA 텍스트와 버튼이 포함된 섹션이 캔버스에 생성되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); await page.getByRole("button", { name: "CTA 템플릿 추가" }).click(); await expect(canvas.getByText("지금 바로 행동을 유도하는 CTA 텍스트를 입력하세요.")).toBeVisible(); const ctaButton = canvas.getByRole("button", { name: "CTA 버튼" }); await expect(ctaButton).toBeVisible(); }); test("FAQ 템플릿 버튼을 클릭하면 질문/답변 텍스트가 세 쌍 이상 생성되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); await page.getByRole("button", { name: "FAQ 템플릿 추가" }).click(); // 기본 FAQ 질문/답변 텍스트들이 렌더되어야 한다. await expect(canvas.getByText("자주 묻는 질문 1")).toBeVisible(); await expect(canvas.getByText("첫 번째 질문에 대한 답변을 여기에 입력하세요.")).toBeVisible(); await expect(canvas.getByText("자주 묻는 질문 2")).toBeVisible(); await expect(canvas.getByText("자주 묻는 질문 3")).toBeVisible(); }); test("Pricing 템플릿 버튼을 클릭하면 3개의 요금제 카드가 생성되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); await page.getByRole("button", { name: "Pricing 템플릿 추가" }).click(); await expect(canvas.getByText("Basic")).toBeVisible(); await expect(canvas.getByText("₩9,900/월")).toBeVisible(); await expect(canvas.getByText("Pro")).toBeVisible(); await expect(canvas.getByText("₩29,900/월")).toBeVisible(); await expect(canvas.getByText("Enterprise")).toBeVisible(); await expect(canvas.getByText("문의")).toBeVisible(); }); test("Testimonials 템플릿 버튼을 클릭하면 3개의 후기 카드가 생성되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); await page.getByRole("button", { name: "Testimonials 템플릿 추가" }).click(); await expect(canvas.getByText("이 서비스 덕분에 랜딩 페이지 제작 시간이 크게 줄었습니다.")).toBeVisible(); await expect(canvas.getByText("디자인을 잘 못해도 깔끔한 페이지를 만들 수 있어요.")).toBeVisible(); await expect(canvas.getByText("팀 전체가 만족하는 빌더입니다.")).toBeVisible(); }); test("Blog 템플릿 버튼을 클릭하면 3개의 포스트 카드(제목/요약)가 생성되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); await page.getByRole("button", { name: "Blog 템플릿 추가" }).click(); await expect(canvas.getByText("블로그 포스트 1")).toBeVisible(); await expect(canvas.getByText("첫 번째 포스트 요약을 여기에 입력하세요.")).toBeVisible(); await expect(canvas.getByText("블로그 포스트 2")).toBeVisible(); await expect(canvas.getByText("블로그 포스트 3")).toBeVisible(); }); test("Team 템플릿 버튼을 클릭하면 3명의 팀 카드가 생성되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); await page.getByRole("button", { name: "Team 템플릿 추가" }).click(); await expect(canvas.getByText("홍길동")).toBeVisible(); await expect(canvas.getByText("Product Designer")).toBeVisible(); await expect(canvas.getByText("김영희")).toBeVisible(); await expect(canvas.getByText("Frontend Engineer")).toBeVisible(); await expect(canvas.getByText("이철수")).toBeVisible(); await expect(canvas.getByText("Backend Engineer")).toBeVisible(); }); test("Footer 템플릿 버튼을 클릭하면 링크/카피라이트 텍스트가 포함된 섹션이 생성되어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); await page.getByRole("button", { name: "Footer 템플릿 추가" }).click(); await expect(canvas.getByText("이용약관 · 개인정보처리방침")).toBeVisible(); await expect(canvas.getByText("© 2025 MyLanding. All rights reserved.")).toBeVisible(); }); test("Cmd/Ctrl+Z로 블록 추가를 Undo 하고 Cmd/Ctrl+Shift+Z로 Redo 할 수 있어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 텍스트 블록을 하나 추가한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); await expect(canvas.getByTestId("editor-block")).toHaveCount(1); // Cmd/Ctrl + Z 로 Undo 한다. const undoShortcut = process.platform === "darwin" ? "Meta+Z" : "Control+Z"; await page.keyboard.press(undoShortcut); // Undo 후에는 블록이 0개가 되어야 한다. await expect(canvas.getByTestId("editor-block")).toHaveCount(0); // Cmd/Ctrl + Shift + Z 로 Redo 한다. const redoShortcut = process.platform === "darwin" ? "Meta+Shift+Z" : "Control+Shift+Z"; await page.keyboard.press(redoShortcut); // Redo 후 다시 블록이 1개가 되어야 한다. await expect(canvas.getByTestId("editor-block")).toHaveCount(1); }); test("ArrowUp/ArrowDown 키로 선택된 블록을 위/아래로 이동하며 순차적으로 선택할 수 있어야 한다", async ({ page }) => { test.skip(process.env.CI === "true"); 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"); await expect(blocks).toHaveCount(3); // 첫 번째 블록을 클릭해 선택한다. await blocks.nth(0).click({ force: true }); await expect(blocks.nth(0)).toHaveAttribute("data-selected", "true"); // ArrowDown 으로 두 번째 블록으로 이동. await page.keyboard.press("ArrowDown"); await expect(blocks.nth(1)).toHaveAttribute("data-selected", "true"); // 다시 ArrowDown 으로 세 번째 블록으로 이동. await page.keyboard.press("ArrowDown"); await expect(blocks.nth(2)).toHaveAttribute("data-selected", "true"); // ArrowDown 을 한 번 더 눌러도 마지막에서 더 내려가지 않는다. await page.keyboard.press("ArrowDown"); await expect(blocks.nth(2)).toHaveAttribute("data-selected", "true"); // ArrowUp 으로 두 번째 블록으로 올라간다. await page.keyboard.press("ArrowUp"); await expect(blocks.nth(1)).toHaveAttribute("data-selected", "true"); }); 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"); await expect(blocks).toHaveCount(2); // 두 번째 블록을 선택한 다음, 속성 패널에서 "블록 삭제" 버튼을 클릭한다. await blocks.nth(1).click({ force: true }); await page.getByRole("button", { name: "블록 삭제" }).click(); // 하나의 블록만 남아야 하며, 첫 번째 블록이 남아 있어야 한다. const remainingBlocks = canvas.getByTestId("editor-block"); await expect(remainingBlocks).toHaveCount(1); await expect(remainingBlocks.nth(0)).toHaveAttribute("data-selected", "true"); }); test("Cmd/Ctrl+D 단축키로 선택된 블록을 복제할 수 있어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 텍스트 블록을 하나 추가한다. await page.getByRole("button", { name: "텍스트 블록 추가" }).click(); let blocks = canvas.getByTestId("editor-block"); await expect(blocks).toHaveCount(1); // 블록의 텍스트를 식별 가능한 값으로 바꾼다. await blocks.nth(0).click({ force: true }); const sidebarEditor = page.getByRole("textbox", { name: "선택한 텍스트 블록 내용" }); await sidebarEditor.fill("복제 대상 블록"); // Cmd/Ctrl + D 로 복제한다. const duplicateShortcut = process.platform === "darwin" ? "Meta+D" : "Control+D"; await page.keyboard.press(duplicateShortcut); // 블록이 두 개가 되어야 한다. blocks = canvas.getByTestId("editor-block"); await expect(blocks).toHaveCount(2); // 두 블록 모두 동일한 텍스트를 가지고 있어야 한다. await expect(blocks.nth(0)).toContainText("복제 대상 블록"); await expect(blocks.nth(1)).toContainText("복제 대상 블록"); }); test("구분선 블록을 추가하면 캔버스에 구분선이 렌더되고 정렬/두께를 변경할 수 있어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 구분선 블록을 추가한다. await page.getByRole("button", { name: "구분선 블록 추가" }).click(); const blocks = canvas.getByTestId("editor-block"); const dividerBlock = blocks.nth(0); // 블록 추가 시 자동으로 선택된다고 가정하고 기본 정렬이 가운데인지 확인한다. await expect(dividerBlock).toHaveClass(/text-center/); // 속성 패널에서 정렬을 오른쪽으로 변경하면 클래스가 바뀌어야 한다. const alignSelect = page.getByRole("combobox", { name: "구분선 정렬" }); await alignSelect.selectOption("right"); await expect(dividerBlock).toHaveClass(/text-right/); // 두께를 보통으로 변경해도 에러 없이 동작해야 한다. const thicknessSelect = page.getByRole("combobox", { name: "구분선 두께" }); await thicknessSelect.selectOption("medium"); }); test("리스트 블록을 추가하고 첫 번째 아이템과 번호 매기기/정렬을 수정할 수 있어야 한다", async ({ page }) => { await page.goto("/editor"); const canvas = page.getByTestId("editor-canvas"); // 리스트 블록을 추가한다. await page.getByRole("button", { name: "리스트 블록 추가" }).click(); const blocks = canvas.getByTestId("editor-block"); const listBlock = blocks.nth(0); // 기본 아이템 텍스트가 렌더되어야 한다. await expect(listBlock).toContainText("리스트 아이템 1"); // 속성 패널에서 첫 번째 아이템 텍스트를 변경하면 캔버스에도 반영되어야 한다. const firstItemInput = page.getByRole("textbox", { name: "리스트 첫 번째 아이템" }); await firstItemInput.fill("첫 번째 할 일"); await expect(listBlock).toContainText("첫 번째 할 일"); // 번호 매기기 토글을 켜고, 정렬을 가운데로 변경해도 에러 없이 동작해야 한다. const orderedCheckbox = page.getByRole("checkbox", { name: "번호 매기기" }); await orderedCheckbox.check(); const alignSelect = page.getByRole("combobox", { name: "리스트 정렬" }); await alignSelect.selectOption("center"); await expect(listBlock).toHaveClass(/text-center/); });