Skip to content

고정 E2E 날짜가 PR 스냅샷 창 밖으로 나가던 실패를 공통 준비 코드로 해결

  • 기간: 2026-09-09. 오너가 #1446·#1447을 함께 확인한 뒤 고정 날짜와 스냅샷 기준을 맞추는 수정을 지시했다.
  • 릴리스: 오너 지시로 다음 패치 release/v0.17.6에 포함한다. v0.18.0은 리팩터링 전용이다. 초기 개발 검증은 2ecd34306bb660c58f5b7a321c1cdf51e139f562에서 수행했으며, 패치 기준은 8ea0f1d134b76687a4f8b58cfd7c5e395d6ac49a다.
  • 랜딩: PR #1455와 후속 #1459release/v0.17.6에 병합됐다. 오너가 통과한 full CI의 반복을 중단하도록 지시했으므로 기존 결과를 보존하며 새 full CI 큐는 요청하지 않는다. staging/Production 승격은 미실행이다. 최종 날짜 정책은 아래 §8을 따른다.
  • 설계서: 없음(수리 건). 이슈 #1446, #1447.
  • 정본: e2e/support/businessDate.mjs, e2e/support/prOverviewFixture.mjs, e2e/support/prOverviewFixtureClock.mjsAGENTS.md §9.
  • 도구: ci:local의 체크아웃별 격리 Supabase, Playwright. 개발 중 다른 세션의 4173 서버와 충돌하지 않도록 레포 밖 실행기가 자체 빌드·임시 포트의 preview를 사용했다.
  • 게이트: tests/react/prOverviewFixture.test.mjs, emptyAccountJourney.e2e.mjs, viewport 6개 spec. 기존 수정 파일은 감사 manifest 미등재이므로 pending-changes 신고 대상이 아니다(미등재 신고는 게이트가 거부한다).
  • DB/앱: 운영 함수·schema·migration·앱 코드 변경 없음.

1. 배경

E2E는 저장·조회·브라우저 달력을 2026-09-07로 고정하지만, 신규 계정과 통계 worker는 실제 DB 오늘을 중심으로 PR 개요 스냅샷 3일을 발행한다. 2026-09-09부터 고정 조회 날짜가 그 창을 벗어나 빈 계정 RPC가 55000으로 실패하고, 홈 리포트 링크를 여는 화면 검사도 실패했다.

2. 문제 제기

업무 날짜를 오늘로 이동하면 재현 입력이 실행일에 따라 바뀐다. 빈 계정 읽기만 오늘로 변경해도 고정 브라우저 날짜의 viewport는 남는다. 계정 생성 때 스냅샷을 한 번 준비해도 이후 통계 worker가 새 세대를 실제 오늘로 발행하므로 저장·삭제 뒤에는 다시 맞춰야 한다.

로컬 DB에서도 매분 예약된 rollover가 실행된다. 고정 날짜로 준비한 뒤 이 예약 작업이 실행되면 실제 오늘의 창으로 다시 교체되므로, 준비 직후 통과만으로는 실행 시각에 따른 경합이 해결됐다고 볼 수 없다.

3. 해결 방안

로컬 service client와 명시적 테스트 owner·업무 날짜를 받는 preparePrOverviewFixture를 고정 달력 E2E의 준비 단계에 사용한다. 실제 서버의 bootstrap과 통계 처리를 먼저 실행한 뒤, 기존 refresh_user_pr_overview_snapshot_window의 날짜 인자로 동일 적용 세대의 스냅샷을 준비한다. 이미 같은 날짜 창이면 다시 생성하지 않는다.

준비 전에 beginPrOverviewFixtureScope로 해당 격리 DB의 통계 예약 작업을 잠시 멈춘다. 기존 pauseStatsCron의 잠금·실행 중 작업 대기·기존 active 값 복원을 재사용한다. 계정 bootstrap과 명시적인 실제 worker 호출은 계속 실행하며, 테스트 계정을 삭제한 뒤 예약 상태를 복원한다. 테스트가 실패해도 정리 단계가 복원과 PG 연결 종료를 시도한다.

v0.17.6 기준에는 v0.17.5에서 분리된 compute·publish 예약 작업이 있으므로 기존 backfill·claim·rollover와 함께 다섯 작업을 격리한다. 이 기준 DB에 해당 마이그레이션을 적용한 뒤 실제 cron 회귀 1/1과 빈 계정 저장·삭제 9/9만 추가 확인했다. 이미 통과한 viewport·전체 check·docs 빌드는 반복하지 않았다.

4. 적용한 내용

  • 재준비 전 requested/applied 세대 일치, worker 오류 없음, rollover 발행 성공, 적용 세대와 연속 3일 헤더의 존재를 검사한다. 누락·실패를 준비 함수가 대신 복구해 테스트를 통과시키지 않는다.
  • 준비 후 세대가 전진하지 않았는지와 요청 날짜 ±1일이 실제 발행됐는지 다시 읽는다. 원격 service client·owner 또는 날짜 누락은 거부한다.
  • 예약 작업 제어 전에 명시한 BARBELIC_DB_SANDBOXcil 프로젝트 ID와 API 포트가 service client와 일치하는지 확인한다. 다른 DB URL·컨테이너 환경변수로 대상을 바꾸지 않는다. 활성 scope가 없거나 끝난 뒤에는 스냅샷 준비도 거부한다.
  • GitHub-hosted full CI는 실제 hosted runner이고 sandbox 경로가 그 job의 GITHUB_WORKSPACE와 같을 때만 기본 CLI 포트를 쓰는 checkout 환경을 허용한다. 빈 계정·viewport 단계에 명시적 sandbox 경로를 전달하며, 실제 예약 작업 회귀도 로컬·hosted full CI에 함께 등록한다.
  • 빈 계정 생성, 첫 기록 저장·통계 완료, 삭제·통계 완료마다 같은 날짜의 PR 읽기를 검사한다.
  • bottom-clearance의 본인·친구, viewport-matrix, dock-geometry, 고정 날짜를 사용하는 프로필·그룹·리포트 디자인 검사도 공통 함수를 사용한다. 기존 화면 단언은 유지한다.
  • 기본 업무 날짜와 환경변수 override를 보존하고, 실행 시각이 2030년으로 바뀌어도 기본값이 움직이지 않는 회귀를 추가했다.
  • bottom-clearance는 월초의 seed 날짜가 이전 달에 속하면 달력을 그 달로 이동한 뒤 정확한 날짜를 선택한다. 이웃 달 셀은 기록을 표시해도 선택 버튼이 아니므로 단순 클릭 대기는 월초에 시간 초과했다.

작업 중 드러난 것

#1449의 오늘 날짜 기본값 제안은 이번 고정 날짜 수정으로 대체되어 닫혔다. 그 초기 큐의 preview 포트 점유, 후속 큐의 CASE-053 재시도 실패는 날짜 오류와 구분한다.

추가 2026-10-01 브라우저 실험은 달력 이동 누락을 먼저 검출했다. 이를 고친 후에는 실제 9월 9일에 발급된 토큰보다 브라우저 시계가 앞서 refresh-token 400으로 인증이 끊겼다. 인증 서버 시계까지 통제하는 변경은 범위 밖이다. 이 실패를 통과로 집계하지 않고, 화면의 월 경계는 과거 2026-09-01로 검증한다. 실제 인증 RPC를 직접 읽는 빈 계정 검사는 미래 2030-01-01 스냅샷도 검증한다.

5. 적용 결과

항목결과
원래 날짜 2026-09-07 빈 계정 검사수정 전 8개 중 1개 실패(7개 통과) → 수정 후 9/9 통과
고정 날짜 경계원래 실패 날짜, 월말·다음 달 첫날, 윤일, 연말·다음 해 첫날, 2030년 첫날의 실제 인증 PR RPC 통과
준비 함수·예약 작업 scope 회귀준비 함수 8/8, scope 12/12 통과; 미완료·실패·누락 발행과 비활성 scope의 준비 거부, hosted 대상 검증·실패 시 복원 포함
실제 예약 작업 경합1/1 통과(skip 0); 실제 scheduled rollover의 격리 전 덮어쓰기 → 격리 중 유지 → 복원 뒤 재실행, 기존 설정 복원 확인
v0.17.6 기준 호환성최신 패치 DB에서 cron 다섯 작업의 격리·복원 1/1, 빈 계정·실제 저장·삭제 9/9 통과. 전체 CI 실행 아님
연말 저장·삭제 회귀E2E_BUSINESS_DATE=2025-12-31로 빈 계정 전체 9/9 통과
viewportcron scope 추가 후 22/22 첫 시도 통과, 2.7분
월초 화면 회귀E2E_BUSINESS_DATE=2026-09-01의 bottom-clearance 1/1 첫 시도 통과, 17개 표면, 45초
전체 checkcron scope 추가 시 3,416 통과·DB 조건부 28 skip·실패 0; 이후 hosted 호환성은 scope 단위 검사 12/12로 확인. 전체 검사는 반복하지 않음
docs/admin 빌드·산출물 대상 검사통과
최종 큐기존 요청 취소. 오너의 중복 full CI 생략 지시로 새 요청 없음; 기존 단계별 통과를 최종 통합 full CI 통과로 표시하지 않음
staging·Production미실행

6. 이번 개선으로 향상된 것

고정 날짜의 회귀 재현과 실제 DB 통계 발행을 함께 검증한다. 새 고정 달력 E2E는 명시한 ci-local sandbox의 scope를 열고, 같은 owner·날짜로 통계 완료 뒤 공통 준비 함수를 호출한 다음 정리 단계에서 scope를 닫는다. bootstrap·worker 자체의 검사는 준비 전 실패를 유지한다.

7. 실행 날짜 의존성 후속 제거 이력 (#1459, §8에서 정책 정정)

이 절은 #1459 당시의 구현과 검증 이력이다. 실행 시각 고정 및 요청 날짜 지정 방식과 전면적인 날짜 의존 금지 원칙은 아래 §8의 오너 지시에 따라 대체한다. 실제 통계 발행·정리 완료 확인은 유지한다.

#1455는 release/v0.17.6에 병합됐다. 후속 감사에서는 CASE-008의 “올해의 지난 세 날짜”가 1월 1일에는 한 날짜, 2일에는 두 날짜밖에 만들지 못했고, PR mapper의 2026년 annual stats fixture는 실행 연도가 2027년이면 null이 됐다.

  • CASE-008의 업무 입력은 2026-09-07로 명시하고 같은 연도의 서로 다른 세 날짜를 만든다. 실제 실행 날짜와 기대값을 함께 움직이지 않는다.
  • Node와 브라우저의 report RPC에는 같은 지원 인자 p_as_of를 전달한다. 브라우저 요청 입력만 지정하고 실제 인증·서버 응답·DB 통계·렌더링·새로고침·정리 단언을 유지한다. 브라우저 전체 Date는 변경하지 않아 JWT와 홈 PR 스냅샷의 실제 서버 시각을 침범하지 않는다. 기본 “오늘” 선택 배선은 이 날짜 집합 검사 범위가 아니다.
  • 실제 E2E가 드러낸 deferred 통계 접수와 완료의 혼동도 보완했다. 요청 세대의 실제 발행을 확인한 뒤 날짜 집합을 읽는다. 정리에서도 deferred 삭제 세대의 발행을 기다린 뒤 기존 세대 일치·열린 작업 0건 단언을 모두 수행한다. 로컬 service profile은 기존 실제 worker drainer, credential profile은 배포된 worker 관찰을 사용한다. 대기 제한은 업무 Date와 독립된 단조 시계를 쓴다.
  • PR mapper 테스트는 명시한 2026-06-12T12:00:00+09:00만 Date mock으로 고정하고 기존 기대값과 단언을 유지한다.
  • E2E 작성 지침, AGENTS.md, error-case 안내에 실행 날짜·연도·시간대 의존 금지, 명시적 업무 입력, 인증/DB/브라우저 시계 구분, 실제 발행 완료 확인, 제품의 월말·연말 경계 동작을 별도로 검증하는 규칙을 추가했다.

후속 검증은 날짜 생성 7/7, 연말 UTC·연초 KST로 실행 시계를 옮긴 mapper 각 1/1, 통계 발행·cleanup 9/9와 실제 로컬 CASE-008 1/1(최종 수정 10.8초, retry 0, skip 0)이 통과했다. 레지스트리·감사 선언·UTF-8 검사와 앱 빌드도 통과했다. 이미 통과한 full CI는 반복하지 않으며, 부분 검증을 최종 통합 full CI 통과로 표시하지 않는다. staging·Production 승격은 이 작업에 포함하지 않는다.

8. 최종 원칙: 실제 실행 날짜 허용, 잘못된 달력 전제 수정

오너는 실행 시각을 입력으로 지정하지 않고 실제 날짜를 사용하되, 실행 도중 자정 통과로 생기는 드문 실패는 감수하도록 범위를 확정했다. 수정 대상은 특정 연도가 항상 과거라는 가정이나 연초에도 올해의 지난 날짜가 세 개라는 가정처럼 정상적인 실행 날짜에 지속적으로 실패하는 잘못된 전제다. AGENTS.md, 테스트 감사 기준, E2E 생성 지침과 error-case 안내의 전면적인 날짜 의존 금지 원칙을 이 기준으로 교체했다. 기존의 좁은 만료·타이머 테스트 도구는 유지하되 일반 테스트를 시각 입력 방식으로 전환하는 근거로 삼지 않는다.

  • CASE-008은 실제 실행 연도의 전년도에 서로 다른 세 기록을 만들고 실제 화면의 기간 이동 버튼으로 그 연도를 선택한다. 완전한 지난 연도를 사용하므로 1월 1일에도 필요한 날짜가 충분하다.
  • 브라우저 report RPC의 p_as_of 덮어쓰기를 제거하고 앱이 보낸 원본 요청의 날짜를 관찰한다. Node 조회도 실제 실행 날짜를 사용한다. 실제 인증·DB 저장·통계 발행·365/366개 잔디·0kg 운동일·새로고침 후 같은 연도 재선택·정리 검증을 유지한다.
  • mapper의 Date mock을 제거하고 실제 현재 연도에 맞춰 연간 통계 fixture를 만든다. desktop PR 차트의 “2025년은 과거” 가정은 실제 현재 연도의 전년도 데이터로 바꾼다. 기대 수치와 내용 단언은 유지한다.
  • 이전 고정 날짜 PR 스냅샷 수리의 검증 이력은 보존한다. 일반 E2E에 실행 시각 입력이나 전역 시계 고정을 새로 확대하지 않는다.

집중 검증은 전년도 날짜 생성 7/7, mapper 해당 동작 1/1, desktop PR 차트 3/3, 실제 로컬 CASE-008 1/1(13.9초, retry 0, skip 0)이 통과했다. 감사 신고·58개 error-case 레지스트리·UTF-8·diff 검사와 문서 사이트 빌드도 통과했다. 새 앱·DB 동작 변경이 없어 기존 앱 빌드를 사용했다. full CI와 staging·Production 승격은 실행하지 않는다.