TDD,E2E 개선, 미적용 스타일 개선
CI / test (push) Failing after 2m54s
CI / e2e (push) Has been skipped
CI / pr_and_merge (push) Has been skipped

This commit is contained in:
2025-12-07 09:52:42 +09:00
parent e726f43f7c
commit a5b432fb7b
167 changed files with 7397 additions and 663 deletions
File diff suppressed because it is too large Load Diff
+12 -11
View File
@@ -982,7 +982,8 @@ test("폼 블록을 선택하면 우측 속성 패널에서 폼 필드/버튼
// 이 그룹 안에 최소 2개의 체크박스(입력/셀렉트)가 있어야 한다.
const fieldCheckboxes = fieldMappingGroup.getByRole("checkbox");
await expect(fieldCheckboxes).toHaveCount(2);
const checkboxCount = await fieldCheckboxes.count();
expect(checkboxCount).toBeGreaterThanOrEqual(2);
// 첫 번째 필드 체크박스를 클릭하면 체크 상태가 되어야 한다.
await fieldCheckboxes.nth(0).check();
@@ -1016,16 +1017,15 @@ test("폼 입력 블록의 라벨/전송 키/필수 여부를 우측 패널에
// 우측 속성 패널에 폼 입력 필드 설정 섹션이 보여야 한다.
const labelInput = page.getByRole("textbox", { name: "필드 라벨" });
const nameInput = page.getByRole("textbox", { name: "전송 키" });
const requiredCheckbox = page.getByRole("checkbox", { name: "필수 필드" });
const requiredHint = page.getByText("필수 여부는 폼 컨트롤러에서 설정합니다.");
await expect(labelInput).toBeVisible();
await expect(nameInput).toBeVisible();
await expect(requiredCheckbox).toBeVisible();
await expect(requiredHint).toBeVisible();
// 라벨과 전송 키를 수정하고, 필수 여부를 토글할 수 있어야 한다.
await labelInput.fill("이메일 주소");
await nameInput.fill("email_address");
await requiredCheckbox.check();
});
test("폼 셀렉트/라디오/체크박스 블록도 우측 패널에서 기본 필드 속성을 편집할 수 있어야 한다", async ({ page }) => {
@@ -1041,15 +1041,14 @@ test("폼 셀렉트/라디오/체크박스 블록도 우측 패널에서 기본
let labelInput = page.getByRole("textbox", { name: "필드 라벨" });
let nameInput = page.getByRole("textbox", { name: "전송 키" });
let requiredCheckbox = page.getByRole("checkbox", { name: "필수 필드" });
let requiredHint = page.getByText("필수 여부는 폼 컨트롤러에서 설정합니다.");
await expect(labelInput).toBeVisible();
await expect(nameInput).toBeVisible();
await expect(requiredCheckbox).toBeVisible();
await expect(requiredHint).toBeVisible();
await labelInput.fill("셀렉트 라벨");
await nameInput.fill("select_field");
await requiredCheckbox.check();
// 폼 라디오 블록
await page.getByRole("button", { name: "단일 선택(Radio)" }).click();
@@ -1059,11 +1058,12 @@ test("폼 셀렉트/라디오/체크박스 블록도 우측 패널에서 기본
labelInput = page.getByRole("textbox", { name: "그룹 타이틀" });
nameInput = page.getByRole("textbox", { name: "전송 키" });
requiredCheckbox = page.getByRole("checkbox", { name: "필수 필드" });
requiredHint = page.getByText("필수 여부는 폼 컨트롤러에서 설정합니다.");
await expect(requiredHint).toBeVisible();
await labelInput.fill("라디오 라벨");
await nameInput.fill("radio_field");
await requiredCheckbox.check();
// 라디오 그룹 타이틀 텍스트 필드는 그대로 편집 가능해야 한다.
@@ -1075,11 +1075,12 @@ test("폼 셀렉트/라디오/체크박스 블록도 우측 패널에서 기본
labelInput = page.getByRole("textbox", { name: "그룹 타이틀" });
nameInput = page.getByRole("textbox", { name: "전송 키" });
requiredCheckbox = page.getByRole("checkbox", { name: "필수 필드" });
requiredHint = page.getByText("필수 여부는 폼 컨트롤러에서 설정합니다.");
await expect(requiredHint).toBeVisible();
await labelInput.fill("체크박스 라벨");
await nameInput.fill("checkbox_field");
await requiredCheckbox.check();
// 체크박스 그룹 타이틀 텍스트 필드도 동일하게 동작해야 한다.
});
@@ -0,0 +1,350 @@
import { test, expect } from "@playwright/test";
function parsePx(value: string): number {
const n = parseFloat(value || "");
return Number.isFinite(n) ? n : 0;
}
test.beforeEach(async ({ page }) => {
await page.route("**/api/auth/me", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ id: "user-form-parity", email: "form-parity@example.com", tokenVersion: 1 }),
});
});
});
test("formInput: 에디터 캔버스와 프리뷰에서 높이/패딩/색상이 동일해야 한다", async ({ page }) => {
await page.goto("/editor");
const canvas = page.getByTestId("editor-canvas");
const sidebar = page.getByTestId("properties-sidebar");
await page.getByRole("button", { name: "입력(Input)" }).click();
await sidebar.getByRole("textbox", { name: "필드 라벨" }).fill("에디터-프리뷰 인풋");
await sidebar.getByLabel("텍스트 정렬").selectOption("center");
await sidebar.getByLabel("너비").selectOption("fixed");
await sidebar.getByLabel("필드 고정 너비 커스텀 (px)").fill("320");
await sidebar.getByLabel("필드 텍스트 색상 HEX").fill("#ff0000");
await sidebar.getByLabel("필드 채움 색상 HEX").fill("#00ff88");
await sidebar.getByLabel("필드 테두리 색상 HEX").fill("#0000ff");
const editorInput = canvas.getByRole("textbox", { name: "에디터-프리뷰 인풋" });
const editorStyles = await editorInput.evaluate((el) => {
const s = window.getComputedStyle(el as HTMLInputElement);
return {
height: s.height,
paddingTop: s.paddingTop,
paddingBottom: s.paddingBottom,
fontSize: s.fontSize,
color: s.color,
backgroundColor: s.backgroundColor,
borderColor: s.borderColor,
borderRadius: s.borderRadius,
};
});
const editorAttrs = await editorInput.evaluate((el) => {
const input = el as HTMLInputElement;
return {
type: input.type,
name: input.name,
placeholder: input.placeholder,
};
});
await page.getByRole("link", { name: "프리뷰 열기" }).click();
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
const previewInput = page.getByRole("textbox", { name: "에디터-프리뷰 인풋" });
const previewStyles = await previewInput.evaluate((el) => {
const s = window.getComputedStyle(el as HTMLInputElement);
return {
height: s.height,
paddingTop: s.paddingTop,
paddingBottom: s.paddingBottom,
fontSize: s.fontSize,
color: s.color,
backgroundColor: s.backgroundColor,
borderColor: s.borderColor,
borderRadius: s.borderRadius,
};
});
const editorHeight = parsePx(editorStyles.height);
const previewHeight = parsePx(previewStyles.height);
expect(Math.abs(previewHeight - editorHeight)).toBeLessThanOrEqual(2);
const editorPaddingTop = parsePx(editorStyles.paddingTop);
const previewPaddingTop = parsePx(previewStyles.paddingTop);
expect(Math.abs(previewPaddingTop - editorPaddingTop)).toBeLessThanOrEqual(3);
const editorPaddingBottom = parsePx(editorStyles.paddingBottom);
const previewPaddingBottom = parsePx(previewStyles.paddingBottom);
expect(Math.abs(previewPaddingBottom - editorPaddingBottom)).toBeLessThanOrEqual(3);
expect(previewStyles.fontSize).toBe(editorStyles.fontSize);
expect(previewStyles.color).toBe(editorStyles.color);
expect(previewStyles.backgroundColor).toBe(editorStyles.backgroundColor);
expect(previewStyles.borderColor).toBe(editorStyles.borderColor);
expect(previewStyles.borderRadius).toBe(editorStyles.borderRadius);
const previewAttrs = await previewInput.evaluate((el) => {
const input = el as HTMLInputElement;
return {
type: input.type,
name: input.name,
placeholder: input.placeholder,
};
});
expect(previewAttrs.type).toBe(editorAttrs.type);
expect(previewAttrs.placeholder).toBe(editorAttrs.placeholder);
expect(previewAttrs.name).toBe(editorAttrs.name);
});
test("formSelect: 에디터 캔버스와 프리뷰에서 높이/패딩/색상이 동일해야 한다", async ({ page }) => {
await page.goto("/editor");
const canvas = page.getByTestId("editor-canvas");
const sidebar = page.getByTestId("properties-sidebar");
await page.getByRole("button", { name: "셀렉트(Select)" }).click();
await sidebar.getByRole("textbox", { name: "필드 라벨" }).fill("에디터-프리뷰 셀렉트");
await sidebar.getByLabel("필드 너비").selectOption("fixed");
await sidebar.getByLabel("필드 고정 너비 커스텀 (px)").fill("360");
await sidebar.getByLabel("셀렉트 텍스트 색상 HEX").fill("#ff00ff");
await sidebar.getByLabel("셀렉트 채움 색상 HEX").fill("#0033ff");
await sidebar.getByLabel("셀렉트 테두리 색상 HEX").fill("#00ffaa");
const editorSelect = canvas.getByRole("combobox", { name: "에디터-프리뷰 셀렉트" });
const editorStyles = await editorSelect.evaluate((el) => {
const s = window.getComputedStyle(el as HTMLSelectElement);
return {
height: s.height,
paddingTop: s.paddingTop,
paddingBottom: s.paddingBottom,
fontSize: s.fontSize,
color: s.color,
backgroundColor: s.backgroundColor,
borderColor: s.borderColor,
borderRadius: s.borderRadius,
};
});
const editorSelectAttrs = await editorSelect.evaluate((el) => {
const select = el as HTMLSelectElement;
return {
name: select.name,
value: select.value,
};
});
await page.getByRole("link", { name: "프리뷰 열기" }).click();
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
const previewSelect = page.getByRole("combobox", { name: "에디터-프리뷰 셀렉트" });
const previewStyles = await previewSelect.evaluate((el) => {
const s = window.getComputedStyle(el as HTMLSelectElement);
return {
height: s.height,
paddingTop: s.paddingTop,
paddingBottom: s.paddingBottom,
fontSize: s.fontSize,
color: s.color,
backgroundColor: s.backgroundColor,
borderColor: s.borderColor,
borderRadius: s.borderRadius,
};
});
const editorHeight = parsePx(editorStyles.height);
const previewHeight = parsePx(previewStyles.height);
expect(Math.abs(previewHeight - editorHeight)).toBeLessThanOrEqual(2);
const editorPaddingTop = parsePx(editorStyles.paddingTop);
const previewPaddingTop = parsePx(previewStyles.paddingTop);
expect(Math.abs(previewPaddingTop - editorPaddingTop)).toBeLessThanOrEqual(3);
const editorPaddingBottom = parsePx(editorStyles.paddingBottom);
const previewPaddingBottom = parsePx(previewStyles.paddingBottom);
expect(Math.abs(previewPaddingBottom - editorPaddingBottom)).toBeLessThanOrEqual(3);
expect(previewStyles.fontSize).toBe(editorStyles.fontSize);
expect(previewStyles.color).toBe(editorStyles.color);
expect(previewStyles.backgroundColor).toBe(editorStyles.backgroundColor);
expect(previewStyles.borderColor).toBe(editorStyles.borderColor);
expect(previewStyles.borderRadius).toBe(editorStyles.borderRadius);
const previewSelectAttrs = await previewSelect.evaluate((el) => {
const select = el as HTMLSelectElement;
return {
name: select.name,
value: select.value,
};
});
expect(previewSelectAttrs.name).toBe(editorSelectAttrs.name);
expect(previewSelectAttrs.value).toBe(editorSelectAttrs.value);
});
test("formCheckbox/formRadio: 에디터 캔버스와 프리뷰에서 그룹 너비/라벨 간격/옵션 간격이 동일해야 한다", async ({ page }) => {
await page.goto("/editor");
const canvas = page.getByTestId("editor-canvas");
const sidebar = page.getByTestId("properties-sidebar");
await page.getByRole("button", { name: "다중 선택(Checkbox)" }).click();
await sidebar.getByLabel("그룹 타이틀", { exact: true }).fill("에디터-프리뷰 체크 그룹");
await sidebar.getByLabel("그룹 타이틀 표시 방식").selectOption("visible");
await sidebar.getByLabel(/^레이아웃/).selectOption("inline");
await sidebar.getByLabel("라벨/필드 간격 (px) 커스텀 (px)").fill("32");
await sidebar.getByLabel("필드 너비").selectOption("fixed");
await sidebar.getByLabel("필드 고정 너비 커스텀 (px)").fill("360");
await sidebar.getByLabel("체크박스 옵션 간격 (px) 커스텀 (px)").fill("20");
const editorCheckboxLabel = canvas.getByText("에디터-프리뷰 체크 그룹");
const editorCheckboxData = await editorCheckboxLabel.evaluate((el) => {
const groupEl = (el as HTMLElement).parentElement as HTMLElement;
const groupStyle = window.getComputedStyle(groupEl);
const optionsEl = groupEl.querySelector(".pb-form-options") as HTMLElement | null;
const optionsStyle = optionsEl ? window.getComputedStyle(optionsEl) : null;
const firstOptionInput = optionsEl?.querySelector("input[type='checkbox']") as
| HTMLInputElement
| null;
const firstOption = firstOptionInput
? { type: firstOptionInput.type, name: firstOptionInput.name, value: firstOptionInput.value }
: { type: "", name: "", value: "" };
return {
groupWidth: groupStyle.width,
groupColumnGap: groupStyle.columnGap,
optionsRowGap: optionsStyle ? optionsStyle.rowGap : "",
firstOption,
};
});
await page.getByRole("button", { name: "단일 선택(Radio)" }).click();
await sidebar.getByLabel("그룹 타이틀", { exact: true }).fill("에디터-프리뷰 라디오 그룹");
await sidebar.getByLabel("그룹 타이틀 표시 방식").selectOption("visible");
await sidebar.getByLabel("그룹 타이틀 레이아웃").selectOption("inline");
await sidebar.getByLabel("라벨/필드 간격 (px) 커스텀 (px)").fill("32");
await sidebar.getByLabel("필드 너비").selectOption("fixed");
await sidebar.getByLabel("필드 고정 너비 커스텀 (px)").fill("360");
await sidebar.getByLabel("라디오 옵션 간격 (px) 커스텀 (px)").fill("20");
const editorRadioLabel = canvas.getByText("에디터-프리뷰 라디오 그룹");
const editorRadioData = await editorRadioLabel.evaluate((el) => {
const groupEl = (el as HTMLElement).parentElement as HTMLElement;
const groupStyle = window.getComputedStyle(groupEl);
const optionsEl = groupEl.querySelector(".pb-form-options") as HTMLElement | null;
const optionsStyle = optionsEl ? window.getComputedStyle(optionsEl) : null;
const firstOptionInput = optionsEl?.querySelector("input[type='radio']") as
| HTMLInputElement
| null;
const firstOption = firstOptionInput
? { type: firstOptionInput.type, name: firstOptionInput.name, value: firstOptionInput.value }
: { type: "", name: "", value: "" };
return {
groupWidth: groupStyle.width,
groupColumnGap: groupStyle.columnGap,
optionsRowGap: optionsStyle ? optionsStyle.rowGap : "",
firstOption,
};
});
await page.getByRole("link", { name: "프리뷰 열기" }).click();
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
const previewCheckboxGroup = page.getByTestId("preview-form-checkbox-group").first();
const previewCheckboxGroupStyles = await previewCheckboxGroup.evaluate((el) => {
const s = window.getComputedStyle(el as HTMLElement);
return {
width: s.width,
columnGap: s.columnGap,
};
});
const previewCheckboxFirstOption = await previewCheckboxGroup.evaluate((el) => {
const input = (el as HTMLElement).querySelector("input[type='checkbox']") as
| HTMLInputElement
| null;
return input
? { type: input.type, name: input.name, value: input.value }
: { type: "", name: "", value: "" };
});
const previewCheckboxOptions = previewCheckboxGroup
.getByTestId("preview-form-checkbox-options")
.first();
const previewCheckboxOptionsStyles = await previewCheckboxOptions.evaluate((el) => {
const s = window.getComputedStyle(el as HTMLElement);
return {
rowGap: s.rowGap,
};
});
const previewRadioGroup = page.getByTestId("preview-form-radio-group").first();
const previewRadioGroupStyles = await previewRadioGroup.evaluate((el) => {
const s = window.getComputedStyle(el as HTMLElement);
return {
width: s.width,
columnGap: s.columnGap,
};
});
const previewRadioFirstOption = await previewRadioGroup.evaluate((el) => {
const input = (el as HTMLElement).querySelector("input[type='radio']") as
| HTMLInputElement
| null;
return input
? { type: input.type, name: input.name, value: input.value }
: { type: "", name: "", value: "" };
});
const previewRadioOptions = previewRadioGroup
.getByTestId("preview-form-radio-options")
.first();
const previewRadioOptionsStyles = await previewRadioOptions.evaluate((el) => {
const s = window.getComputedStyle(el as HTMLElement);
return {
rowGap: s.rowGap,
};
});
const editorCheckboxWidth = parsePx(editorCheckboxData.groupWidth);
const previewCheckboxWidth = parsePx(previewCheckboxGroupStyles.width);
expect(Math.abs(previewCheckboxWidth - editorCheckboxWidth)).toBeLessThanOrEqual(2);
const editorCheckboxLabelGap = parsePx(editorCheckboxData.groupColumnGap);
const previewCheckboxLabelGap = parsePx(previewCheckboxGroupStyles.columnGap);
expect(Math.abs(previewCheckboxLabelGap - editorCheckboxLabelGap)).toBeLessThanOrEqual(2);
const editorCheckboxOptionGap = parsePx(editorCheckboxData.optionsRowGap);
const previewCheckboxOptionGap = parsePx(previewCheckboxOptionsStyles.rowGap);
expect(Math.abs(previewCheckboxOptionGap - editorCheckboxOptionGap)).toBeLessThanOrEqual(2);
const editorRadioWidth = parsePx(editorRadioData.groupWidth);
const previewRadioWidth = parsePx(previewRadioGroupStyles.width);
expect(Math.abs(previewRadioWidth - editorRadioWidth)).toBeLessThanOrEqual(2);
const editorRadioLabelGap = parsePx(editorRadioData.groupColumnGap);
const previewRadioLabelGap = parsePx(previewRadioGroupStyles.columnGap);
expect(Math.abs(previewRadioLabelGap - editorRadioLabelGap)).toBeLessThanOrEqual(2);
const editorRadioOptionGap = parsePx(editorRadioData.optionsRowGap);
const previewRadioOptionGap = parsePx(previewRadioOptionsStyles.rowGap);
expect(Math.abs(previewRadioOptionGap - editorRadioOptionGap)).toBeLessThanOrEqual(2);
expect(editorCheckboxData.firstOption.type).toBe("checkbox");
expect(previewCheckboxFirstOption.type).toBe(editorCheckboxData.firstOption.type);
expect(previewCheckboxFirstOption.name).toBe(editorCheckboxData.firstOption.name);
expect(previewCheckboxFirstOption.value).toBe(editorCheckboxData.firstOption.value);
expect(editorRadioData.firstOption.type).toBe("radio");
expect(previewRadioFirstOption.type).toBe(editorRadioData.firstOption.type);
expect(previewRadioFirstOption.name).toBe(editorRadioData.firstOption.name);
expect(previewRadioFirstOption.value).toBe(editorRadioData.firstOption.value);
});
+29 -8
View File
@@ -60,16 +60,35 @@ test("프리뷰 폼 제출 후 제출 내역 페이지에서 데이터가 보여
},
]);
// 2) 에디터에서 프로젝트 제목/slug 를 설정하고 폼 컨트롤러 블록을 추가한다.
// 2) 에디터에서 프로젝트 제목/slug 를 설정하고 폼 관련 블록을 추가한다.
await page.goto("/editor");
const propertiesSidebar = page.getByTestId("properties-sidebar");
await propertiesSidebar.getByLabel("프로젝트 제목").fill("E2E 폼 프로젝트");
await propertiesSidebar.getByLabel("프로젝트 주소 (slug)").fill(projectSlug);
// 좌측 블록 사이드바에서 "폼 컨트롤러" 버튼을 눌러 기본 contact 폼을 추가한다.
// v2 컨트롤러에서는 더 이상 기본 contact 폼이 프리뷰에 임의로 생성되지 않으므로,
// 실제 입력 필드 3개와 버튼 1개를 먼저 추가해 두고, 마지막에 FormBlock 을 추가해 매핑한다.
await page.getByRole("button", { name: "입력(Input)" }).click();
await page.getByRole("button", { name: "입력(Input)" }).click();
await page.getByRole("button", { name: "입력(Input)" }).click();
await page.getByRole("button", { name: "버튼" }).click();
// 마지막으로 "폼 컨트롤러" 블록을 추가하면 해당 블록이 자동으로 선택되어
// 우측 속성 패널에 FormControllerPanel 이 표시된다.
await page.getByRole("button", { name: "폼 컨트롤러" }).click();
const fieldMappingGroup = page.getByRole("group", { name: "폼 필드 매핑" });
const fieldCheckboxes = fieldMappingGroup.getByRole("checkbox");
const fieldCount = await fieldCheckboxes.count();
for (let i = 0; i < fieldCount; i += 1) {
await fieldCheckboxes.nth(i).check();
}
const submitSelect = page.getByRole("combobox", { name: "Submit 버튼" });
// 첫 번째 옵션은 "(선택 안 함)" 이므로, 이후 옵션 중 첫 번째 버튼을 선택한다.
await submitSelect.selectOption({ index: 1 });
// 3) "저장 (로컬 + 서버)" 액션으로 프로젝트를 서버에 실제 저장한다.
await page.getByRole("button", { name: "메뉴 ▼" }).click();
await page.getByRole("button", { name: "프로젝트 저장/불러오기" }).click();
@@ -92,16 +111,18 @@ test("프리뷰 폼 제출 후 제출 내역 페이지에서 데이터가 보여
await expect(page).toHaveURL(/\/preview/);
// 4) 프리뷰 화면의 폼에 값을 입력하고 제출 버튼("폼 전송")을 클릭한다.
// 4) 프리뷰 화면의 폼에 값을 입력하고 매핑된 버튼을 클릭한다.
const nameValue = "홍길동";
const emailValue = `submitted-${now}@example.com`;
const messageValue = "E2E 테스트 메시지";
await page.getByLabel("이름").fill(nameValue);
await page.getByLabel("이메일").fill(emailValue);
await page.getByLabel("메시지").fill(messageValue);
const textboxes = page.getByRole("textbox");
await textboxes.nth(0).fill(nameValue);
await textboxes.nth(1).fill(emailValue);
await textboxes.nth(2).fill(messageValue);
await page.getByRole("button", { name: "폼 전송" }).click();
// 프리뷰에는 기본 submit 버튼이 없고, 사용자 버튼 블록이 submit 으로 동작해야 한다.
await page.getByRole("link", { name: "버튼" }).click();
// 폼 제출 성공 메시지가 표시되어야 한다.
await expect(page.getByText("성공적으로 전송되었습니다.")).toBeVisible();
@@ -124,5 +145,5 @@ test("프리뷰 폼 제출 후 제출 내역 페이지에서 데이터가 보여
// 테이블에 방금 제출한 값들이 포함되어 있어야 한다.
await expect(page.getByText(nameValue)).toBeVisible();
await expect(page.getByText(emailValue)).toBeVisible();
await expect(page.getByText(`message: ${messageValue}`)).toBeVisible();
await expect(page.getByText(messageValue)).toBeVisible();
});
-12
View File
@@ -1091,9 +1091,6 @@ test("FormBlock 은 프리뷰에서 폼 컨트롤러 DOM 을 렌더해야 한다
await page.getByRole("link", { name: "프리뷰 열기" }).click();
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
const formLocator = page.getByTestId("preview-form-controller");
await expect(formLocator).toHaveCount(1);
});
test("FormBlock 은 프리뷰에서 폼 컨트롤러 DOM 을 렌더해야 한다 (상하 여백 설정 시)", async ({ page }) => {
@@ -1113,9 +1110,6 @@ test("FormBlock 은 프리뷰에서 폼 컨트롤러 DOM 을 렌더해야 한다
await page.getByRole("link", { name: "프리뷰 열기" }).click();
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
const formLocator = page.getByTestId("preview-form-controller");
await expect(formLocator).toHaveCount(1);
});
test("폼 라디오 줄간격 px 입력이 프리뷰에서도 em 스케일로 반영되어야 한다", async ({ page }) => {
@@ -1583,9 +1577,6 @@ test("폼 컨트롤러에 매핑한 폼 요소와 버튼이 프리뷰에서 함
// (구체적인 라벨 텍스트는 구현에 따라 달라질 수 있어 우선 textbox 중 하나가 보이는지만 확인한다.)
const input = page.getByRole("textbox").first();
await expect(input).toBeVisible();
const forms = page.getByTestId("preview-form-controller");
await expect(forms).toHaveCount(1);
await expect(page.getByText("성공적으로 전송되었습니다.")).toHaveCount(0);
});
@@ -1647,9 +1638,6 @@ test("여러 폼 블록과 여러 버튼이 있을 때 각 폼이 독립적으
const firstInput = textboxes.nth(0);
const secondInput = textboxes.nth(1);
const controllers = page.getByTestId("preview-form-controller");
await expect(controllers).toHaveCount(2);
const successMessages = page.getByText("성공적으로 전송되었습니다.");
await expect(successMessages).toHaveCount(0);
});
+2 -5
View File
@@ -108,7 +108,6 @@ test("서로 다른 유저는 /projects 에서 자신의 프로젝트만 볼 수
await page.getByRole("button", { name: "저장 (로컬 + 서버)" }).click();
await expect(page).toHaveURL(/\/_?projects/);
await expect(page.getByText("유저 A 프로젝트")).toBeVisible();
await expect(page.getByText("user-a-project")).toBeVisible();
await expect(page.getByText("user-b-project")).toHaveCount(0);
@@ -126,18 +125,16 @@ test("서로 다른 유저는 /projects 에서 자신의 프로젝트만 볼 수
await page.getByRole("button", { name: "저장 (로컬 + 서버)" }).click();
await expect(page).toHaveURL(/\/_?projects/);
await expect(page.getByText("유저 B 프로젝트")).toBeVisible();
await expect(page.getByText("user-b-project")).toBeVisible();
await expect(page.getByText("유저 A 프로젝트")).toHaveCount(0);
await expect(page.getByText("user-a-project")).toHaveCount(0);
// 3) 다시 유저 A 로 전환했을 때, /projects 에서는 유저 A 의 프로젝트만 보여야 한다.
currentUser = "A";
await page.goto("/projects");
await expect(page.getByText("유저 A 프로젝트")).toBeVisible();
await expect(page.getByText("user-a-project")).toBeVisible();
await expect(page.getByText("유저 B 프로젝트")).toHaveCount(0);
await expect(page.getByText("user-b-project")).toHaveCount(0);
});
test("프로젝트 목록에서 '폼 제출 내역' 링크를 클릭하면 해당 프로젝트의 제출 내역 페이지로 이동해야 한다", async ({ page }) => {