chore: Playwright E2E 테스트 환경 구축#319
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Discord 스레드 연동용 메타데이터입니다. discord-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Discord 알림 연동이 끊깁니다. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughPlaywright 기반 E2E 테스트 환경이 추가되었습니다. 목 API 서버와 fixture, 게스트 인증 상태, 샘플 테스트, Playwright 설정, 실행 스크립트, CI E2E 잡 및 테스트 산출물 제외 규칙이 포함됩니다. ChangesPlaywright E2E 환경 구축
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant CI
participant globalSetup
participant MockApiServer
participant ApiFixture
participant NextApp
participant E2ETest
CI->>globalSetup: 테스트 런 시작
globalSetup->>MockApiServer: 목 서버 시작
E2ETest->>ApiFixture: API 목 응답 등록
E2ETest->>NextApp: 페이지 이동
NextApp->>MockApiServer: SSR API 요청
MockApiServer-->>NextApp: 목 응답 반환
NextApp->>ApiFixture: 클라이언트 API 요청
ApiFixture-->>NextApp: 목 응답 반환
E2ETest->>NextApp: 화면과 URL 검증
globalSetup->>MockApiServer: 서버 종료
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
.github/workflows/ci.yml (2)
53-121: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
e2e잡에timeout-minutes설정 권장Playwright 브라우저 자동화는 네트워크/타이밍 이슈로 예기치 않게 멎을 수 있어, 다른 유사 워크플로 예시들도 대부분 잡 레벨에
timeout-minutes(보통 30~60분)를 명시해 러너가 기본 6시간까지 점유되는 것을 방지합니다. 현재e2e잡에는 이 설정이 없어, 테스트가 행(hang)될 경우 러너 자원이 오래 낭비될 수 있습니다.⏱️ 제안
e2e: name: E2E (Playwright) runs-on: ubuntu-latest + timeout-minutes: 30🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci.yml around lines 53 - 121, The e2e job is missing an explicit timeout, so add a job-level timeout-minutes to the e2e workflow block to prevent a hung Playwright run from occupying the runner too long. Update the e2e job definition in the CI workflow (the job that runs Build web, Run E2E tests, and Upload Playwright report) and choose a reasonable limit consistent with other workflow jobs, such as 30–60 minutes.
53-55: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
ci잡과의 의존 관계 부재로 인한 불필요한 실행 가능성
e2e잡이needs: ci를 지정하지 않아 lint/타입체크/빌드가 실패해도 e2e 잡은 독립적으로 실행됩니다. 브라우저 캐시 복원,pnpm build:web, 전체 테스트 스위트가 매번 도는 구조라, 기존ci잡이 먼저 실패할 게 뻔한 PR에서도 러너 시간이 소모됩니다. 의도적으로 병렬 실행해 피드백 속도를 우선한 것이라면 넘어가도 좋습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci.yml around lines 53 - 55, The e2e job is currently independent from the main ci job, so it can still run and consume runner time even when lint/typecheck/build already failed. Update the workflow by adding a dependency from the e2e job to the ci job in the GitHub Actions YAML, using the e2e job definition as the place to reference needs: ci, unless parallel execution is intentionally desired.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/e2e/setup/mockApiServer.ts`:
- Around line 29-54: `startMockApiServer`의 `server.listen()`에 에러 처리 경로가 없어 포트 충돌
시 프로세스가 종료될 수 있습니다. `http.Server`의 `'error'` 이벤트를 `startMockApiServer` 안에서 처리하고,
Promise를 성공 시 `resolve(server)`만 하지 말고 실패 시 `reject`하도록 바꿔 `EADDRINUSE` 같은
listen 오류가 `globalSetup`에서 명확히 전파되게 하세요. `startMockApiServer`, `server.listen`,
그리고 생성된 `server` 객체를 기준으로 수정하면 됩니다.
---
Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 53-121: The e2e job is missing an explicit timeout, so add a
job-level timeout-minutes to the e2e workflow block to prevent a hung Playwright
run from occupying the runner too long. Update the e2e job definition in the CI
workflow (the job that runs Build web, Run E2E tests, and Upload Playwright
report) and choose a reasonable limit consistent with other workflow jobs, such
as 30–60 minutes.
- Around line 53-55: The e2e job is currently independent from the main ci job,
so it can still run and consume runner time even when lint/typecheck/build
already failed. Update the workflow by adding a dependency from the e2e job to
the ci job in the GitHub Actions YAML, using the e2e job definition as the place
to reference needs: ci, unless parallel execution is intentionally desired.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 47e78ea4-01a3-45b7-ba74-85af8cf61e48
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (16)
.github/workflows/ci.yml.gitignoreapps/web/e2e/consts.tsapps/web/e2e/fixtures/mockApiFixture.tsapps/web/e2e/helpers/apiResponse.tsapps/web/e2e/helpers/fakeJwt.tsapps/web/e2e/home.spec.tsapps/web/e2e/mocks/me.tsapps/web/e2e/mocks/tournament.tsapps/web/e2e/setup/auth.setup.tsapps/web/e2e/setup/globalSetup.tsapps/web/e2e/setup/mockApiServer.tsapps/web/e2e/tournamentCreate.spec.tsapps/web/package.jsonapps/web/playwright.config.tspackage.json
| export const startMockApiServer = (port: number) => | ||
| new Promise<http.Server>(resolve => { | ||
| const server = http.createServer((req, res) => { | ||
| const pathname = new URL(req.url ?? '/', `http://127.0.0.1:${port}`).pathname; | ||
| const body = SSR_MOCK_ROUTES[`${req.method} ${pathname}`]; | ||
|
|
||
| if (!body) { | ||
| res.writeHead(404, { 'content-type': 'application/json' }); | ||
| res.end( | ||
| JSON.stringify( | ||
| createApiError({ | ||
| status: 404, | ||
| code: 'E2E_SSR_UNMOCKED', | ||
| detail: `SSR 목 스텁에 등록되지 않은 요청: ${req.method} ${pathname}`, | ||
| }) | ||
| ) | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| res.writeHead(200, { 'content-type': 'application/json' }); | ||
| res.end(JSON.stringify(body)); | ||
| }); | ||
|
|
||
| server.listen(port, '127.0.0.1', () => resolve(server)); | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
server.listen()에 에러 핸들러가 없어 포트 충돌 시 프로세스가 크래시할 수 있습니다.
http.Server는 EventEmitter이므로 listen() 중 EADDRINUSE 등으로 'error' 이벤트가 발생하면, 리스너가 없을 경우 처리되지 않은 예외로 Node 프로세스가 종료됩니다. 현재 Promise는 성공 콜백에서만 resolve되고 reject 경로가 없어, 이전 실행이 정상 종료되지 않아 포트가 남아있는 경우(예: CI 재시도, 로컬에서 강제 종료 후 재실행) globalSetup 단계에서 명확한 에러 메시지 없이 전체 E2E 실행이 죽어버릴 수 있습니다.
🔧 제안하는 수정
export const startMockApiServer = (port: number) =>
- new Promise<http.Server>(resolve => {
+ new Promise<http.Server>((resolve, reject) => {
const server = http.createServer((req, res) => {
...
});
+ server.on('error', reject);
server.listen(port, '127.0.0.1', () => resolve(server));
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const startMockApiServer = (port: number) => | |
| new Promise<http.Server>(resolve => { | |
| const server = http.createServer((req, res) => { | |
| const pathname = new URL(req.url ?? '/', `http://127.0.0.1:${port}`).pathname; | |
| const body = SSR_MOCK_ROUTES[`${req.method} ${pathname}`]; | |
| if (!body) { | |
| res.writeHead(404, { 'content-type': 'application/json' }); | |
| res.end( | |
| JSON.stringify( | |
| createApiError({ | |
| status: 404, | |
| code: 'E2E_SSR_UNMOCKED', | |
| detail: `SSR 목 스텁에 등록되지 않은 요청: ${req.method} ${pathname}`, | |
| }) | |
| ) | |
| ); | |
| return; | |
| } | |
| res.writeHead(200, { 'content-type': 'application/json' }); | |
| res.end(JSON.stringify(body)); | |
| }); | |
| server.listen(port, '127.0.0.1', () => resolve(server)); | |
| }); | |
| export const startMockApiServer = (port: number) => | |
| new Promise<http.Server>((resolve, reject) => { | |
| const server = http.createServer((req, res) => { | |
| const pathname = new URL(req.url ?? '/', `http://127.0.0.1:${port}`).pathname; | |
| const body = SSR_MOCK_ROUTES[`${req.method} ${pathname}`]; | |
| if (!body) { | |
| res.writeHead(404, { 'content-type': 'application/json' }); | |
| res.end( | |
| JSON.stringify( | |
| createApiError({ | |
| status: 404, | |
| code: 'E2E_SSR_UNMOCKED', | |
| detail: `SSR 목 스텁에 등록되지 않은 요청: ${req.method} ${pathname}`, | |
| }) | |
| ) | |
| ); | |
| return; | |
| } | |
| res.writeHead(200, { 'content-type': 'application/json' }); | |
| res.end(JSON.stringify(body)); | |
| }); | |
| server.on('error', reject); | |
| server.listen(port, '127.0.0.1', () => resolve(server)); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/e2e/setup/mockApiServer.ts` around lines 29 - 54,
`startMockApiServer`의 `server.listen()`에 에러 처리 경로가 없어 포트 충돌 시 프로세스가 종료될 수 있습니다.
`http.Server`의 `'error'` 이벤트를 `startMockApiServer` 안에서 처리하고, Promise를 성공 시
`resolve(server)`만 하지 말고 실패 시 `reject`하도록 바꿔 `EADDRINUSE` 같은 listen 오류가
`globalSetup`에서 명확히 전파되게 하세요. `startMockApiServer`, `server.listen`, 그리고 생성된
`server` 객체를 기준으로 수정하면 됩니다.
|
완전 최고다 |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/e2e/setup/globalSetup.ts`:
- Around line 6-7: Update the server reuse logic around startMockApiServer so an
EADDRINUSE/null result is accepted only after a health check or identifying
response confirms the process is the expected mock API server; otherwise fail
setup instead of reusing the process. Preserve reuse for valid existing mock
servers and keep the normal newly-started server path unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3b95d009-1ddc-497a-80a7-4339fcd89b77
📒 Files selected for processing (3)
apps/web/e2e/fixtures/mockApiFixture.tsapps/web/e2e/setup/globalSetup.tsapps/web/e2e/setup/mockApiServer.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/e2e/setup/mockApiServer.ts
| /** null 이면 다른 세션(UI 모드 등)이 띄운 기존 스텁을 재사용 — 내리지 않는다 */ | ||
| const server = await startMockApiServer(MOCK_API_PORT); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Files ==\n'
git ls-files 'apps/web/e2e/setup/*' 'apps/web/e2e/**/*mock*' 'apps/web/e2e/**/*setup*' | sort
printf '\n== Outline: globalSetup ==\n'
ast-grep outline apps/web/e2e/setup/globalSetup.ts --view expanded
printf '\n== Outline: mockApiServer ==\n'
ast-grep outline apps/web/e2e/setup/mockApiServer.ts --view expanded
printf '\n== Relevant excerpts ==\n'
sed -n '1,220p' apps/web/e2e/setup/globalSetup.ts | cat -n
printf '\n--- mockApiServer.ts ---\n'
sed -n '1,260p' apps/web/e2e/setup/mockApiServer.ts | cat -nRepository: TeamPiKi/PiKi-Client
Length of output: 3846
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== MOCK_API_PORT references ==\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/.next/**' 'MOCK_API_PORT|startMockApiServer\(' apps/web
printf '\n== consts file ==\n'
sed -n '1,220p' apps/web/e2e/consts.ts | cat -n
printf '\n== e2e setup files ==\n'
sed -n '1,220p' apps/web/e2e/setup/auth.setup.ts | cat -n
printf '\n--- fixture ---\n'
sed -n '1,240p' apps/web/e2e/fixtures/mockApiFixture.ts | cat -n
printf '\n== package scripts mentioning e2e or ui mode ==\n'
sed -n '1,240p' package.json | cat -nRepository: TeamPiKi/PiKi-Client
Length of output: 8127
기존 서버 재사용 전에 신원을 확인하세요
EADDRINUSE를 그대로 null로 처리하면 4010을 점유한 stale/unrelated 프로세스도 목 서버로 간주됩니다. 헬스체크나 식별 응답이 맞을 때만 재사용하고, 그렇지 않으면 실패시키는 편이 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/e2e/setup/globalSetup.ts` around lines 6 - 7, Update the server
reuse logic around startMockApiServer so an EADDRINUSE/null result is accepted
only after a health check or identifying response confirms the process is the
expected mock API server; otherwise fail setup instead of reusing the process.
Preserve reuse for valid existing mock servers and keep the normal newly-started
server path unchanged.
iOdiO89
left a comment
There was a problem hiding this comment.
어렵당 나도 playwright는 제대로 써본적이 없어서 가볍게 리뷰남겼어!
이거 관련 md 파일을 하나 추가하면 어떨까 폴더 구조나 어떻게 사용해야한다는 지침서가 있으면 좋을 것 같아
| - name: Upload Playwright report | ||
| if: failure() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: playwright-report | ||
| path: | | ||
| apps/web/playwright-report/ | ||
| apps/web/test-results/ | ||
| retention-days: 7 |
There was a problem hiding this comment.
failure 발생하면 디스코드로 알림 보내주면 어떨까??
이거는 근데 discord-pr-bot 쪽 워크플로우를 건드려야하는 거긴 해
| await provide({ | ||
| get: (path, data) => | ||
| entries.push({ method: 'GET', path, status: 200, body: createApiSuccess(data) }), | ||
| post: (path, data) => | ||
| entries.push({ method: 'POST', path, status: 200, body: createApiSuccess(data) }), |
There was a problem hiding this comment.
patch, delete가 없는데 이건 e2e 테스트 생성하면서 추가하면 될까? error는 get, post, delete, patch까지 다 대응되어 있는 것 같아서!
| /** 외부 이미지 URL 금지 — next/image 가 서버사이드에서 fetch 해 목킹이 불가능하다 */ | ||
| profileImage: '', |
There was a problem hiding this comment.
아님 이런건 e2e 테스트 실제로 생성하면서 작업하는 게 나을까!

작업 요약
작업 세부 내용
PR CI에서 주요 사용자 플로우를 자동 검증하는 E2E 테스트 환경을 구축했습니다
만약 실서버 기반으로 PR CI를 돌리면
api.error(...)한 줄)목킹 구조
e2e/setup/auth.setup.tsclientApi)page.route()인터셉트e2e/fixtures/mockApiFixture.tsserverApi, RSC 레이아웃)127.0.0.1:4010)e2e/setup/mockApiServer.ts① 인증:
proxy.ts(미들웨어)는access_token쿠키가 없으면 서버사이드에서 게스트 로그인 API를 호출하는데, 이건 브라우저 밖에서 일어나page.route()로 못 잡습니다. 대신 토큰 검증(isTokenValid)이 서명 없이 JWT의exp만 확인하는 점을 이용해, global setup에서 만료가 미래인 가짜 JWT를 조립해storageState(쿠키)로 저장합니다. 네트워크 0회로 인증 상태가 만들어지고 모든 테스트가 재사용합니다.② 브라우저 요청:
apifixture가**/api/v1/**라우트를 선점하고 pathname+method로 매칭합니다. 팀 응답 규약{ status, data, detail, code }를 그대로 따르고, 목킹 안 된 요청은 500으로 실패시키고 테스트 끝에 단언이 터지게 해서 목 누락을 즉시 잡습니다.③ SSR 요청:
tournament/[id]/layout.tsx가 접근 권한 확인을 위해 RSC에서getTournament를 직접 await하므로(실패 시 rethrow), 서버 발 요청은 반드시 성공해야 토너먼트 페이지에 진입할 수 있습니다. 그래서NEXT_PUBLIC_API_URL을127.0.0.1:4010으로 강제하고, globalSetup에서 node 내장 http로 목 스텁 서버를 띄워 응답합니다. 목 데이터 상수는e2e/mocks/를 ②와 공유합니다(단일 소스)파일 구조
실행 방법
3 passed처럼 출력되고, 실패 시 스크린샷 경로 + HTML 리포트가 자동으로 열립니다mobile-chromium을 체크해주세요CI
ci잡(lint/type/build)과 병렬로 도는e2e잡 추가 — 기존 필수 체크에 영향 없음~/.cache/ms-playwright, 버전 기반 키)playwright-report+test-results(trace/스크린샷) 아티팩트 업로드스크린샷
연관 이슈
closes #318
Summary by CodeRabbit