Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- 아이콘을 넣든 태그를 넣든 자유롭게 해보세요!
- [ ] 반복 종료
- 반복 종료 조건을 지정할 수 있다.
- 옵션: 특정 날짜까지, 특정 횟수만큼, 또는 종료 없음 (예제 특성상, 2025-06-30까지)
- 옵션: 특정 날짜까지, 특정 횟수만큼, 또는 종료 없음 (예제 특성상, 2025-10-30까지)
- [ ] 반복 일정 단일 수정
- 반복일정을 수정하면 단일 일정으로 변경됩니다.
- 반복일정 아이콘도 사라집니다.
Expand Down
29 changes: 28 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,31 @@ jobs:
- name: test basic
run: |
pnpm install
pnpm run test
pnpm run test

e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- uses: pnpm/action-setup@v4
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
run: pnpm test:e2e
- uses: actions/upload-artifact@v3
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
3 changes: 3 additions & 0 deletions .vitest-preview/index.html

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# E2E 테스트 가이드

이 프로젝트는 Playwright를 사용하여 E2E 테스트를 구현했습니다.

## 테스트 실행 방법

### 1. 기본 E2E 테스트 실행

```bash
pnpm test:e2e
```

### 2. UI 모드로 테스트 실행 (브라우저에서 시각적으로 확인)

```bash
pnpm test:e2e:ui
```

### 3. 헤드리스가 아닌 모드로 테스트 실행 (브라우저 창이 보임)

```bash
pnpm test:e2e:headed
```

### 4. 디버그 모드로 테스트 실행

```bash
pnpm test:e2e:debug
```

## 테스트 구성

### 테스트 파일

- `e2e/calendar-app.spec.ts`: 캘린더 앱의 주요 기능들을 테스트

### 주요 테스트 시나리오

1. **일정 생성, 수정, 삭제 및 반복 일정 기능**

- 새로운 일정 생성
- 기존 일정 수정
- 반복 일정 생성 (매일, 매주)
- 검색 기능
- 뷰 전환 (주별/월별)
- 일정 삭제

2. **일정 겹침 경고**

- 겹치는 시간대에 일정 생성 시 경고 표시
- 경고 후 계속 진행 기능

3. **매주 반복 일정**
- 매주 반복되는 일정 생성
- 반복 종료일 설정

## 브라우저 지원

- Chromium
- Firefox
- WebKit (Safari)

## CI/CD 지원

- GitHub Actions에서 자동으로 실행됩니다.
- CI 환경에서는 GitHub reporter를 사용합니다.
- 실패 시 Playwright 리포트가 아티팩트로 저장됩니다.

## 주의사항

- E2E 테스트 실행 시 개발 서버가 자동으로 시작됩니다.
- 테스트는 `http://localhost:5173`에서 실행됩니다.
- CI 환경에서는 더 안정적인 설정으로 동작합니다.
- TypeScript 타입 체크를 위한 전용 설정 파일(`tsconfig.playwright.json`) 사용
109 changes: 109 additions & 0 deletions e2e/calendar-app.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { test, expect, type Page } from '@playwright/test';

test.describe('캘린더 앱 E2E 테스트', () => {
test.beforeEach(async ({ page }: { page: Page }) => {
// 애플리케이션으로 이동
await page.goto('/');

// 일정 로딩 완료 대기
await page.waitForSelector('text=일정 로딩 완료!', { timeout: 10000 });
});

test('기본 UI 요소들이 정상적으로 표시된다', async ({ page }: { page: Page }) => {
// 주요 UI 요소들이 표시되는지 확인
await expect(page.getByRole('heading', { name: '일정 추가' })).toBeVisible();
await expect(page.getByRole('heading', { name: '일정 보기' })).toBeVisible();
await expect(page.locator('[data-testid="month-view"]')).toBeVisible();
await expect(page.locator('[data-testid="event-list"]')).toBeVisible();
await expect(page.locator('input[placeholder="검색어를 입력하세요"]')).toBeVisible();
});

test('일정 폼의 모든 입력 필드가 정상적으로 작동한다', async ({ page }: { page: Page }) => {
// 일정 폼 입력 테스트
await page.fill('input[id="title"]', '테스트 회의');
await page.fill('input[id="date"]', '2025-10-15');
await page.fill('input[id="start-time"]', '14:00');
await page.fill('input[id="end-time"]', '15:00');
await page.fill('input[id="description"]', '테스트 설명');
await page.fill('input[id="location"]', '테스트 장소');

// 카테고리 선택
await page.click('[aria-label="카테고리"]');
await page.click('li[aria-label="업무-option"]');

// 입력된 값들이 올바르게 표시되는지 확인
await expect(page.locator('input[id="title"]')).toHaveValue('테스트 회의');
await expect(page.locator('input[id="date"]')).toHaveValue('2025-10-15');
await expect(page.locator('input[id="start-time"]')).toHaveValue('14:00');
await expect(page.locator('input[id="end-time"]')).toHaveValue('15:00');
await expect(page.locator('input[id="description"]')).toHaveValue('테스트 설명');
await expect(page.locator('input[id="location"]')).toHaveValue('테스트 장소');
});

test('반복 일정 설정 UI가 정상적으로 작동한다', async ({ page }: { page: Page }) => {
// 반복 일정 체크박스 클릭
await page.check('input[type="checkbox"]');

// 반복 설정 필드들이 나타나는지 확인
await expect(page.locator('[aria-label="반복 유형"]')).toBeVisible();
await expect(page.locator('input[type="number"]')).toBeVisible();
await expect(page.locator('input[id="repeat-end-date"]')).toBeVisible();

// 반복 유형 선택
await page.click('[aria-label="반복 유형"]');
await page.click('li[aria-label="daily-option"]');

// 간격 설정
await page.fill('input[type="number"]', '2');

// 종료일 설정
await page.fill('input[id="repeat-end-date"]', '2025-10-05');

// 미리보기가 표시되는지 확인 (일정 정보가 입력되어야 미리보기가 나타남)
await page.fill('input[id="title"]', '반복 테스트');
await page.fill('input[id="date"]', '2025-10-01');
await page.fill('input[id="start-time"]', '09:00');
await page.fill('input[id="end-time"]', '10:00');

// 미리보기 확인
await expect(page.locator('text=개의 반복 일정이 생성됩니다.')).toBeVisible();
});

test('뷰 전환이 정상적으로 작동한다', async ({ page }: { page: Page }) => {
// 기본적으로 월별 뷰가 표시되는지 확인
await expect(page.locator('[data-testid="month-view"]')).toBeVisible();

// 주별 뷰로 전환
await page.click('[aria-label="뷰 타입 선택"]');
await page.click('li[aria-label="week-option"]');
await expect(page.locator('[data-testid="week-view"]')).toBeVisible();

// 월별 뷰로 다시 전환
await page.click('[aria-label="뷰 타입 선택"]');
await page.click('li[aria-label="month-option"]');
await expect(page.locator('[data-testid="month-view"]')).toBeVisible();
});

test('달력 네비게이션이 정상적으로 작동한다', async ({ page }: { page: Page }) => {
// 다음 달로 이동
await page.click('[aria-label="Next"]');

// 이전 달로 이동
await page.click('[aria-label="Previous"]');

// 월별 뷰가 여전히 표시되는지 확인
await expect(page.locator('[data-testid="month-view"]')).toBeVisible();
});

test('검색 기능이 정상적으로 작동한다', async ({ page }: { page: Page }) => {
const searchInput = page.locator('input[placeholder="검색어를 입력하세요"]');

// 검색어 입력
await searchInput.fill('테스트');
await expect(searchInput).toHaveValue('테스트');

// 검색어 지우기
await searchInput.fill('');
await expect(searchInput).toHaveValue('');
});
});
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:headed": "playwright test --headed",
"test:e2e:debug": "playwright test --debug",
"build": "tsc -b && vite build",
"lint:eslint": "eslint . --ext ts,tsx --report-unused-disable-directives",
"lint:tsc": "tsc --pretty",
"lint": "pnpm lint:eslint && pnpm lint:tsc"
"lint": "pnpm lint:eslint && pnpm lint:tsc",
"vitest-preview": "vitest-preview"
},
"dependencies": {
"@emotion/react": "^11.11.4",
Expand All @@ -26,10 +31,12 @@
"msw": "^2.10.3",
"notistack": "^3.0.2",
"react": "19.1.0",
"react-dom": "19.1.0"
"react-dom": "19.1.0",
"vitest-preview": "^0.0.1"
},
"devDependencies": {
"@eslint/js": "9.33.0",
"@playwright/test": "^1.55.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.5.2",
Expand Down
76 changes: 76 additions & 0 deletions playwright-report/index.html

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { defineConfig, devices } from '@playwright/test';

/**
* @see https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? 'github' : 'html',
/* Timeout settings */
timeout: 30 * 1000,
expect: {
timeout: 5000,
},
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:5173',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',

/* Screenshot on failure */
screenshot: 'only-on-failure',

/* Video recording */
video: 'retain-on-failure',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
});
Loading
Loading