Skip to content

Commit 0fda82d

Browse files
committed
test(e2e): UI E2E 테스트 추가
- 아이템 시세 페이지 섹션별 검색 독립성 테스트 - 모바일 뷰포트 테이블 가로 스크롤 및 헤더 검증 테스트 - 필터 다이얼로그, 네비게이션, 푸터 팝업 테스트 추가
1 parent 8745eac commit 0fda82d

File tree

5 files changed

+222
-0
lines changed

5 files changed

+222
-0
lines changed

e2e/filter-dialog.spec.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,88 @@ test.describe("필터 다이얼로그", () => {
254254
await expect(excludeRadio).toBeChecked();
255255
await expect(includeRadio).not.toBeChecked();
256256
});
257+
258+
test("컨텐츠 보상 종류 멀티셀렉트 필터가 테이블 시급 계산에 반영됨", async ({
259+
page,
260+
}) => {
261+
// 필터 다이얼로그 열기
262+
await page.getByRole("button", { name: "필터" }).click();
263+
const dialog = page.getByRole("dialog");
264+
265+
// 기본값: 10개 선택됨
266+
const rewardTypeCombobox = dialog.getByRole("combobox", {
267+
name: "컨텐츠 보상 종류",
268+
});
269+
await expect(rewardTypeCombobox).toContainText("10 selected");
270+
271+
// 드롭다운 열기
272+
await rewardTypeCombobox.click();
273+
274+
// 모든 옵션이 선택되어 있는지 확인
275+
const options = page.getByRole("listbox", { name: "컨텐츠 보상 종류" });
276+
await expect(options.getByRole("option", { name: "골드" })).toHaveAttribute(
277+
"aria-selected",
278+
"true"
279+
);
280+
281+
// 골드 선택 해제
282+
await options.getByRole("option", { name: "골드" }).click();
283+
284+
// 9개 선택됨으로 변경 확인
285+
await expect(rewardTypeCombobox).toContainText("9 selected");
286+
287+
// 드롭다운 닫기
288+
await page.keyboard.press("Escape");
289+
290+
// 골드만 보상인 컨텐츠의 시급이 0원으로 표시되는지 확인
291+
const eponaRow = page
292+
.locator("table tbody tr")
293+
.filter({ hasText: "에포나 의뢰" });
294+
await expect(eponaRow.locator("td").nth(5)).toContainText("₩0");
295+
296+
// 다이얼로그 닫기
297+
await dialog.getByRole("button", { name: "close" }).click();
298+
299+
// 다시 필터 열고 골드 다시 선택
300+
await page.getByRole("button", { name: "필터" }).click();
301+
await rewardTypeCombobox.click();
302+
await options.getByRole("option", { name: "골드" }).click();
303+
304+
// 10개 선택됨으로 복구 확인
305+
await expect(rewardTypeCombobox).toContainText("10 selected");
306+
307+
// 드롭다운 닫기
308+
await page.keyboard.press("Escape");
309+
310+
// 에포나 의뢰 시급이 0이 아닌 값으로 복구되는지 확인
311+
await expect(eponaRow.locator("td").nth(5)).not.toContainText("₩0");
312+
});
313+
314+
test("컨텐츠 보상 종류 여러 옵션 선택/해제가 독립적으로 동작함", async ({
315+
page,
316+
}) => {
317+
await page.getByRole("button", { name: "필터" }).click();
318+
const dialog = page.getByRole("dialog");
319+
320+
const rewardTypeCombobox = dialog.getByRole("combobox", {
321+
name: "컨텐츠 보상 종류",
322+
});
323+
await rewardTypeCombobox.click();
324+
325+
const options = page.getByRole("listbox", { name: "컨텐츠 보상 종류" });
326+
327+
// 여러 옵션 선택 해제
328+
await options.getByRole("option", { name: "골드" }).click();
329+
await expect(rewardTypeCombobox).toContainText("9 selected");
330+
331+
await options.getByRole("option", { name: "실링" }).click();
332+
await expect(rewardTypeCombobox).toContainText("8 selected");
333+
334+
await options.getByRole("option", { name: "카드 경험치" }).click();
335+
await expect(rewardTypeCombobox).toContainText("7 selected");
336+
337+
// 다시 선택
338+
await options.getByRole("option", { name: "골드" }).click();
339+
await expect(rewardTypeCombobox).toContainText("8 selected");
340+
});
257341
});

e2e/footer-popups.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@ test.describe("푸터 팝업", () => {
2323
await expect(dialog.getByText(/ /)).toBeVisible();
2424
});
2525

26+
test("수집 현황 팝업에 거래소/경매장 아이템 수집 시간이 모두 표시됨", async ({
27+
page,
28+
}) => {
29+
await page.getByText("수집 현황").click();
30+
31+
const dialog = page.getByRole("dialog");
32+
33+
// 거래소 아이템 수집 시간 확인 (YYYY-MM-DD HH:mm 형식)
34+
await expect(
35+
dialog.getByText(/ : \d{4}-\d{2}-\d{2} \d{2}:\d{2}/)
36+
).toBeVisible();
37+
38+
// 경매장 아이템 수집 시간 확인 (YYYY-MM-DD HH:mm 형식)
39+
await expect(
40+
dialog.getByText(/ : \d{4}-\d{2}-\d{2} \d{2}:\d{2}/)
41+
).toBeVisible();
42+
});
43+
2644
test("후원 안내 클릭 시 팝업이 표시됨", async ({ page }) => {
2745
await page.getByText("후원 안내").click();
2846

e2e/item-price-list.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,48 @@ test.describe("아이템 시세 페이지", () => {
8383
expect(firstItemName).toContain("파편");
8484
});
8585

86+
test("섹션별 검색이 독립적으로 동작함", async ({ page }) => {
87+
const refiningRegion = page.getByRole("region", { name: "재련 재료" });
88+
const additionalRegion = page.getByRole("region", { name: "재련 추가 재료" });
89+
90+
const refiningTable = refiningRegion.locator("table");
91+
const additionalTable = additionalRegion.locator("table");
92+
93+
// 데이터 로딩 대기
94+
await refiningTable.locator("tbody tr").first().waitFor({ timeout: 15000 });
95+
await additionalTable.locator("tbody tr").first().waitFor({ timeout: 15000 });
96+
97+
// 검색 전 각 섹션의 아이템 수 확인
98+
const refiningRowsBefore = await refiningTable.locator("tbody tr").count();
99+
const additionalRowsBefore = await additionalTable.locator("tbody tr").count();
100+
101+
// 재련 재료 섹션에서 "파편" 검색
102+
await refiningRegion.getByPlaceholder("검색").fill("파편");
103+
await refiningRegion.getByPlaceholder("검색").press("Enter");
104+
await page.waitForTimeout(500);
105+
106+
// 재련 재료 섹션만 필터링됨
107+
const refiningRowsAfter = await refiningTable.locator("tbody tr").count();
108+
expect(refiningRowsAfter).toBeLessThan(refiningRowsBefore);
109+
110+
// 재련 추가 재료 섹션은 영향받지 않음
111+
const additionalRowsUnchanged = await additionalTable.locator("tbody tr").count();
112+
expect(additionalRowsUnchanged).toBe(additionalRowsBefore);
113+
114+
// 재련 추가 재료 섹션에서 "용암" 검색 (용암의 숨결만 매칭)
115+
await additionalRegion.getByPlaceholder("검색").fill("용암");
116+
await additionalRegion.getByPlaceholder("검색").press("Enter");
117+
await page.waitForTimeout(500);
118+
119+
// 재련 추가 재료 섹션이 필터링됨
120+
const additionalRowsAfter = await additionalTable.locator("tbody tr").count();
121+
expect(additionalRowsAfter).toBeLessThan(additionalRowsBefore);
122+
123+
// 재련 재료 섹션의 필터링 결과가 유지됨
124+
const refiningRowsFinal = await refiningTable.locator("tbody tr").count();
125+
expect(refiningRowsFinal).toBe(refiningRowsAfter);
126+
});
127+
86128
test("검색어 삭제 시 전체 목록이 복원됨", async ({ page }) => {
87129
const refiningRegion = page.getByRole("region", { name: "재련 재료" });
88130
const searchInput = refiningRegion.getByPlaceholder("검색");

e2e/navigation.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,46 @@ test.describe("네비게이션 링크", () => {
8787
"https://discord.gg/kZApcdSEJ4"
8888
);
8989
});
90+
91+
test("설명서 다이얼로그 모든 아코디언 항목에 내용이 표시됨", async ({ page }) => {
92+
await page.goto("/");
93+
await page.getByRole("button", { name: "설명서" }).click();
94+
95+
const dialog = page.getByRole("dialog", { name: "설명서" });
96+
97+
// 데이터 수집 방식
98+
await dialog.getByRole("button", { name: "데이터 수집 방식" }).click();
99+
const dataCollectionRegion = dialog.getByRole("region", {
100+
name: "데이터 수집 방식",
101+
});
102+
await expect(dataCollectionRegion).toBeVisible();
103+
await expect(dataCollectionRegion.getByRole("list")).toBeVisible();
104+
105+
// 아이템 가격 계산 방식
106+
await dialog.getByRole("button", { name: "아이템 가격 계산 방식" }).click();
107+
const itemPriceRegion = dialog.getByRole("region", {
108+
name: "아이템 가격 계산 방식",
109+
});
110+
await expect(itemPriceRegion).toBeVisible();
111+
await expect(
112+
itemPriceRegion.getByRole("link", { name: "아이템 시세 페이지" })
113+
).toHaveAttribute("href", "/item-price-list");
114+
115+
// 컨텐츠별 보상 책정 방식
116+
await dialog.getByRole("button", { name: "컨텐츠별 보상 책정 방식" }).click();
117+
const rewardRegion = dialog.getByRole("region", {
118+
name: "컨텐츠별 보상 책정 방식",
119+
});
120+
await expect(rewardRegion).toBeVisible();
121+
await expect(
122+
rewardRegion.getByRole("link", { name: "컨텐츠별 보상 페이지" }).first()
123+
).toHaveAttribute("href", "/content-reward-list");
124+
125+
// 시급 책정 방식 (이전 아코디언 닫고 클릭)
126+
await dialog.getByRole("button", { name: "컨텐츠별 보상 책정 방식" }).click();
127+
await dialog.getByRole("button", { name: "시급 책정 방식" }).click();
128+
const wageRegion = dialog.getByRole("region", { name: "시급 책정 방식" });
129+
await expect(wageRegion).toBeVisible();
130+
await expect(wageRegion.getByRole("list")).toBeVisible();
131+
});
90132
});

e2e/responsive-layout.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,42 @@ test.describe("반응형 레이아웃 (모바일)", () => {
5454
await expect(tableRows).not.toHaveCount(0);
5555
});
5656

57+
test("모바일 뷰포트에서 테이블 가로 스크롤이 가능함", async ({ page }) => {
58+
await page.goto("/");
59+
60+
// 테이블 데이터 로딩 대기
61+
await page.locator("table tbody tr").first().waitFor({ timeout: 15000 });
62+
63+
// 테이블 컨테이너에서 가로 스크롤 가능 여부 확인
64+
const scrollInfo = await page.evaluate(() => {
65+
const table = document.querySelector("table");
66+
const container = table?.parentElement;
67+
if (container) {
68+
return {
69+
scrollWidth: container.scrollWidth,
70+
clientWidth: container.clientWidth,
71+
hasHorizontalScroll: container.scrollWidth > container.clientWidth,
72+
};
73+
}
74+
return null;
75+
});
76+
77+
expect(scrollInfo).not.toBeNull();
78+
expect(scrollInfo?.hasHorizontalScroll).toBe(true);
79+
80+
// 테이블 헤더 텍스트가 DOM에 존재하는지 확인 (th 요소 내부)
81+
const headerTexts = await page.evaluate(() => {
82+
const headers = document.querySelectorAll("table thead th");
83+
return Array.from(headers).map((th) => th.textContent?.trim());
84+
});
85+
86+
expect(headerTexts).toContain("즐겨찾기");
87+
expect(headerTexts).toContain("종류");
88+
expect(headerTexts).toContain("이름");
89+
expect(headerTexts).toContain("시급(원)");
90+
expect(headerTexts).toContain("1수당 골드");
91+
});
92+
5793
test("모바일 뷰포트에서 필터 다이얼로그가 정상 동작함", async ({ page }) => {
5894
await page.goto("/");
5995
await page.locator("table tbody tr").first().waitFor({ timeout: 15000 });

0 commit comments

Comments
 (0)