# task-2834 보고서 — PR #118 e2e 회귀(502 "연결 실패" 토스트 2중 렌더) 수정

- 팀: dev2 (오딘)
- 담당: 프레이야(수정), 헤임달(독립 QA)
- 브랜치: `task/task-2833-dev2` (PR #118, merge_policy=none → OPEN 유지)
- worktree: `/home/jay/projects/InsuRo/.worktrees/task-2833-dev2`
- 커밋: `1b8e3bc`

## S — Situation
task-2833(PR #118)이 InfoKeyword 폴링 상한을 키워드수 비례로 동적화한 뒤, e2e
`tests/e2e/keyword-analysis-info-generate.spec.ts` **시나리오2(502 에러 → 한국어 토스트)** 가
`strict mode violation: getByText("인포키워드 서버 연결에 실패했습니다") resolved to 2 elements` 로 실패.

## C — Complication
task는 "502 시 토스트가 2번 렌더된다(앱 버그)"로 원인을 추정. 그러나 팀장 선행 실측:
- 문제의 502는 **generate 엔드포인트** 흐름인데, task-2833 diff는 **analyze/pollStatus 흐름만** 변경(generate 미변경).
- `TOAST_LIMIT=1` (src/config/constants.ts) → 동시 토스트 1개만 렌더. `handleGenerateKeywords`(452행)는 502 시 `toast()` **1회만** 호출.
→ "토스트 2중 렌더" 가설은 코드와 배치. 재현으로 실제 원인을 규명하도록 위임.

## Q — Question
2 elements의 실제 정체는 무엇이며, 진짜 회귀인가? 최소 수정 지점은 앱인가 테스트인가?

## A — Answer (실측 근본원인)
**케이스 B 확정 — 앱 버그 아님, e2e 로케이터 문제(간헐 flaky).**
- 2 elements = ① 실제 보이는 `ToastDescription` div("인포키워드 서버 연결에 실패했습니다")
  + ② Radix `@radix-ui/react-toast`의 **ToastAnnounce**(접근성용 hidden `aria-live` `span[role=status]`).
  후자는 토스트 마운트 후 최대 ~1초간 `"Notification {title}{description}"`("Notification 키워드 생성 실패인포키워드 서버 연결에 실패했습니다")를 담는 별도 DOM 노드로 존재 후 언마운트.
- `getByText(...)`(substring)가 이 hidden 노드까지 잡아 strict mode violation(2 elements) 간헐 발생.
- **회귀 아님**: 부모 `bf44379`(task-2833 이전)에서 동일 테스트 `--repeat-each=8` → 8건 모두 동일 실패 재현.
  즉 task-2833 이전부터 존재하던 flaky 로케이터 버그이며, generate 흐름은 diff 미변경.

### 수정
- 파일: `tests/e2e/keyword-analysis-info-generate.spec.ts` (시나리오2, 1파일만)
- 변경: 토스트 검증 로케이터에 `{ exact: true }` 추가 + 원인 설명 주석.
  ```diff
  - await expect(page.getByText("인포키워드 서버 연결에 실패했습니다")).toBeVisible({ timeout: 10_000 });
  + await expect(page.getByText("인포키워드 서버 연결에 실패했습니다", { exact: true })).toBeVisible({ timeout: 10_000 });
  ```
- `exact: true`는 hidden 공지 노드("Notification ...")를 자동 배제 → 결정적. 보이는 ToastDescription은 정확히 일치하므로 "한국어 502 메시지 노출" 검증 취지 보존. "Failed to fetch 미노출" assertion 그대로 유지.
- **앱(InfoKeyword.tsx) 미변경**: 폴링 상한 동적화(task-2833 `computePollMaxSeconds` 등) 완전 보존. `src/lib/apiFetch`·`use-toast.ts`·`components/ui/**` 미접촉.

## 검증 결과 (헤임달 독립 G2 재검증 — 전부 PASS)
- `npx tsc --noEmit` → 에러 0.
- `npx vitest run src/pages/__tests__` → Test Files 3 passed, **26 tests passed**. `infokeyword-poll.test.ts`(폴링 동적화 3건) 유지·PASS.
- `npx playwright test tests/e2e/keyword-analysis-info-generate.spec.ts` → **시나리오1·2·3 전부 PASS** (3 passed).
- 결정성: 시나리오2 `--repeat-each=10` → **10/10 PASS** (flaky 재발 0).
- 앱 무변경 교차확인: `git show 1b8e3bc --name-only` → e2e spec 1파일만. generate 흐름 diff 0.

## L1 스모크테스트 결과
- 서버 재시작: 해당없음(Playwright webServer `npm run dev` :8080 자동 기동 후 종료).
- API 응답 확인: 해당없음(502는 page.route mock). 실제 앱 동작은 e2e 브라우저로 검증.
- 스크린샷: `/home/jay/workspace/memory/reports/task-2834-502-toast.png`
  - 육안 확인: 우하단 빨간 토스트 "키워드 생성 실패" + "인포키워드 서버 연결에 실패했습니다" **단일** 표시, 화면 어디에도 "Failed to fetch" 없음.

## 발견 이슈 및 해결
- task 전제("main/PR#117에서 SUCCESS")와 실측 상충: 부모에서도 재현되는 기존 flaky(로컬 부하 환경 타이밍 레이스로 CI에서는 간헐 통과했을 가능성). 앱 회귀 아님을 diff·재현으로 확정 후, 로케이터 결정화로 근본 해소.

## 머지 판단
- **머지 필요**: No (merge_policy=none — PR #118 OPEN 유지). ANU 독립검증 후 회장/ANU 판단.
- **브랜치**: `task/task-2833-dev2` (신규 PR 금지, 기존 PR #118에 커밋 반영)
- **머지 의견**: 변경은 e2e spec 1줄 로케이터 결정화로 리스크 극소. 앱 로직·폴링 동적화 무영향. task-2833 폴링 동적화와 함께 PR #118로 검토 권장.

## 모델 사용 기록
- 프레이야(수정): sonnet — 재현·근본원인 규명·수정. 정당성: 디버깅/원인규명은 haiku 부적합.
- 헤임달(독립 QA): sonnet — G2 독립 재검증.
- 팀장(오딘, Opus): 직접 코딩 0, 설계/선행 실측/검토/통합만.

## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회

