Skip to content

근태 현황 프론트엔드 테스트 자동화 설계 ​

Status: Draft (설계 확정, 구현 계획 착수 전) Date: 2026-07-03 Review: 2026-07-03 적대적 멀티에이전트 리뷰 통과 — 확정 지적 11건 반영, 기각 6건 Scope: admin 「근태 현황」 페이지 + team-manager 임베드 화면의 프론트엔드 단위 테스트(Vitest) + E2E(Playwright) 비범위: packages/api 백엔드 로직(finalizer, 연차 계산, 시프트 추론 등) — 담당 범위 밖. 단, 프론트에 위치한 API-계약 바인딩 테스트(enum→라벨 어댑터)는 범위 내


1. 배경 & 목표 ​

근태 현황 파트는 급여 계산의 원천 데이터를 다루지만 프론트 로직 테스트가 사실상 0이다 (이 도메인 커버는 admin kr-holidays.test.ts가 유일. 앱 단위 선례로는 admin scheduler-helpers.test.ts·tm app-helpers.test.ts가 있고, §4의 점진 추출· characterization 패턴이 이들을 따른다). git 이력상 이 도메인의 버그는 조용히 깨지는(silent breakage) 유형이 반복된다:

  • enum→라벨 드리프트 실제 2회 발생 (HOLIDAY_NIGHT 라벨 누락 — 433fe910, 5a68ef6f)
  • 근태 수정 저장 로직 한 달 새 3회 재작성 (d2a3dfb6 → 2b409455 → 68d6de5a)
  • 연차 규칙 표시 로직 한 달 새 4회 수정 (c9024687, cf82549b, 7886bb2e, 45dee93a)
  • iframe 라우팅 드리프트/새로고침 상태 유실 4건 (8cd003f2, 48b1c507, d2a228ed, 70c00eec)

목표: 위 유형의 회귀를 자동으로 잡는 단위 테스트 + 핵심 결재 여정 E2E를 도입한다.

2. 확정된 결정사항 ​

결정내용
범위프론트 전용 — apps/admin, apps/team-manager, packages/test(e2e)
추출 방식점진 추출 — 인스턴스 방식으로 테스트 가능한 것은 그대로, store/DOM에 얽혀 테스트 불가한 고위험 로직만 characterization 후 helpers 모듈로 추출
E2E 깊이단계별 전체 — E1(네이티브 탭 여정) → E2(iframe 개척)
발견 버그문서화만 (§8 Known Issues) — 테스트는 현행 동작을 characterization으로 고정. 수정은 별도 판단
진행 방식단위 트랙(U)과 E2E 트랙(E)을 병행 — 파일 겹침 없음. E 트랙 내부만 E1 → E2 순서
CI 연동범위 외 (사용자 결정 2026-07-03) — 이번 작업은 로컬 실행 기준(§9). CI 파이프라인 연결은 후속
E2E 셀렉터data-testid 적극 사용 (사용자 결정 2026-07-03) — 텍스트·버튼 위치가 바뀌어도 테스트 유지. 상세 §5.3

3. 도메인 요약 (이 파트를 모르는 독자용) ​

「근태 현황」 = admin 앱(:4001)의 단일 페이지 (activeTab === 'attendance', apps/admin/src/views/tabs/attendance.html). 상단 탭 4개:

탭구현데이터
근무 현황team-manager(:4004) iframe /?att=1 (attOnly — 출퇴근 테이블만)attendance.list
연차 현황admin 네이티브 (leave-management.html, 현황/결재 서브탭)leaveRequest.listBalances / list / approve / reject
연장근무 현황team-manager iframe /work?noOffsite=1 — 실제로는 결재 모드(§3.1)teamManager.listOvertime + 승인/반려
외근근무 현황admin 네이티브 (offsite-management.html, 현황+결재 통합, 일괄 결재)offsiteWork.list / approve / reject / approveReport / …

부속 화면: 직원별 근태 관리 모달(employee-attendance.html — 월별 표 + 지문 ±5분 대조), 근태 수정 모달(attendance-edit.html — 소정 4구분 자동 재계산(초과 4종은 유지) + 8구분 합계 불일치 검증), 근태 설정 패널(type-manager.html — shift/연장/외근 유형·연차 기준·휴가 유형 CRUD).

iframe ↔ 부모는 hereby:tm:* postMessage로 nav/모달 dim/부모 탭 전환을 동기화한다 (admin app.ts:6749-6793, tm main.ts:33-63, app.ts:944-986).

로직 대부분이 admin app.ts(8,678줄)·team-manager app.ts(6,779줄)의 Alpine 컴포넌트 객체에 인라인 — 추출된 모듈은 data/kr-holidays.ts 정도뿐이다.

3.1 조사로 확정된 동작 계약 (테스트 어서션의 전제) ​

  • 연장근무 현황 탭은 결재 모드다. iframe URL에 mode 파라미터가 없고 (admin app.ts:6603-6609), team-manager 기본값이 overtimeMode='approval' (tm app.ts:301, 파싱 803-812 — 없으면 기본값 유지). 체크박스·일괄 승인/반려 액션 바가 노출된다(overtime.html:59,102,120). E2E는 "결재 UI 존재"를 어서션한다 — "현황 = 조회 전용" 가정 금지.
  • #attendance 딥링크·F5 복원 불가. VALID_TABS(admin router.ts:2-19)에 attendance가 없어 parseHash가 employees로 폴백 → F5 시 근태 현황을 이탈해 직원 관리로 떨어진다. E2E 진입은 LNB 클릭이 유일한 경로 (§8 KI-2).
  • 상단 4탭은 해시에 인코딩되지 않는다 (switchTab은 settings만 subTab 전달, app.ts:7751-7765). LNB 재진입 시 항상 근무 탭으로 리셋(6838-6842).
  • popstate는 activeTab을 동기화하지 않는다 (router store만 갱신, router.ts:108-115 / app.ts:1502) — page.goBack() 후 콘텐츠 어서션 금지.
  • SECOM 지문 파이프라인은 E2E 시드 가능. secomEvent.ingestEvents가 테스트용 주입 경로로 공식 지원(secom-event.router.ts:9,30, organizationProcedure + ATTENDANCE:CREATE) → processPending으로 트리거. 대상 직원은 attendanceMethod="secom" 시드 필요(모달 게이트, admin app.ts:2542-2544).
  • 모든 목록 로더가 catch에서 목록=[]로 삼킴 — E2E에서 "API 실패"와 "데이터 없음"이 같은 화면. 빈 목록 어서션 대신 waitForResponse 병행.

4. 단위 테스트 트랙 (Track U) ​

도구·배치: Vitest + happy-dom, 소스 옆 co-location(*.test.ts). admin/team-manager 모두 vitest.config.ts 기구성 — 인프라 작업 없음. 완료 기준: 러너 green 그리고 pnpm --filter <app> build green (Vitest는 esbuild라 타입체크를 안 함 — 레포 규칙).

점진 추출 절차 (고위험·테스트 불가 로직에만 적용):

  1. 인스턴스 방식(tm appComponent() / admin adminAppComponent(), admin app.ts:28)으로 현행 동작을 characterization 테스트로 고정 (버그 포함 — 현행이 스펙). 선행 조건: 두 팩토리 모두 객체 리터럴 프로퍼티 sdk: Alpine.store("hereby").sdk (admin :119 / tm :779)를 호출 시점에 즉시 평가하므로, 인스턴스 생성 전 Alpine.store("hereby", 스텁) 등록이 필수 — 공용 store 스텁 헬퍼를 PR-U 첫 태스크로 만든다 (기존 테스트는 순수 함수만 import해 선례 없음). characterization 테스트에는 레포 규칙(.claude/rules/frontend/testing.md)대로 // characterization: locks current behavior, not proven-correct output 주석 마킹
  2. 로직을 helpers 모듈로 추출 (store/DOM 의존은 함수 인자로 주입)
  3. 동일 테스트가 추출 모듈 대상으로 green 유지, app.ts는 위임 호출만 남김

4.1 admin 대상 (위험도 순) ​

#대상 (app.ts)위험방식
1근태 수정 시간 재계산 attnEditRecalcFromTimes :2784-2825 + 합계 검증 attnEditSum/ExpectedHours/Mismatch :2737-2774야간 22–06 경계·익일 퇴근·휴게 차감 — 급여 원천. 3회 재작성 최다 churn추출 (checkIn, checkOut, breakH, isHoliday) → 소정 4구분 — 초과 4종은 계산하지 않고 유지(:2783 docstring). characterization은 초과 4종 불변을 어서션 (0으로 덮는 동작 변경 금지)
2지문 ±5분 대조 secomMatchEvent/_localMinutes/_secomMinutes/secomCheckoutGuess :2547-2709타임존·자정 경계·nextDay — 틀리면 경고 오탐/미탐으로 관리자 오도추출 (포맷터 2개 주입)
3날짜 프리셋 resolveDatePresetRange :7006-7045일요일 특례·월경계·연말 — tm과 미러 쌍, 한쪽만 고치면 조용히 갈라짐추출 + 양쪽 동기화 가드(kr-holidays-sync.test.ts 선례)
4enum→라벨 맵: 연차 :7077-7141, 외근 :7488-7541·7646-7663라벨 드리프트 실버그 2회 전력 유형인스턴스 + it.each(Object.values(Enum)) 전수 계약 — 단일 it 내 루프 금지(no-logic-in-tests)
5외근 일괄결재 단계 선택 offsiteRecSelectedStep/RowSelectable/toggleAll :7669-7706깨지면 잘못된 API로 일괄 승인 — 무증상추출 (records, selectedIds)
6월 이동/말일 setAttendanceMonth/shiftAttendanceMonth :2450-2467윤년·12→1월 롤오버 — 행 누락 무증상추출 용이
7pageWindow :4459-4467 (연차·외근·직원 목록 공용)off-by-one이면 페이지 접근 불가인스턴스 즉시
8연차 차감일수 leaveDeductionDays(UTC 파싱 함정)·통계 leaveStats하루 오차·평균 오표기 무증상인스턴스/추출 용이
9checkOutDayOffset :2516-2531, attnDateLabel/attendanceRowRed :2484-2513, attnEditChanges :2846-2877(+N) 미표시·요일 밀림·감사 diff 소실함수 주입 추출
10외근 byDate 매핑 (loadEmployeeAttendance 내) :3028-3039상태 Set 누락 시 확정 외근이 표에서 소실items 인자 추출
11embedUrlFor :6659-6696?/& 분기·prefix 오류 → 빈 iframe(cfg, ctx) 인자화
12shift 폼 어댑터 openEditType :5865-5893, isActive 변환 :5769·5904KI-1 0값 삼킴 — 현행 characterization 고정, 767b4f36 재발 방지characterization (수정 금지)

4.2 team-manager 대상 ​

#대상 (app.ts)위험방식
1resolveDatePresetRange :1455-1494 + 미래 클램프 setDashAttDatePreset :1513-1523admin 미러 쌍 + 문자열 비교 클램프추출 + 동기화 가드
2pageRange :1307-1316 + attOnly 페이지네이션 산수 :1183-1186·overview.html:95-96HTML 인라인 수식은 현재 테스트 불가순수 추출 (HTML 수식 → 헬퍼)
3일괄결재 step 규칙 overtime/offsite 쌍 :2507-2580복붙 이중화 — 한쪽만 고치는 drift추출 + 파라미터라이즈로 양쪽 동시 검증
4overtimeStatusLabel/BadgeClass :1781-1806, overtimeTypeLabel 폴백 체인 :1526-1534raw enum 노출·그럴듯한 오표기it.each enum 전수 계약 (categoryLabel 주입, 단일 it 내 루프 금지)
5URL 파싱: initialTabFromUrl :187-207(기존 app-helpers.test.ts 기커버 — /overtime·/offsite→work 별칭 분기만 보강), initialWorkKindFromUrl :210-212(비export — export 필요), attOnly/noOffsite getter :1120-1130, mode 파싱 :803-812admin iframe URL과의 암묵 계약. mode 없음→approval 고정(§3.1)기존 파일 보강 + export/location stub
6_dashRange :1152-1158 (단일 헬퍼 — att/외근/연장/연차 로더 4곳 공용), formatKRW :2808-2814UTC 자정 파싱·NaN→'0' 위장순수 추출

4.3 계약(contract) 테스트 — 앱 경계 고정 ​

양쪽 앱이 문자열 리터럴로만 공유하는 계약을 각 앱 테스트에서 고정한다 (한쪽 rename 시 그쪽 테스트가 깨져 드리프트를 즉시 감지):

  • iframe URL 파라미터: att=1, noOffsite=1, embedded=1, teamId (admin embedUrlFor/embedRegistry ↔ tm URL 파서)
  • postMessage 타입: hereby:tm:nav / modal / navigate / doc-approval-changed(tm→admin)
    • hereby:tm:back(admin→tm, 유일한 역방향 채널) — admin 수신 :6749-6793·back 송신 :6807 ↔ tm nav 송신 :979·navigate :1290·doc-approval-changed :2970·back 수신 :909-915·modal 송신 main.ts:33-63. 5개 타입 리터럴 전부 양쪽 앱에서 고정

5. E2E 트랙 (Track E) ​

도구·배치: Playwright, packages/test/e2e/tests/apps/*.spec.ts. 기존 하네스 재사용: global-setup 페르소나 storageState, cross-app.ts tRPC 시드 헬퍼 (superjson envelope {json:...}), 패턴은 leave-approval.spec.ts가 확립한 API 시드 → UI 결재 → API 검증(expect.poll).

5.1 PR-E1 — 네이티브 탭 (admin 단독) ​

선행 인프라 (E1에 포함):

  1. 베이스라인 실행 — 기존 e2e 스위트를 먼저 실행해 현재 상태를 기록 (최근 실행 기록 없음). 깨져 있으면 신규 spec 실패가 회귀로 오판된다. 단 stale 「연차 관리」 케이스는 red가 예상됨 — 이 경우 E1-4를 가장 먼저 수행해 베이스라인을 green으로 만든 뒤 신규 spec에 착수. 그 외 원인의 red는 별도 이슈로 기록
  2. playwright.config.ts webServer에 admin(:4001) 추가 (현재 api+hr-system만 :96-115). 주의 — project 배정은 testMatch 기반: 기본 chromium project가 testIgnore: /apps\// 라서 어떤 project의 testMatch에도 안 걸린 spec은 조용히 수집 제외된다. E1 spec은 admin project의 testMatch(/apps\/admin-.*\.spec\.ts/)에 걸리도록 admin-attendance-*.spec.ts로 명명하고(admin project가 baseURL·adminState.json storageState 공급 — 진입 헬퍼의 page.goto("/") 전제), 첫 실행 시 playwright test --list로 수집 여부를 확인한다
  3. org-timezone 안전 날짜 헬퍼 신설 — 기존 leave-approval.spec.ts:66-70의 toISOString() 패턴은 KST 00–09시 실행 시 하루 밀림. 복사 금지
  4. 시드 헬퍼 확장 (cross-app.ts): 외근 신청/승인/보고서 래퍼 + 연장 상태 조회 래퍼 추가 (E2-2의 expect.poll 검증에 필요 — 기성품은 연차 신청/승인/조회 3종과 연장 신청 submitOvertimeRequest뿐)
  5. 진입 헬퍼 openAttendanceTab(page, tab): page.goto("/") → init 레이스 대기 ('직원 관리' 버튼 visible — admin-tab-routing.spec.ts:36-38 검증 패턴) → LNB 「근태 현황」 클릭(항상 work 탭으로 리셋됨) → 필요 시 상단 탭 클릭 → 패널 가시성 대기. 해시 딥링크 사용 금지 (§3.1)

스펙:

#시나리오검증
E1-1근태 탭 스모크: LNB 진입 → 4탭 순회 → 각 패널 로드URL이 #attendance가 되는 것까지만. F5 폴백(직원 관리행)은 현행 계약으로 주석 명시 — 기존 TABS 루프에 넣지 말고 신규 admin-attendance-*.spec.ts 파일의 별도 describe로 작성
E1-2연차 결재 여정: 시드 → 결재 서브탭 → 승인 / 반려(사유 필수)API 재조회로 상태 검증 + 연차 현황 잔여 반영
E1-3외근 결재 여정 (현재 커버리지 0인 유일 도메인): 시드 2건 → 단일 단계 선택 규칙(타 단계 행 선택 불가) → 일괄 승인상태 갱신 + API 검증
E1-4기존 spec 정리: admin-tab-routing.spec.ts의 stale {label:"연차 관리", hash:"leave"} 케이스 수리 또는 삭제 (LNB에서 제거된 항목 — 타임아웃 예정)—

5.2 PR-E2 — iframe 개척 (admin + team-manager) ​

선행 인프라: team-manager(:4004) webServer 등록 + dual-origin 인증 헬퍼 — storageState는 origin별이므로 admin 토큰만으로는 iframe(:4004)이 비로그인. context.addInitScript로 두 origin의 localStorage (hereby_access_token/hereby_refresh_token)에 토큰 주입. 기존 spec에 iframe 사례가 전무한 신규 개척 지점.

E2 spec의 project 배정을 명시적으로 결정한다: (a) admin project에 태우고 상속된 adminState.json 위에 tm origin 토큰만 addInitScript로 보충하거나, (b) employee-team-flow처럼 storageState 없는 전용 project에서 자체 컨텍스트를 열어 양쪽 origin 토큰을 모두 주입 — 어느 쪽이든 testMatch에 걸리는 파일명(또는 신규 project 추가)이 선행 조건이다.

스펙:

#시나리오검증
E2-1근무 현황 탭: iframe(att=1) 내부 출퇴근 테이블 렌더 → 프리셋 클릭 → 목록 갱신 → 번호 페이지네이션 이동frameLocator, attOnly 모드(카드 숨김). 어서션은 배선만 — 프리셋 클릭 시 재조회 발생·페이지 이동 시 행 교체까지. 날짜 범위 값·페이지 산수 정확성은 U 트랙(§4.2 #1·#2) 소관 — e2e에서 재검증하지 않음
E2-2연장근무 탭: iframe(noOffsite=1) 결재 모드 확정 어서션(체크박스·액션 바 존재, 외근 서브탭 부재) → 시드 pending 승인 + 반려(사유 필수)mode가 status로 "수정"되면 테스트가 깨져 의도 재확인 강제(§3.1)
E2-3postMessage 계약: iframe 안 상세 모달 열기 → 부모 admin 풀페이지 dim (hereby:tm:modal)E2E로만 검증 가능한 유일 계약
E2-4 (선택)SECOM 지문 여정: ingestEvents 시드(정상 페어 + 자정 넘김) → processPending → 직원별 근태 모달 지문 툴팁/! 경고 렌더시드만 백엔드 API — 검증 대상은 프론트 모달

5.3 셀렉터 전략 — data-testid 우선 ​

근태 현황 E2E는 data-testid를 1순위 셀렉터로 사용한다 (사용자 결정 2026-07-03). 근거: 이 도메인은 레포 최고 churn 지점(§1)으로 카피·레이아웃 변경이 잦아, 텍스트/역할 기반 로케이터는 문구 수정만으로 깨진다. 기존 e2e README(:132)의 "data-testid는 last resort" 관례를 이 도메인에 한해 명시적으로 뒤집는다 — 단, README의 자체 프로토콜대로 추가한 testid를 README "data-testid additions" 절에 사유와 함께 등록한다.

  • 네이밍: att- 접두 + kebab-case, 「영역-요소(-행위)」 순. 예: att-tab-work(상단 탭), att-leave-approve(연차 승인), att-offsite-row-check (외근 행 선택), att-edit-save(근태 수정 저장), att-embed-work/att-embed-overtime (iframe). 동적 목록 행은 testid + data-id="<record id>" 병기 후 filter로 특정
  • 부여 범위: 각 spec이 필요한 요소에 그때그때 부여 — 일괄 스윕 금지 (attribute-only 변경이라 동작 무영향이지만 diff를 작게 유지). 대상: admin attendance.html· leave-management.html·offsite-management.html·근태 모달 2종·type-manager.html· dashboard.html(LNB), tm overview.html·work.html·overtime.html
  • 문구 검증의 분업: testid는 텍스트를 검증하지 않지만, 상태/유형 라벨의 정확성은 U 트랙 enum→라벨 전수 계약 테스트(§4.1 #4, §4.2 #4)가 전담한다 — E2E는 배선(요소 존재·반응), 단위가 문구를 맡아 커버리지 손실 없음
  • 예외: 반려 사유처럼 사용자 입력값 반영 자체가 검증 대상인 곳만 텍스트 어서션

6. 범위 제외 — dead code (테스트 불가, 정리 후보) ​

UI 진입점이 없는 표면 — 테스트 범위에서 제외하고 정리 후보로 기록한다. 단 아래 embed 계열은 #embed/<key> 해시 딥링크(router.ts:174 embedSub 파싱 → app.ts:1509-1510 currentEmbed 복원 → embed.html iframe 렌더)로는 여전히 런타임 도달 가능하므로 완전한 dead code는 아니다 — 정리 시 registry 항목·딥링크 하위 호환· 호출 함수를 함께 제거해야 한다:

  • onlyOffsite=1 임베드 분기 (tm app.ts:1128-1130) — 유일한 생성처는 admin embedRegistry offsite-approval 항목(admin app.ts:6557)이며, 이를 가리키는 LNB/UI 진입점 없음
  • embedRegistry overtime-approval(:6538-6544)·offsite-approval(:6555-6561) 항목 — LNB 항목 없음. overtime-approval의 유일 참조는 아래 dead 함수(:2906)
  • goToOvertimeApproval() (admin app.ts:2903-2907) — 2b409455에서 UI 진입점(모달 버튼) 삭제됨. 정리 시 위 registry 항목과 동반 제거
  • #leave·#offsite 좀비 라우트 — VALID_TABS에 남아 있으나 렌더하는 뷰 없음 → 빈 화면

7. 함정 & 리스크 (구현 시 주의) ​

  • 시간 의존: 프리셋·월 이동·'오늘' 파생 전부 — 단위는 vi.setSystemTime 필수 (일요일·월말·연말 케이스 명시), E2E는 '오늘' 기준 동적 시드
  • 테스트 날짜는 kr-holidays 지원 범위(2024–2027) 내로 제한 — 범위 밖 날짜는 공휴일이 전부 평일로 조용히 판정됨
  • admin init 레이스: '직원 관리' 버튼 대기 없이 병렬 실행하면 accessDenied flaky
  • 페르소나 권한: 시드 test.user 계정은 TEAM_LEADER 미부여(global-setup 주석), 팀 리더는 admin으로 시드됨(seed-test-users.ts의 leaderId: savedAdmin.id) — manager 페르소나의 스코프별 조회 테스트는 시드 보강 전 불가. E1/E2는 admin 페르소나로 설계
  • packages/test 구형 src/tests는 타입에러 수백 건 — 패키지 전체 tsc 게이트 불가. E2E 스펙 전용 tsconfig.e2e.json(esign 계획과 동일: e2e/**· playwright.config.ts만 include)으로 분리 타입체크. esign 브랜치 (test/esign-e2e-automation)는 현재 미머지 — PR-E1 시점에 머지돼 있으면 재사용, 아니면 동일 내용으로 직접 생성 (동일 파일·동일 내용이라 이후 머지 충돌 무해)
  • 모달 z-index 중첩 (member-list z-40 < 근태 z-50 < 수정 z-60) + ESC 가드 체인 — E2E 닫기 순서 주의
  • admin app.ts는 활성 개발 파일(9개월 199커밋) — 추출 diff는 작게, PR은 빨리 머지

8. Known Issues (수정하지 않음 — 기록만) ​

#내용위치
KI-1shift 편집 모달이 Number(x) || 8 폴백으로 workHours/breakHours 0값을 8/1로 조용히 덮어씀 (breakHours=0은 유효값 — 자동 휴게와 의미 다름)admin app.ts:5878-5880
KI-2VALID_TABS에 attendance 누락 — F5/딥링크 시 직원 관리로 폴백. 의도 여부 팀 확인 필요admin router.ts:2-19
KI-3#leave/#offsite 좀비 라우트 → 빈 main 렌더router.ts:10-11 + 뷰 부재
KI-4leave-management.html:4 stale 주석 ("결재는 team-manager /leave iframe") — 실제는 admin 네이티브(:165-166). 세 번째 iframe으로 오인 유도해당 파일
KI-5formatTimeOnly가 Asia/Seoul 하드코딩 — org timezone 규칙 위반. 지문 대조 _localMinutes까지 사용해 파급 큼admin app.ts:3079-3093
KI-6OvertimeCategory.HOLIDAY_NIGHT("holiday_night") 라벨 누락 — admin overtimeCategoryLabel 맵과 tm CATEGORY_KEYS 모두 4종만 매핑, raw enum이 화면 노출. hr-system 433fe910 실버그와 동일 패턴 (PR-U 라벨 전수 테스트로 발견, characterization 고정)admin app.ts overtimeCategoryLabel / tm app.ts CATEGORY_KEYS

9. 검증 기준 (전 트랙 공통) ​

  • 각 PR: pnpm lint:check && pnpm build green
  • 단위: pnpm --filter @hereby/admin test / --filter @hereby/team-manager test
    • 해당 앱 build — 둘 다 green이어야 완료
  • E2E: 로컬 make dev + 시드 DB 기준 green (E1은 admin webServer 자동 기동, E2는 team-manager 포함) + 스펙 타입체크 pnpm --filter @hereby/test exec tsc --noEmit -p tsconfig.e2e.json green (packages/test는 build 스크립트가 없어 루트 pnpm build가 e2e 스펙을 타입체크하지 않음 — 이 명령이 유일한 타입 게이트)

10. PR 구성 ​

Track U (병행)          Track E (병행, 내부 순차)
┌─────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│ PR-U: 단위 테스트 │    │ PR-E1: E2E 네이티브 │ →  │ PR-E2: E2E iframe │
│ admin + tm 추출   │    │ 인프라+연차/외근 여정 │    │ dual-origin+결재   │
└─────────────────┘    └──────────────────┘    └──────────────────┘

PR-U가 커지면 admin/team-manager로 분할 가능 — 구현 계획에서 태스크 단위로 확정한다.