Skip to content

Test: 네비게이션 검증 test 추가#205

Closed
seueooo wants to merge 10 commits into
developfrom
test/e2e-test/#161
Closed

Test: 네비게이션 검증 test 추가#205
seueooo wants to merge 10 commits into
developfrom
test/e2e-test/#161

Conversation

@seueooo
Copy link
Copy Markdown
Contributor

@seueooo seueooo commented Aug 25, 2025

📌 Summary

📚 Tasks

  • 테스트 유틸리티 추가 및 네비게이션 검증 코드 작성
  • 접근성 요소들 추가 (탭 키로 focus 이동, 시맨틱 태그 등)

👀 To Reviewer

  • 네비게이션 검증하려는데, api 모킹해서 테스트를 진행해야할 것 같아요.!
    일단 진행중..

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • 접근성 향상: 주요 화면에 main 랜드마크 추가 및 네비게이션 요소에 포커스 가능(tabIndex) 추가, CTA를 링크 기반으로 통합.
  • Style

    • 떠다니는 별 애니메이션 컨테이너에 클래스 추가로 표시 일관성 개선.
  • Refactor

    • 여러 페이지에서 시맨틱 요소로 컨테이너 정리(레이아웃/동작 불변).
  • Tests

    • Playwright E2E/성능/내비게이션/홈/종합 테스트 추가, 테스트 유틸 및 실행 스크립트 도입, 예제 더미 테스트 삭제.
  • Documentation

    • 한국어 Playwright 테스트 가이드 추가.

@seueooo seueooo self-assigned this Aug 25, 2025
@seueooo seueooo requested a review from seung365 as a code owner August 25, 2025 14:41
@seueooo seueooo linked an issue Aug 25, 2025 that may be closed by this pull request
@vercel
Copy link
Copy Markdown

vercel Bot commented Aug 25, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
time-capsule Ready Ready Preview Comment Aug 25, 2025 3:47pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 25, 2025

Walkthrough

여러 페이지에서 컨테이너를 div↔main으로 교체해 시맨틱 랜드마크를 정비했습니다. 홈 CTA는 Link 자체에 버튼 스타일을 적용했고, 네비게이션에 tabIndex 제어를 추가했습니다. 별 애니메이션에 보조 클래스가 추가되고 Playwright 테스트 인프라와 여러 E2E/성능 테스트가 추가되었습니다.

Changes

Cohort / File(s) Summary
Semantic landmark 정리 (div→main)
app/(auth)/login/page.tsx, app/(main)/explore/page.tsx, app/(main)/my-capsule/page.tsx, app/(main)/setting/page.tsx, app/(sub)/capsule-detail/[invite-code]/[id]/page.tsx, app/(sub)/create-capsule/page.tsx, app/(sub)/search/page.tsx, app/not-found.tsx
주요 콘텐츠 래퍼를 div에서 main으로 교체(또는 새 main 삽입). DOM 구조 변경만 있고 로직/데이터 흐름은 불변.
IntroStep 래퍼 조정 (main→div)
app/(sub)/create-capsule/_components/steps/intro-step/index.tsx
루트 래퍼를 main에서 div로 변경. 내부 로직/프로퍼티는 그대로.
홈 CTA 구조 단순화
app/(main)/_components/home-button-section/index.tsx
Link 내부에 있던 button 제거하고 Link 자체에 버튼 스타일 적용. Explore LinktabIndex={0} 추가. 네비게이션 대상은 동일.
Navbar 포커스 가능 제어
shared/ui/navbar/navbar-main/index.tsx
주요 링크와 햄버거 버튼에 tabIndex={0} 추가. 모바일 메뉴 항목은 tabIndex={isMenuOpen ? 0 : -1}로 조건부 포커스 처리. 동작 로직 변화 없음(접근성 개선).
스타 애니메이션 클래스 보강
shared/ui/floating-stars-container/index.tsx
루트 컨테이너 클래스에 floating-stars 보조 클래스 추가(프레젠테이션용).
테스트 인프라 및 유틸 추가
tests/README.md, tests/utils/test-helpers.ts, tests/run-tests.sh
Playwright 가이드 문서, TestHelpers 유틸 클래스(.ts) 및 실행 스크립트(.sh) 추가. 테스트 실행/환경/유틸 관련 파일 신설.
E2E/성능 테스트 추가
tests/home.spec.ts, tests/navigation.spec.ts, tests/performance.spec.ts, tests/comprehensive.spec.ts
홈/내비게이션/성능/종합 시나리오 Playwright 테스트 신설(쿠키 주입으로 인증 시뮬레이션, URL 전환, 이미지/콘솔/네트워크/반응형/접근성 검증 등).
샘플 테스트 제거
tests/example.spec.ts
기존 더미 테스트 파일 삭제.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant H as HomePage
  participant L as Link(as Button)
  participant R as Router
  participant P as TargetPage

  U->>H: 페이지 로드
  H->>L: CTA Link 렌더링 (Link가 버튼 역할)
  U->>L: Click / Enter
  L-->>R: client-side navigation (href)
  R-->>P: route transition
  P-->>U: 새 페이지 렌더링
  note right of L: 구조 변경: 내부 button 제거 → Link 자체가 인터랙티브 요소
Loading
sequenceDiagram
  autonumber
  actor U as Keyboard User
  participant N as Navbar
  participant M as MobileMenu

  U->>N: Tab 키 입력
  N-->>U: tabIndex=0 요소로 포커스 이동
  alt 모바일 메뉴 닫힘
    M-->>U: 메뉴 항목 tabIndex=-1 (포커스 불가)
  else 모바일 메뉴 열림
    M-->>U: 메뉴 항목 tabIndex=0 (포커스 가능)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test/e2e-test/#161

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

This pull request (commit a4f1f71) has been deployed to Vercel ▲ - View GitHub Actions Workflow Logs

Name Link
🌐 Unique https://time-capsule-rel6zwmiu-hs-projects-b4a69d5f.vercel.app
🔍 Inspect https://vercel.com/hs-projects-b4a69d5f/time-capsule/7L5Gh4jDMQpoMQe96GuuFDgy5bZX

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 25, 2025

🚀 Storybook 배포

📖 Storybook: https://683d91ab23651aa0b399e435-iwscwifygk.chromatic.com/
🔗 Chromatic Build: https://www.chromatic.com/build?appId=683d91ab23651aa0b399e435&number=228
✅ Status: success

@seueooo seueooo changed the title Test: e2e 테스트 작성 및 UI/UX 개선으로 테스트 안정화 Test: e2e 테스트 작성 및 UI/UX 개선 Aug 25, 2025
@seueooo seueooo changed the title Test: e2e 테스트 작성 및 UI/UX 개선 Test: e2e 테스트 작성 및 접근성 개선 Aug 25, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/(main)/setting/page.tsx (1)

45-76: 보안: window.open('_blank')에 noopener 미지정으로 reverse tabnabbing 위험이 있습니다.

외부 링크를 새 탭으로 열 때는 noopener/noreferrer를 적용하거나 opener를 명시적으로 끊어야 합니다.

안전한 onClick 예시(현재 변경 구간 내에서 수정 가능):

-            <SettingItem key={link.label} onClick={() => window.open(link.url, "_blank")}>
+            <SettingItem
+              key={link.label}
+              onClick={() => {
+                const w = window.open(link.url, "_blank", "noopener,noreferrer");
+                if (w) w.opener = null;
+              }}
+            >

대안(권장): SettingItem이 앵커를 지원한다면 <a target="_blank" rel="noopener noreferrer">로 렌더링하세요.

🧹 Nitpick comments (41)
app/(main)/my-capsule/page.tsx (1)

64-71: 메인 랜드마크 추가는 적절합니다. 다만 접근성 신호(aria-busy)와 스킵 링크용 id 추가를 권장합니다.

무한 스크롤/초기 로딩 상태를 SR에 노출하려면 main에 aria-busy를 걸어두는 편이 좋습니다. 스킵 링크를 위해 id를 두는 것도 테스트와 a11y에 유용합니다.

아래처럼 main에 속성을 추가해 주세요:

-      <main>
+      <main id="main-content" aria-busy={isPending || isFetchingNextPage}>

추가 제안(선택):

  • 로딩 스피너 영역에 role="status" aria-live="polite"를 부여하면 추가 페이지 로딩 시점이 SR에 안전하게 전달됩니다.
  • 페이지의 주 제목(TitleSection)이 실제로 문서의 메인 헤딩이라면 main 내부에 배치하는 것도 고려해 주세요(의미론 정합성 향상).
app/not-found.tsx (1)

12-24: 404 페이지의 main 적용은 적절합니다. h1 헤딩과 aria-labelledby 연결을 권장합니다.

현재 첫 문구가 p 태그라 메인 영역의 대표 헤딩이 없습니다. SR·SEO 관점에서 h1을 두고 main과 연결하는 편이 좋습니다.

변경 예시(변경 라인 내에서 가능한 보완):

-    <main className={styles.container}>
+    <main className={styles.container} aria-labelledby="notfound-title">

변경 예시(참고: 변경 라인 외 보완 코드) — 대표 문구를 h1으로 교체하고 id를 부여:

<h1 id="notfound-title" className={styles.title}>페이지를 찾을 수 없습니다.</h1>

추가 제안(선택):

  • NoneImage가 장식 목적이라면 <NoneImage aria-hidden />로 스크린 리더 노이즈를 줄여주세요.
app/(sub)/create-capsule/page.tsx (1)

115-137: 폼을 main으로 감싼 구조는 좋습니다. 제출 과정 노출을 위해 aria-busy를 main에 연결하세요.

react-hook-form의 form.formState.isSubmitting을 활용해 제출 중 상태를 SR에 알리면 테스트 안정성과 접근성이 함께 좋아집니다.

-      <main className={styles.container}>
+      <main className={styles.container} aria-busy={form.formState.isSubmitting} id="create-capsule-main">
app/(sub)/create-capsule/_components/steps/intro-step/index.tsx (1)

34-81: 중첩 main 제거는 잘 하셨습니다. 입력 요소에 레이블·설명 연결을 보완해 주세요.

입력/텍스트영역이 placeholder만으로 노출되고 있어 SR 사용자에겐 레이블 맥락이 약합니다. 또한 글자수 카운터는 aria-describedby로 연결하면 접근성이 좋아집니다.

보완 예시(참고 코드):

<label htmlFor="capsule-title" className="sr-only">타임캡슐 이름</label>
<input
  id="capsule-title"
  type="text"
  placeholder="타임캡슐 이름"
  className={styles.titleInput}
  maxLength={15}
  aria-describedby="title-charcount"
  {...titleField}
/>
<span id="title-charcount" className={styles.charCountTitle} aria-live="polite">
  {(titleField.value?.length || 0)}/15
</span>

<label htmlFor="capsule-subtitle" className="sr-only">타임캡슐 소개</label>
<textarea
  id="capsule-subtitle"
  placeholder="내 타임캡슐을 소개해보세요."
  className={styles.descriptionInput}
  maxLength={80}
  aria-describedby="subtitle-charcount"
  {...subtitleField}
/>
<span id="subtitle-charcount" className={styles.charCountDescription} aria-live="polite">
  {(subtitleField.value?.length || 0)}/80
</span>
app/(main)/setting/page.tsx (1)

45-76: main 적용은 적절합니다. 클릭 가능한 항목의 시맨틱/포커스 가능 여부를 확인해 주세요.

SettingItem이 div 기반이라면 role="button"과 키보드 핸들러(Enter/Space)를 제공하거나 a/button으로 렌더링하는 prop을 노출하는 것이 접근성 측면에서 바람직합니다.

검토 포인트:

  • SettingItem이 a 혹은 button로 렌더링 가능한지(as prop 등)
  • div 클릭 시 tabIndex={0}, role="button", onKeyDown(Enter/Space 트리거) 제공 여부
app/(auth)/login/page.tsx (2)

40-41: 장식 이미지라면 스크린 리더 제외 권장.

Lettie 캐릭터가 의미 전달이 아닌 장식이라면 alt=""로 비어두고(또는 aria-hidden), 의미가 있다면 더 구체적인 대체 텍스트로 바꿔주세요.

가능한 수정 예:

-<Image src={LettieImage} alt="Lettie" className={styles.lettieImage} />
+<Image src={LettieImage} alt="" aria-hidden className={styles.lettieImage} />

35-35: Skip 링크 연계를 위한 main id 부여 제안.

E2E/접근성 테스트에서 “본문으로 건너뛰기” 링크를 추가할 여지를 위해 id="main-content"를 달아두면 좋습니다.

-<main className={styles.contentsContainer}>
+<main id="main-content" className={styles.contentsContainer}>
app/(sub)/capsule-detail/[invite-code]/[id]/page.tsx (1)

132-142: 메인 랜드마크 추가는 적절합니다만, 페이지 주요 제목(InfoTitle)을 main 내부로 포함 권장.

스크린 리더 사용자에게 메인 영역의 ‘이름’을 제공하려면 주요 헤딩이 main 안에 있거나 aria-labelledby로 연결되는 편이 좋습니다. 현재 InfoTitle이 main 바깥에 있어 문맥 파악성이 다소 떨어질 수 있습니다.

예시(간단 이동):

-      <RevealMotion>
-        <InfoTitle
-          title={result.title}
-          participantCount={result.participantCount}
-          joinLettersCount={result.letterCount}
-        />
-      </RevealMotion>
-      <main>
+      <main id="main-content">
+        <RevealMotion>
+          <InfoTitle
+            title={result.title}
+            participantCount={result.participantCount}
+            joinLettersCount={result.letterCount}
+          />
+        </RevealMotion>
         <CapsuleImage imageUrl={result.beadVideoUrl} />
shared/ui/navbar/navbar-main/index.tsx (3)

32-53: a 태그에 부여된 tabIndex={0}는 중복이며 제거 권장.

앵커(링크)는 기본적으로 포커스 가능하므로 불필요한 tabIndex={0}은 유지보수성만 떨어뜨립니다.

-<Link href={PATH.HOME} tabIndex={0}>
+<Link href={PATH.HOME}>
...
-<Link href={PATH.EXPLORE} className={styles.buttonStyle} tabIndex={0}>
+<Link href={PATH.EXPLORE} className={styles.buttonStyle}>
...
-<Link href={PATH.MY_CAPSULE} className={styles.buttonStyle} tabIndex={0}>
+<Link href={PATH.MY_CAPSULE} className={styles.buttonStyle}>
...
-<Link href={PATH.SETTING} className={styles.buttonStyle} tabIndex={0}>
+<Link href={PATH.SETTING} className={styles.buttonStyle}>

64-70: 모바일 메뉴 비가시 상태의 포커스/스크린 리더 차단 보강.

tabIndex={-1}만으로는 SR에서 읽힐 수 있습니다. 컨테이너에 aria-hidden(또는 inert)을 함께 적용해 주세요. 또한 버튼과 aria-controls로 연결될 id를 부여해 주세요.

-<ul
+<ul
   className={cn(
     styles.menuContainer,
     isMenuOpen ? styles.menuContainerOpen : styles.navHomeStyleClose,
   )}
+  id="main-menu"
+  aria-hidden={!isMenuOpen}
 >

추가로 nav에 명시적 이름을 줄 수 있으면 더 좋습니다:

-<nav
+<nav aria-label="메인 내비게이션"
   className={cn(
     styles.navHomeStyle,
     isMenuOpen ? styles.navHomeStyleOpen : styles.navHomeStyleClose,
   )}
>

Also applies to: 71-100


59-60: ARIA 속성 연결 및 tabIndex 제거 제안

HamburgerMenuButton은 네이티브 <button> 요소로 구현되어 있어 tabIndex={0}은 불필요합니다.
또한 메뉴 열림 상태를 스크린 리더 등에 알리기 위해 aria-expandedaria-controls를 함께 전달하는 것을 권장합니다.

• 대상 파일/위치
shared/ui/navbar/navbar-main/index.tsx (59–60행)

• 변경 사항

  • 기존:
    <HamburgerMenuButton
      isMenuOpen={isMenuOpen}
      onClick={handleMenuClick}
      tabIndex={0}
    />
  • 제안:
    <HamburgerMenuButton
      isMenuOpen={isMenuOpen}
      onClick={handleMenuClick}
    - tabIndex={0}
    + aria-controls="main-menu"
    + aria-expanded={isMenuOpen}
    />

이미 HamburgerMenuButton 컴포넌트는 ...props로 전달받은 ARIA 속성을 내부 <button>에 스프레드하므로, 위와 같이 간단히 추가 가능합니다.

app/(main)/explore/page.tsx (1)

48-53: 주요 헤딩(TitleSection)을 main 내부로 이동 또는 연결 권장.

현재 TitleSection이 main 바깥에 있어 SR 사용자 관점에서 메인 영역의 제목 연계가 약합니다. 컴포넌트 이동이 어렵다면 mainaria-labelledby로 연결하는 방법도 있습니다.

간단 이동 예:

-  <RevealMotion>
-    <TitleSection />
-  </RevealMotion>
-  <main>
+  <main id="main-content">
+    <RevealMotion>
+      <TitleSection />
+    </RevealMotion>
     <SelectTabSection
app/(sub)/search/page.tsx (1)

41-41: Skip 링크 연계를 위한 main id 부여 제안.

다른 페이지들과 일관성 있게 id="main-content"를 도입하면 좋습니다.

-<main>
+<main id="main-content">
app/(main)/_components/home-button-section/index.tsx (1)

10-10: 불필요한 tabIndex 제거 제안

앵커(<a>)는 기본적으로 포커스 가능하므로 tabIndex={0}은 중복입니다. 포커스 순서 혼선 방지를 위해 제거를 권장합니다.

-      <Link href={PATH.EXPLORE} className={styles.exploreButton} tabIndex={0}>
+      <Link href={PATH.EXPLORE} className={styles.exploreButton}>
tests/navigation.spec.ts (2)

104-123: 키보드 내비게이션 테스트가 포커스 순서에 의존해 플래키합니다

글로벌 네비/햄버거 등 포커스 가능한 요소가 늘어나면 Tab 횟수 기반 검증은 흔들립니다. 타깃 요소에 직접 포커스를 주고 Enter를 보내는 방식으로 안정화하세요.

-    // Test tab navigation through interactive elements
-    await page.keyboard.press("Tab");
-
-    // Check if focus moves to the first interactive element
-    const focusedElement = page.locator(":focus");
-    await expect(focusedElement).toBeVisible();
-
-    // Test tab navigation through multiple elements
-    await page.keyboard.press("Tab");
-
-    // Test Enter key on focused button
-    await page.keyboard.press("Enter");
+    // 타깃 링크에 직접 포커스 후 Enter
+    const exploreLink = page.getByRole("link", { name: "타임캡슐 보러가기" });
+    await exploreLink.focus();
+    await expect(exploreLink).toBeFocused();
+    await page.keyboard.press("Enter");

78-86: 직접 URL 내비게이션의 리다이렉트 판정이 타이밍 이슈에 취약합니다

page.goto("/my-capsule") 직후 page.url()을 체크하면 리다이렉트가 아직 완료되지 않았을 수 있습니다. URL 패턴 대기 또는 로드 상태 대기를 추가하세요.

-    await page.goto("/my-capsule");
-    // This might redirect to login if not authenticated
-    const currentUrl = page.url();
+    await page.goto("/my-capsule");
+    // 이 단계에서 로그인 리다이렉트가 발생할 수 있으므로 URL 패턴을 대기
+    await page.waitForURL(/\/(my-capsule|login)(\/|$)/);
+    const currentUrl = page.url();
tests/run-tests.sh (2)

1-8: 스크립트 안정성을 위해 에러 옵션을 활성화하세요

CI에서 조기 실패 감지를 위해 set -Eeuo pipefail 추가를 권장합니다.

 #!/bin/bash
-
-# Playwright Test Runner Script
+set -Eeuo pipefail
+
+# Playwright Test Runner Script

80-93: 테스트 파일 경로를 명시적으로 지정해 경로 해석 이슈를 방지하세요

pnpm test가 루트에서 실행될 때 파일만 넘기면 매칭 실패할 수 있습니다. tests/ 접두어를 붙여 안정화하세요.

-        run_tests "home.spec.ts" ""
+        run_tests "tests/home.spec.ts" ""
@@
-        run_tests "auth.spec.ts" ""
+        run_tests "tests/auth.spec.ts" ""
@@
-        run_tests "navigation.spec.ts" ""
+        run_tests "tests/navigation.spec.ts" ""
@@
-        run_tests "performance.spec.ts" ""
+        run_tests "tests/performance.spec.ts" ""
@@
-        run_tests "comprehensive.spec.ts" ""
+        run_tests "tests/comprehensive.spec.ts" ""
tests/utils/test-helpers.ts (5)

44-55: 이미지 검증 순서를 조정해 실패 메시지를 명확히 하세요

첫 요소 가시성 검증 전에 개수를 먼저 확인하면 “매칭 0건” 케이스에서 더 명확하게 실패합니다.

   static async checkImagesLoaded(page: Page, selector: string) {
     const images = page.locator(selector);
-    await expect(images.first()).toBeVisible();
-
-    const imageCount = await images.count();
-    expect(imageCount).toBeGreaterThan(0);
+    const imageCount = await images.count();
+    expect(imageCount).toBeGreaterThan(0);
+    await expect(images.first()).toBeVisible();

57-67: 콘솔 리스너를 정리해 메모리 누수를 방지하세요

테스트마다 핸들러가 누적되지 않도록 완료 후 page.off로 해제하세요.

   static async checkConsoleErrors(page: Page) {
     const consoleErrors: string[] = [];
-    page.on("console", (msg) => {
+    const handler = (msg: any) => {
       if (msg.type() === "error") {
         consoleErrors.push(msg.text());
       }
-    });
+    };
+    page.on("console", handler);
 
     await page.waitForLoadState("networkidle");
+    page.off("console", handler);
     return consoleErrors;
   }

69-77: 요청 실패 리스너도 동일하게 해제하세요

완료 후 page.off("requestfailed", handler) 호출을 추가하세요.

   static async checkFailedRequests(page: Page) {
     const failedRequests: string[] = [];
-    page.on("requestfailed", (request) => {
+    const handler = (request: any) => {
       failedRequests.push(request.url());
-    });
+    };
+    page.on("requestfailed", handler);
 
     await page.waitForLoadState("networkidle");
+    page.off("requestfailed", handler);
     return failedRequests;
   }

79-83: 느린 네트워크 시뮬레이션에 정리(cleanup)를 제공하면 테스트 간 간섭을 줄일 수 있습니다

현재 라우트는 명시적으로 해제되지 않습니다. 호출부에서 해제할 수 있도록 클린업을 반환하는 유틸 대안을 제안합니다.

아래는 새로운 헬퍼 예시입니다(외부 변경을 돕기 위한 추가 코드 블록):

// 추가 유틸 (새 메서드로 제안)
static async withSlowNetwork<T>(page: Page, delayMs = 1000, fn: () => Promise<T>) {
  const routeGlob = "**/*";
  await page.route(routeGlob, (route) => setTimeout(() => route.continue(), delayMs));
  try {
    return await fn();
  } finally {
    await page.unroute(routeGlob);
  }
}

98-110: 숫자 텍스트 폴링이 광범위합니다 — 선택자/범위를 좁히는 것을 고려하세요

text=/\d+/는 페이지 전역 숫자 모두에 매칭됩니다. data-testid 기반 특정 영역 한정 또는 더 구체적인 셀렉터를 받는 시그니처로 확장하는 것을 권장합니다.

tests/performance.spec.ts (2)

14-16: 성능 임계값을 환경변수로 조절 가능하게 하여 플래키를 줄이세요

CI 환경 변동을 고려해 상수(3000ms) 대신 환경변수 기반으로 완화하세요(예: 기본 5000ms).

-    // Home page should load within 3 seconds
-    expect(loadTime).toBeLessThan(3000);
+    const maxMs = Number(process.env.PERF_HOME_MAX_MS ?? "5000");
+    // Home page should load within configurable threshold
+    expect(loadTime).toBeLessThan(maxMs);

75-79: 페이지 전환 시간 임계값도 설정 가능하도록 통일하세요

전환 1초 기준 역시 환경변수로 조정 가능하게 두면 환경차로 인한 flaky를 줄일 수 있습니다.

-    // Navigation should be fast (less than 1 second)
-    expect(navigationTime).toBeLessThan(1000);
+    const maxNavMs = Number(process.env.PERF_NAV_MAX_MS ?? "1500");
+    // Navigation should be fast
+    expect(navigationTime).toBeLessThan(maxNavMs);
shared/ui/floating-stars-container/index.tsx (4)

63-63: 테스트 안정성과 접근성 향상: data-testid/aria-hidden 추가를 권장합니다

E2E 테스트가 클래스명에 결합되지 않도록 안정적인 선택자(data-testid)를 노출하고, 순수 장식 요소임을 명시(스크린리더 제외)하면 접근성과 유지보수성이 좋아집니다. 필요 시 포인터 차단으로 상호작용 영역을 보호할 수 있습니다.

아래처럼 수정을 제안합니다:

-    <div className={`${styles.container} floating-stars`}>
+    <div
+      className={`${styles.container} floating-stars`}
+      data-testid="floating-stars"
+      aria-hidden="true"
+      style={{ pointerEvents: "none" }}
+    >

35-36: any[] 지양: Star 타입 명시로 가독성과 안전성 개선

useState<any[]>는 타입 안정성을 해칩니다. 내부 구조가 고정되어 있으므로 명시적 Star 인터페이스를 두고 제네릭을 지정해주세요.

예시(파일 상단 인접 위치에 추가):

interface Star {
  id: string;
  left: string;
  speed: number;
  size: number;
  sway: number;
  startPosition: number;
  swayOffset: number;
}
-  const [stars, setStars] = useState<any[]>([]);
+  const [stars, setStars] = useState<Star[]>([]);

42-44: 속도 0/음수 방지: duration 분모 보호(clamp) 필요

duration: 10 / star.speedspeed가 0 또는 매우 작을 때 런타임 오류/과도한 duration을 유발할 수 있습니다. star.speed 산출 시 하한선을 두세요.

-      speed: speed * (1 + (seededRandom(seed + 1) - 0.5) * 2 * speedRandomness),
+      speed: Math.max(
+        0.1,
+        speed * (1 + (seededRandom(seed + 1) - 0.5) * 2 * speedRandomness)
+      ),

Also applies to: 81-87


74-79: 단위 혼용(px ↔ %)으로 인한 애니메이션 불안정 가능성

초기 스타일은 bottom: -{size}px인데 애니메이션은 bottom: ["0%", "100%"]로 단위가 다릅니다. 브라우저/모션 엔진에 따라 경고/깜빡임이 생길 수 있습니다. 하나의 단위로 통일하거나 initial 프롭을 사용해 동일 단위로 시작/종료를 정의해주세요.

예시(단위 통일):

-          style={{ ... , bottom: `${-star.size}px` }}
+          style={{ ... , bottom: "0%" }}
-          animate={{ bottom: ["0%", "100%"], ... }}
+          animate={{ bottom: ["0%", "100%"], ... }}

또는 transform 기반 이동(y)으로 전환하여 레이아웃 영향 최소화도 고려해볼 만합니다.

Also applies to: 77-79

tests/README.md (3)

7-17: 마크다운 린트(MD040) 해결: 코드 블록에 언어 지정

파일 트리 코드 블록에 언어가 없습니다. markdownlint가 경고합니다. text 등을 명시해주세요.

-```
+```text
 tests/
 ├── README.md                    # 이 파일
 ...
-```
+```

114-121: 웹서버 커맨드/모드 명확화: 로컬과 CI 분리

Playwright webServerpnpm run start로 고정하면 로컬에서 빌드 필요 등 번거로움이 있습니다. 로컬은 dev, CI는 start로 분기하거나, 빌드/스타트를 config 또는 CI에서 명시하세요.

예시:

// playwright.config.ts
const isCI = !!process.env.CI;
webServer: {
  command: isCI ? "pnpm run start" : "pnpm run dev",
  url: "http://localhost:3000",
  reuseExistingServer: !isCI,
}

126-142: 쿠키 기반 인증 가이드 보완: addCookies 시점/속성 주의

문서에 쿠키 주입 예시를 추가할 때, 반드시 page.goto 이전에 context.addCookies를 호출하고, localhostdomain 대신 url을 쓰는 것을 권장합니다(호스트 전용 쿠키 호환성).

예시:

await page.context().addCookies([{
  name: "accessToken",
  value: "TEST_TOKEN",
  url: "http://localhost:3000",
  path: "/",
  httpOnly: true,
  secure: false,
}]);
await page.goto("/");

또는 storageState를 활용해 전역 설정:

// playwright.config.ts
testDir: "./tests",
use: { storageState: "tests/.auth/state.json" }
tests/home.spec.ts (3)

27-33: 역할(Role) 일치: CTA가 Link라면 button 대신 link를 사용

PR 요약에 따르면 CTA는 Link 스타일입니다. 실제 DOM 역할이 anchor(link)라면 getByRole("button")은 취약합니다. getByRole("link")로 교체하고 클릭/네비게이션은 동일하게 동작합니다.

-  page.getByRole("button", { name: "타임캡슐 보러가기" })
+  page.getByRole("link", { name: "타임캡슐 보러가기" })

-  page.getByRole("button", { name: "타임캡슐 만들기" })
+  page.getByRole("link", { name: "타임캡슐 만들기" })

Also applies to: 41-49, 54-60


63-69: 고정 대기 제거: 폴링/조건 대기 적용으로 플레이크 감소

waitForTimeout(2000)은 환경에 따라 불안정합니다. 조건 기반 대기 또는 헬퍼를 사용하세요.

간단 폴링:

-    await page.waitForTimeout(2000);
-    const userCountElement = page.locator("text=/\\d+/").first();
-    await expect(userCountElement).toBeVisible();
+    await expect(page.locator("text=/\\d+/").first()).toBeVisible({ timeout: 10000 });

또는 TestHelpers 활용:

import { TestHelpers } from "./utils/test-helpers";
expect(await TestHelpers.waitForUserCountToLoad(page, 10000)).toBe(true);

81-84: 클래스 기반 선택자 최소화: floating-stars도 testId 사용

앞서 UI에 data-testid="floating-stars"를 추가했다면 테스트도 이를 사용해주세요.

-    await expect(page.locator("[class*='floating-stars']")).toBeVisible();
+    await expect(page.getByTestId("floating-stars")).toBeVisible();
tests/comprehensive.spec.ts (6)

5-17: 쿠키 속성 정비: localhost 호환성 향상을 위해 url 사용

domain: "localhost"는 브라우저/헬퍼 구현에 따라 호환성 이슈가 있을 수 있습니다. url을 사용해 호스트 전용 쿠키로 설정하는 편이 안전합니다.

-      {
-        name: "accessToken",
-        value: "TEST_TOKEN",
-        domain: "localhost",
-        path: "/",
-        httpOnly: true,
-        secure: false,
-      },
+      {
+        name: "accessToken",
+        value: "TEST_TOKEN",
+        url: "http://localhost:3000",
+        path: "/",
+        httpOnly: true,
+        secure: false,
+      },

50-69: 성능 임계값 완화/환경 분기: CI에서 과도하게 빡빡할 수 있음

홈 로드 < 3초, 탐색 네비 < 1초 조건은 CI(공유 머신)에서 플레이크를 유발합니다. CI 여부에 따라 임계값을 분기하거나, 측정 구간을 축소(URL 변경까지만 측정)하세요.

예시(임계값 분기):

-    expect(loadTime).toBeLessThan(3000);
+    expect(loadTime).toBeLessThan(process.env.CI ? 5000 : 3000);
...
-    expect(navigationTime).toBeLessThan(1000);
+    expect(navigationTime).toBeLessThan(process.env.CI ? 3000 : 1000);

또는 네비 성능은 waitForURL까지만 시간 측정:

const t0 = Date.now();
await page.getByRole("link", { name: "타임캡슐 보러가기" }).click();
await page.waitForURL("**/explore");
const t = Date.now() - t0;

71-84: 콘솔/네트워크 에러 감시 시점 조정

현재는 홈 이동 후 리스너를 등록하므로 초기 로드 중 발생한 에러를 놓칠 수 있습니다. 리스너를 먼저 등록한 뒤 네비게이션/로딩을 수행하세요.

예시:

const consoleErrors = await TestHelpers.checkConsoleErrors(page);
const failedRequests = await TestHelpers.checkFailedRequests(page);
await TestHelpers.navigateToHome(page);
expect(consoleErrors.length).toBe(0);
expect(failedRequests.length).toBe(0);

91-102: 부하 테스트 안정화: 반복 횟수/타임아웃 조정

5회 왕복은 로컬에선 OK여도 CI에서 느려질 수 있습니다. test.slow() 지정 또는 CI에서 반복 횟수 축소를 고려해주세요.

test.slow();
const loops = process.env.CI ? 3 : 5;
for (let i = 0; i < loops; i++) { ... }

104-111: 키보드 내비게이션 검증 강화

Tab 1회 후 :focus 가시성만으로는 충분치 않습니다. 포커스가 의도한 첫 포커스 가능한 요소(예: 주요 CTA/네비)에 도달했는지까지 확인하면 신뢰도가 올라갑니다.

예시:

await page.keyboard.press("Tab");
await expect(page.getByRole("link", { name: "타임캡슐 보러가기" })).toBeFocused();

129-146: API 콜 검증 정밀화: 엔드포인트/상태코드 기준으로 대기

url.includes("user")는 모호합니다. 구체 엔드포인트와 2xx 응답을 기준으로 waitForResponse를 사용하면 플레이크를 줄일 수 있습니다.

-    const userCountApiCall = apiCalls.find((url) => url.includes("user"));
-    expect(userCountApiCall).toBeTruthy();
+    const resp = await page.waitForResponse((r) =>
+      r.url().includes("/api/users") && r.status() >= 200 && r.status() < 300
+    , { timeout: 10000 });
+    expect(resp.ok()).toBeTruthy();
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 37dca81 and a4f1f71.

📒 Files selected for processing (20)
  • app/(auth)/login/page.tsx (2 hunks)
  • app/(main)/_components/home-button-section/index.tsx (1 hunks)
  • app/(main)/explore/page.tsx (1 hunks)
  • app/(main)/my-capsule/page.tsx (1 hunks)
  • app/(main)/setting/page.tsx (2 hunks)
  • app/(sub)/capsule-detail/[invite-code]/[id]/page.tsx (2 hunks)
  • app/(sub)/create-capsule/_components/steps/intro-step/index.tsx (2 hunks)
  • app/(sub)/create-capsule/page.tsx (2 hunks)
  • app/(sub)/search/page.tsx (1 hunks)
  • app/not-found.tsx (2 hunks)
  • shared/ui/floating-stars-container/index.tsx (1 hunks)
  • shared/ui/navbar/navbar-main/index.tsx (4 hunks)
  • tests/README.md (1 hunks)
  • tests/comprehensive.spec.ts (1 hunks)
  • tests/example.spec.ts (0 hunks)
  • tests/home.spec.ts (1 hunks)
  • tests/navigation.spec.ts (1 hunks)
  • tests/performance.spec.ts (1 hunks)
  • tests/run-tests.sh (1 hunks)
  • tests/utils/test-helpers.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • tests/example.spec.ts
🧰 Additional context used
🧬 Code graph analysis (5)
tests/performance.spec.ts (1)
app/(main)/_components/home-button-section/home-button-section.css.ts (1)
  • exploreButton (43-52)
tests/comprehensive.spec.ts (1)
tests/utils/test-helpers.ts (1)
  • TestHelpers (3-140)
shared/ui/navbar/navbar-main/index.tsx (1)
shared/constants/path.ts (1)
  • PATH (1-13)
tests/home.spec.ts (1)
app/(main)/_components/home-button-section/home-button-section.css.ts (1)
  • exploreButton (43-52)
app/(main)/_components/home-button-section/index.tsx (1)
shared/constants/path.ts (1)
  • PATH (1-13)
🪛 markdownlint-cli2 (0.17.2)
tests/README.md

7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: storybook-deploy
  • GitHub Check: test
🔇 Additional comments (6)
app/(auth)/login/page.tsx (2)

35-61: 메인 랜드마크 도입: 접근성 측면에서 적절합니다 (LGTM).

헤더와 본문을 명확히 분리했고, 테스트 안정화(랜드마크 탐색)에도 유리합니다.


21-26: 내부 경로 검증 로직 확인 완료

shared/utils/oauth.tssaveNextUrl 구현을 보면 nextUrl?.startsWith("/") && !nextUrl.startsWith("//") 조건으로 오직 “/”로 시작하고 “//”로 시작하지 않는 상대경로만 sessionStorage에 저장하도록 되어 있습니다.
외부 도메인이나 프로토콜·호스트가 포함된 값은 저장되지 않아, 오픈 리다이렉트 위험이 없음을 확인했습니다.
추가 검증 로직이나 화이트리스트 도입 없이도 안전하므로, 해당 리뷰는 해결된 것으로 보입니다.

app/(main)/explore/page.tsx (1)

51-59: 메인 랜드마크 추가: 전반적으로 적절합니다.

무한 스크롤/카드 목록이 main 아래로 정리되어 탐색이 수월합니다.

app/(sub)/search/page.tsx (1)

41-44: 메인 랜드마크 추가: 검색 UX에 적합합니다.

검색바와 결과 목록이 main 하위로 묶여 있어 테스트 및 키보드 탐색이 쉬워졌습니다.

app/(main)/_components/home-button-section/index.tsx (1)

7-24: Link 기반 CTA 전환은 접근성/시맨틱 측면에서 적절합니다

네비게이션 목적의 CTA를 링크로 통일하고, 시맨틱 구조를 보존한 채 스타일링하는 접근이 좋습니다.

tests/README.md (1)

21-31: 테스트 실행 스크립트 확인 완료

package.json의 scripts.test가 이미 playwright test로 정의되어 있어, 문서에 안내된 pnpm test 명령이 올바르게 Playwright를 호출합니다. 별도 수정이 필요 없습니다.

Comment on lines +10 to 19
<Link href={PATH.EXPLORE} className={styles.exploreButton} tabIndex={0}>
타임캡슐 보러가기
<div className={styles.lineInteractContainer}>
<div className={styles.lineInteract}></div>
</div>
</Link>

<Link href={PATH.CREATE_CAPSULE}>
<button type="button" className={styles.button}>
타임캡슐 만들기
</button>
<Link href={PATH.CREATE_CAPSULE} className={styles.button}>
타임캡슐 만들기
</Link>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

CTA를 Link로 바꾼 뒤 테스트 쿼리(role=button)와 불일치합니다

현재 CTA는 Next.js <Link>로 변경되어 ARIA role이 "link"입니다. 반면 본 PR에 포함된 e2e/헬퍼는 "button" 역할로 쿼리하고 있어 실패합니다. 네비게이션 목적의 요소는 링크가 맞으니 컴포넌트를 다시 버튼으로 되돌리기보다는 테스트/헬퍼 쪽을 "link"로 정정하는 것을 권장합니다. 관련 수정 제안을 tests/navigation.spec.ts 및 tests/utils/test-helpers.ts 코멘트에 첨부했습니다.

Comment on lines +53 to 55
<Link href={PATH.SEARCH} className={styles.searchButtonStyle} tabIndex={0}>
<SearchIcon width={"2rem"} height={"2rem"} />
</Link>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

아이콘 전용 검색 링크에 접근 가능한 이름 추가 필수.

시각적 텍스트가 없으므로 스크린 리더에 ‘검색’으로 읽히도록 aria-label을 부여해주세요.

-<Link href={PATH.SEARCH} className={styles.searchButtonStyle} tabIndex={0}>
+<Link href={PATH.SEARCH} className={styles.searchButtonStyle} aria-label="검색">
   <SearchIcon width={"2rem"} height={"2rem"} />
 </Link>
📝 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.

Suggested change
<Link href={PATH.SEARCH} className={styles.searchButtonStyle} tabIndex={0}>
<SearchIcon width={"2rem"} height={"2rem"} />
</Link>
<Link href={PATH.SEARCH} className={styles.searchButtonStyle} aria-label="검색">
<SearchIcon width={"2rem"} height={"2rem"} />
</Link>
🤖 Prompt for AI Agents
shared/ui/navbar/navbar-main/index.tsx lines 53-55: the icon-only search link
lacks an accessible name for screen readers; add an aria-label (e.g.,
aria-label="검색") to the Link element so assistive tech reads "검색", keep existing
tabIndex, and ensure the label is localized if needed.

Comment thread tests/home.spec.ts
Comment on lines +5 to +17
await page.goto("/");
// 테스트 시작 전에 인증 쿠키 추가
await page.context().addCookies([
{
name: "accessToken",
value: "TEST_TOKEN",
domain: "localhost",
path: "/",
httpOnly: true,
secure: false,
},
]);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

인증 쿠키 주입 시점 수정: goto 이전 + domain 대신 url 사용

현재 page.goto("/") 이후에 쿠키를 추가하고 있어 첫 요청이 비인증 상태로 진행됩니다. 또한 localhost에는 domain보다 url 지정이 호환성에 안전합니다. 아래처럼 순서를 바꾸고 속성을 정정해주세요.

-  test.beforeEach(async ({ page }) => {
-    await page.goto("/");
-    // 테스트 시작 전에 인증 쿠키 추가
-    await page.context().addCookies([
-      {
-        name: "accessToken",
-        value: "TEST_TOKEN",
-        domain: "localhost",
-        path: "/",
-        httpOnly: true,
-        secure: false,
-      },
-    ]);
-  });
+  test.beforeEach(async ({ page }) => {
+    await page.context().addCookies([
+      {
+        name: "accessToken",
+        value: "TEST_TOKEN",
+        url: "http://localhost:3000",
+        path: "/",
+        httpOnly: true,
+        secure: false,
+      },
+    ]);
+    await page.goto("/");
+  });
📝 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.

Suggested change
await page.goto("/");
// 테스트 시작 전에 인증 쿠키 추가
await page.context().addCookies([
{
name: "accessToken",
value: "TEST_TOKEN",
domain: "localhost",
path: "/",
httpOnly: true,
secure: false,
},
]);
});
test.beforeEach(async ({ page }) => {
await page.context().addCookies([
{
name: "accessToken",
value: "TEST_TOKEN",
url: "http://localhost:3000",
path: "/",
httpOnly: true,
secure: false,
},
]);
await page.goto("/");
});
🤖 Prompt for AI Agents
In tests/home.spec.ts around lines 5 to 17, the cookie is added after page.goto
and uses domain which can be incompatible; move the page.context().addCookies
call to before page.goto and replace the cookie's domain field with a url field
(e.g., "http://localhost/") while keeping name, value, path and other flags
intact so the initial request is sent with the injected auth cookie.

Comment thread tests/home.spec.ts
Comment on lines +35 to +36
await expect(page.locator("[class*='physicsContainer']")).toBeVisible();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

해시된 클래스 의존 최소화: data-testid로 교체 권장

[class*='physicsContainer']는 vanilla-extract/빌드 설정에 따라 프로덕션에서 식별자가 소실될 수 있어 매우 취약합니다. 안정적인 data-testid="physics-container"를 노출하고 테스트를 해당 testId로 전환하세요.

-    await expect(page.locator("[class*='physicsContainer']")).toBeVisible();
+    await expect(page.getByTestId("physics-container")).toBeVisible();

UI 측(물리 컨테이너 루트)에 다음 속성 추가를 권장합니다(별도 PR/커밋 가능):

<div data-testid="physics-container"> ... </div>
🤖 Prompt for AI Agents
In tests/home.spec.ts around lines 35-36, the test is relying on a
hashed/fragile CSS selector "[class*='physicsContainer']" which can break in
production; update the test to use a stable test id by selecting
page.locator('[data-testid="physics-container"]') instead, and coordinate a UI
change (add data-testid="physics-container" to the physics container root
element) if not already present so the test targets the stable attribute.

Comment thread tests/navigation.spec.ts
Comment on lines +4 to +17
test.beforeEach(async ({ page }) => {
await page.goto("/");
// 테스트 시작 전에 인증 쿠키 추가
await page.context().addCookies([
{
name: "accessToken",
value: "TEST_TOKEN",
domain: "localhost",
path: "/",
httpOnly: true,
secure: false,
},
]);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

인증 쿠키 추가 시점이 늦습니다 — SSR/리다이렉트 일관성을 위해 쿠키를 먼저 설정하세요

await page.goto("/") 이후에 쿠키를 넣으면 첫 페이지 로딩(SSR/미들웨어) 시 인증이 반영되지 않아 플래키할 수 있습니다. 쿠키 설정을 먼저 하고 이후 goto 하도록 순서를 바꾸세요.

   test.beforeEach(async ({ page }) => {
-    await page.goto("/");
-    // 테스트 시작 전에 인증 쿠키 추가
-    await page.context().addCookies([
+    // 테스트 시작 전에 인증 쿠키 추가
+    await page.context().addCookies([
       {
         name: "accessToken",
         value: "TEST_TOKEN",
         domain: "localhost",
         path: "/",
         httpOnly: true,
         secure: false,
       },
     ]);
+    await page.goto("/");
   });
📝 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.

Suggested change
test.beforeEach(async ({ page }) => {
await page.goto("/");
// 테스트 시작 전에 인증 쿠키 추가
await page.context().addCookies([
{
name: "accessToken",
value: "TEST_TOKEN",
domain: "localhost",
path: "/",
httpOnly: true,
secure: false,
},
]);
});
test.beforeEach(async ({ page }) => {
// 테스트 시작 전에 인증 쿠키 추가
await page.context().addCookies([
{
name: "accessToken",
value: "TEST_TOKEN",
domain: "localhost",
path: "/",
httpOnly: true,
secure: false,
},
]);
await page.goto("/");
});
🤖 Prompt for AI Agents
In tests/navigation.spec.ts around lines 4–17, the test sets the authentication
cookie after calling page.goto, which means the first request (SSR/middleware)
won’t see the cookie and can cause flakiness; move the await
page.context().addCookies([...]) call to run before await page.goto("/") so the
cookie is present for the initial navigation (optionally ensure a fresh context
by clearing existing cookies before adding if needed).

Comment thread tests/navigation.spec.ts Outdated
Comment thread tests/performance.spec.ts
Comment on lines +103 to +126
await page.goto("/");

// Check for any console errors
const consoleErrors: string[] = [];
page.on("console", (msg) => {
if (msg.type() === "error") {
consoleErrors.push(msg.text());
}
});

await page.waitForLoadState("networkidle");

// Should not have any console errors
expect(consoleErrors.length).toBe(0);

// Check for any failed requests
const failedRequests: string[] = [];
page.on("requestfailed", (request) => {
failedRequests.push(request.url());
});

// Should not have any failed requests
expect(failedRequests.length).toBe(0);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

초기 콘솔/네트워크 오류를 놓치지 않도록 핸들러 등록 순서를 바꾸세요

오류/실패한 요청 리스너를 goto("/") 이전에 등록해야 초기 로드 중 발생한 이슈를 포착할 수 있습니다.

-  test("should optimize bundle size and loading", async ({ page }) => {
-    await page.goto("/");
-
-    // Check for any console errors
-    const consoleErrors: string[] = [];
-    page.on("console", (msg) => {
+  test("should optimize bundle size and loading", async ({ page }) => {
+    // 등록을 먼저 해서 초기 로드 이슈 포착
+    const consoleErrors: string[] = [];
+    page.on("console", (msg) => {
       if (msg.type() === "error") {
         consoleErrors.push(msg.text());
       }
     });
-
-    await page.waitForLoadState("networkidle");
-
-    // Should not have any console errors
-    expect(consoleErrors.length).toBe(0);
-
-    // Check for any failed requests
-    const failedRequests: string[] = [];
-    page.on("requestfailed", (request) => {
+    const failedRequests: string[] = [];
+    page.on("requestfailed", (request) => {
       failedRequests.push(request.url());
     });
 
+    await page.goto("/");
+    await page.waitForLoadState("networkidle");
+
+    // Should not have any console errors
+    expect(consoleErrors.length).toBe(0);
     // Should not have any failed requests
     expect(failedRequests.length).toBe(0);
   });
📝 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.

Suggested change
await page.goto("/");
// Check for any console errors
const consoleErrors: string[] = [];
page.on("console", (msg) => {
if (msg.type() === "error") {
consoleErrors.push(msg.text());
}
});
await page.waitForLoadState("networkidle");
// Should not have any console errors
expect(consoleErrors.length).toBe(0);
// Check for any failed requests
const failedRequests: string[] = [];
page.on("requestfailed", (request) => {
failedRequests.push(request.url());
});
// Should not have any failed requests
expect(failedRequests.length).toBe(0);
});
test("should optimize bundle size and loading", async ({ page }) => {
// 등록을 먼저 해서 초기 로드 이슈 포착
const consoleErrors: string[] = [];
page.on("console", (msg) => {
if (msg.type() === "error") {
consoleErrors.push(msg.text());
}
});
const failedRequests: string[] = [];
page.on("requestfailed", (request) => {
failedRequests.push(request.url());
});
await page.goto("/");
await page.waitForLoadState("networkidle");
// Should not have any console errors
expect(consoleErrors.length).toBe(0);
// Should not have any failed requests
expect(failedRequests.length).toBe(0);
});
🤖 Prompt for AI Agents
In tests/performance.spec.ts around lines 103 to 126, the console and
requestfailed event handlers are registered after page.goto("/"), so errors and
failed requests that occur during initial navigation are missed; move the
page.on("console", ...) and page.on("requestfailed", ...) registrations so they
execute before calling page.goto("/"), then perform
page.waitForLoadState("networkidle") and the expect assertions after navigation
completes to capture any initial load errors or failed requests.

Comment thread tests/utils/test-helpers.ts Outdated
@github-actions
Copy link
Copy Markdown

This pull request (commit 0dbb8b3) has been deployed to Vercel ▲ - View GitHub Actions Workflow Logs

Name Link
🌐 Unique https://time-capsule-cz5e1kyvw-hs-projects-b4a69d5f.vercel.app
🔍 Inspect https://vercel.com/hs-projects-b4a69d5f/time-capsule/Fgitz2cvdJn5zKAFy9RPq2hfMPRB

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
tests/home.spec.ts (2)

35-36: 해시 클래스 의존 제거: 안정적인 test id로 전환

빌드 후 클래스명이 바뀌면 테스트가 깨집니다. data-testid로 교체 권장.

-    await expect(page.locator("[class*='physicsContainer']")).toBeVisible();
+    await expect(page.getByTestId("physics-container")).toBeVisible();

UI에도 루트에 data-testid="physics-container"를 노출해 주세요.


4-17: 인증 쿠키 주입 시점/속성 수정: goto 이전 + url 사용

첫 요청이 비인증 상태로 나가며, domain 대신 url을 사용해야 로컬 환경에서 호환성이 높습니다. 아래처럼 수정해 주세요.

   test.beforeEach(async ({ page }) => {
-    await page.goto("/");
-    // 테스트 시작 전에 인증 쿠키 추가
-    await page.context().addCookies([
+    // 테스트 시작 전에 인증 쿠키 추가
+    await page.context().addCookies([
       {
         name: "accessToken",
         value: "TEST_TOKEN",
-        domain: "localhost",
+        url: "http://localhost:3000",
         path: "/",
         httpOnly: true,
         secure: false,
       },
     ]);
+    await page.goto("/");
   });

추가로, 중복 설정 방지를 위해 storageState(globalSetup) 기반 인증 고정도 고려해 주세요. 필요 시 설정 템플릿 제공 가능합니다.

🧹 Nitpick comments (11)
tests/README.md (4)

7-17: 코드 블록 언어 지정 누락(MD040) — language 지정으로 lint 에러 제거

디렉토리 트리 예시는 언어 지정을 안 해서 markdownlint(MD040)에 걸립니다. text 등으로 지정해주세요.

-```
+```text
 tests/
 ├── README.md                    # 이 파일
 ├── utils/
 │   └── test-helpers.ts         # 공통 테스트 유틸리티
 ├── home.spec.ts                # 홈페이지 기능 테스트
 ├── auth.spec.ts                # 인증 기능 테스트
 ├── navigation.spec.ts          # 네비게이션 테스트
 ├── performance.spec.ts         # 성능 및 로딩 테스트
 ├── comprehensive.spec.ts       # 종합 테스트 스위트

---

`26-31`: **실행 예시에 누락된 스펙 추가**

아래 섹션의 단일 파일 실행 예시에 `performance.spec.ts`, `comprehensive.spec.ts`도 포함해 주세요. 사용자가 README만 보고도 전체 스펙을 쉽게 실행할 수 있도록 합니다.


```diff
 pnpm test home.spec.ts
 pnpm test auth.spec.ts
 pnpm test navigation.spec.ts
+pnpm test performance.spec.ts
+pnpm test comprehensive.spec.ts

114-117: CI 환경 설정 예시를 코드로 명시 — 재현성 향상

문장 설명만으로는 설정이 모호합니다. retries/workers를 CI에서 강제하는 예시 코드를 추가해 주세요.

 - `CI`: CI 환경에서만 재시도 및 제한된 워커 사용
 - `CI` 환경에서는 2회 재시도, 1개 워커 사용
+
+예시 설정:
+```ts
+// playwright.config.ts (발췌)
+export default defineConfig({
+  retries: process.env.CI ? 2 : 0,
+  workers: process.env.CI ? 1 : undefined,
+});
+```

195-214: GitHub Actions 안정성 개선: pnpm 캐시 + with-deps 설치

CI에서 WebKit 등 시스템 의존 패키지가 필요합니다. --with-deps 사용과 pnpm 캐시를 추가하면 플레이크를 줄이고 속도를 개선할 수 있습니다. Node 액션도 최신 메이저로 교체 권장.

 name: Playwright Tests
 on: [push, pull_request]
 jobs:
   test:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v3
-      - uses: actions/setup-node@v3
+      - uses: actions/setup-node@v4
         with:
           node-version: 18
-      - run: pnpm install
-      - run: pnpm exec playwright install
+          cache: 'pnpm'
+      - uses: pnpm/action-setup@v4
+        with:
+          version: 9
+      - run: pnpm install --frozen-lockfile
+      - run: pnpm exec playwright install --with-deps
       - run: pnpm test
       - uses: actions/upload-artifact@v3
         if: always()
         with:
           name: playwright-report
           path: playwright-report/
tests/home.spec.ts (7)

1-1: 공통 유틸 사용으로 중복 제거

이미 tests/utils/test-helpers.ts가 존재하므로 본 스펙도 헬퍼를 활용해 유지보수성을 높이면 좋습니다. 예: 반응형/로드 대기/에러 체크 등.

-import { expect, test } from "@playwright/test";
+import { expect, test } from "@playwright/test";
+import { TestHelpers } from "./utils/test-helpers";

23-25: 시맨틱/접근성 우선 셀렉터 사용

h1 태그 직접 선택 대신 Role 기반이 내구성이 높습니다.

-    await expect(page.locator("h1")).toBeVisible();
+    await expect(page.getByRole("heading", { level: 1 })).toBeVisible();

46-49: 중복 단언 축소: toHaveURL 한 줄로 대체

waitForURL + page.url() 이중 검증은冗長합니다.

-    // Wait for navigation
-    await page.waitForURL("**/explore");
-    expect(page.url()).toContain("/explore");
+    await expect(page).toHaveURL(/\/explore(?:$|\?)/);

57-60: URL 검증 일관화

위와 동일하게 toHaveURL 사용으로 간결화하세요.

-    // Wait for navigation
-    await page.waitForURL("**/create-capsule");
-    expect(page.url()).toContain("/create-capsule");
+    await expect(page).toHaveURL(/\/create-capsule(?:$|\?)/);

71-79: 접근성 친화적 선택자 사용 또는 헬퍼 위임

img[src*='bead']는 구현에 강하게 결합됩니다. 대체로 alt 기반 Role 선택자 또는 TestID가 낫습니다. 또한 동일 검증을 TestHelpers로 위임해 중복을 줄이세요.

-    const physicsImages = page.locator("img[src*='bead']");
+    const physicsImages = page.getByRole("img", { name: /bead/i });

Alt 텍스트가 없다면 data-testid="physics-image"로 노출 후 getByTestId 사용을 권장합니다. 더 나아가 await TestHelpers.checkPhysicsElements(page)로 캡슐화하면 재사용성이 올라갑니다.


81-84: 애니메이션 컨테이너도 TestID로 고정

클래스명 변경에 취약합니다. TestID로 교체 권장.

-    await expect(page.locator("[class*='floating-stars']")).toBeVisible();
+    await expect(page.getByTestId("floating-stars")).toBeVisible();

UI에는 data-testid="floating-stars" 속성 추가를 요청드립니다.


86-98: 반응형 검증을 헬퍼로 추출해 중복 제거

뷰포트 변경 + 동일 단언이 반복됩니다. 헬퍼로 위임하면 가독성과 유지보수성이 향상됩니다.

-  test("should be responsive on different screen sizes", async ({ page }) => {
-    // Test mobile viewport
-    await page.setViewportSize({ width: 375, height: 667 });
-    await expect(page.locator("main")).toBeVisible();
-
-    // Test tablet viewport
-    await page.setViewportSize({ width: 768, height: 1024 });
-    await expect(page.locator("main")).toBeVisible();
-
-    // Test desktop viewport
-    await page.setViewportSize({ width: 1920, height: 1080 });
-    await expect(page.locator("main")).toBeVisible();
-  });
+  test("should be responsive on different screen sizes", async ({ page }) => {
+    await TestHelpers.testResponsiveDesign(page, [
+      { width: 375, height: 667 },   // mobile
+      { width: 768, height: 1024 },  // tablet
+      { width: 1920, height: 1080 }, // desktop
+    ]);
+  });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a4f1f71 and 0dbb8b3.

📒 Files selected for processing (4)
  • tests/README.md (1 hunks)
  • tests/home.spec.ts (1 hunks)
  • tests/navigation.spec.ts (1 hunks)
  • tests/utils/test-helpers.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/utils/test-helpers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/navigation.spec.ts
🧰 Additional context used
🧬 Code graph analysis (1)
tests/home.spec.ts (1)
app/(main)/_components/home-button-section/home-button-section.css.ts (1)
  • exploreButton (43-52)
🪛 markdownlint-cli2 (0.17.2)
tests/README.md

7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: deploy
  • GitHub Check: test

Comment thread tests/home.spec.ts
Comment on lines +62 to +69
test("should display user count in caption section", async ({ page }) => {
// Wait for the user count to be loaded
await page.waitForTimeout(2000);

// Check if user count is displayed (it should be a number)
const userCountElement = page.locator("text=/\\d+/").first();
await expect(userCountElement).toBeVisible();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

플레이크 유발 waitForTimeout 제거 — 조건 기반 대기로 전환

고정 지연은 CI 환경에서 불안정합니다. TestID를 노출하고 값 패턴을 직접 검증해 주세요.

-    // Wait for the user count to be loaded
-    await page.waitForTimeout(2000);
-
-    // Check if user count is displayed (it should be a number)
-    const userCountElement = page.locator("text=/\\d+/").first();
-    await expect(userCountElement).toBeVisible();
+    // 사용자 수 로드 대기 (조건 기반)
+    const userCountElement = page.getByTestId("user-count");
+    await expect(userCountElement).toHaveText(/^\d{1,3}(,\d{3})*$/);

UI 측에 data-testid="user-count" 추가가 필요합니다. 가능하면 로딩 완료 신호(예: data-loaded="true" 또는 네트워크 idle)로 대기 조건을 보강하세요.

📝 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.

Suggested change
test("should display user count in caption section", async ({ page }) => {
// Wait for the user count to be loaded
await page.waitForTimeout(2000);
// Check if user count is displayed (it should be a number)
const userCountElement = page.locator("text=/\\d+/").first();
await expect(userCountElement).toBeVisible();
});
test("should display user count in caption section", async ({ page }) => {
// 사용자 수 로드 대기 (조건 기반)
const userCountElement = page.getByTestId("user-count");
await expect(userCountElement).toHaveText(/^\d{1,3}(,\d{3})*$/);
});
🤖 Prompt for AI Agents
In tests/home.spec.ts around lines 62 to 69, remove the hardcoded await
page.waitForTimeout(2000) and instead target a stable test hook: add
data-testid="user-count" to the UI element that displays the user count (and if
possible a loading flag like data-loaded="true" or use network idle) and in the
test use a test-id selector (e.g. getByTestId or
locator('[data-testid="user-count"]')) with a conditional wait/assert such as
waiting for it to be visible and toHaveText matching a number regex (/\d+/) so
the test waits for the real readiness signal rather than a fixed timeout.

Comment thread tests/README.md
Comment on lines +13 to +14
├── auth.spec.ts # 인증 기능 테스트
├── navigation.spec.ts # 네비게이션 테스트
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

README의 파일 목록과 실제 파일 존재 여부 확인 필요

auth.spec.ts가 목록에 있지만 실제 PR에 포함되지 않았습니다. 문서와 코드의 싱크를 맞춰 주세요. 존재하지 않으면 목록에서 제외하거나 “추가 예정”으로 표기하는 것이 좋습니다.

다음 스크립트로 파일 존재 여부를 확인할 수 있습니다:


🏁 Script executed:

#!/bin/bash
fd -t f -g "tests/auth.spec.ts" -H || echo "tests/auth.spec.ts: not found"

Length of output: 34


파일 존재 여부를 확실히 확인하기 위해 아래 스크립트를 실행해 주세요:


🏁 Script executed:

#!/bin/bash
# tests 디렉토리 파일 목록 확인
echo "🔍 tests 디렉토리 파일 목록:"
ls -1 tests

# fd로 auth.spec.ts 검색
echo "🔍 fd로 auth.spec.ts 검색:"
fd -t f auth.spec.ts -H || echo "auth.spec.ts: not found"

# find로 auth.spec.ts 검색
echo "🔍 find로 auth.spec.ts 검색:"
find tests -type f -name auth.spec.ts || echo "auth.spec.ts: not found"

Length of output: 351


테스트 파일 목록 동기화 필요

현재 tests/README.md (13–14라인)에 auth.spec.ts가 나열되어 있지만, 실제로는 tests 디렉토리에 해당 파일이 없습니다. 문서와 코드가 어긋나면 유지보수가 어려워지므로, 아래처럼 수정해주세요.

  • 파일: tests/README.md
  • 라인: 13–14
- ├── auth.spec.ts                # 인증 기능 테스트
+ # ├── auth.spec.ts                # 인증 기능 테스트 (추가 예정)

또는

- ├── auth.spec.ts                # 인증 기능 테스트

— 목록에서 완전히 제거

위 두 가지 중 하나를 선택해 반영해 주세요.

📝 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.

Suggested change
├── auth.spec.ts # 인증 기능 테스트
├── navigation.spec.ts # 네비게이션 테스트
├── navigation.spec.ts # 네비게이션 테스트
🤖 Prompt for AI Agents
In tests/README.md around lines 13–14, the README lists auth.spec.ts which does
not exist in the tests directory; either add the missing tests/auth.spec.ts file
to the repo (with appropriate content and link) or remove the auth.spec.ts line
from the README so the document matches the actual test files—pick one approach
and update the README accordingly to keep the list in sync with the filesystem.

@seueooo seueooo marked this pull request as draft August 25, 2025 16:28
@seueooo seueooo changed the title Test: e2e 테스트 작성 및 접근성 개선 Test: 네비게이션 검증 test 작성 Aug 28, 2025
@seueooo seueooo changed the title Test: 네비게이션 검증 test 작성 Test: 네비게이션 검증 test 추가 Aug 28, 2025
@seueooo seueooo marked this pull request as ready for review December 28, 2025 11:10
@seueooo seueooo closed this Dec 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Test]: 테스트 코드 작성

1 participant