From 4a98d3cf32c0053d2e402c2ba8316390693eec2e Mon Sep 17 00:00:00 2001 From: "leo.yi" Date: Tue, 29 Jul 2025 20:50:23 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=97=85,=20=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- e2e/fixtures/{index.ts => index.fixture.ts} | 0 e2e/fixtures/pages.fixture.ts | 27 +- e2e/pages/community/notice/create.page.ts | 37 +++ e2e/pages/community/notice/detail.page.ts | 74 +++++ e2e/pages/community/notice/edit.page.ts | 68 +++++ e2e/pages/community/notice/index.page.ts | 55 +++- e2e/tests/community/notice.test.ts | 30 -- e2e/tests/community/notice/create.test.ts | 211 ++++++++++++++ e2e/tests/community/notice/detail.test.ts | 242 ++++++++++++++++ e2e/tests/community/notice/edit.test.ts | 146 ++++++++++ e2e/tests/community/notice/index.test.ts | 298 ++++++++++++++++++++ 11 files changed, 1153 insertions(+), 35 deletions(-) rename e2e/fixtures/{index.ts => index.fixture.ts} (100%) create mode 100644 e2e/pages/community/notice/detail.page.ts create mode 100644 e2e/pages/community/notice/edit.page.ts delete mode 100644 e2e/tests/community/notice.test.ts create mode 100644 e2e/tests/community/notice/create.test.ts create mode 100644 e2e/tests/community/notice/detail.test.ts create mode 100644 e2e/tests/community/notice/edit.test.ts create mode 100644 e2e/tests/community/notice/index.test.ts diff --git a/e2e/fixtures/index.ts b/e2e/fixtures/index.fixture.ts similarity index 100% rename from e2e/fixtures/index.ts rename to e2e/fixtures/index.fixture.ts diff --git a/e2e/fixtures/pages.fixture.ts b/e2e/fixtures/pages.fixture.ts index 7cdf3c19..fe0cc099 100644 --- a/e2e/fixtures/pages.fixture.ts +++ b/e2e/fixtures/pages.fixture.ts @@ -4,17 +4,24 @@ import { Page } from '@playwright/test'; import { NoticeCreatePage } from '../pages/community/notice/create.page'; -import { NoticePage } from '../pages/community/notice/index.page'; +import { NoticeDetailPage } from '../pages/community/notice/detail.page'; +import { NoticeEditPage } from '../pages/community/notice/edit.page'; +import { NoticeListPage } from '../pages/community/notice/index.page'; import { test as base } from './role.fixture'; type PagesFixtures = { - noticePage: NoticePage; + noticeListPage: NoticeListPage; noticeCreatePage: NoticeCreatePage; + noticeEditPage: NoticeEditPage; + noticeDetailPage: NoticeDetailPage; }; export const test = base.extend({ - noticePage: async ({ page }: { page: Page }, use: (page: NoticePage) => Promise) => { - await use(new NoticePage(page)); + noticeListPage: async ( + { page }: { page: Page }, + use: (page: NoticeListPage) => Promise, + ) => { + await use(new NoticeListPage(page)); }, noticeCreatePage: async ( { page }: { page: Page }, @@ -22,4 +29,16 @@ export const test = base.extend({ ) => { await use(new NoticeCreatePage(page)); }, + noticeEditPage: async ( + { page }: { page: Page }, + use: (page: NoticeEditPage) => Promise, + ) => { + await use(new NoticeEditPage(page)); + }, + noticeDetailPage: async ( + { page }: { page: Page }, + use: (page: NoticeDetailPage) => Promise, + ) => { + await use(new NoticeDetailPage(page)); + }, }); diff --git a/e2e/pages/community/notice/create.page.ts b/e2e/pages/community/notice/create.page.ts index 58c04bca..8a8aec08 100644 --- a/e2e/pages/community/notice/create.page.ts +++ b/e2e/pages/community/notice/create.page.ts @@ -4,12 +4,24 @@ export class NoticeCreatePage { readonly page: Page; readonly titleInput: Locator; readonly contentEditor: Locator; + readonly tagInput: Locator; + readonly attachmentInput: Locator; + readonly importantToggle: Locator; + readonly privateToggle: Locator; + readonly pinnedToggle: Locator; + readonly mainDisplayToggle: Locator; readonly submitButton: Locator; constructor(page: Page) { this.page = page; this.titleInput = page.getByPlaceholder('제목을 입력하세요.'); this.contentEditor = page.locator('div.sun-editor-editable'); + this.tagInput = page.locator('TODO_TAG_INPUT_SELECTOR'); // TODO: 실제 태그 입력 필드 selector 확인 필요 + this.attachmentInput = page.locator('input[type="file"]'); + this.importantToggle = page.locator('TODO_IMPORTANT_TOGGLE_SELECTOR'); // TODO: 중요 안내 토글 selector 확인 필요 + this.privateToggle = page.locator('TODO_PRIVATE_TOGGLE_SELECTOR'); // TODO: 비공개 토글 selector 확인 필요 + this.pinnedToggle = page.locator('TODO_PINNED_TOGGLE_SELECTOR'); // TODO: 상단고정 토글 selector 확인 필요 + this.mainDisplayToggle = page.locator('TODO_MAIN_DISPLAY_TOGGLE_SELECTOR'); // TODO: 메인표시 토글 selector 확인 필요 this.submitButton = page.getByRole('button', { name: /게시/ }); } @@ -25,6 +37,31 @@ export class NoticeCreatePage { await this.contentEditor.fill(content); } + async addTag(tag: string) { + await this.tagInput.fill(tag); + await this.page.keyboard.press('Enter'); + } + + async uploadAttachment(filePath: string) { + await this.attachmentInput.setInputFiles(filePath); + } + + async toggleImportant() { + await this.importantToggle.click(); + } + + async togglePrivate() { + await this.privateToggle.click(); + } + + async togglePinned() { + await this.pinnedToggle.click(); + } + + async toggleMainDisplay() { + await this.mainDisplayToggle.click(); + } + async clickSubmitButton() { await this.submitButton.click(); } diff --git a/e2e/pages/community/notice/detail.page.ts b/e2e/pages/community/notice/detail.page.ts new file mode 100644 index 00000000..a733f606 --- /dev/null +++ b/e2e/pages/community/notice/detail.page.ts @@ -0,0 +1,74 @@ +import { type Locator, type Page } from '@playwright/test'; + +export class NoticeDetailPage { + readonly page: Page; + readonly title: Locator; + readonly content: Locator; + readonly editButton: Locator; + readonly deleteButton: Locator; + readonly listButton: Locator; + readonly prevButton: Locator; + readonly nextButton: Locator; + readonly attachments: Locator; + readonly tags: Locator; + + constructor(page: Page) { + this.page = page; + this.title = page.locator('TODO_NOTICE_TITLE_SELECTOR'); // TODO: 공지사항 제목 영역 selector 확인 필요 + this.content = page.locator('TODO_NOTICE_CONTENT_SELECTOR'); // TODO: 공지사항 내용 영역 selector 확인 필요 + this.editButton = page.getByRole('button', { name: '편집' }); + this.deleteButton = page.getByRole('button', { name: '삭제' }); + this.listButton = page.getByRole('button', { name: '목록' }); + this.prevButton = page.locator('TODO_PREV_BUTTON_SELECTOR'); // TODO: 이전글 버튼 selector 확인 필요 + this.nextButton = page.locator('TODO_NEXT_BUTTON_SELECTOR'); // TODO: 다음글 버튼 selector 확인 필요 + this.attachments = page.locator('TODO_ATTACHMENTS_SECTION_SELECTOR'); // TODO: 첨부파일 영역 selector 확인 필요 + this.tags = page.locator('TODO_TAGS_SECTION_SELECTOR'); // TODO: 태그 영역 selector 확인 필요 + } + + async goto(id: string) { + await this.page.goto(`/community/notice/${id}`); + } + + async clickEditButton() { + await this.editButton.click(); + } + + async clickDeleteButton() { + await this.deleteButton.click(); + } + + async confirmDelete() { + // 삭제 확인 모달에서 확인 버튼 클릭 + await this.page.getByRole('button', { name: '확인' }).click(); + } + + async clickListButton() { + await this.listButton.click(); + } + + async clickPrevButton() { + await this.prevButton.click(); + } + + async clickNextButton() { + await this.nextButton.click(); + } + + async getTitle(): Promise { + return (await this.title.textContent()) || ''; + } + + async getContent(): Promise { + return (await this.content.textContent()) || ''; + } + + async getTags(): Promise { + const tagElements = await this.tags.locator('TODO_TAG_ITEM_SELECTOR').all(); // TODO: 개별 태그 item selector 확인 필요 + const tags = []; + for (const tag of tagElements) { + const text = await tag.textContent(); + if (text) tags.push(text); + } + return tags; + } +} diff --git a/e2e/pages/community/notice/edit.page.ts b/e2e/pages/community/notice/edit.page.ts new file mode 100644 index 00000000..c4f1168e --- /dev/null +++ b/e2e/pages/community/notice/edit.page.ts @@ -0,0 +1,68 @@ +import { type Locator, type Page } from '@playwright/test'; + +export class NoticeEditPage { + readonly page: Page; + readonly titleInput: Locator; + readonly contentEditor: Locator; + readonly tagInput: Locator; + readonly attachmentInput: Locator; + readonly importantToggle: Locator; + readonly privateToggle: Locator; + readonly pinnedToggle: Locator; + readonly mainDisplayToggle: Locator; + readonly submitButton: Locator; + + constructor(page: Page) { + this.page = page; + this.titleInput = page.getByPlaceholder('제목을 입력하세요.'); + this.contentEditor = page.locator('div.sun-editor-editable'); + this.tagInput = page.locator('TODO_TAG_INPUT_SELECTOR'); // TODO: 실제 태그 입력 필드 selector 확인 필요 + this.attachmentInput = page.locator('input[type="file"]'); + this.importantToggle = page.locator('TODO_IMPORTANT_TOGGLE_SELECTOR'); // TODO: 중요 안내 토글 selector 확인 필요 + this.privateToggle = page.locator('TODO_PRIVATE_TOGGLE_SELECTOR'); // TODO: 비공개 토글 selector 확인 필요 + this.pinnedToggle = page.locator('TODO_PINNED_TOGGLE_SELECTOR'); // TODO: 상단고정 토글 selector 확인 필요 + this.mainDisplayToggle = page.locator('TODO_MAIN_DISPLAY_TOGGLE_SELECTOR'); // TODO: 메인표시 토글 selector 확인 필요 + this.submitButton = page.getByRole('button', { name: /수정/ }); + } + + async goto(id: string) { + await this.page.goto(`/community/notice/${id}/edit`); + } + + async fillTitle(title: string) { + await this.titleInput.fill(title); + } + + async fillContent(content: string) { + await this.contentEditor.fill(content); + } + + async addTag(tag: string) { + await this.tagInput.fill(tag); + await this.page.keyboard.press('Enter'); + } + + async uploadAttachment(filePath: string) { + await this.attachmentInput.setInputFiles(filePath); + } + + async toggleImportant() { + await this.importantToggle.click(); + } + + async togglePrivate() { + await this.privateToggle.click(); + } + + async togglePinned() { + await this.pinnedToggle.click(); + } + + async toggleMainDisplay() { + await this.mainDisplayToggle.click(); + } + + async clickSubmitButton() { + await this.submitButton.click(); + } +} diff --git a/e2e/pages/community/notice/index.page.ts b/e2e/pages/community/notice/index.page.ts index 0336920f..840068af 100644 --- a/e2e/pages/community/notice/index.page.ts +++ b/e2e/pages/community/notice/index.page.ts @@ -1,12 +1,28 @@ import { type Locator, type Page } from '@playwright/test'; -export class NoticePage { +export class NoticeListPage { readonly page: Page; readonly newPostButton: Locator; + readonly searchInput: Locator; + readonly searchButton: Locator; + readonly tagButtons: Locator; + readonly selectAllCheckbox: Locator; + readonly noticeCheckboxes: Locator; + readonly bulkDeleteButton: Locator; + readonly bulkUnpinButton: Locator; + readonly noticeItems: Locator; constructor(page: Page) { this.page = page; this.newPostButton = page.getByRole('button', { name: '새 게시글' }); + this.searchInput = page.locator('TODO_SEARCH_INPUT_SELECTOR'); // TODO: 검색 입력 필드 selector 확인 필요 + this.searchButton = page.getByRole('button', { name: '검색' }); + this.tagButtons = page.locator('TODO_TAG_FILTER_BUTTONS_SELECTOR'); // TODO: 태그 필터 버튼들 selector 확인 필요 + this.selectAllCheckbox = page.locator('TODO_SELECT_ALL_CHECKBOX_SELECTOR'); // TODO: 전체 선택 체크박스 selector 확인 필요 + this.noticeCheckboxes = page.locator('TODO_NOTICE_CHECKBOX_SELECTOR'); // TODO: 개별 공지사항 체크박스 selector 확인 필요 + this.bulkDeleteButton = page.locator('TODO_BULK_DELETE_BUTTON_SELECTOR'); // TODO: 일괄 삭제 버튼 selector 확인 필요 + this.bulkUnpinButton = page.locator('TODO_BULK_UNPIN_BUTTON_SELECTOR'); // TODO: 일괄 고정해제 버튼 selector 확인 필요 + this.noticeItems = page.locator('TODO_NOTICE_ITEM_SELECTOR'); // TODO: 공지사항 목록 아이템 selector 확인 필요 } async goto() { @@ -16,4 +32,41 @@ export class NoticePage { async clickNewPostButton() { await this.newPostButton.click(); } + + async searchByKeyword(keyword: string) { + await this.searchInput.fill(keyword); + await this.searchButton.click(); + } + + async filterByTag(tagName: string) { + await this.tagButtons.filter({ hasText: tagName }).click(); + } + + async selectAllNotices() { + await this.selectAllCheckbox.click(); + } + + async selectNotice(index: number) { + await this.noticeCheckboxes.nth(index).click(); + } + + async bulkDelete() { + await this.bulkDeleteButton.click(); + // 확인 모달에서 확인 버튼 클릭 + await this.page.getByRole('button', { name: '확인' }).click(); + } + + async bulkUnpin() { + await this.bulkUnpinButton.click(); + // 확인 모달에서 확인 버튼 클릭 + await this.page.getByRole('button', { name: '확인' }).click(); + } + + async getNoticeCount(): Promise { + return await this.noticeItems.count(); + } + + async clickNoticeByTitle(title: string) { + await this.page.getByText(title).click(); + } } diff --git a/e2e/tests/community/notice.test.ts b/e2e/tests/community/notice.test.ts deleted file mode 100644 index c9498724..00000000 --- a/e2e/tests/community/notice.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { expect } from '@playwright/test'; - -import { test } from '../../fixtures'; - -test.describe('Notice creation', () => { - test('should allow STAFF to create a new notice', async ({ - page, - loginAs, - noticePage, - noticeCreatePage, - }) => { - // 1. Arrange - await noticePage.goto(); - const noticeTitle = `[Test] New Notice - ${Date.now()}`; - const noticeContent = 'This is a test notice content.'; - await loginAs('STAFF'); - - // 2. Act - await noticePage.clickNewPostButton(); - await expect(page).toHaveURL('/community/notice/create'); - await noticeCreatePage.fillTitle(noticeTitle); - await noticeCreatePage.fillContent(noticeContent); - // TODO: suneditor 비동기 로드 때문인지 몇 초 대기해야 제대로 작동함 - await page.waitForTimeout(1000); - await noticeCreatePage.clickSubmitButton(); - - // 3. Assert - await expect(page.getByText(noticeTitle).first()).toBeVisible(); - }); -}); diff --git a/e2e/tests/community/notice/create.test.ts b/e2e/tests/community/notice/create.test.ts new file mode 100644 index 00000000..29ef7728 --- /dev/null +++ b/e2e/tests/community/notice/create.test.ts @@ -0,0 +1,211 @@ +import { expect } from '@playwright/test'; + +import { test } from '../../../fixtures/index.fixture'; + +test.describe('공지사항 작성 페이지 (/community/notice/create)', () => { + test('제목, 내용, 태그, 첨부파일을 포함한 기본 공지사항을 작성할 수 있다', async ({ + page, + loginAs, + noticeListPage, + noticeCreatePage, + }) => { + // 1. Arrange + const noticeTitle = `[테스트] 기본 공지사항 - ${Date.now()}`; + const noticeContent = '이것은 테스트용 공지사항 내용입니다.'; + const testTag = '테스트'; + + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + await noticeListPage.clickNewPostButton(); + await expect(page).toHaveURL('/community/notice/create'); + + await noticeCreatePage.fillTitle(noticeTitle); + await noticeCreatePage.fillContent(noticeContent); + // TODO: 태그 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeCreatePage.addTag(testTag); + + // TODO: 실제 테스트 파일 업로드 (테스트용 파일 경로 설정 필요) + // await noticeCreatePage.uploadAttachment('e2e/fixtures/test-files/sample.pdf'); + + await page.waitForTimeout(1000); // suneditor 로드 대기 + await noticeCreatePage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(noticeTitle).first()).toBeVisible(); + }); + + test('중요 안내로 공지사항을 작성할 수 있다', async ({ + page, + loginAs, + noticeListPage, + noticeCreatePage, + }) => { + // 1. Arrange + const importantNoticeTitle = `[중요] 중요 안내 공지사항 - ${Date.now()}`; + const noticeContent = '이것은 중요한 공지사항입니다.'; + + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + await noticeListPage.clickNewPostButton(); + await noticeCreatePage.fillTitle(importantNoticeTitle); + await noticeCreatePage.fillContent(noticeContent); + // TODO: 중요 안내 토글 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeCreatePage.toggleImportant(); + + await page.waitForTimeout(1000); + await noticeCreatePage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(importantNoticeTitle).first()).toBeVisible(); + // TODO: 중요 안내 표시 UI 확인 (실제 구현에 따라 assertion 추가 필요) + }); + + test('비공개 글로 공지사항을 작성할 수 있다', async ({ + page, + loginAs, + noticeListPage, + noticeCreatePage, + }) => { + // 1. Arrange + const privateNoticeTitle = `[비공개] 비공개 공지사항 - ${Date.now()}`; + const noticeContent = '이것은 비공개 공지사항입니다.'; + + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + await noticeListPage.clickNewPostButton(); + await noticeCreatePage.fillTitle(privateNoticeTitle); + await noticeCreatePage.fillContent(noticeContent); + // TODO: 비공개 토글 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeCreatePage.togglePrivate(); + + await page.waitForTimeout(1000); + await noticeCreatePage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(privateNoticeTitle).first()).toBeVisible(); + // TODO: 비공개 표시 UI 확인 (실제 구현에 따라 assertion 추가 필요) + }); + + test('목록 상단 고정으로 공지사항을 작성할 수 있다', async ({ + page, + loginAs, + noticeListPage, + noticeCreatePage, + }) => { + // 1. Arrange + const pinnedNoticeTitle = `[고정] 상단 고정 공지사항 - ${Date.now()}`; + const noticeContent = '이것은 상단에 고정될 공지사항입니다.'; + + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + await noticeListPage.clickNewPostButton(); + await noticeCreatePage.fillTitle(pinnedNoticeTitle); + await noticeCreatePage.fillContent(noticeContent); + // TODO: 상단 고정 토글 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeCreatePage.togglePinned(); + + await page.waitForTimeout(1000); + await noticeCreatePage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(pinnedNoticeTitle).first()).toBeVisible(); + + // 목록 페이지로 이동하여 상단 고정 확인 + await noticeListPage.goto(); + // TODO: 고정된 글이 상단에 위치하는지 확인 (실제 UI 구조에 따라 수정 필요) + }); + + test('메인 페이지 중요 안내에 표시되는 공지사항을 작성할 수 있다', async ({ + page, + loginAs, + noticeListPage, + noticeCreatePage, + }) => { + // 1. Arrange + const mainDisplayNoticeTitle = `[메인표시] 메인 페이지 표시 공지사항 - ${Date.now()}`; + const noticeContent = '이것은 메인 페이지에 표시될 공지사항입니다.'; + + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + await noticeListPage.clickNewPostButton(); + await noticeCreatePage.fillTitle(mainDisplayNoticeTitle); + await noticeCreatePage.fillContent(noticeContent); + // TODO: 메인 표시 토글 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeCreatePage.toggleMainDisplay(); + + await page.waitForTimeout(1000); + await noticeCreatePage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(mainDisplayNoticeTitle).first()).toBeVisible(); + + // 메인 페이지로 이동하여 중요 안내 영역에 표시되는지 확인 + await page.goto('/'); + // TODO: 메인 페이지 중요 안내 영역 UI 확인 (실제 구현에 따라 assertion 추가 필요) + }); + + test('모든 옵션을 적용한 공지사항을 작성할 수 있다', async ({ + page, + loginAs, + noticeListPage, + noticeCreatePage, + }) => { + // 1. Arrange + const fullOptionNoticeTitle = `[풀옵션] 모든 옵션 적용 공지사항 - ${Date.now()}`; + const noticeContent = '모든 옵션이 적용된 테스트 공지사항입니다.'; + const testTags = ['테스트', '풀옵션', '중요']; + + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + await noticeListPage.clickNewPostButton(); + await noticeCreatePage.fillTitle(fullOptionNoticeTitle); + await noticeCreatePage.fillContent(noticeContent); + + // TODO: 태그 기능이 실제로 구현되어 있는지 확인 후 활성화 + // for (const tag of testTags) { + // await noticeCreatePage.addTag(tag); + // } + + // TODO: 모든 옵션 토글이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeCreatePage.toggleImportant(); + // await noticeCreatePage.togglePinned(); + // await noticeCreatePage.toggleMainDisplay(); + + await page.waitForTimeout(1000); + await noticeCreatePage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(fullOptionNoticeTitle).first()).toBeVisible(); + }); + + test('필수 필드가 비어있으면 공지사항을 작성할 수 없다', async ({ + page, + loginAs, + noticeListPage, + noticeCreatePage, + }) => { + await loginAs('STAFF'); + await noticeListPage.goto(); + await noticeListPage.clickNewPostButton(); + + // 제목만 입력하고 내용 없이 제출 시도 + await noticeCreatePage.fillTitle('제목만 있는 공지사항'); + await noticeCreatePage.clickSubmitButton(); + + // 에러 메시지 또는 제출 실패 확인 + // TODO: 실제 폼 검증 방식에 따라 assertion 수정 필요 + await expect(page).toHaveURL('/community/notice/create'); // 페이지가 그대로 유지되는지 확인 + }); +}); diff --git a/e2e/tests/community/notice/detail.test.ts b/e2e/tests/community/notice/detail.test.ts new file mode 100644 index 00000000..a1e76c9a --- /dev/null +++ b/e2e/tests/community/notice/detail.test.ts @@ -0,0 +1,242 @@ +import { expect } from '@playwright/test'; + +import { test } from '../../../fixtures/index.fixture'; + +test.describe('공지사항 상세보기 페이지 (/community/notice/:id)', () => { + let firstNoticeId: string; + let secondNoticeId: string; + let thirdNoticeId: string; + + test.beforeAll(async ({ browser }) => { + // 테스트용 공지사항 3개 생성 (이전글/다음글 테스트를 위해) + const context = await browser.newContext(); + const page = await context.newPage(); + + // STAFF 로그인 + await page.goto('/community/notice'); + await page.waitForLoadState('networkidle'); + await page.getByRole('button', { name: 'STAFF', exact: true }).click(); + + // 첫 번째 공지사항 생성 + await page.goto('/community/notice'); + await page.getByRole('button', { name: '새 게시글' }).click(); + await page.getByPlaceholder('제목을 입력하세요.').fill('[상세테스트1] 첫 번째 공지사항'); + await page.locator('div.sun-editor-editable').fill('첫 번째 테스트 내용입니다.'); + await page.waitForTimeout(1000); + await page.getByRole('button', { name: /게시/ }).click(); + + const url1 = page.url(); + firstNoticeId = url1.split('/').pop() || ''; + + // 두 번째 공지사항 생성 + await page.goto('/community/notice'); + await page.getByRole('button', { name: '새 게시글' }).click(); + await page.getByPlaceholder('제목을 입력하세요.').fill('[상세테스트2] 두 번째 공지사항'); + await page.locator('div.sun-editor-editable').fill('두 번째 테스트 내용입니다.'); + await page.waitForTimeout(1000); + await page.getByRole('button', { name: /게시/ }).click(); + + const url2 = page.url(); + secondNoticeId = url2.split('/').pop() || ''; + + // 세 번째 공지사항 생성 + await page.goto('/community/notice'); + await page.getByRole('button', { name: '새 게시글' }).click(); + await page.getByPlaceholder('제목을 입력하세요.').fill('[상세테스트3] 세 번째 공지사항'); + await page.locator('div.sun-editor-editable').fill('세 번째 테스트 내용입니다.'); + await page.waitForTimeout(1000); + await page.getByRole('button', { name: /게시/ }).click(); + + const url3 = page.url(); + thirdNoticeId = url3.split('/').pop() || ''; + + await context.close(); + }); + + test('공지사항을 삭제할 수 있다', async ({ page, loginAs, noticeDetailPage, noticeListPage }) => { + // 1. Arrange + await loginAs('STAFF'); + + // 2. Act + await noticeDetailPage.goto(firstNoticeId); + await expect(page.getByText('[상세테스트1] 첫 번째 공지사항').first()).toBeVisible(); + + await noticeDetailPage.clickDeleteButton(); + await noticeDetailPage.confirmDelete(); + + // 3. Assert + // 목록 페이지로 리다이렉트되고 해당 공지사항이 더 이상 보이지 않음 + await expect(page).toHaveURL('/community/notice'); + await expect(page.getByText('[상세테스트1] 첫 번째 공지사항')).not.toBeVisible(); + }); + + test('편집 버튼을 클릭하면 편집 페이지로 이동한다', async ({ + page, + loginAs, + noticeDetailPage, + }) => { + // 1. Arrange + await loginAs('STAFF'); + + // 2. Act + await noticeDetailPage.goto(secondNoticeId); + await noticeDetailPage.clickEditButton(); + + // 3. Assert + await expect(page).toHaveURL(`/community/notice/${secondNoticeId}/edit`); + }); + + test('목록 버튼을 클릭하면 목록 페이지로 이동한다', async ({ + page, + loginAs, + noticeDetailPage, + }) => { + // 1. Arrange + await loginAs('STAFF'); + + // 2. Act + await noticeDetailPage.goto(secondNoticeId); + await noticeDetailPage.clickListButton(); + + // 3. Assert + await expect(page).toHaveURL('/community/notice'); + }); + + test('이전글 버튼을 클릭하면 이전 공지사항으로 이동한다', async ({ + page, + loginAs, + noticeDetailPage, + }) => { + // 1. Arrange + await loginAs('STAFF'); + + // 2. Act + await noticeDetailPage.goto(thirdNoticeId); // 세 번째 글에서 + // TODO: 이전글 버튼 selector 확인 후 활성화 + // await noticeDetailPage.clickPrevButton(); // 이전글(두 번째)로 이동 + + // 3. Assert + // TODO: 이전글 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await expect(page).toHaveURL(`/community/notice/${secondNoticeId}`); + // await expect(page.getByText('[상세테스트2] 두 번째 공지사항').first()).toBeVisible(); + }); + + test('다음글 버튼을 클릭하면 다음 공지사항으로 이동한다', async ({ + page, + loginAs, + noticeDetailPage, + }) => { + // 1. Arrange + await loginAs('STAFF'); + + // 2. Act + await noticeDetailPage.goto(secondNoticeId); // 두 번째 글에서 + // TODO: 다음글 버튼 selector 확인 후 활성화 + // await noticeDetailPage.clickNextButton(); // 다음글(세 번째)로 이동 + + // 3. Assert + // TODO: 다음글 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await expect(page).toHaveURL(`/community/notice/${thirdNoticeId}`); + // await expect(page.getByText('[상세테스트3] 세 번째 공지사항').first()).toBeVisible(); + }); + + test('공지사항 내용이 올바르게 표시된다', async ({ page, loginAs, noticeDetailPage }) => { + // 1. Arrange + await loginAs('STAFF'); + + // 2. Act + await noticeDetailPage.goto(secondNoticeId); + + // 3. Assert + // TODO: 제목과 내용 selector 확인 후 활성화 + // const title = await noticeDetailPage.getTitle(); + // const content = await noticeDetailPage.getContent(); + // expect(title).toContain('[상세테스트2] 두 번째 공지사항'); + // expect(content).toContain('두 번째 테스트 내용입니다.'); + + // 현재는 페이지에서 직접 확인 + await expect(page.getByText('[상세테스트2] 두 번째 공지사항').first()).toBeVisible(); + await expect(page.getByText('두 번째 테스트 내용입니다.').first()).toBeVisible(); + }); + + test('첨부파일이 있는 경우 첨부파일 영역이 표시된다', async ({ + page, + loginAs, + noticeDetailPage, + }) => { + // 1. Arrange - 첨부파일이 있는 공지사항이 필요 + // TODO: 첨부파일 기능이 실제로 구현되어 있는지 확인 후 테스트 활성화 + await loginAs('STAFF'); + + // 2. Act + await noticeDetailPage.goto(secondNoticeId); + + // 3. Assert + // TODO: 첨부파일 영역 UI 확인 (실제 구현에 따라 assertion 추가 필요) + // const attachments = noticeDetailPage.attachments; + // await expect(attachments).toBeVisible(); + }); + + test('태그가 있는 경우 태그 영역이 표시된다', async ({ page, loginAs, noticeDetailPage }) => { + // 1. Arrange - 태그가 있는 공지사항이 필요 + // TODO: 태그 기능이 실제로 구현되어 있는지 확인 후 테스트 활성화 + await loginAs('STAFF'); + + // 2. Act + await noticeDetailPage.goto(secondNoticeId); + + // 3. Assert + // TODO: 태그 영역 UI 확인 (실제 구현에 따라 assertion 추가 필요) + // const tags = await noticeDetailPage.getTags(); + // expect(tags.length).toBeGreaterThan(0); + }); + + test('권한이 없는 사용자에게는 편집/삭제 버튼이 표시되지 않는다', async ({ + page, + loginAs, + noticeDetailPage, + }) => { + // 1. Arrange - 권한이 없는 사용자로 로그인 (또는 로그아웃 상태) + // TODO: 권한이 없는 사용자 로그인 방식 확인 후 활성화 + // await loginAs('GUEST'); // 권한이 낮은 역할이 있다면 + + // 2. Act + await noticeDetailPage.goto(secondNoticeId); + + // 3. Assert + // TODO: 권한별 버튼 표시/숨김 로직 확인 후 활성화 + // await expect(noticeDetailPage.editButton).not.toBeVisible(); + // await expect(noticeDetailPage.deleteButton).not.toBeVisible(); + }); + + test.afterAll(async ({ browser }) => { + // 테스트 후 생성된 공지사항들 정리 + const context = await browser.newContext(); + const page = await context.newPage(); + + try { + // STAFF 로그인 + await page.goto('/community/notice'); + await page.waitForLoadState('networkidle'); + await page.getByRole('button', { name: 'STAFF', exact: true }).click(); + + // 남은 공지사항들 삭제 + const remainingIds = [secondNoticeId, thirdNoticeId].filter((id) => id); + + for (const id of remainingIds) { + try { + await page.goto(`/community/notice/${id}`); + await page.getByRole('button', { name: '삭제' }).click(); + await page.getByRole('button', { name: '확인' }).click(); + await page.waitForTimeout(500); + } catch (error) { + console.log(`Failed to delete notice ${id}:`, error); + } + } + } catch (error) { + console.log('Clean up failed:', error); + } + + await context.close(); + }); +}); diff --git a/e2e/tests/community/notice/edit.test.ts b/e2e/tests/community/notice/edit.test.ts new file mode 100644 index 00000000..b3c87e3d --- /dev/null +++ b/e2e/tests/community/notice/edit.test.ts @@ -0,0 +1,146 @@ +import { expect } from '@playwright/test'; + +import { test } from '../../../fixtures/index.fixture'; + +test.describe('공지사항 수정 페이지 (/community/notice/:id/edit)', () => { + let createdNoticeId: string; + let originalTitle: string; + + test.beforeEach(async ({ page, loginAs, noticeListPage, noticeCreatePage }) => { + // 테스트용 공지사항 생성 + originalTitle = `[수정테스트] 원본 공지사항 - ${Date.now()}`; + const originalContent = '수정 테스트를 위한 원본 내용입니다.'; + + await loginAs('STAFF'); + await noticeListPage.goto(); + await noticeListPage.clickNewPostButton(); + + await noticeCreatePage.fillTitle(originalTitle); + await noticeCreatePage.fillContent(originalContent); + await page.waitForTimeout(1000); + await noticeCreatePage.clickSubmitButton(); + + // 생성된 공지사항의 ID 추출 (URL에서) + await expect(page.getByText(originalTitle).first()).toBeVisible(); + const currentUrl = page.url(); + const urlParts = currentUrl.split('/'); + createdNoticeId = urlParts[urlParts.length - 1]; + }); + + test('공지사항 제목을 수정할 수 있다', async ({ + page, + loginAs, + noticeEditPage, + noticeDetailPage, + }) => { + // 1. Arrange + const updatedTitle = `[수정완료] 수정된 공지사항 제목 - ${Date.now()}`; + + await loginAs('STAFF'); + + // 2. Act + await noticeEditPage.goto(createdNoticeId); + await expect(page).toHaveURL(`/community/notice/${createdNoticeId}/edit`); + + await noticeEditPage.fillTitle(updatedTitle); + await page.waitForTimeout(1000); + await noticeEditPage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(updatedTitle).first()).toBeVisible(); + + // 상세 페이지에서도 제목이 변경되었는지 확인 + await noticeDetailPage.goto(createdNoticeId); + await expect(page.getByText(updatedTitle).first()).toBeVisible(); + }); + + test('공지사항 내용을 수정할 수 있다', async ({ page, loginAs, noticeEditPage }) => { + // 1. Arrange + const updatedContent = '이것은 수정된 공지사항 내용입니다.'; + + await loginAs('STAFF'); + + // 2. Act + await noticeEditPage.goto(createdNoticeId); + await noticeEditPage.fillContent(updatedContent); + await page.waitForTimeout(1000); + await noticeEditPage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(updatedContent).first()).toBeVisible(); + }); + + test('공지사항에 태그를 추가할 수 있다', async ({ page, loginAs, noticeEditPage }) => { + // 1. Arrange + const newTag = '수정된태그'; + + await loginAs('STAFF'); + + // 2. Act + await noticeEditPage.goto(createdNoticeId); + // TODO: 태그 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeEditPage.addTag(newTag); + await page.waitForTimeout(1000); + await noticeEditPage.clickSubmitButton(); + + // 3. Assert + // TODO: 태그가 실제로 추가되었는지 확인 (실제 UI 구조에 따라 수정 필요) + // await expect(page.getByText(newTag)).toBeVisible(); + }); + + test('공지사항 옵션들을 수정할 수 있다', async ({ page, loginAs, noticeEditPage }) => { + await loginAs('STAFF'); + + // 2. Act + await noticeEditPage.goto(createdNoticeId); + + // TODO: 옵션 토글 기능들이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeEditPage.toggleImportant(); + // await noticeEditPage.togglePinned(); + + await page.waitForTimeout(1000); + await noticeEditPage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(originalTitle).first()).toBeVisible(); + // TODO: 옵션 변경 사항이 실제로 반영되었는지 확인 (실제 UI 구조에 따라 수정 필요) + }); + + test('첨부파일을 추가할 수 있다', async ({ page, loginAs, noticeEditPage }) => { + await loginAs('STAFF'); + + await noticeEditPage.goto(createdNoticeId); + + // TODO: 실제 테스트 파일 업로드 (테스트용 파일 경로 설정 필요) + // await noticeEditPage.uploadAttachment('e2e/fixtures/test-files/sample.pdf'); + + await page.waitForTimeout(1000); + await noticeEditPage.clickSubmitButton(); + + // TODO: 첨부파일이 실제로 추가되었는지 확인 (실제 UI 구조에 따라 수정 필요) + }); + + test('수정 권한이 없는 사용자는 수정 페이지에 접근할 수 없다', async ({ page, loginAs }) => { + // TODO: 권한이 없는 사용자 로그인 방식 확인 후 활성화 + // await loginAs('GUEST'); // 권한이 낮은 역할이 있다면 + + // 직접 수정 URL로 접근 시도 + await page.goto(`/community/notice/${createdNoticeId}/edit`); + + // 권한 없음 메시지 또는 로그인 페이지로 리다이렉트 확인 + // TODO: 실제 권한 처리 방식에 따라 assertion 수정 필요 + await expect(page).not.toHaveURL(`/community/notice/${createdNoticeId}/edit`); + }); + + test.afterEach(async ({ page, loginAs, noticeDetailPage }) => { + // 테스트 후 생성된 공지사항 정리 + try { + await loginAs('STAFF'); + await noticeDetailPage.goto(createdNoticeId); + await noticeDetailPage.clickDeleteButton(); + await noticeDetailPage.confirmDelete(); + } catch (error) { + console.log('Clean up failed:', error); + } + }); +}); diff --git a/e2e/tests/community/notice/index.test.ts b/e2e/tests/community/notice/index.test.ts new file mode 100644 index 00000000..bc88a1a4 --- /dev/null +++ b/e2e/tests/community/notice/index.test.ts @@ -0,0 +1,298 @@ +import { expect } from '@playwright/test'; + +import { test } from '../../../fixtures/index.fixture'; + +test.describe('공지사항 목록 조회 페이지 (/community/notice)', () => { + const testNoticeIds: string[] = []; + + test.beforeAll(async ({ browser }) => { + // 테스트용 공지사항들 생성 + const context = await browser.newContext(); + const page = await context.newPage(); + + // STAFF 로그인 + await page.goto('/community/notice'); + await page.waitForLoadState('networkidle'); + await page.getByRole('button', { name: 'STAFF', exact: true }).click(); + + const notices = [ + { + title: '[목록테스트1] 검색 테스트 공지사항', + content: '검색용 내용입니다', + tags: ['검색', '테스트'], + }, + { + title: '[목록테스트2] 태그 필터 테스트', + content: '태그 필터용 내용입니다', + tags: ['필터', '태그'], + }, + { + title: '[목록테스트3] 일괄 작업 테스트', + content: '일괄 작업용 내용입니다', + tags: ['일괄', '작업'], + }, + { + title: '[목록테스트4] 고정 테스트 공지사항', + content: '고정 테스트용 내용입니다', + tags: ['고정'], + }, + { + title: '[목록테스트5] 삭제 테스트 공지사항', + content: '삭제 테스트용 내용입니다', + tags: ['삭제'], + }, + ]; + + for (const notice of notices) { + await page.goto('/community/notice/create'); + await page.getByPlaceholder('제목을 입력하세요.').fill(notice.title); + await page.locator('div.sun-editor-editable').fill(notice.content); + + // TODO: 태그 기능이 실제로 구현되어 있는지 확인 후 활성화 + // for (const tag of notice.tags) { + // await page.locator('TODO_TAG_INPUT_SELECTOR').fill(tag); + // await page.keyboard.press('Enter'); + // } + + await page.waitForTimeout(1000); + await page.getByRole('button', { name: /게시/ }).click(); + + const url = page.url(); + const id = url.split('/').pop(); + if (id) testNoticeIds.push(id); + } + + await context.close(); + }); + + test('키워드로 공지사항을 검색할 수 있다', async ({ page, loginAs, noticeListPage }) => { + // 1. Arrange + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + // TODO: 검색 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeListPage.searchByKeyword('검색 테스트'); + + // 3. Assert + // TODO: 검색 결과 확인 (실제 구현에 따라 assertion 추가 필요) + // await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).toBeVisible(); + // await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).not.toBeVisible(); + + // 현재는 기본 목록 표시 확인 + await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).toBeVisible(); + }); + + test('태그로 공지사항을 필터링할 수 있다', async ({ page, loginAs, noticeListPage }) => { + // 1. Arrange + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + // TODO: 태그 필터 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeListPage.filterByTag('필터'); + + // 3. Assert + // TODO: 태그 필터 결과 확인 (실제 구현에 따라 assertion 추가 필요) + // await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).toBeVisible(); + // await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).not.toBeVisible(); + + // 현재는 기본 목록 표시 확인 + await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).toBeVisible(); + }); + + test('전체 선택 체크박스로 모든 공지사항을 선택할 수 있다', async ({ + page, + loginAs, + noticeListPage, + }) => { + // 1. Arrange + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + // TODO: 전체 선택 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeListPage.selectAllNotices(); + + // 3. Assert + // TODO: 전체 선택 결과 확인 (실제 UI 구조에 따라 assertion 추가 필요) + // const checkboxes = await page.locator('TODO_NOTICE_CHECKBOX_SELECTOR').all(); + // for (const checkbox of checkboxes) { + // await expect(checkbox).toBeChecked(); + // } + }); + + test('개별 공지사항을 선택할 수 있다', async ({ page, loginAs, noticeListPage }) => { + // 1. Arrange + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + // TODO: 개별 선택 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeListPage.selectNotice(0); // 첫 번째 공지사항 선택 + + // 3. Assert + // TODO: 개별 선택 결과 확인 (실제 UI 구조에 따라 assertion 추가 필요) + // await expect(page.locator('TODO_NOTICE_CHECKBOX_SELECTOR').first()).toBeChecked(); + }); + + test('선택한 공지사항들을 일괄 삭제할 수 있다', async ({ page, loginAs, noticeListPage }) => { + // 1. Arrange + await loginAs('STAFF'); + await noticeListPage.goto(); + + // TODO: 공지사항 개수 확인 기능이 실제로 구현되어 있는지 확인 후 활성화 + // const initialCount = await noticeListPage.getNoticeCount(); + + // 2. Act + // TODO: 일괄 삭제 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeListPage.selectNotice(initialCount - 1); + // await noticeListPage.bulkDelete(); + + // 3. Assert + // TODO: 일괄 삭제 결과 확인 (실제 구현에 따라 assertion 추가 필요) + // const finalCount = await noticeListPage.getNoticeCount(); + // expect(finalCount).toBe(initialCount - 1); + // await expect(page.getByText('[목록테스트5] 삭제 테스트 공지사항')).not.toBeVisible(); + }); + + test('선택한 공지사항들의 고정을 일괄 해제할 수 있다', async ({ + page, + loginAs, + noticeListPage, + }) => { + // 1. Arrange - 먼저 공지사항을 고정 상태로 만들어야 함 + await loginAs('STAFF'); + + // 고정할 공지사항 생성 + await page.goto('/community/notice/create'); + await page.getByPlaceholder('제목을 입력하세요.').fill('[고정해제테스트] 고정된 공지사항'); + await page.locator('div.sun-editor-editable').fill('고정 해제 테스트용 내용입니다.'); + // TODO: 고정 옵션이 실제로 구현되어 있는지 확인 후 활성화 + // await page.locator('TODO_PINNED_TOGGLE_SELECTOR').click(); // 고정 옵션 활성화 + await page.waitForTimeout(1000); + await page.getByRole('button', { name: /게시/ }).click(); + + await noticeListPage.goto(); + + // 2. Act + // TODO: 일괄 고정 해제 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await page.getByText('[고정해제테스트] 고정된 공지사항').locator('..').locator('TODO_NOTICE_CHECKBOX_SELECTOR').click(); + // await noticeListPage.bulkUnpin(); + + // 3. Assert + // TODO: 고정 해제 결과 확인 (실제 UI 구조에 따라 assertion 추가 필요) + // 고정 표시가 사라졌는지 또는 고정 영역에서 제거되었는지 확인 + }); + + test('공지사항 제목을 클릭하면 상세 페이지로 이동한다', async ({ + page, + loginAs, + noticeListPage, + }) => { + // 1. Arrange + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + await noticeListPage.clickNoticeByTitle('[목록테스트1] 검색 테스트 공지사항'); + + // 3. Assert + await expect(page).toHaveURL(new RegExp('/community/notice/\\d+')); + await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).toBeVisible(); + }); + + test('공지사항 목록이 올바르게 표시된다', async ({ page, loginAs, noticeListPage }) => { + // 1. Arrange + await loginAs('STAFF'); + + // 2. Act + await noticeListPage.goto(); + + // 3. Assert + // TODO: 공지사항 개수 확인 기능이 실제로 구현되어 있는지 확인 후 활성화 + // const noticeCount = await noticeListPage.getNoticeCount(); + // expect(noticeCount).toBeGreaterThan(0); + + // 테스트로 생성한 공지사항들이 표시되는지 확인 + await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).toBeVisible(); + await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).toBeVisible(); + await expect(page.getByText('[목록테스트3] 일괄 작업 테스트')).toBeVisible(); + }); + + test('검색 결과가 없을 때 적절한 메시지가 표시된다', async ({ + page, + loginAs, + noticeListPage, + }) => { + // 1. Arrange + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act + // TODO: 검색 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeListPage.searchByKeyword('존재하지않는검색어12345'); + + // 3. Assert + // TODO: 검색 결과 없음 메시지 확인 (실제 UI 구조에 따라 assertion 추가 필요) + // await expect(page.getByText('검색 결과가 없습니다')).toBeVisible(); + }); + + test('페이지네이션이 올바르게 작동한다', async ({ page, loginAs, noticeListPage }) => { + // 1. Arrange + await loginAs('STAFF'); + await noticeListPage.goto(); + + // 2. Act & Assert + // TODO: 페이지네이션 기능이 실제로 구현되어 있는지 확인 후 테스트 추가 + // 현재 테스트 데이터가 적어서 페이지네이션이 없을 수 있음 + // 실제 페이지네이션 구현에 따라 테스트 추가 필요 + }); + + test.afterAll(async ({ browser }) => { + // 테스트 후 생성된 공지사항들 정리 + const context = await browser.newContext(); + const page = await context.newPage(); + + try { + // STAFF 로그인 + await page.goto('/community/notice'); + await page.waitForLoadState('networkidle'); + await page.getByRole('button', { name: 'STAFF', exact: true }).click(); + + // 생성된 테스트 공지사항들 삭제 + for (const id of testNoticeIds) { + try { + await page.goto(`/community/notice/${id}`); + const deleteButton = page.getByRole('button', { name: '삭제' }); + if (await deleteButton.isVisible()) { + await deleteButton.click(); + await page.getByRole('button', { name: '확인' }).click(); + await page.waitForTimeout(500); + } + } catch (error) { + console.log(`Failed to delete notice ${id}:`, error); + } + } + + // 추가로 생성된 공지사항도 정리 + await page.goto('/community/notice'); + const testNotices = await page.locator('text=/\\[.*테스트.*\\]/').all(); + for (const notice of testNotices) { + try { + await notice.click(); + await page.getByRole('button', { name: '삭제' }).click(); + await page.getByRole('button', { name: '확인' }).click(); + await page.waitForTimeout(500); + await page.goto('/community/notice'); + } catch (error) { + console.log('Failed to delete test notice:', error); + } + } + } catch (error) { + console.log('Clean up failed:', error); + } + + await context.close(); + }); +}); From f21130c25dfd8a28b737a4a167a5216df8c10d38 Mon Sep 17 00:00:00 2001 From: "leo.yi" Date: Wed, 6 Aug 2025 01:21:38 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- e2e/fixtures/pages.fixture.ts | 2 +- e2e/fixtures/role.fixture.ts | 2 +- e2e/pages/community/notice/create.page.ts | 2 + e2e/tests/community/notice/create.test.ts | 75 +++---- e2e/tests/community/notice/detail.test.ts | 242 --------------------- e2e/tests/community/notice/edit.test.ts | 140 ++----------- e2e/tests/community/notice/index.test.ts | 245 ++++------------------ 7 files changed, 97 insertions(+), 611 deletions(-) delete mode 100644 e2e/tests/community/notice/detail.test.ts diff --git a/e2e/fixtures/pages.fixture.ts b/e2e/fixtures/pages.fixture.ts index fe0cc099..73e8a9e9 100644 --- a/e2e/fixtures/pages.fixture.ts +++ b/e2e/fixtures/pages.fixture.ts @@ -2,12 +2,12 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { Page } from '@playwright/test'; +import { test as base } from '@playwright/test'; import { NoticeCreatePage } from '../pages/community/notice/create.page'; import { NoticeDetailPage } from '../pages/community/notice/detail.page'; import { NoticeEditPage } from '../pages/community/notice/edit.page'; import { NoticeListPage } from '../pages/community/notice/index.page'; -import { test as base } from './role.fixture'; type PagesFixtures = { noticeListPage: NoticeListPage; diff --git a/e2e/fixtures/role.fixture.ts b/e2e/fixtures/role.fixture.ts index 35304f2e..2ce2381c 100644 --- a/e2e/fixtures/role.fixture.ts +++ b/e2e/fixtures/role.fixture.ts @@ -12,8 +12,8 @@ type RoleFixtures = { export const test = base.extend({ loginAs: async ({ page }, use) => { const login = async (role: Role) => { - await page.waitForLoadState('networkidle'); await page.getByRole('button', { name: role, exact: true }).click(); + await page.waitForLoadState('networkidle'); }; await use(login); }, diff --git a/e2e/pages/community/notice/create.page.ts b/e2e/pages/community/notice/create.page.ts index 8a8aec08..b13380a2 100644 --- a/e2e/pages/community/notice/create.page.ts +++ b/e2e/pages/community/notice/create.page.ts @@ -34,7 +34,9 @@ export class NoticeCreatePage { } async fillContent(content: string) { + await this.page.waitForTimeout(1000); // suneditor 대기 await this.contentEditor.fill(content); + await this.page.waitForTimeout(1000); // suneditor 대기 } async addTag(tag: string) { diff --git a/e2e/tests/community/notice/create.test.ts b/e2e/tests/community/notice/create.test.ts index 29ef7728..6a11cc88 100644 --- a/e2e/tests/community/notice/create.test.ts +++ b/e2e/tests/community/notice/create.test.ts @@ -3,7 +3,7 @@ import { expect } from '@playwright/test'; import { test } from '../../../fixtures/index.fixture'; test.describe('공지사항 작성 페이지 (/community/notice/create)', () => { - test('제목, 내용, 태그, 첨부파일을 포함한 기본 공지사항을 작성할 수 있다', async ({ + test('제목과 내용을 포함한 기본 공지사항을 작성할 수 있다', async ({ page, loginAs, noticeListPage, @@ -12,7 +12,32 @@ test.describe('공지사항 작성 페이지 (/community/notice/create)', () => // 1. Arrange const noticeTitle = `[테스트] 기본 공지사항 - ${Date.now()}`; const noticeContent = '이것은 테스트용 공지사항 내용입니다.'; - const testTag = '테스트'; + + await noticeListPage.goto(); + await loginAs('STAFF'); + + // 2. Act + await noticeListPage.clickNewPostButton(); + await expect(page).toHaveURL('/community/notice/create'); + + await noticeCreatePage.fillTitle(noticeTitle); + await noticeCreatePage.fillContent(noticeContent); + await noticeCreatePage.clickSubmitButton(); + + // 3. Assert + await expect(page.getByText(noticeTitle).first()).toBeVisible(); + }); + + test.skip('제목, 내용, 태그, 첨부파일을 포함한 기본 공지사항을 작성할 수 있다', async ({ + page, + loginAs, + noticeListPage, + noticeCreatePage, + }) => { + // 1. Arrange + const noticeTitle = `[테스트] 기본 공지사항 - ${Date.now()}`; + const noticeContent = '이것은 테스트용 공지사항 내용입니다.'; + // const testTag = '테스트'; await loginAs('STAFF'); await noticeListPage.goto(); @@ -36,7 +61,7 @@ test.describe('공지사항 작성 페이지 (/community/notice/create)', () => await expect(page.getByText(noticeTitle).first()).toBeVisible(); }); - test('중요 안내로 공지사항을 작성할 수 있다', async ({ + test.skip('중요 안내로 공지사항을 작성할 수 있다', async ({ page, loginAs, noticeListPage, @@ -64,7 +89,7 @@ test.describe('공지사항 작성 페이지 (/community/notice/create)', () => // TODO: 중요 안내 표시 UI 확인 (실제 구현에 따라 assertion 추가 필요) }); - test('비공개 글로 공지사항을 작성할 수 있다', async ({ + test.skip('비공개 글로 공지사항을 작성할 수 있다', async ({ page, loginAs, noticeListPage, @@ -92,7 +117,7 @@ test.describe('공지사항 작성 페이지 (/community/notice/create)', () => // TODO: 비공개 표시 UI 확인 (실제 구현에 따라 assertion 추가 필요) }); - test('목록 상단 고정으로 공지사항을 작성할 수 있다', async ({ + test.skip('목록 상단 고정으로 공지사항을 작성할 수 있다', async ({ page, loginAs, noticeListPage, @@ -123,7 +148,7 @@ test.describe('공지사항 작성 페이지 (/community/notice/create)', () => // TODO: 고정된 글이 상단에 위치하는지 확인 (실제 UI 구조에 따라 수정 필요) }); - test('메인 페이지 중요 안내에 표시되는 공지사항을 작성할 수 있다', async ({ + test.skip('메인 페이지 중요 안내에 표시되는 공지사항을 작성할 수 있다', async ({ page, loginAs, noticeListPage, @@ -154,43 +179,7 @@ test.describe('공지사항 작성 페이지 (/community/notice/create)', () => // TODO: 메인 페이지 중요 안내 영역 UI 확인 (실제 구현에 따라 assertion 추가 필요) }); - test('모든 옵션을 적용한 공지사항을 작성할 수 있다', async ({ - page, - loginAs, - noticeListPage, - noticeCreatePage, - }) => { - // 1. Arrange - const fullOptionNoticeTitle = `[풀옵션] 모든 옵션 적용 공지사항 - ${Date.now()}`; - const noticeContent = '모든 옵션이 적용된 테스트 공지사항입니다.'; - const testTags = ['테스트', '풀옵션', '중요']; - - await loginAs('STAFF'); - await noticeListPage.goto(); - - // 2. Act - await noticeListPage.clickNewPostButton(); - await noticeCreatePage.fillTitle(fullOptionNoticeTitle); - await noticeCreatePage.fillContent(noticeContent); - - // TODO: 태그 기능이 실제로 구현되어 있는지 확인 후 활성화 - // for (const tag of testTags) { - // await noticeCreatePage.addTag(tag); - // } - - // TODO: 모든 옵션 토글이 실제로 구현되어 있는지 확인 후 활성화 - // await noticeCreatePage.toggleImportant(); - // await noticeCreatePage.togglePinned(); - // await noticeCreatePage.toggleMainDisplay(); - - await page.waitForTimeout(1000); - await noticeCreatePage.clickSubmitButton(); - - // 3. Assert - await expect(page.getByText(fullOptionNoticeTitle).first()).toBeVisible(); - }); - - test('필수 필드가 비어있으면 공지사항을 작성할 수 없다', async ({ + test.skip('필수 필드가 비어있으면 공지사항을 작성할 수 없다', async ({ page, loginAs, noticeListPage, diff --git a/e2e/tests/community/notice/detail.test.ts b/e2e/tests/community/notice/detail.test.ts deleted file mode 100644 index a1e76c9a..00000000 --- a/e2e/tests/community/notice/detail.test.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { expect } from '@playwright/test'; - -import { test } from '../../../fixtures/index.fixture'; - -test.describe('공지사항 상세보기 페이지 (/community/notice/:id)', () => { - let firstNoticeId: string; - let secondNoticeId: string; - let thirdNoticeId: string; - - test.beforeAll(async ({ browser }) => { - // 테스트용 공지사항 3개 생성 (이전글/다음글 테스트를 위해) - const context = await browser.newContext(); - const page = await context.newPage(); - - // STAFF 로그인 - await page.goto('/community/notice'); - await page.waitForLoadState('networkidle'); - await page.getByRole('button', { name: 'STAFF', exact: true }).click(); - - // 첫 번째 공지사항 생성 - await page.goto('/community/notice'); - await page.getByRole('button', { name: '새 게시글' }).click(); - await page.getByPlaceholder('제목을 입력하세요.').fill('[상세테스트1] 첫 번째 공지사항'); - await page.locator('div.sun-editor-editable').fill('첫 번째 테스트 내용입니다.'); - await page.waitForTimeout(1000); - await page.getByRole('button', { name: /게시/ }).click(); - - const url1 = page.url(); - firstNoticeId = url1.split('/').pop() || ''; - - // 두 번째 공지사항 생성 - await page.goto('/community/notice'); - await page.getByRole('button', { name: '새 게시글' }).click(); - await page.getByPlaceholder('제목을 입력하세요.').fill('[상세테스트2] 두 번째 공지사항'); - await page.locator('div.sun-editor-editable').fill('두 번째 테스트 내용입니다.'); - await page.waitForTimeout(1000); - await page.getByRole('button', { name: /게시/ }).click(); - - const url2 = page.url(); - secondNoticeId = url2.split('/').pop() || ''; - - // 세 번째 공지사항 생성 - await page.goto('/community/notice'); - await page.getByRole('button', { name: '새 게시글' }).click(); - await page.getByPlaceholder('제목을 입력하세요.').fill('[상세테스트3] 세 번째 공지사항'); - await page.locator('div.sun-editor-editable').fill('세 번째 테스트 내용입니다.'); - await page.waitForTimeout(1000); - await page.getByRole('button', { name: /게시/ }).click(); - - const url3 = page.url(); - thirdNoticeId = url3.split('/').pop() || ''; - - await context.close(); - }); - - test('공지사항을 삭제할 수 있다', async ({ page, loginAs, noticeDetailPage, noticeListPage }) => { - // 1. Arrange - await loginAs('STAFF'); - - // 2. Act - await noticeDetailPage.goto(firstNoticeId); - await expect(page.getByText('[상세테스트1] 첫 번째 공지사항').first()).toBeVisible(); - - await noticeDetailPage.clickDeleteButton(); - await noticeDetailPage.confirmDelete(); - - // 3. Assert - // 목록 페이지로 리다이렉트되고 해당 공지사항이 더 이상 보이지 않음 - await expect(page).toHaveURL('/community/notice'); - await expect(page.getByText('[상세테스트1] 첫 번째 공지사항')).not.toBeVisible(); - }); - - test('편집 버튼을 클릭하면 편집 페이지로 이동한다', async ({ - page, - loginAs, - noticeDetailPage, - }) => { - // 1. Arrange - await loginAs('STAFF'); - - // 2. Act - await noticeDetailPage.goto(secondNoticeId); - await noticeDetailPage.clickEditButton(); - - // 3. Assert - await expect(page).toHaveURL(`/community/notice/${secondNoticeId}/edit`); - }); - - test('목록 버튼을 클릭하면 목록 페이지로 이동한다', async ({ - page, - loginAs, - noticeDetailPage, - }) => { - // 1. Arrange - await loginAs('STAFF'); - - // 2. Act - await noticeDetailPage.goto(secondNoticeId); - await noticeDetailPage.clickListButton(); - - // 3. Assert - await expect(page).toHaveURL('/community/notice'); - }); - - test('이전글 버튼을 클릭하면 이전 공지사항으로 이동한다', async ({ - page, - loginAs, - noticeDetailPage, - }) => { - // 1. Arrange - await loginAs('STAFF'); - - // 2. Act - await noticeDetailPage.goto(thirdNoticeId); // 세 번째 글에서 - // TODO: 이전글 버튼 selector 확인 후 활성화 - // await noticeDetailPage.clickPrevButton(); // 이전글(두 번째)로 이동 - - // 3. Assert - // TODO: 이전글 기능이 실제로 구현되어 있는지 확인 후 활성화 - // await expect(page).toHaveURL(`/community/notice/${secondNoticeId}`); - // await expect(page.getByText('[상세테스트2] 두 번째 공지사항').first()).toBeVisible(); - }); - - test('다음글 버튼을 클릭하면 다음 공지사항으로 이동한다', async ({ - page, - loginAs, - noticeDetailPage, - }) => { - // 1. Arrange - await loginAs('STAFF'); - - // 2. Act - await noticeDetailPage.goto(secondNoticeId); // 두 번째 글에서 - // TODO: 다음글 버튼 selector 확인 후 활성화 - // await noticeDetailPage.clickNextButton(); // 다음글(세 번째)로 이동 - - // 3. Assert - // TODO: 다음글 기능이 실제로 구현되어 있는지 확인 후 활성화 - // await expect(page).toHaveURL(`/community/notice/${thirdNoticeId}`); - // await expect(page.getByText('[상세테스트3] 세 번째 공지사항').first()).toBeVisible(); - }); - - test('공지사항 내용이 올바르게 표시된다', async ({ page, loginAs, noticeDetailPage }) => { - // 1. Arrange - await loginAs('STAFF'); - - // 2. Act - await noticeDetailPage.goto(secondNoticeId); - - // 3. Assert - // TODO: 제목과 내용 selector 확인 후 활성화 - // const title = await noticeDetailPage.getTitle(); - // const content = await noticeDetailPage.getContent(); - // expect(title).toContain('[상세테스트2] 두 번째 공지사항'); - // expect(content).toContain('두 번째 테스트 내용입니다.'); - - // 현재는 페이지에서 직접 확인 - await expect(page.getByText('[상세테스트2] 두 번째 공지사항').first()).toBeVisible(); - await expect(page.getByText('두 번째 테스트 내용입니다.').first()).toBeVisible(); - }); - - test('첨부파일이 있는 경우 첨부파일 영역이 표시된다', async ({ - page, - loginAs, - noticeDetailPage, - }) => { - // 1. Arrange - 첨부파일이 있는 공지사항이 필요 - // TODO: 첨부파일 기능이 실제로 구현되어 있는지 확인 후 테스트 활성화 - await loginAs('STAFF'); - - // 2. Act - await noticeDetailPage.goto(secondNoticeId); - - // 3. Assert - // TODO: 첨부파일 영역 UI 확인 (실제 구현에 따라 assertion 추가 필요) - // const attachments = noticeDetailPage.attachments; - // await expect(attachments).toBeVisible(); - }); - - test('태그가 있는 경우 태그 영역이 표시된다', async ({ page, loginAs, noticeDetailPage }) => { - // 1. Arrange - 태그가 있는 공지사항이 필요 - // TODO: 태그 기능이 실제로 구현되어 있는지 확인 후 테스트 활성화 - await loginAs('STAFF'); - - // 2. Act - await noticeDetailPage.goto(secondNoticeId); - - // 3. Assert - // TODO: 태그 영역 UI 확인 (실제 구현에 따라 assertion 추가 필요) - // const tags = await noticeDetailPage.getTags(); - // expect(tags.length).toBeGreaterThan(0); - }); - - test('권한이 없는 사용자에게는 편집/삭제 버튼이 표시되지 않는다', async ({ - page, - loginAs, - noticeDetailPage, - }) => { - // 1. Arrange - 권한이 없는 사용자로 로그인 (또는 로그아웃 상태) - // TODO: 권한이 없는 사용자 로그인 방식 확인 후 활성화 - // await loginAs('GUEST'); // 권한이 낮은 역할이 있다면 - - // 2. Act - await noticeDetailPage.goto(secondNoticeId); - - // 3. Assert - // TODO: 권한별 버튼 표시/숨김 로직 확인 후 활성화 - // await expect(noticeDetailPage.editButton).not.toBeVisible(); - // await expect(noticeDetailPage.deleteButton).not.toBeVisible(); - }); - - test.afterAll(async ({ browser }) => { - // 테스트 후 생성된 공지사항들 정리 - const context = await browser.newContext(); - const page = await context.newPage(); - - try { - // STAFF 로그인 - await page.goto('/community/notice'); - await page.waitForLoadState('networkidle'); - await page.getByRole('button', { name: 'STAFF', exact: true }).click(); - - // 남은 공지사항들 삭제 - const remainingIds = [secondNoticeId, thirdNoticeId].filter((id) => id); - - for (const id of remainingIds) { - try { - await page.goto(`/community/notice/${id}`); - await page.getByRole('button', { name: '삭제' }).click(); - await page.getByRole('button', { name: '확인' }).click(); - await page.waitForTimeout(500); - } catch (error) { - console.log(`Failed to delete notice ${id}:`, error); - } - } - } catch (error) { - console.log('Clean up failed:', error); - } - - await context.close(); - }); -}); diff --git a/e2e/tests/community/notice/edit.test.ts b/e2e/tests/community/notice/edit.test.ts index b3c87e3d..ba52ddf6 100644 --- a/e2e/tests/community/notice/edit.test.ts +++ b/e2e/tests/community/notice/edit.test.ts @@ -3,144 +3,46 @@ import { expect } from '@playwright/test'; import { test } from '../../../fixtures/index.fixture'; test.describe('공지사항 수정 페이지 (/community/notice/:id/edit)', () => { - let createdNoticeId: string; - let originalTitle: string; - - test.beforeEach(async ({ page, loginAs, noticeListPage, noticeCreatePage }) => { - // 테스트용 공지사항 생성 - originalTitle = `[수정테스트] 원본 공지사항 - ${Date.now()}`; - const originalContent = '수정 테스트를 위한 원본 내용입니다.'; - - await loginAs('STAFF'); - await noticeListPage.goto(); - await noticeListPage.clickNewPostButton(); - - await noticeCreatePage.fillTitle(originalTitle); - await noticeCreatePage.fillContent(originalContent); - await page.waitForTimeout(1000); - await noticeCreatePage.clickSubmitButton(); - - // 생성된 공지사항의 ID 추출 (URL에서) - await expect(page.getByText(originalTitle).first()).toBeVisible(); - const currentUrl = page.url(); - const urlParts = currentUrl.split('/'); - createdNoticeId = urlParts[urlParts.length - 1]; - }); - test('공지사항 제목을 수정할 수 있다', async ({ page, loginAs, noticeEditPage, noticeDetailPage, + noticeCreatePage, + noticeListPage, }) => { // 1. Arrange - const updatedTitle = `[수정완료] 수정된 공지사항 제목 - ${Date.now()}`; - - await loginAs('STAFF'); - - // 2. Act - await noticeEditPage.goto(createdNoticeId); - await expect(page).toHaveURL(`/community/notice/${createdNoticeId}/edit`); - - await noticeEditPage.fillTitle(updatedTitle); - await page.waitForTimeout(1000); - await noticeEditPage.clickSubmitButton(); - - // 3. Assert - await expect(page.getByText(updatedTitle).first()).toBeVisible(); - - // 상세 페이지에서도 제목이 변경되었는지 확인 - await noticeDetailPage.goto(createdNoticeId); - await expect(page.getByText(updatedTitle).first()).toBeVisible(); - }); - - test('공지사항 내용을 수정할 수 있다', async ({ page, loginAs, noticeEditPage }) => { - // 1. Arrange - const updatedContent = '이것은 수정된 공지사항 내용입니다.'; - - await loginAs('STAFF'); - - // 2. Act - await noticeEditPage.goto(createdNoticeId); - await noticeEditPage.fillContent(updatedContent); - await page.waitForTimeout(1000); - await noticeEditPage.clickSubmitButton(); - - // 3. Assert - await expect(page.getByText(updatedContent).first()).toBeVisible(); - }); - - test('공지사항에 태그를 추가할 수 있다', async ({ page, loginAs, noticeEditPage }) => { - // 1. Arrange - const newTag = '수정된태그'; - + await noticeListPage.goto(); await loginAs('STAFF'); + await noticeCreatePage.goto(); - // 2. Act - await noticeEditPage.goto(createdNoticeId); - // TODO: 태그 기능이 실제로 구현되어 있는지 확인 후 활성화 - // await noticeEditPage.addTag(newTag); - await page.waitForTimeout(1000); - await noticeEditPage.clickSubmitButton(); - - // 3. Assert - // TODO: 태그가 실제로 추가되었는지 확인 (실제 UI 구조에 따라 수정 필요) - // await expect(page.getByText(newTag)).toBeVisible(); - }); + const noticeTitle = `[테스트] 기본 공지사항 - ${Date.now()}`; + const noticeContent = '이것은 테스트용 공지사항 내용입니다.'; + await noticeCreatePage.fillTitle(noticeTitle); + await noticeCreatePage.fillContent(noticeContent); + await noticeCreatePage.clickSubmitButton(); - test('공지사항 옵션들을 수정할 수 있다', async ({ page, loginAs, noticeEditPage }) => { - await loginAs('STAFF'); + await noticeListPage.goto(); + await page.waitForLoadState('networkidle'); + await noticeListPage.clickNoticeByTitle(noticeTitle); // 2. Act - await noticeEditPage.goto(createdNoticeId); - - // TODO: 옵션 토글 기능들이 실제로 구현되어 있는지 확인 후 활성화 - // await noticeEditPage.toggleImportant(); - // await noticeEditPage.togglePinned(); - - await page.waitForTimeout(1000); + const updatedTitle = `[수정완료] 수정된 공지사항 제목 - ${Date.now()}`; + await noticeDetailPage.clickEditButton(); + await noticeEditPage.fillTitle(updatedTitle); await noticeEditPage.clickSubmitButton(); // 3. Assert - await expect(page.getByText(originalTitle).first()).toBeVisible(); - // TODO: 옵션 변경 사항이 실제로 반영되었는지 확인 (실제 UI 구조에 따라 수정 필요) + await expect(page.getByText(updatedTitle).first()).toBeVisible(); }); - test('첨부파일을 추가할 수 있다', async ({ page, loginAs, noticeEditPage }) => { - await loginAs('STAFF'); - - await noticeEditPage.goto(createdNoticeId); + test.skip('공지사항 내용을 수정할 수 있다', async () => {}); - // TODO: 실제 테스트 파일 업로드 (테스트용 파일 경로 설정 필요) - // await noticeEditPage.uploadAttachment('e2e/fixtures/test-files/sample.pdf'); + test.skip('공지사항에 태그를 추가할 수 있다', async () => {}); - await page.waitForTimeout(1000); - await noticeEditPage.clickSubmitButton(); - - // TODO: 첨부파일이 실제로 추가되었는지 확인 (실제 UI 구조에 따라 수정 필요) - }); - - test('수정 권한이 없는 사용자는 수정 페이지에 접근할 수 없다', async ({ page, loginAs }) => { - // TODO: 권한이 없는 사용자 로그인 방식 확인 후 활성화 - // await loginAs('GUEST'); // 권한이 낮은 역할이 있다면 + test.skip('공지사항 옵션들을 수정할 수 있다', async () => {}); - // 직접 수정 URL로 접근 시도 - await page.goto(`/community/notice/${createdNoticeId}/edit`); + test.skip('첨부파일을 추가할 수 있다', async () => {}); - // 권한 없음 메시지 또는 로그인 페이지로 리다이렉트 확인 - // TODO: 실제 권한 처리 방식에 따라 assertion 수정 필요 - await expect(page).not.toHaveURL(`/community/notice/${createdNoticeId}/edit`); - }); - - test.afterEach(async ({ page, loginAs, noticeDetailPage }) => { - // 테스트 후 생성된 공지사항 정리 - try { - await loginAs('STAFF'); - await noticeDetailPage.goto(createdNoticeId); - await noticeDetailPage.clickDeleteButton(); - await noticeDetailPage.confirmDelete(); - } catch (error) { - console.log('Clean up failed:', error); - } - }); + test.skip('수정 권한이 없는 사용자는 수정 페이지에 접근할 수 없다', async () => {}); }); diff --git a/e2e/tests/community/notice/index.test.ts b/e2e/tests/community/notice/index.test.ts index bc88a1a4..7ca298fa 100644 --- a/e2e/tests/community/notice/index.test.ts +++ b/e2e/tests/community/notice/index.test.ts @@ -3,140 +3,75 @@ import { expect } from '@playwright/test'; import { test } from '../../../fixtures/index.fixture'; test.describe('공지사항 목록 조회 페이지 (/community/notice)', () => { - const testNoticeIds: string[] = []; - - test.beforeAll(async ({ browser }) => { - // 테스트용 공지사항들 생성 - const context = await browser.newContext(); - const page = await context.newPage(); - - // STAFF 로그인 - await page.goto('/community/notice'); - await page.waitForLoadState('networkidle'); - await page.getByRole('button', { name: 'STAFF', exact: true }).click(); - - const notices = [ - { - title: '[목록테스트1] 검색 테스트 공지사항', - content: '검색용 내용입니다', - tags: ['검색', '테스트'], - }, - { - title: '[목록테스트2] 태그 필터 테스트', - content: '태그 필터용 내용입니다', - tags: ['필터', '태그'], - }, - { - title: '[목록테스트3] 일괄 작업 테스트', - content: '일괄 작업용 내용입니다', - tags: ['일괄', '작업'], - }, - { - title: '[목록테스트4] 고정 테스트 공지사항', - content: '고정 테스트용 내용입니다', - tags: ['고정'], - }, - { - title: '[목록테스트5] 삭제 테스트 공지사항', - content: '삭제 테스트용 내용입니다', - tags: ['삭제'], - }, - ]; - - for (const notice of notices) { - await page.goto('/community/notice/create'); - await page.getByPlaceholder('제목을 입력하세요.').fill(notice.title); - await page.locator('div.sun-editor-editable').fill(notice.content); - - // TODO: 태그 기능이 실제로 구현되어 있는지 확인 후 활성화 - // for (const tag of notice.tags) { - // await page.locator('TODO_TAG_INPUT_SELECTOR').fill(tag); - // await page.keyboard.press('Enter'); - // } - - await page.waitForTimeout(1000); - await page.getByRole('button', { name: /게시/ }).click(); - - const url = page.url(); - const id = url.split('/').pop(); - if (id) testNoticeIds.push(id); - } - - await context.close(); - }); - - test('키워드로 공지사항을 검색할 수 있다', async ({ page, loginAs, noticeListPage }) => { + test('생성한 공지사항을 진입할 수 있다', async ({ + page, + noticeListPage, + noticeCreatePage, + loginAs, + }) => { // 1. Arrange - await loginAs('STAFF'); await noticeListPage.goto(); + await loginAs('STAFF'); // 2. Act - // TODO: 검색 기능이 실제로 구현되어 있는지 확인 후 활성화 - // await noticeListPage.searchByKeyword('검색 테스트'); - - // 3. Assert - // TODO: 검색 결과 확인 (실제 구현에 따라 assertion 추가 필요) - // await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).toBeVisible(); - // await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).not.toBeVisible(); + const noticeTitle = `[테스트] 기본 공지사항 - ${Date.now()}`; + const noticeContent = '이것은 테스트용 공지사항 내용입니다.'; - // 현재는 기본 목록 표시 확인 - await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).toBeVisible(); - }); - - test('태그로 공지사항을 필터링할 수 있다', async ({ page, loginAs, noticeListPage }) => { - // 1. Arrange - await loginAs('STAFF'); + await noticeCreatePage.goto(); + await noticeCreatePage.fillTitle(noticeTitle); + await noticeCreatePage.fillContent(noticeContent); + await noticeCreatePage.clickSubmitButton(); await noticeListPage.goto(); - // 2. Act - // TODO: 태그 필터 기능이 실제로 구현되어 있는지 확인 후 활성화 - // await noticeListPage.filterByTag('필터'); - // 3. Assert - // TODO: 태그 필터 결과 확인 (실제 구현에 따라 assertion 추가 필요) - // await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).toBeVisible(); - // await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).not.toBeVisible(); - - // 현재는 기본 목록 표시 확인 - await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).toBeVisible(); + await noticeListPage.clickNoticeByTitle(noticeTitle); + await expect(page).toHaveURL(new RegExp('/community/notice/\\d+')); }); - test('전체 선택 체크박스로 모든 공지사항을 선택할 수 있다', async ({ + test.skip('키워드로 공지사항을 검색할 수 있다', async ({ page, loginAs, noticeListPage, + noticeCreatePage, }) => { // 1. Arrange - await loginAs('STAFF'); await noticeListPage.goto(); + await loginAs('STAFF'); // 2. Act - // TODO: 전체 선택 기능이 실제로 구현되어 있는지 확인 후 활성화 - // await noticeListPage.selectAllNotices(); + const noticeTitle = `[테스트] 기본 공지사항 - ${Date.now()}`; + const noticeContent = '이것은 테스트용 공지사항 내용입니다.'; + + await noticeCreatePage.goto(); + await noticeCreatePage.fillTitle(noticeTitle); + await noticeCreatePage.fillContent(noticeContent); + await noticeCreatePage.clickSubmitButton(); + await noticeListPage.goto(); + await noticeListPage.searchByKeyword('검색 테스트'); // 3. Assert - // TODO: 전체 선택 결과 확인 (실제 UI 구조에 따라 assertion 추가 필요) - // const checkboxes = await page.locator('TODO_NOTICE_CHECKBOX_SELECTOR').all(); - // for (const checkbox of checkboxes) { - // await expect(checkbox).toBeChecked(); - // } + await expect(page.getByText(noticeTitle)).toBeVisible(); }); - test('개별 공지사항을 선택할 수 있다', async ({ page, loginAs, noticeListPage }) => { + test.skip('태그로 공지사항을 필터링할 수 있다', async ({ page, loginAs, noticeListPage }) => { // 1. Arrange await loginAs('STAFF'); await noticeListPage.goto(); // 2. Act - // TODO: 개별 선택 기능이 실제로 구현되어 있는지 확인 후 활성화 - // await noticeListPage.selectNotice(0); // 첫 번째 공지사항 선택 + // TODO: 태그 필터 기능이 실제로 구현되어 있는지 확인 후 활성화 + // await noticeListPage.filterByTag('필터'); // 3. Assert - // TODO: 개별 선택 결과 확인 (실제 UI 구조에 따라 assertion 추가 필요) - // await expect(page.locator('TODO_NOTICE_CHECKBOX_SELECTOR').first()).toBeChecked(); + // TODO: 태그 필터 결과 확인 (실제 구현에 따라 assertion 추가 필요) + // await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).toBeVisible(); + // await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).not.toBeVisible(); + + // 현재는 기본 목록 표시 확인 + await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).toBeVisible(); }); - test('선택한 공지사항들을 일괄 삭제할 수 있다', async ({ page, loginAs, noticeListPage }) => { + test.skip('선택한 공지사항들을 일괄 삭제할 수 있다', async ({ loginAs, noticeListPage }) => { // 1. Arrange await loginAs('STAFF'); await noticeListPage.goto(); @@ -156,7 +91,7 @@ test.describe('공지사항 목록 조회 페이지 (/community/notice)', () => // await expect(page.getByText('[목록테스트5] 삭제 테스트 공지사항')).not.toBeVisible(); }); - test('선택한 공지사항들의 고정을 일괄 해제할 수 있다', async ({ + test.skip('선택한 공지사항들의 고정을 일괄 해제할 수 있다', async ({ page, loginAs, noticeListPage, @@ -185,60 +120,7 @@ test.describe('공지사항 목록 조회 페이지 (/community/notice)', () => // 고정 표시가 사라졌는지 또는 고정 영역에서 제거되었는지 확인 }); - test('공지사항 제목을 클릭하면 상세 페이지로 이동한다', async ({ - page, - loginAs, - noticeListPage, - }) => { - // 1. Arrange - await loginAs('STAFF'); - await noticeListPage.goto(); - - // 2. Act - await noticeListPage.clickNoticeByTitle('[목록테스트1] 검색 테스트 공지사항'); - - // 3. Assert - await expect(page).toHaveURL(new RegExp('/community/notice/\\d+')); - await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).toBeVisible(); - }); - - test('공지사항 목록이 올바르게 표시된다', async ({ page, loginAs, noticeListPage }) => { - // 1. Arrange - await loginAs('STAFF'); - - // 2. Act - await noticeListPage.goto(); - - // 3. Assert - // TODO: 공지사항 개수 확인 기능이 실제로 구현되어 있는지 확인 후 활성화 - // const noticeCount = await noticeListPage.getNoticeCount(); - // expect(noticeCount).toBeGreaterThan(0); - - // 테스트로 생성한 공지사항들이 표시되는지 확인 - await expect(page.getByText('[목록테스트1] 검색 테스트 공지사항')).toBeVisible(); - await expect(page.getByText('[목록테스트2] 태그 필터 테스트')).toBeVisible(); - await expect(page.getByText('[목록테스트3] 일괄 작업 테스트')).toBeVisible(); - }); - - test('검색 결과가 없을 때 적절한 메시지가 표시된다', async ({ - page, - loginAs, - noticeListPage, - }) => { - // 1. Arrange - await loginAs('STAFF'); - await noticeListPage.goto(); - - // 2. Act - // TODO: 검색 기능이 실제로 구현되어 있는지 확인 후 활성화 - // await noticeListPage.searchByKeyword('존재하지않는검색어12345'); - - // 3. Assert - // TODO: 검색 결과 없음 메시지 확인 (실제 UI 구조에 따라 assertion 추가 필요) - // await expect(page.getByText('검색 결과가 없습니다')).toBeVisible(); - }); - - test('페이지네이션이 올바르게 작동한다', async ({ page, loginAs, noticeListPage }) => { + test.skip('페이지네이션이 올바르게 작동한다', async ({ loginAs, noticeListPage }) => { // 1. Arrange await loginAs('STAFF'); await noticeListPage.goto(); @@ -248,51 +130,4 @@ test.describe('공지사항 목록 조회 페이지 (/community/notice)', () => // 현재 테스트 데이터가 적어서 페이지네이션이 없을 수 있음 // 실제 페이지네이션 구현에 따라 테스트 추가 필요 }); - - test.afterAll(async ({ browser }) => { - // 테스트 후 생성된 공지사항들 정리 - const context = await browser.newContext(); - const page = await context.newPage(); - - try { - // STAFF 로그인 - await page.goto('/community/notice'); - await page.waitForLoadState('networkidle'); - await page.getByRole('button', { name: 'STAFF', exact: true }).click(); - - // 생성된 테스트 공지사항들 삭제 - for (const id of testNoticeIds) { - try { - await page.goto(`/community/notice/${id}`); - const deleteButton = page.getByRole('button', { name: '삭제' }); - if (await deleteButton.isVisible()) { - await deleteButton.click(); - await page.getByRole('button', { name: '확인' }).click(); - await page.waitForTimeout(500); - } - } catch (error) { - console.log(`Failed to delete notice ${id}:`, error); - } - } - - // 추가로 생성된 공지사항도 정리 - await page.goto('/community/notice'); - const testNotices = await page.locator('text=/\\[.*테스트.*\\]/').all(); - for (const notice of testNotices) { - try { - await notice.click(); - await page.getByRole('button', { name: '삭제' }).click(); - await page.getByRole('button', { name: '확인' }).click(); - await page.waitForTimeout(500); - await page.goto('/community/notice'); - } catch (error) { - console.log('Failed to delete test notice:', error); - } - } - } catch (error) { - console.log('Clean up failed:', error); - } - - await context.close(); - }); });