Skip to content

Commit aeeb289

Browse files
committed
test(e2e): UI E2E 테스트 추가
필터 다이얼로그, 콘텐츠 상세, 보상 목록, 아이템 가격 등 다양한 페이지의 E2E 테스트 케이스 추가 - 관문 분리/합쳐보기 기능 테스트 - 더보기 포함 여부 라디오 버튼 테스트 - 테이블 정렬 및 반응형 레이아웃 테스트
1 parent eeff150 commit aeeb289

8 files changed

+434
-0
lines changed

e2e/authenticated.auth.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,68 @@ test.describe("로그인 상태 테스트", () => {
3535
const isPressedAfter = await favoriteButton.getAttribute("aria-pressed");
3636
expect(isPressedBefore).not.toBe(isPressedAfter);
3737
});
38+
39+
test("로그인 후 골드 환율 설정 버튼이 활성화됨", async ({ page }) => {
40+
await page.goto("/");
41+
await page.locator("table tbody tr").first().waitFor();
42+
43+
const exchangeRateButton = page.getByRole("button", {
44+
name: / /,
45+
});
46+
await expect(exchangeRateButton).toBeVisible();
47+
await expect(exchangeRateButton).toBeEnabled();
48+
});
49+
50+
test("로그인 후 골드 환율 설정 다이얼로그가 열림", async ({ page }) => {
51+
await page.goto("/");
52+
await page.locator("table tbody tr").first().waitFor();
53+
54+
await page.getByRole("button", { name: / / }).click();
55+
56+
const dialog = page.getByRole("dialog");
57+
await expect(dialog).toBeVisible();
58+
await expect(dialog.getByText("골드 환율 설정")).toBeVisible();
59+
});
60+
61+
test("로그인 후 컨텐츠 상세에서 소요시간 수정 버튼이 활성화됨", async ({
62+
page,
63+
}) => {
64+
await page.goto("/");
65+
66+
const firstRow = page.locator("table tbody tr").first();
67+
await firstRow.click();
68+
69+
const dialog = page
70+
.getByRole("dialog")
71+
.filter({ has: page.getByRole("heading", { name: "컨텐츠 상세 정보" }) });
72+
await expect(dialog).toBeVisible();
73+
74+
// 소요 시간 수정 버튼 확인
75+
const durationEditButton = dialog
76+
.locator("dt", { hasText: /^ $/ })
77+
.locator("..")
78+
.locator("..")
79+
.getByRole("button");
80+
await expect(durationEditButton).toBeEnabled();
81+
});
82+
83+
test("로그아웃 후 UI가 비로그인 상태로 변경됨", async ({ page }) => {
84+
await page.goto("/");
85+
await page.locator("table tbody tr").first().waitFor();
86+
87+
// 사용자 메뉴 클릭
88+
const userMenu = page.getByRole("button", {
89+
name: new RegExp(E2E_USER_DISPLAY_NAME),
90+
});
91+
await userMenu.click();
92+
93+
// 로그아웃 클릭
94+
await page.getByRole("menuitem", { name: "로그아웃" }).click();
95+
96+
// 비로그인 상태 확인: 골드 환율 설정 버튼 비활성화
97+
const exchangeRateButton = page.getByRole("button", {
98+
name: / /,
99+
});
100+
await expect(exchangeRateButton).toBeDisabled();
101+
});
38102
});

e2e/content-detail-dialog.spec.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,88 @@ test.describe("컨텐츠 상세 다이얼로그", () => {
9090
await expect(filterDialog.getByText("더보기 포함 여부")).toBeVisible();
9191
await expect(filterDialog.getByText("귀속 재료 포함 여부")).toBeVisible();
9292
});
93+
94+
test("시급 정보 필터 팝오버에서 더보기 포함 여부 라디오 버튼 변경이 동작함", async ({
95+
page,
96+
}) => {
97+
await page.locator("table tbody tr").first().click();
98+
99+
const dialog = page
100+
.getByRole("dialog")
101+
.filter({ has: page.getByRole("heading", { name: "컨텐츠 상세 정보" }) });
102+
103+
// 시급 정보 섹션의 필터 버튼 클릭
104+
await dialog.getByRole("button", { name: "필터" }).first().click();
105+
106+
const filterDialog = dialog.getByRole("dialog");
107+
await expect(filterDialog).toBeVisible();
108+
109+
const seeMoreGroup = filterDialog
110+
.getByRole("group")
111+
.filter({ hasText: "더보기 포함 여부" });
112+
const excludeRadio = seeMoreGroup.getByRole("radio", {
113+
name: "미포함",
114+
exact: true,
115+
});
116+
const includeRadio = seeMoreGroup.getByRole("radio", {
117+
name: "포함",
118+
exact: true,
119+
});
120+
121+
// 기본값: 미포함 선택됨
122+
await expect(excludeRadio).toBeChecked();
123+
await expect(includeRadio).not.toBeChecked();
124+
125+
// 포함으로 변경 (라벨 클릭)
126+
await seeMoreGroup.locator("label").filter({ hasText: /^$/ }).click();
127+
await expect(includeRadio).toBeChecked();
128+
await expect(excludeRadio).not.toBeChecked();
129+
130+
// 다시 미포함으로 변경
131+
await seeMoreGroup.locator("label").filter({ hasText: "미포함" }).click();
132+
await expect(excludeRadio).toBeChecked();
133+
await expect(includeRadio).not.toBeChecked();
134+
});
135+
136+
test("시급 정보 필터 팝오버에서 귀속 재료 포함 여부 라디오 버튼 변경이 동작함", async ({
137+
page,
138+
}) => {
139+
await page.locator("table tbody tr").first().click();
140+
141+
const dialog = page
142+
.getByRole("dialog")
143+
.filter({ has: page.getByRole("heading", { name: "컨텐츠 상세 정보" }) });
144+
145+
// 시급 정보 섹션의 필터 버튼 클릭
146+
await dialog.getByRole("button", { name: "필터" }).first().click();
147+
148+
const filterDialog = dialog.getByRole("dialog");
149+
await expect(filterDialog).toBeVisible();
150+
151+
const boundGroup = filterDialog
152+
.getByRole("group")
153+
.filter({ hasText: "귀속 재료 포함 여부" });
154+
const excludeRadio = boundGroup.getByRole("radio", {
155+
name: "미포함",
156+
exact: true,
157+
});
158+
const includeRadio = boundGroup.getByRole("radio", {
159+
name: "포함",
160+
exact: true,
161+
});
162+
163+
// 기본값: 미포함 선택됨
164+
await expect(excludeRadio).toBeChecked();
165+
await expect(includeRadio).not.toBeChecked();
166+
167+
// 포함으로 변경 (라벨 클릭)
168+
await boundGroup.locator("label").filter({ hasText: /^$/ }).click();
169+
await expect(includeRadio).toBeChecked();
170+
await expect(excludeRadio).not.toBeChecked();
171+
172+
// 다시 미포함으로 변경
173+
await boundGroup.locator("label").filter({ hasText: "미포함" }).click();
174+
await expect(excludeRadio).toBeChecked();
175+
await expect(includeRadio).not.toBeChecked();
176+
});
93177
});

e2e/content-reward-list.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,55 @@ test.describe("컨텐츠별 보상 페이지", () => {
130130
// 보상 정보 섹션 확인
131131
await expect(dialog.getByText("보상 정보")).toBeVisible();
132132
});
133+
134+
test("검색어 입력 시 테이블이 필터링됨", async ({ page }) => {
135+
await page.goto("/content-reward-list");
136+
await page.locator("table tbody tr").first().waitFor({ timeout: 15000 });
137+
138+
// 전체 행 수 저장
139+
const initialRowCount = await page.locator("table tbody tr").count();
140+
expect(initialRowCount).toBeGreaterThan(4);
141+
142+
// 검색어 입력
143+
const searchInput = page.getByRole("textbox", { name: "검색" });
144+
await searchInput.fill("베히모스");
145+
await searchInput.press("Enter");
146+
147+
// 필터링 결과 대기
148+
await expect(page.locator("table tbody tr")).not.toHaveCount(initialRowCount);
149+
150+
// 필터링된 결과 확인
151+
const filteredRowCount = await page.locator("table tbody tr").count();
152+
expect(filteredRowCount).toBeLessThan(initialRowCount);
153+
expect(filteredRowCount).toBeGreaterThan(0);
154+
155+
// 모든 결과가 검색어를 포함하는지 확인
156+
const rows = page.locator("table tbody tr");
157+
for (let i = 0; i < filteredRowCount; i++) {
158+
await expect(rows.nth(i)).toContainText("베히모스");
159+
}
160+
});
161+
162+
test("검색어 삭제 시 전체 목록이 복원됨", async ({ page }) => {
163+
await page.goto("/content-reward-list");
164+
await page.locator("table tbody tr").first().waitFor({ timeout: 15000 });
165+
166+
// 전체 행 수 저장
167+
const initialRowCount = await page.locator("table tbody tr").count();
168+
169+
// 검색어 입력
170+
const searchInput = page.getByRole("textbox", { name: "검색" });
171+
await searchInput.fill("베히모스");
172+
await searchInput.press("Enter");
173+
174+
// 필터링 결과 대기
175+
await expect(page.locator("table tbody tr")).not.toHaveCount(initialRowCount);
176+
177+
// 검색어 삭제 (input 비우고 Enter)
178+
await searchInput.clear();
179+
await searchInput.press("Enter");
180+
181+
// 전체 목록 복원 대기
182+
await expect(page.locator("table tbody tr")).toHaveCount(initialRowCount);
183+
});
133184
});

e2e/filter-dialog.spec.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,118 @@ test.describe("필터 다이얼로그", () => {
136136
const allRowCount = await page.locator("table tbody tr").count();
137137
expect(allRowCount).toBeGreaterThan(filteredRowCount);
138138
});
139+
140+
test("관문 분리하기 선택 시 레이드가 관문별로 분리됨", async ({ page }) => {
141+
// 합쳐보기 상태에서 베히모스 행 확인
142+
const mergedRow = page
143+
.locator("table tbody tr")
144+
.filter({ hasText: "[노말]폭풍의 지휘관, 베히모스" });
145+
await expect(mergedRow).toHaveCount(1);
146+
await expect(mergedRow).not.toContainText("관문");
147+
148+
// 필터 다이얼로그 열기 및 분리하기 선택 (라벨 클릭)
149+
await page.getByRole("button", { name: "필터" }).click();
150+
const dialog = page.getByRole("dialog");
151+
await dialog.locator("label").filter({ hasText: "분리하기" }).click();
152+
await dialog.getByRole("button", { name: "close" }).click();
153+
154+
// 분리된 관문별 행 확인
155+
const gate1Row = page
156+
.locator("table tbody tr")
157+
.filter({ hasText: "[노말]폭풍의 지휘관, 베히모스 1관문" });
158+
const gate2Row = page
159+
.locator("table tbody tr")
160+
.filter({ hasText: "[노말]폭풍의 지휘관, 베히모스 2관문" });
161+
162+
await expect(gate1Row).toHaveCount(1);
163+
await expect(gate2Row).toHaveCount(1);
164+
});
165+
166+
test("관문 합쳐보기 선택 시 레이드가 합쳐서 표시됨", async ({ page }) => {
167+
// 먼저 분리하기로 변경 (라벨 클릭)
168+
await page.getByRole("button", { name: "필터" }).click();
169+
const dialog = page.getByRole("dialog");
170+
await dialog.locator("label").filter({ hasText: "분리하기" }).click();
171+
await dialog.getByRole("button", { name: "close" }).click();
172+
173+
// 분리된 상태 확인
174+
const gate1Row = page
175+
.locator("table tbody tr")
176+
.filter({ hasText: "[노말]폭풍의 지휘관, 베히모스 1관문" });
177+
await expect(gate1Row).toHaveCount(1);
178+
179+
// 다시 합쳐보기로 변경 (라벨 클릭)
180+
await page.getByRole("button", { name: "필터" }).click();
181+
await dialog.locator("label").filter({ hasText: /^$/ }).click();
182+
await dialog.getByRole("button", { name: "close" }).click();
183+
184+
// 합쳐진 상태 확인
185+
const mergedRow = page
186+
.locator("table tbody tr")
187+
.filter({ hasText: "[노말]폭풍의 지휘관, 베히모스" });
188+
await expect(mergedRow).toHaveCount(1);
189+
await expect(mergedRow).not.toContainText("관문");
190+
});
191+
192+
test("더보기 포함 여부 라디오 버튼 변경이 동작함", async ({ page }) => {
193+
await page.getByRole("button", { name: "필터" }).click();
194+
const dialog = page.getByRole("dialog");
195+
196+
// 기본값 확인: 미포함 선택됨
197+
const seeMoreGroup = dialog
198+
.getByRole("group")
199+
.filter({ hasText: "더보기 포함 여부" });
200+
const excludeRadio = seeMoreGroup.getByRole("radio", {
201+
name: "미포함",
202+
exact: true,
203+
});
204+
const includeRadio = seeMoreGroup.getByRole("radio", {
205+
name: "포함",
206+
exact: true,
207+
});
208+
209+
await expect(excludeRadio).toBeChecked();
210+
await expect(includeRadio).not.toBeChecked();
211+
212+
// 포함으로 변경 (라벨 클릭)
213+
await seeMoreGroup.locator("label").filter({ hasText: /^$/ }).click();
214+
await expect(includeRadio).toBeChecked();
215+
await expect(excludeRadio).not.toBeChecked();
216+
217+
// 다시 미포함으로 변경
218+
await seeMoreGroup.locator("label").filter({ hasText: "미포함" }).click();
219+
await expect(excludeRadio).toBeChecked();
220+
await expect(includeRadio).not.toBeChecked();
221+
});
222+
223+
test("귀속 재료 포함 여부 라디오 버튼 변경이 동작함", async ({ page }) => {
224+
await page.getByRole("button", { name: "필터" }).click();
225+
const dialog = page.getByRole("dialog");
226+
227+
// 기본값 확인: 미포함 선택됨
228+
const boundGroup = dialog
229+
.getByRole("group")
230+
.filter({ hasText: "귀속 재료 포함 여부" });
231+
const excludeRadio = boundGroup.getByRole("radio", {
232+
name: "미포함",
233+
exact: true,
234+
});
235+
const includeRadio = boundGroup.getByRole("radio", {
236+
name: "포함",
237+
exact: true,
238+
});
239+
240+
await expect(excludeRadio).toBeChecked();
241+
await expect(includeRadio).not.toBeChecked();
242+
243+
// 포함으로 변경 (라벨 클릭)
244+
await boundGroup.locator("label").filter({ hasText: /^$/ }).click();
245+
await expect(includeRadio).toBeChecked();
246+
await expect(excludeRadio).not.toBeChecked();
247+
248+
// 다시 미포함으로 변경
249+
await boundGroup.locator("label").filter({ hasText: "미포함" }).click();
250+
await expect(excludeRadio).toBeChecked();
251+
await expect(includeRadio).not.toBeChecked();
252+
});
139253
});

e2e/item-price-list.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,58 @@ test.describe("아이템 시세 페이지", () => {
199199
await expect(dialog).toBeVisible();
200200
await expect(dialog).toContainText("즉시 구매가 최저가순 첫 페이지 10개 항목 평균입니다");
201201
});
202+
203+
test("기타 아이템 탭 아코디언 expand/collapse 동작", async ({ page }) => {
204+
// 기타 아이템 탭으로 이동
205+
await page.getByRole("tab", { name: "기타 아이템" }).click();
206+
await expect(page).toHaveURL(/tab=extra-item/);
207+
208+
const accordionButton = page.getByRole("button", { name: "기타 아이템" });
209+
const accordionRegion = page.getByRole("region", { name: "기타 아이템" });
210+
211+
// 초기 상태: expanded
212+
await expect(accordionButton).toHaveAttribute("aria-expanded", "true");
213+
await expect(accordionRegion).toBeVisible();
214+
215+
// collapse
216+
await accordionButton.click();
217+
await expect(accordionButton).toHaveAttribute("aria-expanded", "false");
218+
await expect(accordionRegion).not.toBeVisible();
219+
220+
// expand
221+
await accordionButton.click();
222+
await expect(accordionButton).toHaveAttribute("aria-expanded", "true");
223+
await expect(accordionRegion).toBeVisible();
224+
});
225+
226+
test("기타 아이템 탭 테이블에 데이터 표시됨", async ({ page }) => {
227+
// 기타 아이템 탭으로 이동
228+
await page.getByRole("tab", { name: "기타 아이템" }).click();
229+
230+
const extraRegion = page.getByRole("region", { name: "기타 아이템" });
231+
const table = extraRegion.locator("table");
232+
233+
// 테이블 로드 대기
234+
await table.locator("tbody tr").first().waitFor();
235+
236+
// 테이블 헤더 확인
237+
await expect(table.locator("th")).toContainText(["아이템", "개당 골드 가치"]);
238+
239+
// 데이터 행이 존재하는지 확인
240+
const rowCount = await table.locator("tbody tr").count();
241+
expect(rowCount).toBeGreaterThan(0);
242+
});
243+
244+
test("기타 아이템 탭 info 버튼 클릭 시 툴팁 표시", async ({ page }) => {
245+
// 기타 아이템 탭으로 이동
246+
await page.getByRole("tab", { name: "기타 아이템" }).click();
247+
248+
const extraRegion = page.getByRole("region", { name: "기타 아이템" });
249+
await extraRegion.getByRole("button", { name: "info" }).click();
250+
251+
// 툴팁 다이얼로그 확인
252+
const dialog = page.getByRole("dialog");
253+
await expect(dialog).toBeVisible();
254+
await expect(dialog).toContainText("로그인 후 수정 가능합니다");
255+
});
202256
});

0 commit comments

Comments
 (0)