테스트는 ‘로컬에서 가끔 돌리는 것’일 때는 힘이 없습니다. PR마다 자동으로 도는 관문이 될 때 비로소 팀을 지키죠. 이 글은 GitHub Actions로 그 관문 — 단위 테스트·E2E·접근성 검사가 통과해야만 코드가 합쳐지는 머지 게이트 — 를 워크플로우 파일 하나로 세우는 방법입니다. CI가 처음이어도 됩니다. 용어부터 3분 정리하고 시작해요.
시리즈로 따라오셨다면 4장의 시작입니다 — 지금까지 배운 ‘무엇을 어떻게 검증할지’가 여기서 ‘자동으로, 항상’이 되고, 이 장만 지나면 팀에서 테스트 이야기를 주도하는 쪽이 됩니다. 검색으로 처음 오셨어도 워크플로우는 그대로 복사해 쓸 수 있어요.
실습 코드: frontend-testing-lab — 이 편 기준 스냅샷은
step-17태그에 고정돼 있습니다. 워크플로우 파일은.github/workflows/test.yml하나입니다.
이번 편에서 배울 것#
- 단위+빌드+E2E를 잡(job)으로 분리
- 접근성(axe) 검사를 머지 게이트로
- 실패 아티팩트(리포트) 남기기

CI가 처음이라면 — 3분 용어 정리#
- CI(지속적 통합): 코드를 저장소에 올릴 때마다 서버가 자동으로 빌드와 테스트를 돌려주는 것. “내 컴퓨터에선 됐는데"를 없애는 장치예요.
- PR(Pull Request): “이 변경을 main 브랜치에 합쳐주세요"라는 요청. 팀이 코드를 리뷰하는 단위입니다.
- 머지 게이트: PR의 검사들이 통과해야만 합칠 수 있게 막는 규칙. 오늘 만들 것이 바로 이겁니다.
GitHub Actions는 GitHub가 제공하는 CI 실행기예요. 아래 파일 하나면 시작할 수 있습니다. (덧붙여 npm ci는 npm install의 CI 전용 버전 — 잠금파일 그대로, 매번 깨끗하게 설치합니다.)
워크플로우#
repo의 실제 워크플로우입니다. (.github/workflows/test.yml) 파일 구조부터 감을 잡으면 — on은 “언제 돌릴까”(main에 push되거나 PR이 열릴 때), jobs는 “무엇을 돌릴까”, 각 잡의 steps는 “순서대로 할 일"입니다. 잡들은 서로 다른 가상 머신에서 동시에 돌아서, 단위와 E2E를 나눠두면 전체 시간이 절반이 돼요.
name: test
on:
push:
branches: [main]
pull_request:
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm test # Vitest (단위·컴포넌트)
- run: npm run typecheck # 테스트·e2e 파일 타입 검사
- run: npm run build # 프로덕션 번들
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium # CI 머신에 브라우저 설치
- run: npm run e2e # E2E + axe 접근성 스캔
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }} # 실패했어도 리포트는 남긴다 (원인 추적용)
with:
name: playwright-report
path: playwright-report/
retention-days: 7포인트: e2e 잡 안에 axe 스캔이 포함돼 있으므로, 접근성 위반도 이 파이프라인이 잡습니다.
push하면 Actions 탭에서 이렇게 끝납니다 — 이 repo의 실제 실행 기록이에요.
✓ main test · 29518543463
JOBS
✓ unit in 23s
✓ e2e in 42sunit과 e2e가 서로 다른 머신에서 동시에 돌았기 때문에, 전체 소요는 둘의 합(65초)이 아니라 긴 쪽(42초)입니다. 잡을 나눈 보람이죠.
게이트로 만들기#
GitHub 저장소 설정 → Branch protection에서 이 체크들을 required로 지정하면, 초록불이 아니면 머지가 막힙니다. 접근성 위반도, 깨진 테스트도 배포 전에 걸려요.

실제로 이 repo에는 배포 워크플로우(deploy-pages.yml)도 함께 들어 있어서, main에 코드가 들어오면 라이브 데모가 자동으로 갱신됩니다. 테스트 게이트를 통과한 코드만 사용자 앞에 서는 작은 파이프라인이 완성되는 거죠.
실패를 증거로#
upload-artifact로 Playwright 리포트를 남겨두면, CI 실패 시 Actions 페이지에서 리포트를 내려받아 트레이스까지 볼 수 있습니다. “CI에서만 깨져요"가 더 이상 미스터리가 아니게 돼요.
여기 함정이 하나 있는데, 실제로 밟아본 이야기입니다. playwright-report/ 폴더는 html 리포터가 설정돼 있어야 생겨요. 그걸 빼먹은 채 upload-artifact만 두면 CI가 이런 경고를 남기고 빈손으로 끝납니다.
! No files were found with the provided path: playwright-report/.
No artifacts will be uploaded.이 repo도 처음엔 정확히 이 경고를 받았어요. 그래서 설정에 한 줄 들어갔습니다. (repo playwright.config.ts)
reporter: process.env.CI
? [['list'], ['html', { open: 'never' }]] // CI: 아티팩트용 HTML 리포트 생성
: 'list',로컬에선 터미널 출력만, CI에선 업로드할 HTML 리포트까지 — 필요한 곳에서만 만드는 구성입니다.
접근성을 ‘가끔 점검’에서 ‘머지 조건’으로 올리는 순간, 팀 문화가 바뀝니다. 잔소리 대신 파이프라인이 지켜주니까요.
한 장 요약#
- CI = 코드를 올릴 때마다 서버가 자동으로 검사 · PR = 합쳐달라는 요청 · 머지 게이트 = 검사 통과 없인 못 합침
- 워크플로우 파일 구조:
on(언제) →jobs(무엇을, 서로 병렬) →steps(순서대로) —.github/workflows/에 두면 끝 - 단위와 E2E를 별도 잡으로 나누면 병렬로 돌아 빠름 — E2E 잡에 axe 스캔도 포함돼 접근성까지 게이트
- Branch protection에서 체크를 required로 지정해야 진짜 게이트가 됩니다
- 실패는 증거로:
upload-artifact로 Playwright 리포트를 남기면 트레이스까지 원격 부검 가능 - 함정 주의:
playwright-report/는 html 리포터를 설정해야 생깁니다 — 없으면 upload-artifact가 “No files were found” 경고와 함께 빈손
이제 기계가 지킵니다#
자동화가 섰습니다. 다음 편은 그 결과를 읽는 법 — 커버리지와 품질 지표입니다.
CI가 처음으로 내 PR을 빨간불로 막아설 때는 얄밉지만, 그 덕에 프로덕션이 안 터진 거예요. CI는 미움받는 수호천사입니다.
레벨업: 머지 게이트를 세워 팀의 품질을 자동으로 지킬 수 있습니다.
다음 편: 커버리지 읽는 법과 품질 지표
낯선 용어가 있었다면 — 용어집에 전부 한 줄씩 정리돼 있어요.
