Slack에서 /sanity 한 줄로 유저웹의 핵심 기능을 자동 검증하는 새니티 테스트 봇입니다.
Playwright가 실제 브라우저 4종(데스크톱/모바일웹 × 크롬/사파리)으로 스테이징 환경(dev~dev8, nextweek, wwwtest)을 사람처럼 돌아다니며 확인하고, 결과와 실패 증거(스크린샷·영상·trace)를 Slack으로 보내줍니다. wwwtest 배포가 끝나면 CI가 자동으로 실행합니다.
- Slack 슬래시 커맨드 —
/sanity선택 UI 또는/sanity dev3 이력서,회원 safari즉시 실행 - 시나리오 6종 × 브라우저 4종 — 회원 / 프로필 / 채용공고 / 교육·이벤트 / 소셜 / 이력서 (35+ 테스트)
- 실시간 진행률 — 채널 메시지가
진행 12/35 · 이력서 진행 중으로 갱신, ⏹ 취소 버튼(실행자 전용) - 대기열 — 실행 중 새 요청은 큐(최대 5)에 등록, 차례가 되면 자동 실행 + DM 알림
- 결과 리포트 — 채널에 요약, 스레드에 시나리오별 케이스 상세(✅/❌/⏭️ + 건너뜀 사유) + 실패 증거 자동 첨부
- 재실행 버튼 —
▶️ 같은 조건으로 재실행/🔄 실패만 재실행(실패한 시나리오만) - 배포 연동 — userweb wwwtest 배포 성공 시 GitHub Actions가 Slack으로 트리거를 보내고, 봇이 웹→모바일웹 순차 자동 실행
👤 팀원 ──/sanity──▶ 💬 Slack ◀──Socket Mode──▶ 🤖 봇 서버 (Backyard backend)
│ spawn
▼
🎭 Playwright ×4
│ 접속/클릭
▼
🌐 dev ~ wwwtest.wanted.co.kr
🚀 userweb wwwtest 배포 ──▶ GitHub Actions ──트리거(HMAC 서명)──▶ 💬 Slack ──▶ 🤖 봇이 실행·결과 게시
- 봇은 Socket Mode로 동작해 외부 URL 노출이 없습니다 (인바운드 포트 0개).
- 배포 후 자동 실행도 봇이 수행합니다 — GitHub 러너는 CloudFront 지역/IP 차단으로 wwwtest에 접근할 수 없어, 워크플로우는 서명된 트리거 메시지만 Slack에 게시합니다.
- in-memory lock/대기열 특성상 봇은 반드시 단일 인스턴스로 운영해야 합니다.
├── src/
│ ├── server.ts # Slack Bolt 서버 — 커맨드/버튼/배포 트리거 핸들러, 오케스트레이터, 대기열
│ ├── deploy-sanity.ts # 배포 새니티 공통 로직 — 워밍업 → 브라우저 순차 실행 → 리포트/시트 기록
│ ├── ci.ts # 직접 실행 진입점 (npm run ci) — 봇 없이 deploy-sanity 수행
│ ├── test-runner.ts # Playwright spawn, 진행률 파싱, 결과(JSON 리포트) 파싱, lock/취소
│ ├── slack-ui.ts # Block Kit 메시지 빌더 (선택 UI/진행/결과/대기열/사용법)
│ ├── slack-report.ts # 스레드 상세 + 실패 아티팩트(스크린샷/영상/trace) 업로드
│ ├── sheets-report.ts # QA TC 스프레드시트 기록 (+ sheets-client / sheet-mapping)
│ └── config.ts # 환경/시나리오/브라우저 정의, 커맨드 인자 파서, 타임아웃 정책
├── e2e/
│ ├── member.spec.ts / profile.spec.ts / job-posting.spec.ts
│ ├── education-event.spec.ts / social.spec.ts / resume.spec.ts
│ └── helpers/ # 로그인, 세션 캐시(auth.setup), 팝업/스낵바 정리, 뷰포트 유틸
├── playwright.sanity.config.ts # 브라우저 프로젝트 4종 + setup(세션 캐시)
└── Dockerfile # Playwright 공식 이미지 기반 (비루트 실행)
- Node.js 22+
- Slack App (Socket Mode 활성화,
/sanity슬래시 커맨드 등록)- Bot Token Scopes:
chat:write,commands,files:write - App-Level Token:
connections:write
- Bot Token Scopes:
npm ci
npx playwright install chromium webkit # 로컬 브라우저 (최초 1회)
cp .env.example .env # 환경변수 채우기
npm run dev # 봇 서버 (tsx watch)| 변수 | 용도 | 필수 |
|---|---|---|
SLACK_BOT_TOKEN |
Slack Bot OAuth Token (xoxb-) |
✅ |
SLACK_APP_TOKEN |
Socket Mode App Token (xapp-) |
봇 서버만 |
SLACK_SIGNING_SECRET |
Slack App Signing Secret | 봇 서버만 |
TEST_USER_EMAIL / TEST_USER_PASSWORD |
테스트 계정 | ✅ |
SANITY_TRIGGER_SECRET |
배포 트리거 HMAC 검증 키 — userweb GitHub Secret과 동일 값 | 배포 연동 시 |
SANITY_TRIGGER_CHANNEL |
배포 트리거를 수신할 채널 ID | 배포 연동 시 |
SANITY_GSHEET_CREDENTIALS / SANITY_SHEET_ID |
QA TC 스프레드시트 기록 (미설정 시 기록만 건너뜀) | 선택 |
SLACK_FAILURE_MENTION |
실패 시 결과에 함께 멘션할 대상 (예: <!subteam^S...>) |
선택 |
E2E_BASE_URL=https://dev.wanted.co.kr npm test # 전체
E2E_BASE_URL=https://dev.wanted.co.kr npx playwright test \
--config=playwright.sanity.config.ts \
--project=mobile-safari --project=mobile-safari-auth \
--grep "이력서" # 브라우저/시나리오 지정/sanity # 선택 UI (환경/시나리오/브라우저)
/sanity dev3 이력서,회원 safari # 즉시 실행 — 환경 뒤 인자는 순서 무관, 한/영 혼용
/sanity help # 전체 옵션 안내
- 환경:
devdev2~dev8nextweekwwwtest(프로덕션 www 제외) - 브라우저:
chrome(기본) ·safari·mobile-chrome(Pixel 7) ·mobile-safari(iPhone 14) — 별칭크롬/사파리/모바일크롬/모바일사파리 - 결과의 ⏭️ 건너뜀은 실패가 아니라 환경/조건 문제입니다 (시드 데이터 부재, 모바일 미제공 기능 등 — 사유가 함께 표기됨)
- 실패 스레드의 trace.zip을 trace.playwright.dev에 드래그하면 클릭·네트워크 타임라인을 재생할 수 있습니다
userweb release.yml의 sanity-test 잡(deploy-end 이후)이 Slack 트리거 채널에 sanity-deploy env=wwwtest ts=<unix초> sig=<hmac> 메시지를 게시하면, 봇이 서명·유효시간(10분)·채널을 검증한 뒤 웹→모바일웹 순차로 실행하고 결과를 같은 채널에 게시합니다.
- 러너에서 직접 실행하지 않는 이유: GitHub 러너(해외 IP)는 CloudFront 차단으로 wwwtest 접근 불가
- 트리거는 DEPLOY 봇 토큰으로 게시해야 합니다 — 새니티 봇은 자기 메시지를 무시(ignoreSelf)
- 두 봇(DEPLOY·새니티) 모두 트리거 채널에 초대돼 있어야 합니다
- userweb 쪽 필요 Secret:
DEPLOY_SLACK_BOT_TOKEN,SANITY_SLACK_CHANNEL,SANITY_TRIGGER_SECRET(봇의SANITY_TRIGGER_SECRET과 동일 값) - 브라우저는 순차 실행합니다 — 동일 테스트 계정의 상태(기본 이력서 등)를 공유하므로 병렬 금지
- 배포 새니티 실행 중 수동
/sanity요청은 대기열로, 중복 트리거는 무시됩니다 - 교육·이벤트는 봇 서버에서 자동으로 건너뜁니다 —
event-wwwtest.wanted.co.kr이 사내망 전용 사설 IP로만 해석돼 Backyard 파드에서 접근 불가(항상 타임아웃). 스펙의beforeEach가isReachable로 도달 여부를 확인해 ⏭️ 건너뜀(사유 포함)으로 처리하므로, 배포 트리거·수동/sanity·재실행 버튼 어디서 실행해도 동일하게 적용됩니다. VPN이 붙은 로컬에서는 그대로 실행되고, 인프라에서 공인 노출되면 코드 수정 없이 자동으로 다시 검증됩니다
봇을 거치지 않고 직접 실행해야 할 때(로컬 검증 등)는 npm run ci를 쓸 수 있습니다 (SANITY_ENV/SANITY_BROWSERS/SLACK_REPORT_CHANNEL/SANITY_DRY_RUN=1 등은 src/ci.ts 주석 참고).
사내 Backyard backend 컴포넌트로 운영합니다. Playwright 고정 버전 이미지 기반 Dockerfile로 빌드해 레지스트리에 :latest로 push하면 자동 롤아웃됩니다. 환경변수는 Backyard 시크릿으로 등록합니다 (.env 파일은 이미지에 포함되지 않음 — .dockerignore 참고).
docker build -t oci.wntd.co/backyard/sanity-slack-bot .
docker push oci.wntd.co/backyard/sanity-slack-botworkers: 3에서는 합계 3.6GB로 한도에 닿아(파드 cgroup 30분). 그래도 불안하면 memory.events max 다수 관측) 페이지가 멈추고 사파리/모바일사파리에서 타임아웃 실패가 무더기로 났습니다. 그래서 workers: 2 + timeout: 45s로 낮춰 속도보다 안정성을 우선합니다(브라우저 4종 전체 약 23분 → 28SANITY_WORKERS=1로 더 내릴 수 있습니다.
이 저장소의 셀렉터/대기 컨벤션 — userWeb 개편에서 살아남는 테스트를 위해:
data-속성 우선 (data-gnb-kind,data-attribute-id,data-menu) — CSS Modules 클래스(Foo_bar__hash)는 개편 한 번에 전멸합니다- 동일 요소가 데스크톱/모바일 중복 렌더되는 경우가 많으므로 visible 필터(
.locator('visible=true')/.filter({ visible: true }))를 기본으로 waitForTimeout금지 — API 응답(waitForResumeApi), 토스트, URL 변화 등 결정적 신호를 기다릴 것waitForURL/reload에는waitUntil: 'domcontentloaded'— WebKit은 서드파티 리소스로load가 안 끝나는 경우가 있음- 모바일 오버레이(앱 유도 팝업, AI 스낵바)가 클릭을 가로챌 수 있음 —
dismissEventPopup/dismissResumeSnackbar헬퍼를 진입 시 호출 - 상태를 변경하는 시나리오(이력서/프로필)는 파일 상단
test.describe.configure({ mode: 'default' })로 순차 실행 유지 - 테스트가 데이터를 생성하면 반드시 정리 (API DELETE) — 잔여물이 다음 실행을 깨뜨립니다
- 시드 데이터 의존 테스트는
getSeedResume패턴으로 — 시드가 없으면 실패 대신 사유와 함께 skip