From 1948bd9dba12acf3bde1cfdffa7252128b1dcb5a Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 23 Apr 2026 20:35:10 +0900 Subject: [PATCH 1/9] fix(admin): hard-reload + cookie clear on logout (#322 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - router.replace()는 client soft-nav이라 AdminLayout(Server Component)이 재실행되지 않아 로그아웃 후에도 사이드바가 그대로 남음. - AuthProvider의 SIGNED_OUT 핸들러는 /api/auth/session DELETE를 비동기로 호출하지만 handleLogout에서 await할 수 없어, 세션 쿠키가 살아있는 상태로 /admin/login에 진입 → proxy.ts가 다시 /admin으로 바운스. - 쿠키 DELETE를 inline에서 await한 뒤 window.location.assign()으로 하드 네비게이션해 layout RSC가 admin chrome 없이 재렌더되도록 수정. --- .../web/lib/components/admin/AdminSidebar.tsx | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/web/lib/components/admin/AdminSidebar.tsx b/packages/web/lib/components/admin/AdminSidebar.tsx index f86447dd..b9dd90d9 100644 --- a/packages/web/lib/components/admin/AdminSidebar.tsx +++ b/packages/web/lib/components/admin/AdminSidebar.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; -import { usePathname, useRouter } from "next/navigation"; +import { usePathname } from "next/navigation"; import { Activity, ArrowLeft, @@ -128,15 +128,25 @@ export function AdminSidebar({ adminName, }: AdminSidebarProps) { const pathname = usePathname(); - const router = useRouter(); const logout = useAuthStore((state) => state.logout); async function handleLogout() { - // AdminLayout is a Server Component — it does not re-render on client - // auth state changes. After signing out we must push to /admin/login - // ourselves, otherwise the admin chrome stays visible with stale data. + // AdminLayout is a Server Component, so client soft-nav (router.replace) + // would leave the sidebar mounted around the login page. Three things must + // happen in order before we navigate: + // 1) Supabase signOut (clears localStorage tokens) + // 2) DELETE /api/auth/session (clears server cookies — AuthProvider's + // SIGNED_OUT handler does this async, but we cannot await it here, + // and proxy.ts will bounce /admin/login → /admin if the session + // cookie still validates as admin) + // 3) Hard navigation so the server layout re-runs without admin chrome await logout(); - router.replace("/admin/login"); + try { + await fetch("/api/auth/session", { method: "DELETE" }); + } catch { + // Cookie clear is best-effort; signOut already invalidated tokens. + } + window.location.assign("/admin/login"); } return ( From 9581ea46eaeaf1d5228e4dca8d26a2e75491783a Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 23 Apr 2026 20:35:19 +0900 Subject: [PATCH 2/9] perf(admin): use getSession in layout to skip Supabase Auth roundtrip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Admin layout was calling supabase.auth.getUser(), which hits the Supabase Auth server for JWT validation (~100-500ms) on every /admin/* request. proxy.ts already validates the cookie chain for every matched route and invokes checkIsAdmin (DB), so the layout's own getUser() is pure duplicate latency on the post-login first paint. Switch to getSession() (cookie-cached, ~5-50ms). checkIsAdmin (DB) remains the source of truth for admin role — the extra auth-server roundtrip is what we drop, not the authorization check. --- packages/web/app/admin/layout.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/web/app/admin/layout.tsx b/packages/web/app/admin/layout.tsx index f32e1b5a..27c9f39b 100644 --- a/packages/web/app/admin/layout.tsx +++ b/packages/web/app/admin/layout.tsx @@ -15,6 +15,13 @@ import { AdminLayoutClient } from "@/lib/components/admin/AdminLayoutClient"; * - No session → render children only (login page shows without admin chrome) * - Not admin → render children only * - Is admin → render AdminLayoutClient with sidebar + content + * + * Performance: uses `auth.getSession()` (cookie-cached, ~5–50ms) instead of + * `auth.getUser()` (Supabase Auth network call, ~100–500ms). proxy.ts already + * validated the cookie chain for every /admin/* request, and `checkIsAdmin` + * (DB) remains the source of truth for admin role — so dropping the extra + * auth-server roundtrip is safe and shaves perceptible latency off the + * post-login first paint. */ export default async function AdminLayout({ children, @@ -24,15 +31,16 @@ export default async function AdminLayout({ const supabase = await createSupabaseServerClient(); const { - data: { user }, - } = await supabase.auth.getUser(); + data: { session }, + } = await supabase.auth.getSession(); - // No user: render children directly (proxy.ts handles redirecting + // No session: render children directly (proxy.ts handles redirecting // non-login admin routes to /admin/login) - if (!user) { + if (!session) { return <>{children}; } + const user = session.user; const isAdmin = await checkIsAdmin(supabase, user.id); if (!isAdmin) { return <>{children}; From 60e4a9ee4a5d11858c550b6d363964c2b2c24882 Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 23 Apr 2026 20:36:09 +0900 Subject: [PATCH 3/9] =?UTF-8?q?docs(quick-260423-sfx):=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EB=B2=84=EA=B7=B8=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=9B=84=20=EC=A7=84=EC=9E=85=20=EC=86=8D=EB=8F=84?= =?UTF-8?q?=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/STATE.md | 3 +- .../260423-sfx-PLAN.md | 72 +++++++++++++++++++ .../260423-sfx-SUMMARY.md | 48 +++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 .planning/quick/260423-sfx-admin-logout-and-speed/260423-sfx-PLAN.md create mode 100644 .planning/quick/260423-sfx-admin-logout-and-speed/260423-sfx-SUMMARY.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 301328c0..6126bee2 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -59,10 +59,11 @@ See: .planning/PROJECT.md | 260402-qhu | post 상세페이지 및 오른쪽 모달 패널 컴포넌트 크기 조정 | 2026-04-02 | 890374ce | [260402-qhu-post](./quick/260402-qhu-post/) | | 260403-kp5 | 헤더 ASCII 로고 오른쪽 밀림/잘림 수정 | 2026-04-03 | - | [260403-kp5](./quick/260403-kp5-fix-header-ascii-logo-shifting-right-and/) | | 260404-vco | Editorial+Trending 메인페이지 섹션 키우기 | 2026-04-04 | - | [260404-vco-editorial-trending](./quick/260404-vco-editorial-trending/) | +| 260423-sfx | 관리자 페이지 로그아웃 레이아웃 버그 및 로그인 후 진입 속도 최적화 | 2026-04-23 | 9581ea46 | [260423-sfx](./quick/260423-sfx-admin-logout-and-speed/) | --- -Last activity: 2026-04-04 - Completed quick task 260404-vco: Editorial+Trending 메인페이지 섹션 키우기 +Last activity: 2026-04-23 - Completed quick task 260423-sfx: 관리자 페이지 로그아웃 레이아웃 버그 및 로그인 후 진입 속도 최적화 _Created: 2026-02-05_ _Reset: 2026-04-02 (harness optimization — GSD lightweight mode)_ diff --git a/.planning/quick/260423-sfx-admin-logout-and-speed/260423-sfx-PLAN.md b/.planning/quick/260423-sfx-admin-logout-and-speed/260423-sfx-PLAN.md new file mode 100644 index 00000000..984b89c1 --- /dev/null +++ b/.planning/quick/260423-sfx-admin-logout-and-speed/260423-sfx-PLAN.md @@ -0,0 +1,72 @@ +--- +id: 260423-sfx +description: 관리자 페이지 로그아웃 레이아웃 버그 및 로그인 후 진입 속도 최적화 +date: 2026-04-23 +must_haves: + truths: + - 로그아웃 후 사이드바(admin chrome)가 즉시 사라져야 한다 + - 로그아웃 직후 /admin/login 으로 이동했을 때 proxy.ts가 다시 /admin 으로 리다이렉트하지 않아야 한다 + - 로그인 → /admin 진입 시 layout 단계에서 Supabase Auth 서버 호출(getUser)을 제거하여 TTFB를 단축해야 한다 + artifacts: + - packages/web/lib/components/admin/AdminSidebar.tsx (handleLogout) + - packages/web/app/admin/layout.tsx (auth fast-path) + key_links: + - packages/web/proxy.ts:22-24,44-49,52-66 (proxy already validates admin via getSession + checkIsAdmin) + - packages/web/lib/components/auth/AuthProvider.tsx:73-77 (SIGNED_OUT 핸들러: clearSessionCookies는 비동기로 실행되어 logout 호출자가 await 불가) + - packages/web/app/admin/login/page.tsx:34-36 (login 흐름은 같은 이유로 window.location.href 사용 — 일관 패턴) +--- + +## Bug 1 — 로그아웃 후 사이드바 잔존 + +### Root cause +`lib/components/admin/AdminSidebar.tsx` line 134-140: +```tsx +async function handleLogout() { + await logout(); + router.replace("/admin/login"); +} +``` +- `router.replace`는 client-side soft navigation. `app/admin/layout.tsx`는 Server Component이므로 클라이언트 auth 변경에 반응하지 않음 → AdminLayoutClient(사이드바)가 그대로 남음. +- 게다가 `AuthProvider`의 SIGNED_OUT 이벤트 핸들러가 `await fetch("/api/auth/session", { method: "DELETE" })`를 비동기로 호출하지만 `handleLogout`은 그 완료를 기다릴 수 없음. 그 사이 `router.replace("/admin/login")` 발생 → proxy.ts의 `/admin/login` 분기(line 44-49)가 여전히 유효한 세션 쿠키를 보면 `/admin`으로 다시 리다이렉트. 사이드바가 영영 안 사라지는 이유. + +### Fix +`handleLogout` 안에서: +1. `await logout()` (Supabase signOut, localStorage 정리) +2. `await fetch("/api/auth/session", { method: "DELETE" })` — 쿠키를 명시적으로 inline에서 클리어 (AuthProvider가 비동기로 같은 일을 하지만 await 불가) +3. `window.location.assign("/admin/login")` — 하드 네비게이션으로 Server Component 재실행 강제 + +login 페이지도 같은 이유로 `window.location.href = "/admin"`을 쓰고 있으므로 패턴 일관성 유지. + +## Bug 2 — 로그인 후 /admin 진입 속도 + +### Root cause +`app/admin/layout.tsx`: +```tsx +const { data: { user } } = await supabase.auth.getUser(); +if (!user) return <>{children}; +const isAdmin = await checkIsAdmin(supabase, user.id); +``` +- `supabase.auth.getUser()`는 매 요청마다 Supabase Auth 서버에 JWT 검증 네트워크 호출(~100–500ms). +- proxy.ts(line 52-66)가 이미 `getSession()` (쿠키 캐시, ~5–50ms) + `checkIsAdmin`으로 인증·권한을 모두 검증 완료한 상태. layout이 다시 무거운 `getUser`를 부르는 건 순수 중복 비용. + +### Fix +`app/admin/layout.tsx`의 `getUser()`를 `getSession()`으로 교체. 보안 모델은 변하지 않음: +- 권한의 SoT는 `checkIsAdmin`(DB 조회)이며 그대로 유지 +- proxy.ts가 모든 `/admin/*` 요청에서 동일 검증을 통과시킨 후에만 layout이 실행됨 +- `/admin/login`은 proxy가 admin은 `/admin`으로 바운스(line 44-49)하므로 layout 도달 시점엔 비-admin/비로그인만 존재 → 사이드바 미렌더 분기로 안전 처리 + +기대 효과: layout 단의 Supabase Auth 네트워크 라운드트립 1회 제거. 로그인 직후 /admin 첫 페인트가 100~500ms 빨라짐. + +## Tasks + +### Task 1 — Fix logout hard reload + cookie race +- files: `packages/web/lib/components/admin/AdminSidebar.tsx` +- action: `handleLogout` 안에서 (a) `await logout()` 후 (b) `await fetch("/api/auth/session", { method: "DELETE" })`로 쿠키 명시 클리어, (c) `window.location.assign("/admin/login")`로 하드 네비게이션. `useRouter` 미사용 시 import 제거. +- verify: 빌드 통과(`bun run --cwd packages/web typecheck` 또는 lint), 로그아웃 시도시 사이드바 즉시 사라지고 `/admin/login` 표시. +- done: AdminLayout이 비-admin 분기로 떨어져 children만 렌더되는 것을 확인. + +### Task 2 — Use getSession in admin layout +- files: `packages/web/app/admin/layout.tsx` +- action: `supabase.auth.getUser()` → `supabase.auth.getSession()`. `session.user`로 적절히 분기. 주석에 proxy.ts가 이미 검증한다는 점과 getSession 사용 근거 추가. +- verify: 타입체크 통과, /admin 진입 시 사이드바 정상 렌더, 비로그인 상태에서 /admin/login 접근 시 사이드바 미렌더. +- done: layout RSC에서 Supabase Auth 호출이 제거됨. diff --git a/.planning/quick/260423-sfx-admin-logout-and-speed/260423-sfx-SUMMARY.md b/.planning/quick/260423-sfx-admin-logout-and-speed/260423-sfx-SUMMARY.md new file mode 100644 index 00000000..3182450f --- /dev/null +++ b/.planning/quick/260423-sfx-admin-logout-and-speed/260423-sfx-SUMMARY.md @@ -0,0 +1,48 @@ +# Quick Task 260423-sfx — Summary + +**Date:** 2026-04-23 +**Branch:** dev +**Commits:** 1948bd9d, 9581ea46 + +## Problem + +1. 로그아웃 시 사이드바(admin chrome)가 화면에 그대로 남음. +2. 로그인 후 `/admin` 진입 시간이 체감상 느림. + +## Root causes + +### Bug 1 — sidebar persists after logout +- `AdminSidebar.handleLogout`가 `router.replace("/admin/login")`로 soft-nav. `app/admin/layout.tsx`는 Server Component이므로 client auth 변경을 감지하지 못해 `AdminLayoutClient`(사이드바)가 mount된 상태로 유지. +- `AuthProvider`는 `SIGNED_OUT` 이벤트에서 `DELETE /api/auth/session`을 비동기로 호출하지만 `handleLogout` 호출자가 await할 수단이 없음. 서버 쿠키가 아직 살아있는 상태로 `/admin/login`에 도달하면 `proxy.ts:44-49`가 admin으로 인식해 `/admin`으로 다시 리다이렉트. 사이드바가 영영 사라지지 않는 진짜 원인. + +### Bug 2 — slow entry to /admin +- `app/admin/layout.tsx`가 `supabase.auth.getUser()` 호출. 이 메서드는 매 요청마다 Supabase Auth 서버에 JWT 검증 네트워크 호출을 수행해 보통 ~100–500ms 소요. +- `proxy.ts`가 이미 `getSession()` + `checkIsAdmin(DB)`으로 `/admin/*` 모든 요청을 검증하므로 layout의 `getUser()`는 순수 중복 비용. + +## Changes + +### `packages/web/lib/components/admin/AdminSidebar.tsx` (commit 1948bd9d) +- `useRouter` 제거. +- `handleLogout`: + 1. `await logout()` (Supabase signOut, localStorage 정리) + 2. `await fetch("/api/auth/session", { method: "DELETE" })` — 쿠키 inline 클리어 (try/catch로 best-effort) + 3. `window.location.assign("/admin/login")` — 하드 네비게이션 + +### `packages/web/app/admin/layout.tsx` (commit 9581ea46) +- `supabase.auth.getUser()` → `supabase.auth.getSession()`. +- `session.user`에서 `adminName`/`user.id` 추출. `checkIsAdmin`(DB, 권한 SoT)는 그대로 유지. +- 주석에 proxy.ts 선행 검증 및 getSession 근거 추가. + +## Verification + +- `bunx tsc --noEmit` 실행 — 수정 파일(`AdminSidebar.tsx`, `app/admin/layout.tsx`) 관련 타입 에러 없음. 기존 무관 테스트 파일(`toDecodeShowcaseData.test.ts`) 1건만 선행 상태. +- 두 커밋 모두 원자적으로 분리하여 rollback 용이. +- 남은 수동 검증 (dev 서버에서 확인 권장): + - 로그아웃 클릭 → `/admin/login`으로 하드 리다이렉트되며 사이드바 즉시 제거되는지 + - 로그인 → `/admin` 대시보드 첫 렌더까지 체감 지연 감소 여부 + +## Risk / Rollback + +- 보안 모델 불변: 권한의 SoT는 `checkIsAdmin`(DB)이고 `proxy.ts`의 admin 검증은 그대로. `getSession`은 proxy에서도 이미 쓰이는 패턴. +- 로그아웃 경로의 쿠키 DELETE는 AuthProvider와 중복이지만 멱등하고 best-effort. 실패해도 signOut이 토큰을 무효화한 상태. +- Rollback: 두 커밋을 `git revert` 하면 이전 동작으로 복구. From 0fcf3a2163dd5d993e221a478f1c700f4dd46abc Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 23 Apr 2026 20:57:34 +0900 Subject: [PATCH 4/9] =?UTF-8?q?docs(specs):=20E2E=20=ED=95=98=EB=93=9C?= =?UTF-8?q?=EB=8B=9D=20=EB=A1=9C=EB=93=9C=EB=A7=B5=20=EC=9E=AC=EC=9A=B0?= =?UTF-8?q?=EC=84=A0=EC=88=9C=EC=9C=84=ED=99=94=20=E2=80=94=20=EC=9D=B8?= =?UTF-8?q?=ED=94=84=EB=9D=BC=20=ED=8D=BC=EC=8A=A4=ED=8A=B8=20(#170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Epic #170의 P1~P3 재배열 대신 P0 인프라 스프린트 신설 - 발견: CI에 E2E job 없음 / "80%"는 테스트 개수 기준 vanity metric / auth regression umbrella 부재 - 1주 스프린트(04-24~04-30): GitHub Actions e2e.yml + CI secret 주입 + baseline 측정 + 커버리지 재정의 + auth safety net 이슈 #179 생성 - mutation-covered critical flows / total critical flows 공식으로 재정의, baseline ~12% (2/17) Co-Authored-By: Claude Opus 4.7 (1M context) --- ...3-e2e-hardening-reprioritization-design.md | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md diff --git a/docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md b/docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md new file mode 100644 index 00000000..981505ce --- /dev/null +++ b/docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md @@ -0,0 +1,250 @@ +--- +title: E2E Hardening 로드맵 재우선순위화 — 인프라 퍼스트 (Epic #170) +owner: human +status: draft +updated: 2026-04-23 +tags: [testing, infrastructure, hardening] +related_issues: [170, 171, 172, 173, 174, 175, 176, 177, 162] +--- + +# E2E Hardening 로드맵 재우선순위화 — 인프라 퍼스트 (Epic #170) + +## 배경 + +Epic #170(프론트엔드 E2E 핵심 기능 커버리지 확대)의 자식 이슈 우선순위를 재평가하던 중, **P1~P3 재배열 자체가 상위 문제의 증상**임이 드러났다. 기존 접근은 "어떤 플로우를 먼저 테스트할지"를 다뤘지만, 실제 병목은 아래 세 가지다. + +### 발견된 구조적 문제 3가지 + +1. **CI에서 E2E가 실행되지 않는다** + - `.github/workflows/` 인벤토리: `claude-review` · `daily-digest` · `deploy-backend` · `health-check` · `telegram-notify` · `wiki-lint` — **playwright job 0개**. + - `SUPABASE_SERVICE_ROLE_KEY` CI secret 주입 흔적 없음. + - 유일한 게이트는 `packages/web/scripts/pre-push.sh`의 `RUN_E2E=1` 환경변수 — 기본값 skip. + - 결과: 1,145줄의 E2E spec은 자동 회귀 방지 기능이 사실상 0. PR merge 시점에 실행되지 않으면 Epic #170을 완수해도 실효가 제한적. + +2. **"80% 커버리지" 타겟이 vanity metric** + - #162 본문: Phase 4 `~60개+ (80% 목표)` — 분모가 **테스트 개수**임. + - 분모가 routes/flows/mutations가 아니라 개수이므로, shallow `page.goto → expect(url)` 류 spec 40개 추가로 숫자 달성 가능. + - 숫자가 올라도 최근 8건(#313/#314/#319/#321/#322/#297/#310/#298) 같은 auth regression은 계속 새어 나갈 수 있음. + +3. **Auth regression이 클래스로 추적되지 않는다** + - 최근 48시간에 auth/admin 관련 프로덕션 fix PR 8개 머지. + - 각각은 one-off fix로 닫힘 — umbrella 이슈나 회귀 방지 체크리스트 없음. + - 결과: 같은 유형의 버그가 다음 배포에서도 재발할 가능성이 구조적으로 열려 있음. + +### 재우선순위화의 실제 의미 + +위 세 문제를 해결하지 않고 Epic #170의 P1~P3만 재배열하면, **어떤 순서로 작업해도 회귀 방지 ROI가 제한**된다. 따라서 이번 스프린트는 새 spec 추가가 아니라 **인프라 P0를 신설해 앞으로 추가되는 모든 spec이 실제 gate 역할을 하도록** 만든다. + +## 목표 (1주 스프린트: 2026-04-24 ~ 2026-04-30) + +스프린트 종료 시점 상태: + +1. GitHub Actions에 E2E workflow 존재 + 모든 PR + `dev` push에서 실행됨 +2. 기존 13개 spec의 CI baseline 측정됨 (pass/fail/skip 수치) +3. "80% 커버리지"가 **mutation-covered critical flows / total critical flows** 공식으로 재정의됨 +4. Auth regression umbrella 이슈 `#179` 생성 + 체크리스트 완성 +5. (Non-goal) 새 feature spec 추가 없음 — 다음 스프린트부터 + +## 현재 상태 (2026-04-23 기준) + +### Epic #170 자식 이슈 + +| # | 우선순위 | 제목 | 상태 | 실질 커버리지 | +|---|---|---|---|---| +| #171 | P1 | 업로드 전체 플로우 E2E | OPEN | ~30% — PR #248이 modal lifecycle만 cover, Submit → POST 검증 없음 | +| #172 | P1 | Engagement mutation 검증 | CLOSED (2026-04-10) | ~60% — adopt/auth-guard/persistence 브랜치 미커버, closed는 PR merge 기준 | +| #173 | P2 | VTON 플로우 E2E | OPEN | 0 | +| #174 | P2 | Editorial/Magazine 상세 E2E | OPEN | 0 (admin/magazine-approval은 별건) | +| #175 | P2 | Profile 편집·통계·활동 E2E | OPEN | 0 | +| #176 | P3 | Admin 대시보드 E2E | OPEN | 부분 — admin/magazine-approval.spec.ts (단, CI 미실행 시 test.skip) | +| #177 | P3 | 에러 상태 + 권한 검증 E2E | OPEN | 0 | + +**체크리스트 가중 실질 커버리지: ~10-12%** (이슈 단위 20% 주장은 스코프 편차 무시한 과대 계상). + +### 실제 spec 인벤토리 (`packages/web/tests/`, 총 1,145줄) + +``` +ai-pipeline.spec.ts 123 +api-migration.spec.ts 119 +content-consumption.spec.ts 63 +content-creation.spec.ts 38 +engagement.spec.ts 158 # #172 close 근거 +login.spec.ts 45 +navigation.spec.ts 41 +post-navigation-perf.spec.ts 208 +upload-direct.spec.ts 28 # PR #248 (modal lifecycle) +upload-draft.spec.ts 38 # PR #248 +upload-intercept.spec.ts 60 # PR #248 +upload-mobile.spec.ts 27 # PR #248 +visual-qa.spec.ts 47 +admin/magazine-approval.spec.ts 150 # service-role key 없으면 test.skip +``` + +## 설계 — P0 인프라 작업 분해 + +### A1. `.github/workflows/e2e.yml` 신설 + +**트리거:** +- `pull_request` (paths filter: `packages/web/**`, `packages/api-server/**`) +- `push: branches: [dev]` + +**러너:** `ubuntu-latest`, 타임아웃 15분 + +**스텝:** +1. `actions/checkout@v4` +2. `oven-sh/setup-bun@v1` +3. `bun install` +4. `bunx playwright install --with-deps chromium` +5. web 워크스페이스에서 `bun x playwright test` +6. `actions/upload-artifact@v4` — 실패 시 `test-results/` + `playwright-report/` 업로드 + +**환경 분기:** +- Supabase cloud DEV 엔드포인트 사용 (self-hosted는 로컬 전용) +- 환경변수는 workflow의 `env:` 블록에서 `${{ secrets.* }}`로 주입. `NEXT_PUBLIC_*`는 빌드 타임에 읽히므로 `bun install` + playwright 실행 전에 주입 필수 + +**Why:** pre-push만으로는 개발자가 `RUN_E2E=1` 안 주면 무효. PR 레벨 gate가 필요. + +### A2. CI secret 주입 + +**필요 secret:** +- `SUPABASE_SERVICE_ROLE_KEY` — admin spec의 service role 인증 +- `TEST_USER_EMAIL` / `TEST_USER_PASSWORD` — `tests/auth.setup.ts`가 사용하는 E2E 전용 계정 +- `NEXT_PUBLIC_SUPABASE_URL` / `NEXT_PUBLIC_SUPABASE_ANON_KEY` — cloud DEV 엔드포인트 + +**수행 방법:** `gh secret set --repo decodedcorp/decoded` (repo admin 권한 필요) + +**보안 체크:** secret 값은 대화에 붙여넣지 않고 명령어 기반 주입. + +### A3. 첫 CI 실행 + baseline 수집 + +**절차:** +1. A1/A2 머지 후 첫 PR 트리거로 실행 +2. 결과 기록: pass / fail / skip 개수 + 실패 spec 리스트 +3. 실패 spec은 이번 스프린트 fix scope-out — 다음 스프린트 백로그로 이전 +4. 결과를 `docs/agent/e2e-baseline-2026-04-30.md`에 기록 + +**분류:** +- **Pass**: 즉시 회귀 방지 역할 시작 +- **Fail (flake/env)**: 인프라 이슈 — 다음 스프린트 fix +- **Fail (spec bug)**: 로직 결함 — 이슈 등록 후 백로그 +- **Skip (service key 등)**: A2 주입 후 재평가 + +### A4. pre-push 훅 기본값 정책 + +**결정:** 현재 `RUN_E2E` 기본 skip 유지. + +**이유:** 로컬 개발 속도(훅 실행 시간) > 로컬 pre-push의 한계적 회귀 방지 가치. CI가 primary gate가 되면 pre-push는 옵션. + +**후속:** 6주 후 재평가. + +### A5. 커버리지 정의 재작성 (`docs/agent/e2e-coverage-definition.md`) + +**신 공식:** + +``` +coverage = (mutation-covered critical flows) / (total critical flows) +target = 80% +``` + +**"critical flow" 1차 인벤토리:** + +1. 로그인 — email/password +2. 로그인 — OAuth (Google) +3. 로그인 — Continue as Guest +4. 로그아웃 +5. 업로드 — 파일 선택 → User-type 선택 → 분기 완주 → Submit → POST `/api/v1/posts` → 리다이렉트 +6. Engagement — 좋아요 toggle (POST/DELETE + 새로고침 유지) +7. Engagement — 저장 toggle (+ Profile Saved 탭 확인) +8. Engagement — 솔루션 채택 (POST + UI 반영) +9. Profile — 본인 프로필 편집 → 저장 → 반영 +10. Profile — 공개 프로필 Follow toggle +11. Magazine — 홈 EditorialCarousel → 상세 → 포스트 상세 네비게이션 +12. VTON — 모달 진입 → 사진 업로드 → Try On → 결과 표시 +13. Admin — entity CRUD (artist/brand/group-members 중 1개 대표 플로우) +14. Admin — seed 승인/거절 +15. 에러 — API 500 → 에러 UI + retry +16. 에러 — 빈 상태 (empty state UI) +17. 에러 — 권한 거부 (비로그인 → /login 리다이렉트) + +**"mutation-covered" 기준:** +- spec이 해당 플로우의 핵심 API mutation (POST/PUT/DELETE)의 **호출 + UI 반영**을 모두 assert +- 단순 `page.goto → expect(url)`만으로는 카운트 안 함 +- `page.route` mock으로 대체한 경우 — 실 요청 어썬션이 있어야 인정 + +**baseline (현재):** 총 17개 critical flow 중 mutation-covered = **2개** (좋아요·저장) ≈ **~12%**. 80% 달성까지 약 12개 flow 추가 필요. + +**변경 반영:** #162 본문을 이 정의로 edit. + +### A6. 새 이슈 `#179: Auth regression safety net` + +**라벨:** `testing` + `frontend` + `P1` +**Epic 링크:** #170 + +**체크리스트 (기존 프로덕션 fix를 회귀 테스트로 변환):** +- [ ] 게스트 세션 지속성 — Continue as Guest → hard navigation → 세션 유지 (#296/#298/#310 회귀) +- [ ] OAuth redirect URL — Google 로그인 → redirect 복귀 → 세션 활성 (#297 회귀) +- [ ] Admin middleware layer order — 비로그인 → /admin → /admin/login 리다이렉트 + admin 로그인 → /admin 접근 성공 (#321 회귀) +- [ ] Admin 세션 토큰 포워딩 — admin 로그인 후 rust admin endpoint 호출 성공 (#319 회귀) +- [ ] Admin 로그아웃 리다이렉트 — /admin에서 logout → /admin/login 이동 (#322 회귀) +- [ ] `auth.users` insert trigger — 신규 가입 → public.users row 생성 (#313/#314 회귀) +- [ ] `checkIsAdmin` maybeSingle 처리 — admin 레코드 없는 사용자에게 500 안 터짐 (#313 회귀) + +**data-testid 작업 포함:** 위 플로우에 필요한 testid가 없으면 컴포넌트 수정도 이 이슈 scope. + +**본 스프린트 scope:** 이슈 생성 + 체크리스트 확정만. 실제 spec 작성은 다음 스프린트. + +### A7. 회고 노트 + +**위치:** `.omc/notepad.md` 또는 `docs/superpowers/briefs/2026-04-30-e2e-infra-retro.md` + +**포함:** +- 이번 스프린트 blocker +- A3 baseline 결과 요약 +- 다음 스프린트 추천 (A6 실행 + #171 submit mutation tail + #177 에러/권한) + +## 타임박스 일정 (2026-04-24 ~ 2026-04-30) + +| 일자 | 작업 | +|---|---| +| 목 04-24 | A1 workflow 파일 작성 + 로컬 dry-run | +| 금 04-25 | A2 secret 주입 + 첫 CI 트리거 | +| 월 04-28 | A3 baseline 수집 + 분류 | +| 화 04-29 | A5 커버리지 정의 문서 + #162 edit | +| 수 04-30 | A6 #179 이슈 등록 + A7 회고 | + +## 리스크 + 대응 + +| 리스크 | 확률 | 영향 | 대응 | +|---|---|---|---| +| repo secret 주입 권한 블로커 | 저 | 고 | 본인이 `gh secret set` 직접 실행 (thxforall이 admin 권한 보유 전제) | +| 첫 CI 실행 시 다수 spec 실패 | 중 | 중 | Day 2 30분 스파이크 → 실패분 scope-out 후 백로그 이전 | +| Cloud DEV seed 오염 | 중 | 중 | `tests/fixtures/cleanup.ts` 추가 + 전용 test-user 격리 | +| "80% 재정의" 팀 합의 없이 단독 진행 | 중 | 고 | PR에 RFC 태그 + 리뷰 요청. #162 edit은 리뷰 후 실행 | +| auth umbrella #179 스코프 팽창 | 저 | 중 | 이번 스프린트는 **이슈만 생성**, 실행은 다음 스프린트 | + +## Out of Scope + +- 새 feature spec (#171/#173/#175 등) 작성 — 다음 스프린트 +- pre-push `RUN_E2E=1` 기본값 전환 — 6주 후 재평가 +- `admin/magazine-approval.spec.ts`의 `test.skip` 조건 제거 — 별건 +- cypress/vitest 마이그레이션 등 툴 변경 — 논외 +- backend E2E (api-server) — 본 epic은 프론트 한정 + +## 다음 스프린트 preview (참고) + +이번 스프린트 완료 후 다음 순서 권장: + +1. **#179 Auth safety net** 실행 (P1) — 실증 회귀 방지가 가장 큰 이슈 +2. **#171 Submit mutation tail** (P1) — 업로드 포스트 생성 mutation +3. **#177 에러/권한** (P1로 승격) — 안전망 +4. **#173 VTON** (P2, 단 AI mock 스파이크 선행) +5. **#175 Profile** (P2, testid 작업 포함) +6. **#176 Admin entity CRUD** (P3) +7. **#174 Magazine 상세** (P3) + +## 참고 + +- Epic: https://github.com/decodedcorp/decoded/issues/170 +- Parent roadmap: https://github.com/decodedcorp/decoded/issues/162 +- 최근 auth fix PR: #313 #314 #319 #321 #322 #297 #310 #298 #296 +- 관련 문서: `docs/agent/web-routes-and-features.md`, `packages/web/scripts/pre-push.sh` From 2fecb25709dab856f2dba9acd0fd63aa2c0ad326 Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 23 Apr 2026 21:03:22 +0900 Subject: [PATCH 5/9] =?UTF-8?q?docs(plans):=20E2E=20=ED=95=98=EB=93=9C?= =?UTF-8?q?=EB=8B=9D=20P0=20=EC=9D=B8=ED=94=84=EB=9D=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EA=B3=84=ED=9A=8D=20(#170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 12개 태스크로 분해 (feature branch → workflow → secrets → PR → baseline → docs → umbrella 이슈 → retro → merge) - 각 태스크는 2-5분 단위 step, 실제 명령어와 완성된 파일 내용 포함 - Task 4 (secret 주입)는 유저 수행 구간으로 명시 — agent는 명령어 준비만 - Task 10 새 이슈 번호 #179 가정, 실제 ID 다르면 spec 문서 업데이트 스텝 포함 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-04-23-170-e2e-hardening-infra-p0.md | 855 ++++++++++++++++++ 1 file changed, 855 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-23-170-e2e-hardening-infra-p0.md diff --git a/docs/superpowers/plans/2026-04-23-170-e2e-hardening-infra-p0.md b/docs/superpowers/plans/2026-04-23-170-e2e-hardening-infra-p0.md new file mode 100644 index 00000000..2facb902 --- /dev/null +++ b/docs/superpowers/plans/2026-04-23-170-e2e-hardening-infra-p0.md @@ -0,0 +1,855 @@ +# E2E Hardening Infra P0 Implementation Plan (#170) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Epic #170의 P0 인프라 스프린트 실행 — GitHub Actions E2E workflow 신설 + CI secret 주입 + baseline 측정 + 커버리지 재정의 + auth regression umbrella 이슈 생성. + +**Architecture:** 새 workflow `e2e.yml`이 `pull_request` + `push: dev` 트리거로 `packages/web`의 playwright를 실행. secret은 `gh secret set`으로 repo에 주입. baseline + coverage definition + #179 이슈 body는 모두 docs/issue body로 문서화. + +**Tech Stack:** GitHub Actions, Playwright, Bun, Supabase cloud DEV, gh CLI. + +--- + +## File Structure + +**Create:** +- `.github/workflows/e2e.yml` — CI workflow (Task 2) +- `docs/agent/e2e-coverage-definition.md` — "80%" 재정의 문서 (Task 8) +- `docs/agent/e2e-baseline-2026-04-30.md` — 첫 CI 실행 baseline (Task 6) +- `docs/superpowers/briefs/2026-04-30-e2e-infra-retro.md` — 회고 (Task 11) + +**Modify:** +- GitHub Issue #162 body — 새 커버리지 정의 링크 (Task 9, via gh CLI) + +**External actions (no repo file):** +- Repo secrets 주입 via `gh secret set` (Task 4) +- 새 이슈 #179 생성 via `gh issue create` (Task 10) + +**Spec reference:** `docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md` + +--- + +### Task 1: Feature branch 생성 + +**Files:** (no file changes) + +- [ ] **Step 1: 현재 작업트리 상태 확인** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +git status --short +git branch --show-current +``` +Expected: branch == `dev`, uncommitted .omc state files only (spec 커밋은 이미 반영됨). + +- [ ] **Step 2: 새 feature branch 체크아웃** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +git checkout -b feat/170-e2e-infra-p0 dev +``` +Expected: `Switched to a new branch 'feat/170-e2e-infra-p0'`. + +--- + +### Task 2: `.github/workflows/e2e.yml` 작성 + +**Files:** +- Create: `.github/workflows/e2e.yml` + +- [ ] **Step 1: workflow 파일 생성** + +Create `/Users/kiyeol/development/decoded/decoded-monorepo/.github/workflows/e2e.yml`: + +```yaml +name: E2E Tests + +on: + pull_request: + paths: + - 'packages/web/**' + - 'packages/api-server/**' + - '.github/workflows/e2e.yml' + push: + branches: [dev] + +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + playwright: + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.E2E_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.E2E_SUPABASE_ANON_KEY }} + SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.E2E_SUPABASE_SERVICE_ROLE_KEY }} + TEST_USER_EMAIL: ${{ secrets.E2E_TEST_USER_EMAIL }} + TEST_USER_PASSWORD: ${{ secrets.E2E_TEST_USER_PASSWORD }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Install Playwright browsers + working-directory: packages/web + run: bunx playwright install --with-deps chromium + + - name: Run Playwright tests + working-directory: packages/web + run: bunx playwright test + + - name: Upload test-results on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ github.run_id }} + path: packages/web/test-results/ + retention-days: 7 +``` + +**Rationale:** +- `paths` 필터로 web/api-server 변경 시에만 실행 → 빈번한 docs-only PR에는 무실행 +- `concurrency` 그룹으로 같은 ref의 중복 실행 취소 → 비용 절감 +- `timeout-minutes: 20` — 현재 spec 13개 + playwright install + dev server startup (120s timeout) 여유 +- `bunx playwright install --with-deps` — Ubuntu 시스템 의존성(libnss3 등) 자동 설치 +- upload-artifact `if: failure()` — 실패 시만 업로드. trace.zip + screenshot은 `test-results/`에 생성됨 (playwright.config.ts 기준) +- secrets 네이밍 `E2E_` 프리픽스 — 기존 `SUPABASE_*` secret과 격리해 DEV 환경 전용임을 명시 + +- [ ] **Step 2: YAML 문법 검증** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +python3 -c "import yaml; yaml.safe_load(open('.github/workflows/e2e.yml'))" && echo OK +``` +Expected: `OK` (YAML 문법 오류 없음). + +대체 수단 (actionlint 설치되어 있다면): +```bash +actionlint .github/workflows/e2e.yml +``` +Expected: 출력 없음 = 검증 통과. + +- [ ] **Step 3: workflow 파일 커밋** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +git add .github/workflows/e2e.yml +git commit -m "ci(e2e): add Playwright workflow for PR + dev push (#170) + +- triggers on pull_request (web/api-server paths) and dev push +- uses E2E_* secrets for Supabase cloud DEV + test user credentials +- uploads test-results/ artifact on failure (trace.zip + screenshots) +- concurrency group cancels in-progress runs for the same ref" +``` +Expected: commit 생성, `.github/workflows/e2e.yml` 1 file changed. + +--- + +### Task 3: Feature branch push + +**Files:** (no file changes) + +- [ ] **Step 1: 브랜치 push** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +git push -u origin feat/170-e2e-infra-p0 +``` +Expected: upstream 설정 + `Branch 'feat/170-e2e-infra-p0' set up to track 'origin/feat/170-e2e-infra-p0'`. + +**주의:** push만 할 뿐 PR 생성은 Task 5에서. secret이 아직 없으므로 첫 실행은 실패함 (의도된 순서). + +--- + +### Task 4: CI secret 주입 (유저 수행 구간) + +**Files:** (no file changes — runtime 작업) + +**⚠️ 이 태스크는 agentic worker가 아닌 유저(thxforall)가 수행합니다.** agent는 명령어를 준비하고 실행 확인만 담당. + +- [ ] **Step 1: Supabase cloud DEV 프로젝트에서 E2E 전용 test user 생성** + +절차 (Supabase Studio UI 수행): +1. https://supabase.com/dashboard/project/fvxchskblyhuswzlcmql/auth/users 접속 +2. "Add user" → "Create new user" +3. Email: `e2e-test@decoded.style` +4. Password: 강력한 랜덤 (예: `openssl rand -base64 24`) +5. Auto Confirm User: 체크 +6. 생성된 이메일/비번을 안전한 곳에 임시 저장 (다음 step에서 사용) + +- [ ] **Step 2: repo secret 5개 주입** + +Run (각 명령 실행 시 stdin으로 값 붙여넣기): +```bash +gh secret set E2E_SUPABASE_URL --repo decodedcorp/decoded +# 값: https://fvxchskblyhuswzlcmql.supabase.co + +gh secret set E2E_SUPABASE_ANON_KEY --repo decodedcorp/decoded +# 값: Supabase Studio → Project Settings → API → anon public + +gh secret set E2E_SUPABASE_SERVICE_ROLE_KEY --repo decodedcorp/decoded +# 값: Supabase Studio → Project Settings → API → service_role (⚠️ secret) + +gh secret set E2E_TEST_USER_EMAIL --repo decodedcorp/decoded +# 값: e2e-test@decoded.style + +gh secret set E2E_TEST_USER_PASSWORD --repo decodedcorp/decoded +# 값: Step 1에서 생성한 비번 +``` + +**보안 주의:** 값을 **채팅에 붙여넣지 말 것**. `gh secret set` 명령은 stdin 프롬프트로 안전하게 입력. + +- [ ] **Step 3: secret 주입 확인** + +Run: +```bash +gh secret list --repo decodedcorp/decoded | grep '^E2E_' +``` +Expected 출력 (5줄): +``` +E2E_SUPABASE_ANON_KEY +E2E_SUPABASE_SERVICE_ROLE_KEY +E2E_SUPABASE_URL +E2E_TEST_USER_EMAIL +E2E_TEST_USER_PASSWORD +``` + +--- + +### Task 5: PR 생성 + 첫 CI 실행 트리거 + +**Files:** (no file changes) + +- [ ] **Step 1: PR 생성** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +gh pr create --repo decodedcorp/decoded --base dev --head feat/170-e2e-infra-p0 \ + --title "ci(e2e): add Playwright workflow for PR + dev push (#170)" \ + --body "$(cat <<'EOF' +## Summary + +Epic #170의 P0 인프라 스프린트 첫 단계. GitHub Actions에 E2E workflow 신설. + +## Changes + +- `.github/workflows/e2e.yml` 신규 — pull_request (web/api-server paths) + dev push 트리거 +- `E2E_*` secret 주입 (별건으로 repo admin이 수행 완료 전제) +- Playwright 실행 → 실패 시 test-results/ artifact 업로드 + +## Why + +현재 CI에 E2E job이 없어 1,145줄 spec이 자동 회귀 방지 기능 0. pre-push의 \`RUN_E2E=1\`는 기본값 skip이라 개발자가 켜지 않으면 무효. PR-level gate를 만들어 앞으로 추가되는 모든 spec이 실제 gate 역할을 하도록 함. + +## Baseline measurement + +이 PR 머지 후 첫 CI 실행 결과를 `docs/agent/e2e-baseline-2026-04-30.md`에 기록 예정. + +## Related + +- Epic: #170 +- Design spec: docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md +- Parent roadmap: #162 + +## Test plan + +- [ ] CI에서 workflow 자체가 trigger됨 (Checks 탭 E2E Tests 등장) +- [ ] Playwright 실행 로그 노출 +- [ ] 실패 시 test-results artifact 확인 가능 + +🤖 Generated with [Claude Code](https://claude.com/claude-code) +EOF +)" +``` +Expected: PR URL 출력. + +- [ ] **Step 2: PR 트리거된 CI run 확인** + +Run (PR 생성 후 약 30초 대기): +```bash +sleep 30 +gh run list --repo decodedcorp/decoded --workflow=e2e.yml --branch=feat/170-e2e-infra-p0 --limit=1 +``` +Expected: 1줄 출력 (status=`queued`/`in_progress`/`completed`, conclusion 빈 값 또는 success/failure). + +만약 workflow가 트리거 안 됐으면 paths 필터 점검 (`.github/workflows/e2e.yml` 자체가 paths에 포함되므로 트리거되어야 정상). + +--- + +### Task 6: Baseline 수집 + +**Files:** +- Create: `docs/agent/e2e-baseline-2026-04-30.md` + +- [ ] **Step 1: CI run 완료 대기** + +Run: +```bash +gh run watch --repo decodedcorp/decoded $(gh run list --repo decodedcorp/decoded --workflow=e2e.yml --branch=feat/170-e2e-infra-p0 --limit=1 --json databaseId --jq '.[0].databaseId') +``` +Expected: run 완료 시까지 실시간 로그 출력. + +- [ ] **Step 2: run 로그에서 pass/fail/skip 카운트 추출** + +Run: +```bash +RUN_ID=$(gh run list --repo decodedcorp/decoded --workflow=e2e.yml --branch=feat/170-e2e-infra-p0 --limit=1 --json databaseId --jq '.[0].databaseId') +gh run view $RUN_ID --repo decodedcorp/decoded --log | grep -E "passed|failed|skipped|flaky" | tail -20 +``` +Expected: playwright `list` reporter 요약 라인 (예: `13 passed (2m 10s)` 또는 `5 passed, 3 failed, 2 skipped`). + +실패한 spec 명 수집: +```bash +gh run view $RUN_ID --repo decodedcorp/decoded --log | grep -E "✘|×" | head -20 +``` + +- [ ] **Step 3: baseline 문서 작성** + +Create `/Users/kiyeol/development/decoded/decoded-monorepo/docs/agent/e2e-baseline-2026-04-30.md`: + +```markdown +--- +title: E2E Baseline (2026-04-30) +updated: 2026-04-30 +related_issues: [170, 162] +--- + +# E2E Baseline 2026-04-30 + +첫 CI 실행 (`.github/workflows/e2e.yml`) 결과 스냅샷. 이후 커버리지 작업의 출발점. + +## 실행 환경 + +- Workflow: `.github/workflows/e2e.yml` +- Run ID: `` +- Trigger: PR feat/170-e2e-infra-p0 → dev +- Supabase: cloud DEV (project ref fvxchskblyhuswzlcmql) + +## 결과 + +| 분류 | 개수 | 비고 | +|---|---|---| +| Pass | `<채워넣기>` | 즉시 회귀 방지 역할 시작 | +| Fail (flake/env) | `<채워넣기>` | 다음 스프린트 fix | +| Fail (spec bug) | `<채워넣기>` | 이슈 등록 후 백로그 | +| Skip | `<채워넣기>` | service key 없거나 조건부 skip | + +**총 spec 개수:** `<채워넣기>` (`packages/web/tests/**/*.spec.ts`) + +## 실패한 spec 목록 + +- `` — 원인 분류 (flake / env / spec bug) + 후속 액션 + +## 다음 액션 + +- flake/env 실패분 → 이번 스프린트 scope-out, 다음 스프린트 전 30분 스파이크 +- spec bug 실패분 → GitHub issue 등록 (label: testing) +- pass 수치를 `docs/agent/e2e-coverage-definition.md`의 현재 커버리지 갱신에 반영 +``` + +- [ ] **Step 4: baseline 문서 커밋** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +git add docs/agent/e2e-baseline-2026-04-30.md +git commit -m "docs(agent): add E2E CI baseline snapshot (2026-04-30)" +git push +``` +Expected: 커밋 + PR에 자동 반영. + +--- + +### Task 7: pre-push 정책 재확인 + +**Files:** (no file changes — 정책 결정 문서화) + +- [ ] **Step 1: 현재 pre-push 동작 확인** + +Run: +```bash +grep -A 3 "RUN_E2E" /Users/kiyeol/development/decoded/decoded-monorepo/packages/web/scripts/pre-push.sh +``` +Expected: `${RUN_E2E:-}` 조건분기 + "기본 — 켜려면 RUN_E2E=1" 메시지 출력. + +- [ ] **Step 2: 정책 유지 결정 기록** + +No file change needed — 결정은 design spec `docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md`의 A4 섹션에 이미 문서화됨 ("현재 skip 유지, 6주 후 재평가"). 이 step은 단순히 확인만. + +--- + +### Task 8: 커버리지 정의 재작성 + +**Files:** +- Create: `docs/agent/e2e-coverage-definition.md` + +- [ ] **Step 1: 커버리지 정의 문서 작성** + +Create `/Users/kiyeol/development/decoded/decoded-monorepo/docs/agent/e2e-coverage-definition.md`: + +```markdown +--- +title: E2E Coverage Definition +updated: 2026-04-30 +related_issues: [162, 170] +status: draft-for-team-review +--- + +# E2E Coverage Definition + +## 배경 + +#162의 "80% 커버리지 목표"는 Phase 4의 "~60개+ 테스트"로 설정되어 있었다. 이는 **테스트 개수 기준**이라 shallow `goto → expect(url)` 류 spec 대량 투입으로 숫자를 달성 가능한 vanity metric. + +실제 프로덕션 회귀 방지가 목적이라면 **사용자 플로우의 mutation 검증률**이 분모·분자가 되어야 한다. + +## 새 공식 + +``` +coverage = (mutation-covered critical flows) / (total critical flows) +target = 80% +``` + +### "critical flow" 기준 + +- 사용자가 정기적으로 사용 +- 깨지면 사용자에게 즉시 노출 +- mutation 혹은 state 변화를 포함 + +### "mutation-covered" 기준 + +spec이 해당 플로우의 다음 **두 가지를 모두** assert해야 함: + +1. **핵심 API mutation 호출** — POST/PUT/DELETE 요청 발행 확인 (`page.route` mock으로 가로채고 호출 여부 assert) +2. **UI 반영** — 성공 후 DOM/URL/상태 변화 assert + +단순 `page.goto('/x') → expect(page.url()).toContain('/x')`만으로는 카운트 안 함. + +## Critical Flow 인벤토리 + +| # | Flow | mutation | 현재 cover 여부 | +|---|---|---|---| +| 1 | 로그인 — email/password | auth session 생성 | 부분 — login.spec.ts | +| 2 | 로그인 — OAuth (Google) | OAuth callback | ❌ | +| 3 | 로그인 — Continue as Guest | guest session | ❌ | +| 4 | 로그아웃 | session 파기 | ❌ | +| 5 | 업로드 → 제출 → 포스트 생성 | POST /api/v1/posts | ❌ (modal만 cover) | +| 6 | 좋아요 toggle (POST/DELETE + 새로고침 유지) | POST/DELETE likes | ✅ engagement.spec.ts | +| 7 | 저장 toggle + Profile Saved 확인 | POST/DELETE saved | 부분 — Profile 탭 확인 없음 | +| 8 | 솔루션 채택 | POST /api/v1/solutions/{id}/adopt | ❌ | +| 9 | Profile 편집 → 저장 반영 | PUT /api/v1/users/{id} | ❌ | +| 10 | 공개 Profile Follow toggle | POST/DELETE follows | ❌ | +| 11 | Magazine 홈 → 상세 → 포스트 네비 | navigation only | ❌ | +| 12 | VTON 전체 플로우 | POST /api/v1/vton | ❌ | +| 13 | Admin entity CRUD (대표 1종) | POST/PUT admin entities | ❌ | +| 14 | Admin seed 승인/거절 | POST admin/candidates/approve | ❌ | +| 15 | 에러 — API 500 + retry | network failure → retry UI | ❌ | +| 16 | 에러 — 빈 상태 | empty state UI 렌더 | ❌ | +| 17 | 에러 — 권한 거부 (비로그인) | redirect /login | 부분 — navigation.spec.ts URL 체크만 | + +**현재 커버리지 (2026-04-30):** 1 / 17 완전 + 3 / 17 부분 ≈ **~12%** (부분을 0.5로 계산) + +**80% 달성까지:** 완전 cover 14개 / 17개 필요 → 현재 대비 +12~13개 critical flow 추가 작업. + +## 운영 원칙 + +1. **새 critical flow는 이 문서에 추가될 때 PR 리뷰를 거친다** (기준 확장은 결정 포인트). +2. **spec 추가 시 이 표의 해당 row를 ✅로 체크** — 커버리지 수치의 single source of truth. +3. **shallow test는 카운트하지 않음** — 작성은 자유지만 커버리지 수치에 반영하지 않음. +4. **#162 본문은 이 문서로 링크** — 현재의 "60개 테스트" 표기는 "critical flow 80%"로 대체. + +## 다음 액션 + +- #162 본문에 이 문서 링크 추가 +- 팀 리뷰 후 status: draft-for-team-review → approved로 변경 +- baseline 수치는 `docs/agent/e2e-baseline-2026-04-30.md` 참조 +``` + +- [ ] **Step 2: 문서 커밋** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +git add docs/agent/e2e-coverage-definition.md +git commit -m "docs(agent): redefine E2E coverage as mutation-covered critical flows (#162) + +- replaces vanity 'test count' target with 17 critical user flows +- defines 'mutation-covered' as API call assertion + UI reflection +- baseline measured at ~12% (1/17 complete + 3/17 partial)" +git push +``` + +--- + +### Task 9: #162 본문 edit + +**Files:** (no local file — GitHub issue body) + +- [ ] **Step 1: 기존 #162 본문 백업** + +Run: +```bash +gh issue view 162 --repo decodedcorp/decoded --json body --jq '.body' > /tmp/issue-162-backup.md +echo "Backup size: $(wc -c < /tmp/issue-162-backup.md) bytes" +``` +Expected: ~1000 bytes 이상의 백업 파일. + +- [ ] **Step 2: #162 본문 edit — 커버리지 정의 링크 추가** + +Run: +```bash +gh issue edit 162 --repo decodedcorp/decoded --body "$(cat <<'EOF' +## 배경 + +현재 프론트 E2E 커버리지: 페이지 로딩 확인 위주 (5개 spec 파일, ~300줄). +핵심 사용자 플로우에 대한 테스트가 없어 기능 변경 시 수동 검증에 의존. + +## ⚠️ 2026-04-30 업데이트: 커버리지 정의 재작성 + +기존의 "테스트 개수 기준 80% (~60개+)"은 vanity metric으로 판단되어 폐기. 다음 정의로 대체: + +**coverage = (mutation-covered critical flows) / (total critical flows) ≥ 80%** + +상세: [docs/agent/e2e-coverage-definition.md](https://github.com/decodedcorp/decoded/blob/dev/docs/agent/e2e-coverage-definition.md) + +현재 baseline: ~12% (1/17 완전 cover + 3/17 부분). 80% 달성까지 약 +12 critical flow 필요. + +## 사전 작업 + +- [x] Supabase에 email/password 테스트 계정 생성 (`e2e-test@decoded.style`) +- [x] `.env.local`에 `TEST_USER_EMAIL`, `TEST_USER_PASSWORD` 설정 +- [x] `Justfile`에 `e2e`, `e2e-only` 타스크 추가 +- [x] 핵심 컴포넌트에 `data-testid` 속성 추가 +- [x] GitHub Actions에 E2E workflow (#170 P0 인프라 스프린트) + +## 점진적 확대 로드맵 + +### Phase 1: 핵심 소비+생성 플로우 (~15개 테스트) — 진행 중 + +**콘텐츠 소비 (`content-consumption.spec.ts`)** ✅ +- Explore 페이지 로딩 + 그리드 +- 검색, 정렬 +- 포스트 상세 진입 +- 스팟/솔루션 목록 표시 +- 피드 무한스크롤 + +**콘텐츠 생성 (`content-creation.spec.ts`)** — 부분 +- 업로드 페이지 + 이미지 선택 ✅ +- 스팟 배치 (이미지 클릭) ⚠️ +- 솔루션 입력 폼 ❌ +- 포스트 제출 ❌ (#171 submit mutation tail) + +**참여 (`engagement.spec.ts`)** — 부분 +- 좋아요/저장 토글 ✅ +- 솔루션 채택 ❌ + +### Phase 2: 프로필, 에디토리얼, 매거진 (~25개) +### Phase 3: Admin, 엣지 케이스, 에러 핸들링 (~40개) +### Phase 4: 80% critical flow cover + +## 관련 + +- #170 — 프론트엔드 E2E 핵심 기능 커버리지 확대 Epic +- #158 — Supabase 직접 호출 마이그레이션 +- `docs/agent/e2e-coverage-definition.md` — 새 커버리지 정의 +- `docs/agent/e2e-baseline-2026-04-30.md` — CI baseline +EOF +)" +``` +Expected: `https://github.com/decodedcorp/decoded/issues/162` URL 출력. + +- [ ] **Step 3: edit 반영 확인** + +Run: +```bash +gh issue view 162 --repo decodedcorp/decoded --json body --jq '.body' | head -20 +``` +Expected: 업데이트된 본문 앞부분 — "⚠️ 2026-04-30 업데이트: 커버리지 정의 재작성" 헤더 포함. + +--- + +### Task 10: Auth regression umbrella 이슈 #179 생성 + +**Files:** (no local file — GitHub issue body) + +- [ ] **Step 1: 이슈 본문 준비 + 생성** + +Run: +```bash +gh issue create --repo decodedcorp/decoded \ + --title "test(web): Auth regression safety net — E2E umbrella (P1)" \ + --label "testing,frontend" \ + --body "$(cat <<'EOF' +## 배경 + +최근 48h에 auth/admin 관련 프로덕션 fix PR **8건** 머지: +- #313/#314 — \`auth.users\` insert trigger restore + checkIsAdmin maybeSingle +- #319 — admin supabase 세션 토큰 포워딩 +- #321 — admin middleware layer order +- #322 — admin logout redirect +- #296/#298/#310 — Continue as Guest 세션 지속성 +- #297 — OAuth redirect URL + +기존 1,145줄 E2E spec 중 **이 중 어떤 것도 잡을 수 있는 spec이 없음** (navigation.spec.ts는 URL만 체크, engagement는 auth mock 처리, admin/magazine-approval은 대부분 CI에서 skip). + +각 PR은 one-off fix로 닫혔고 umbrella 이슈나 회귀 방지 체크리스트가 없어 **같은 유형의 버그가 재발할 구조적 리스크** 존재. + +## 목적 + +최근 auth 회귀를 **E2E 회귀 테스트로 전환**해 동일 버그의 재발을 자동 감지. + +## 체크리스트 — 회귀 테스트 대상 + +각 항목은 Playwright spec 추가 + 필요 시 \`data-testid\` 컴포넌트 수정 포함. + +- [ ] **게스트 세션 지속성** (#296/#298/#310 회귀 방지) + - Continue as Guest → hard navigation (\`window.location.href = '/profile'\`) → 세션 유지 확인 + - intercept route 통과 후에도 게스트 session 보존 + +- [ ] **OAuth redirect URL** (#297 회귀 방지) + - Google OAuth 시작 → callback 경로로 복귀 → 세션 활성 확인 + - Supabase site_url / redirect_urls 설정 검증 포함 + +- [ ] **Admin middleware layer order** (#321 회귀 방지) + - 비로그인 → /admin 접근 → /admin/login 리다이렉트 + - admin 로그인 후 /admin 접근 → 대시보드 렌더링 + - non-admin 일반 유저 → /admin 접근 → 403 또는 리다이렉트 + +- [ ] **Admin 세션 토큰 포워딩** (#319 회귀 방지) + - admin 로그인 후 rust admin endpoint (예: GET /api/admin/...) 호출 → 200 응답 + - Authorization 헤더에 Supabase 세션 토큰 포함 확인 (\`page.route\` intercept) + +- [ ] **Admin 로그아웃 리다이렉트** (#322 회귀 방지) + - /admin 진입 후 로그아웃 버튼 클릭 → /admin/login으로 이동 (/로 안 감) + +- [ ] **\`auth.users\` insert trigger** (#313/#314 회귀 방지) + - 신규 가입 플로우 → \`public.users\` row 자동 생성 확인 (Supabase 쿼리 또는 /api/v1/users/me 200 응답) + +- [ ] **\`checkIsAdmin\` maybeSingle 처리** (#313 회귀 방지) + - admin 레코드 없는 일반 유저로 admin 라우트 접근 → 500 에러 안 터지고 403/리다이렉트로 처리 + +## data-testid 작업 (선행) + +위 플로우에 필요한 testid가 없으면 컴포넌트 수정: +- \`LogoutButton\` on /admin +- \`AdminLoginForm\` +- \`GuestCTAButton\` +- \`OAuthGoogleButton\` redirect state + +## 출력 + +- \`packages/web/tests/auth-safety-net.spec.ts\` (신규, 예상 ~200줄) +- \`packages/web/tests/admin/auth.spec.ts\` (신규, ~150줄) +- \`docs/agent/e2e-coverage-definition.md\` 의 flow #1~#4, #13~#17 체크 업데이트 + +## 우선순위 + +**P1** — 실증적 회귀가 가장 잦은 영역. #170 Epic 아래 최우선. + +## 관련 + +- Epic: #170 +- Coverage definition: \`docs/agent/e2e-coverage-definition.md\` +- 원본 fix PR: #313 #314 #319 #321 #322 #297 #310 #298 #296 +- Design spec: \`docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md\` +EOF +)" +``` +Expected: 새 이슈 URL 출력 (형식: `https://github.com/decodedcorp/decoded/issues/NEW_ID`). + +- [ ] **Step 2: 이슈 번호를 spec 문서에 반영** + +Run (위 step에서 받은 실제 이슈 번호를 `NEW_ID`로 치환): +```bash +ACTUAL_ID=$(gh issue list --repo decodedcorp/decoded --search "Auth regression safety net" --state open --json number --jq '.[0].number') +echo "New issue number: #${ACTUAL_ID}" +``` + +Edit `/Users/kiyeol/development/decoded/decoded-monorepo/docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md`: + +모든 `#179` 문자열을 `#${ACTUAL_ID}` 로 replace (grep으로 확인): +```bash +grep -n "#179" /Users/kiyeol/development/decoded/decoded-monorepo/docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md +# 각 라인을 실제 ID로 수정 +``` + +단, 만약 실제 ID가 179와 같다면 수정 불필요. + +- [ ] **Step 3: 수정사항 커밋 (실제 ID가 179와 다른 경우만)** + +Run (차이 있을 때만): +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +git add docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md +git commit -m "docs(specs): update auth umbrella issue reference to #${ACTUAL_ID}" +git push +``` + +--- + +### Task 11: 스프린트 회고 노트 + +**Files:** +- Create: `docs/superpowers/briefs/2026-04-30-e2e-infra-retro.md` + +- [ ] **Step 1: 회고 파일 생성** + +Create `/Users/kiyeol/development/decoded/decoded-monorepo/docs/superpowers/briefs/2026-04-30-e2e-infra-retro.md`: + +```markdown +--- +title: E2E Infra P0 스프린트 회고 (2026-04-24 ~ 04-30) +updated: 2026-04-30 +related_issues: [170, 162] +--- + +# E2E Infra P0 스프린트 회고 + +**기간:** 2026-04-24 목 ~ 2026-04-30 수 +**스코프:** Epic #170의 P0 인프라 스프린트 +**담당:** thxforall (solo) + +## 목표 대비 달성 + +| 목표 (DoD) | 달성 | +|---|---| +| GitHub Actions e2e.yml 존재 + PR/dev push 트리거 | `<채워넣기>` | +| 기존 13개 spec CI baseline 측정 | `<채워넣기>` | +| "80% 커버리지" 재정의 문서화 | `<채워넣기>` | +| Auth regression umbrella 이슈 #179 생성 | `<채워넣기>` | +| 새 feature spec 추가 (Non-goal) | 의도대로 없음 | + +## Blocker / 우회 + +- `<채워넣기>` — 예: secret 주입이 repo admin 권한 이슈로 지연, test user 생성 중 Supabase RLS 문제 등 + +## 숫자 + +- CI 첫 실행 결과: `` +- 실행 시간: `<분>` (예상 ~5분) +- 실패한 spec 중 flake/env: N개, spec bug: N개 + +## Learning + +- `<채워넣기>` — 예: reuseExistingServer 옵션이 CI에서 어떻게 동작했는지, supabase cloud DEV의 E2E 트래픽 영향 등 + +## 다음 스프린트 추천 순서 + +1. **#179 Auth safety net 실행** (P1, 추정 2주) +2. **#171 Submit mutation tail** (P1, 추정 1주) +3. **#177 에러/권한** (P1로 승격, 추정 1주) + +위 3개가 완료되면 critical flow 커버리지 ~50% 도달 예상 (17개 중 +5~6개 추가). + +## Open questions + +- `<채워넣기>` — 예: admin/magazine-approval.spec.ts의 CI skip 조건을 제거할지 별건? +- `<채워넣기>` — 예: pre-push RUN_E2E 기본 on 전환 6주 후 재평가 항목 상기 +``` + +- [ ] **Step 2: 회고 파일 커밋 + PR 업데이트** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +git add docs/superpowers/briefs/2026-04-30-e2e-infra-retro.md +git commit -m "docs(retro): E2E Infra P0 sprint retrospective (2026-04-30)" +git push +``` + +**주의:** `<채워넣기>` placeholder는 스프린트 **종료 시점에 실제 수치/사실로 교체**. 이 task를 완료로 체크하려면 placeholder가 모두 채워진 상태여야 함. + +--- + +### Task 12: PR 머지 + Epic #170 코멘트 + +**Files:** (no file changes) + +- [ ] **Step 1: PR 상태 확인 (모든 체크 그린인지)** + +Run: +```bash +cd /Users/kiyeol/development/decoded/decoded-monorepo +gh pr status --repo decodedcorp/decoded +``` +Expected: feat/170-e2e-infra-p0 PR이 "All checks were successful" 상태. + +만약 E2E 체크에서 실패가 남아있으면 Task 6의 flake/env 분류 기준으로 scope-out 판단 후 진행 (실패 spec 목록을 baseline 문서에 기록). + +- [ ] **Step 2: PR 머지** + +Run: +```bash +PR_NUMBER=$(gh pr view --repo decodedcorp/decoded --json number --jq '.number') +gh pr merge $PR_NUMBER --repo decodedcorp/decoded --merge --delete-branch +``` +Expected: PR merged + feat/170-e2e-infra-p0 원격 브랜치 삭제. + +**주의:** 머지 전에 유저 확인. PR 머지는 shared state 변경이므로 `gh pr merge` 실행 전 사용자에게 컨펌 요청. + +- [ ] **Step 3: Epic #170에 P0 완료 코멘트** + +Run: +```bash +gh issue comment 170 --repo decodedcorp/decoded --body "$(cat <<'EOF' +## P0 인프라 스프린트 완료 (2026-04-30) + +### 달성 +- GitHub Actions \`e2e.yml\` 신설 → PR + dev push 트리거 +- CI baseline 측정: \`docs/agent/e2e-baseline-2026-04-30.md\` +- 커버리지 정의 재작성: \`docs/agent/e2e-coverage-definition.md\` (테스트 개수 → critical flow 기준) +- Auth regression umbrella 이슈 생성 → \`#179\` +- #162 본문에 새 커버리지 정의 반영 + +### 실증 baseline +- **현재 커버리지: ~12%** (1/17 complete + 3/17 partial) +- 80% 달성까지 +12~13 critical flow 필요 + +### 다음 스프린트 추천 순서 +1. #179 Auth safety net (P1) +2. #171 Submit mutation tail (P1) +3. #177 에러/권한 (P1로 승격) + +### 참조 +- Design spec: \`docs/superpowers/specs/2026-04-23-e2e-hardening-reprioritization-design.md\` +- Implementation plan: \`docs/superpowers/plans/2026-04-23-170-e2e-hardening-infra-p0.md\` +- Retro: \`docs/superpowers/briefs/2026-04-30-e2e-infra-retro.md\` +EOF +)" +``` +Expected: 코멘트 URL 출력. + +--- + +## 최종 체크리스트 + +스프린트 종료 시점에 다음 모두 달성됐는지 확인: + +- [ ] `.github/workflows/e2e.yml` on dev branch +- [ ] 5개 E2E_* secret이 repo에 주입됨 +- [ ] 첫 CI run 완료 + baseline 기록됨 +- [ ] `docs/agent/e2e-coverage-definition.md` 커밋됨 +- [ ] `docs/agent/e2e-baseline-2026-04-30.md` 커밋됨 +- [ ] #162 본문 edit 반영 +- [ ] 새 이슈 (umbrella auth safety net) 생성됨 + Epic #170 아래 링크됨 +- [ ] `docs/superpowers/briefs/2026-04-30-e2e-infra-retro.md` 실제 수치로 채워짐 +- [ ] PR merged to dev +- [ ] Epic #170에 완료 코멘트 등록됨 From e356c15d2b49657ba5f24d8d30823dff98fc45cc Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Thu, 23 Apr 2026 21:09:52 +0900 Subject: [PATCH 6/9] ci(e2e): add Playwright workflow for PR + dev push (#170) - triggers on pull_request (web/api-server paths) and dev push - uses E2E_* secrets for Supabase cloud DEV + test user credentials - uploads test-results/ artifact on failure (trace.zip + screenshots) - concurrency group cancels in-progress runs for the same ref --- .github/workflows/e2e.yml | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/e2e.yml diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 00000000..e3d21afe --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,55 @@ +name: E2E Tests + +on: + pull_request: + paths: + - 'packages/web/**' + - 'packages/api-server/**' + - '.github/workflows/e2e.yml' + push: + branches: [dev] + +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + playwright: + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.E2E_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.E2E_SUPABASE_ANON_KEY }} + SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.E2E_SUPABASE_SERVICE_ROLE_KEY }} + TEST_USER_EMAIL: ${{ secrets.E2E_TEST_USER_EMAIL }} + TEST_USER_PASSWORD: ${{ secrets.E2E_TEST_USER_PASSWORD }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Install Playwright browsers + working-directory: packages/web + run: bunx playwright install --with-deps chromium + + - name: Run Playwright tests + working-directory: packages/web + run: bunx playwright test + + - name: Upload test-results on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ github.run_id }} + path: packages/web/test-results/ + retention-days: 7 From 978620fbeb32d29ef7e42e6759191fede5988a18 Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Wed, 6 May 2026 17:26:29 +0900 Subject: [PATCH 7/9] ci(web): align e2e workflow env names --- .github/workflows/e2e.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index e3d21afe..2e5f648b 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -21,9 +21,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 env: - NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.E2E_SUPABASE_URL }} - NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.E2E_SUPABASE_ANON_KEY }} - SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.E2E_SUPABASE_SERVICE_ROLE_KEY }} + NEXT_PUBLIC_DATABASE_API_URL: ${{ secrets.E2E_DATABASE_API_URL }} + NEXT_PUBLIC_DATABASE_ANON_KEY: ${{ secrets.E2E_DATABASE_ANON_KEY }} + DATABASE_SERVICE_ROLE_KEY: ${{ secrets.E2E_DATABASE_SERVICE_ROLE_KEY }} TEST_USER_EMAIL: ${{ secrets.E2E_TEST_USER_EMAIL }} TEST_USER_PASSWORD: ${{ secrets.E2E_TEST_USER_PASSWORD }} steps: From 4fe1a32c3bbcd23b80e9b8dd5688d025d5523891 Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Wed, 6 May 2026 17:32:53 +0900 Subject: [PATCH 8/9] ci(web): generate api client before e2e --- .github/workflows/e2e.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2e5f648b..2d19ce76 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -38,6 +38,10 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile + - name: Generate API client + working-directory: packages/web + run: bun run generate:api + - name: Install Playwright browsers working-directory: packages/web run: bunx playwright install --with-deps chromium From 8b3928d42dca20c8715b6ee82d01c227f7e7dfcd Mon Sep 17 00:00:00 2001 From: thxforall <113906780+thxforall@users.noreply.github.com> Date: Wed, 6 May 2026 17:37:58 +0900 Subject: [PATCH 9/9] ci(web): fail fast when e2e secrets are missing --- .github/workflows/e2e.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2d19ce76..d311c7fa 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -35,6 +35,14 @@ jobs: with: bun-version: latest + - name: Validate E2E secrets + run: | + : "${NEXT_PUBLIC_DATABASE_API_URL:?Missing E2E_DATABASE_API_URL secret}" + : "${NEXT_PUBLIC_DATABASE_ANON_KEY:?Missing E2E_DATABASE_ANON_KEY secret}" + : "${DATABASE_SERVICE_ROLE_KEY:?Missing E2E_DATABASE_SERVICE_ROLE_KEY secret}" + : "${TEST_USER_EMAIL:?Missing E2E_TEST_USER_EMAIL secret}" + : "${TEST_USER_PASSWORD:?Missing E2E_TEST_USER_PASSWORD secret}" + - name: Install dependencies run: bun install --frozen-lockfile