[cicd-80/e2e] GitHub Actions E2E 테스트 워크플로우 추가 - #209
Conversation
10개 도메인(인증, 랜딩페이지, 그룹, 초대, 정산, 채팅, 커뮤니티, 알림, 친구, 마이페이지)의 테스트 케이스 초안 143건을 output에 남긴다. raw에는 두 건의 작업 원본을 함께 보존한다. - CI 속성 삭제와 Test Guide 재작성 배경 - 도메인별 초안 작성 과정, 그리고 검토용으로 위임한 에이전트가 승인 없이 Notion에 push한 사고의 경위와 사후 처리 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Notion Test Guide와 테스트 시나리오 DB 136건을 실측해 문서와 데이터가 어긋난 부분을 정리했고, 그 과정과 결과를 llm-wiki에 남긴다. 문서 개정 - 시나리오 판정 기준과 ID 작성 규칙을 새로 정의 - 속성별 필수 여부와 기본값을 명시하고 실제 스키마 이름에 맞춤 - 에이전트 작업 경계 절 신설 - 줄바꿈이 리터럴 n으로 깨져 있던 실패 진단 정책 절 복구 - 설계서 v2의 CI 실행 범위 서술을 Test Guide 기준으로 정정 DB 정리 - 시나리오 129개를 43개로 재정리하고 ID 117건을 3세그먼트로 재부여 - 자동화 상태 135건, 테스트 레벨 공백 73건을 채움 - 회귀 28건 재분류, Smoke 8건을 태그로 이관 - TEMPLATE-000을 일반 페이지로 분리하고 폐기 select 옵션 제거 - 우선순위를 P0 15, P1 35, P2 72, P3 13으로 재배정 결정 기록 - 도메인은 단일 select를 유지한다. 도메인 분류보다 시나리오와 케이스 내용이 중요하고, 도메인이 spec 파일을 결정하기 때문이다 - 우선순위 판정 기준은 이 검증이 없을 때 사용자가 무엇을 잃는가로 둔다 수정 전 원본 두 건도 롤백용으로 함께 보존한다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
E2E 테스트가 금액, 참여 인원, 납부 체크박스를 짚을 때 Tailwind 클래스와 XPath에 의존하고 있었다. 디자인을 수정하면 테스트가 함께 깨지므로 role이나 텍스트로 짚을 수 없는 지점에만 data-testid를 붙인다. - 엔빵 조회 카드의 총 금액, 참여 인원, 엔빵 금액, 정기 결제일 값 - 엔빵 생성 폼의 참여 인원, 엔빵 금액, 정기 결제일 입력 - 참여자 카드와 납부 체크박스 Checkbox는 공용 컴포넌트라 testId를 선택 prop으로 받게 한다. 값을 넘기지 않으면 data-testid 자체가 렌더되지 않아 기존 사용처의 출력은 그대로다. className과 DOM 구조는 바꾸지 않았다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Notion 테스트 시나리오 DB에서 P0로 배정한 15건을 코드로 옮긴다. spec 파일은 도메인 단위, describe는 시나리오 단위, test는 케이스 하나에 대응하고 각 test 위의 주석이 Notion ID다. - auth 2건: 비로그인 보호 경로 차단, 로그인 콜백 복귀 경로 - group 5건: 그룹 생성 3건, 홈에서 상세 진입, 참여자 수정 권한 차단 - invite 2건: 초대 링크 조회, 초대 수락 - settlement 6건: 납부 상태 변경 3건, 정산 기간 기록 2건, 저장 실패 공용 fixture - 세션은 Supabase 클라이언트를 메모리 저장소로 만들어 로그인시킨 뒤 라이브러리가 기록한 항목을 그대로 브라우저에 옮긴다. 저장 형식이 바뀌어도 테스트가 따라 깨지지 않는다 - seed는 테스트가 만든 사용자, 그룹, 참여자, 초대, 정산 기록을 추적해 실패해도 되돌린다. service role key는 Node helper에서만 쓰고 브라우저로 넘기지 않는다 - 테스트 계정 비밀번호는 E2E_TEST_USER_PASSWORD 환경변수로 받는다 next dev가 경로마다 첫 진입에서 컴파일하는 탓에 기본 타임아웃과 병렬 워커로는 7건이 실패했다. 타임아웃을 90초와 20초로 올리고 워커를 1개로 고정한다. 병렬 3.5분 7실패에서 직렬 2.7분 전건 통과로 바뀌었다. 개발 DB를 대상으로 16건 전부 통과하고, 실행 후 잔여 데이터가 없음을 확인했다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Playwright 테스트는 await가 빠져도 어설션이 조용히 통과한다. 타입 정보를 켜고 잡도록 e2e에만 규칙을 켠다. next lint는 기본으로 src만 훑어서 e2e가 한 번도 린트되지 않고 있었다. lint 스크립트에 대상 디렉터리를 명시한다. fixture 인자 이름 use가 React Hook으로 오인되는 문제가 있어 같은 범위에서만 rules-of-hooks를 끈다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
납부 상태 저장이 실패하면 참여자 카드가 토스트를 띄운 뒤 오류를 다시 던진다. 던지는 값이 Error가 아니라 PostgrestError 객체라 브라우저에는 메시지 없는 처리되지 않은 거부만 남고, 오류 추적에 쓸 정보가 사라진다. 사용자에게 보이는 동작은 정상이라 지금은 고치지 않는다. 정산 기록 갱신을 API route로 옮길 때 오류를 구체화하기로 했고, 그때 근거로 쓸 수 있도록 확인한 내용과 재현 방법을 raw에 남긴다. log.md에는 P0 구현 과정과 Notion 자동화 상태 갱신 결과를 함께 기록한다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
develop에 추가된 gh-commit 스킬이 이 브랜치에는 없어 커밋 작업에서 쓸 수 없었다. 해당 경로만 develop에서 가져온다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seeder.createUser가 tag를 네 자리 난수로 뽑는데 user_tag_key 유니크 제약이 걸려 있다. 한 번 실행에 계정을 23개 만들기 때문에 실행당 약 3% 확률로 23505 duplicate key로 실패한다. 다섯 번째 실행에서 실제로 나왔다. user 행 삽입을 insertUserRow로 분리하고 tag 충돌일 때만 최대 열 번까지 다른 값으로 다시 시도한다. 다른 오류는 그대로 던진다. 가입 트리거가 user 행을 만들 수 있다는 기존 주석은 사실과 달라 함께 고쳤다. 인증 사용자를 만든 직후 user 행이 없음을 확인했고, 이 helper가 유일한 생성 경로다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
서비스가 모바일 우선인데 지금까지 Desktop Chrome으로만 검증했다. 프로젝트를 mobile과 desktop 둘로 나눠 평소에는 모바일을 본다. 모바일은 Pixel 5를 쓴다. iPhone 프리셋은 WebKit 기반이라 브라우저 바이너리를 하나 더 받아야 한다. CI에서는 next dev 대신 빌드 결과를 실행한다. 경로마다 첫 진입에서 컴파일하는 탓에 느리고 결과가 흔들리던 문제가 사라진다. 같은 16건이 2.9분에서 49초로 줄었고, 병렬도 안정적이라 CI 워커를 2로 올렸다. 27.9초가 된다. 로컬은 dev 서버를 그대로 쓰므로 워커 1을 유지한다. 모바일과 PC 각각 16건 전부 통과하고 개발 DB 잔여 데이터가 없음을 확인했다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
develop과 main 대상 PR에서 Playwright 테스트를 실행한다. develop 대상은 모바일 뷰포트만 보고, 배포 직전인 main 대상에서만 PC 뷰포트를 함께 확인한다. 단위 테스트 워크플로와 별도 파일로 둔다. - 문서만 바뀐 PR은 paths-ignore로 건너뛴다 - 같은 PR에 새 커밋이 오면 concurrency로 이전 실행을 취소한다 - 실패했을 때만 리포트와 trace를 산출물로 올린다. 보존 7일 - Chromium 하나만 설치한다. 두 프로젝트 모두 Chromium 기반이다 secrets 이름에는 E2E_를 붙여 운영 DB 값과 섞이지 않게 하고 워크플로 env에서 앱 환경변수 이름으로 연결한다. 키 이름은 Supabase의 현재 명칭인 publishable key와 secret key를 따른다. 이름이 틀리면 값이 빈 문자열로 들어오고 테스트가 실패가 아니라 건너뛴 채 초록으로 끝난다. 그래서 빌드 전에 Check secrets 단계로 먼저 멈춘다. env 파일을 모두 치우고 워크플로가 주입하는 값만으로 빌드와 테스트가 통과하는 것을 확인했다. 러너에는 .env.local이 없으므로 같은 조건이다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
# Conflicts: # llm-wiki/index.md # llm-wiki/log.md
첫 실행에서 checkout과 setup-node가 Node.js 20을 대상으로 해 러너가 강제로 Node.js 24에서 돌린다는 경고가 떴다. 세 액션 모두 현재 메이저인 v7로 올린다. upload-artifact는 실패했을 때만 도는 단계라 이번 실행으로는 검증되지 않는다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c829d6712d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| env: | ||
| NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.E2E_SUPABASE_URL }} | ||
| NEXT_PUBLIC_SUPABASE_KEY: ${{ secrets.E2E_SUPABASE_PUBLISHABLE_KEY }} | ||
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.E2E_SUPABASE_SECRET_KEY }} |
There was a problem hiding this comment.
Keep the service-role secret out of pull-request code
For same-repository pull requests, this job checks out the proposed merge commit and then runs its Playwright configuration, tests, and Next.js server with the Supabase service-role secret in the environment. Anyone permitted to open a branch PR can therefore add arbitrary Node or server code that transmits this key before the change is reviewed; because the key bypasses RLS, disclosure grants unrestricted access to the test project. Gate this privileged step behind a trusted environment approval or execute only trusted test code rather than passing the secret directly to candidate code.
AGENTS.md reference: AGENTS.md:L105-L109
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
유효한 지적입니다. 메커니즘을 확인했습니다.
pull_request 이벤트가 제안된 병합 커밋의 코드를 실행하고, 그 코드가 SUPABASE_SERVICE_ROLE_KEY가 든 환경에서 돌아가는 것이 맞습니다. fork PR을 배제한 설정은 이 경로를 막지 못합니다. 공격자가 fork가 아니라 같은 저장소의 브랜치를 쓰기 때문입니다.
이번 PR에서는 수용하고 #212로 분리했습니다.
노출되는 값은 개발 Supabase의 secret key 하나이고, 해당 프로젝트에는 실제 고객 데이터가 없습니다(user 행 4개). 악용 가능한 사람도 조직에 push 권한이 있는 인원으로 한정됩니다. 운영에는 닿지 않습니다.
제안하신 environment 승인 게이트는 검토했으나 선택하지 않았습니다. 노출은 막지만 매 PR마다 수동 승인이 생겨, 자동 게이트를 도입하는 이 PR의 목적과 정면으로 부딪힙니다.
구조를 바꾸는 것은 권한 자체를 좁히는 방식이라고 판단해 #212에서 다룹니다. seed와 cleanup을 서버 쪽 진입점 뒤로 옮기고, CI에는 E2E 테스트 계정과 그 데이터만 다룰 수 있는 자격증명만 두는 방향입니다.
다만 이 판단은 "개발 DB에 지킬 데이터가 없다"는 전제에 기대고 있어, 전제가 바뀌면 #212의 우선순위를 올려야 합니다. 수용 근거는 PR 본문에도 남겼습니다.
Summary
Notion
테스트 시나리오DB의 P0 15건을 Playwright 테스트 코드로 작성했습니다.develop과main대상 PR에서 이 테스트를 실행하는 GitHub Actions 워크플로우를 추가했습니다.구현 설계서 v2의 Task 6(#210)과 Task 7(#211)에 해당합니다.
Why
왜 이 작업을 진행했나요?
Decision
어떤 구현 방식을 선택했나요?
선택한 구현 방식
describe는 시나리오,test는 케이스 하나이고 각test위 주석이 NotionID입니다.localStorage에 주입하고 시작합니다.next dev가 아니라 빌드 결과(next start)를 실행합니다.검토한 대안
develop→main단계에서만 실행하는 방법선택 이유
data-testid는 변경 범위가 넓은 대신 UI 구조 변경에 견딥니다.Changes
무엇이 변경되었나요?
Feature
Fix
tag가user_tag_key유니크 제약과 충돌하면 다른 값으로 다시 시도하도록 수정했습니다.Refactor
data-testid9개를 제품 코드에 추가했습니다. className, DOM 구조, 로직은 바꾸지 않았습니다.Checkbox에 선택 proptestId를 추가했습니다. 넘기지 않으면 속성이 렌더되지 않습니다.Test
e2e/{auth,group,invite,settlement}.spec.ts로 작성했습니다.env,seed,session,ui,testmobile(Pixel 5)과desktop(Desktop Chrome)으로 분리했습니다.e2e디렉터리에@typescript-eslint/no-floating-promises를 적용하고next lint범위를 넓혔습니다.Docs
.github/workflows/ci-e2e-test.yml을 추가했습니다.e2e/README.md에 실행 준비, 인증 방식, 뷰포트, CI 절차를 정리했습니다.llm-wiki/raw에 보존했습니다.Trade-off
한계와 트레이드오프
PostgrestError객체를 다시 던져 메시지 없는 unhandled rejection이 남습니다. API route 이관 시 구체화하기로 하고 이번에는 사용자에게 보이는 결과만 검증합니다.수용한 보안 지적
service role key가 PR 코드와 같은 환경에서 실행됩니다
Codex 리뷰에서 P1으로 지적된 내용이며, 메커니즘은 사실로 확인했습니다.
pull_request이벤트는 제안된 병합 커밋의 코드를 실행합니다. 테스트 단계는 그 코드를SUPABASE_SERVICE_ROLE_KEY가 든 환경에서 돌리므로, 브랜치 PR을 열 수 있는 사람은 리뷰 전에 키를 외부로 보내는 코드를 넣을 수 있습니다. fork PR을 배제한 설정은 이 경로를 막지 못합니다. 공격자가 fork가 아니라 같은 저장소의 브랜치를 쓰기 때문입니다.이번 PR에서는 수용하고 #212로 분리했습니다. 근거는 다음과 같습니다.
E2E_SUPABASE_SECRET_KEY하나입니다. publishable key와 URL은 공개를 전제로 한 값입니다user행 4개)검토했으나 선택하지 않은 대안
권한 자체를 좁히는 것만이 구조를 바꾸므로 #212에서 다룹니다. 다만 이 판단은 "개발 DB에 지킬 데이터가 없다"는 전제에 기대고 있습니다. 참여 인원이 늘거나 개발 DB에 의미 있는 데이터가 쌓이면 #212의 우선순위를 올려야 합니다.
Impact
기존 기능에 미치는 영향
data-testid속성 추가와Checkbox의 선택 prop 추가뿐이고, 렌더 결과와 동작은 동일합니다.Checkbox는 참여자 카드와 약관 동의 폼이 함께 사용합니다.testId를 넘기지 않는 약관 동의 폼에는 속성이 렌더되지 않는 것을 확인했습니다.develop대상 모든 PR에 E2E 게이트가 걸립니다.Edge Cases
Edge Case 및 실패 시나리오
tag충돌: 네 자리 난수라 실행당 약 3% 확률로 유니크 제약에 걸립니다. 실제로 발생했고 재시도로 해결했습니다.Check secrets단계로 빌드 전에 멈춥니다.pull_request이벤트는 병합 커밋 위에서 실행되므로 충돌이 있으면 워크플로우가 아예 돌지 않습니다. 실패로 보이지도 않습니다.Review Points
리뷰어가 집중해서 봐야 할 부분
🔴 High
data-testid위치: 조회 카드는 값 div에, 편집 카드는 입력 요소에 붙였습니다. 컴포넌트 소유자 관점에서 어색한 곳이 있는지 봐주세요.🟡 Medium
develop은 모바일만,main에서 PC를 추가합니다. 이 분배가 맞는지 판단이 필요합니다.retries: 2: 실행이 짧아져 재시도 비용은 작지만, 재시도가 성공하면 간헐 실패가 결과에 묻힙니다. 1로 낮출지 의견을 주세요.E2E_접두어에 Supabase 현재 명칭(publishable/secret)을 붙였습니다. 앱 환경변수 이름은 그대로 두고 워크플로우env에서 연결합니다.🟢 Low
Checkbox의testIdprop: 선택 prop을 추가하는 방식이 적절한지 봐주세요.e2e한정 lint 규칙:react-hooks/rules-of-hooks를 껐습니다. Playwright fixture 인자 이름이use라 오탐이 납니다.Validation
어떻게 검증했나요?
실행 결과
eslint e2eexit 0, prettier 통과확인하지 못한 것
upload-artifact는 실패 시에만 도는 단계라 이번 성공 실행으로는 검증되지 않았습니다.main대상 PR의 PC 뷰포트 matrix는 아직 실제로 실행된 적이 없습니다.