「프론트엔드 테스트 제대로 하기」 시리즈의 17편입니다. 전체 목차 보기 · 용어집

테스트는 ‘로컬에서 가끔 돌리는 것’일 때는 힘이 없습니다. PR마다 자동으로 도는 관문이 될 때 비로소 팀을 지키죠. 이 글은 GitHub Actions로 그 관문 — 단위 테스트·E2E·접근성 검사가 통과해야만 코드가 합쳐지는 머지 게이트 — 를 워크플로우 파일 하나로 세우는 방법입니다. CI가 처음이어도 됩니다. 용어부터 3분 정리하고 시작해요.

시리즈로 따라오셨다면 4장의 시작입니다 — 지금까지 배운 ‘무엇을 어떻게 검증할지’가 여기서 ‘자동으로, 항상’이 되고, 이 장만 지나면 팀에서 테스트 이야기를 주도하는 쪽이 됩니다. 검색으로 처음 오셨어도 워크플로우는 그대로 복사해 쓸 수 있어요.

실습 코드: frontend-testing-lab — 이 편 기준 스냅샷은 step-17 태그에 고정돼 있습니다. 워크플로우 파일은 .github/workflows/test.yml 하나입니다.

이번 편에서 배울 것

  • 단위+빌드+E2E를 잡(job)으로 분리
  • 접근성(axe) 검사를 머지 게이트로
  • 실패 아티팩트(리포트) 남기기
PR에서 단위·E2E·접근성 검사가 순서대로 실행되어 머지 게이트를 이루는 파이프라인 다이어그램
PR에서 단위·E2E·접근성 검사가 순서대로 실행되어 머지 게이트를 이루는 파이프라인 다이어그램

CI가 처음이라면 — 3분 용어 정리

  • CI(지속적 통합): 코드를 저장소에 올릴 때마다 서버가 자동으로 빌드와 테스트를 돌려주는 것. “내 컴퓨터에선 됐는데"를 없애는 장치예요.
  • PR(Pull Request): “이 변경을 main 브랜치에 합쳐주세요"라는 요청. 팀이 코드를 리뷰하는 단위입니다.
  • 머지 게이트: PR의 검사들이 통과해야만 합칠 수 있게 막는 규칙. 오늘 만들 것이 바로 이겁니다.

GitHub Actions는 GitHub가 제공하는 CI 실행기예요. 아래 파일 하나면 시작할 수 있습니다. (덧붙여 npm cinpm install의 CI 전용 버전 — 잠금파일 그대로, 매번 깨끗하게 설치합니다.)


워크플로우

repo의 실제 워크플로우입니다. (.github/workflows/test.yml) 파일 구조부터 감을 잡으면 — on은 “언제 돌릴까”(main에 push되거나 PR이 열릴 때), jobs는 “무엇을 돌릴까”, 각 잡의 steps는 “순서대로 할 일"입니다. 잡들은 서로 다른 가상 머신에서 동시에 돌아서, 단위와 E2E를 나눠두면 전체 시간이 절반이 돼요.

yaml
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의 실제 실행 기록이에요.

bash
✓ main test · 29518543463

JOBS
✓ unit in 23s
✓ e2e in 42s

unit과 e2e가 서로 다른 머신에서 동시에 돌았기 때문에, 전체 소요는 둘의 합(65초)이 아니라 긴 쪽(42초)입니다. 잡을 나눈 보람이죠.


게이트로 만들기

GitHub 저장소 설정 → Branch protection에서 이 체크들을 required로 지정하면, 초록불이 아니면 머지가 막힙니다. 접근성 위반도, 깨진 테스트도 배포 전에 걸려요.

머지 게이트의 작동 다이어그램 - PR이 열리면 unit 잡과 e2e 잡이 병렬로 돌고, 둘 다 초록불이면 머지 버튼이 열려 main에 합쳐진 뒤 자동 배포로 이어지지만, 하나라도 빨간불이면 머지 버튼이 잠깁니다. 테스트 게이트를 통과한 코드만 사용자 앞에 선다는 설명이 붙어 있습니다
머지 게이트의 작동 다이어그램 - PR이 열리면 unit 잡과 e2e 잡이 병렬로 돌고, 둘 다 초록불이면 머지 버튼이 열려 main에 합쳐진 뒤 자동 배포로 이어지지만, 하나라도 빨간불이면 머지 버튼이 잠깁니다. 테스트 게이트를 통과한 코드만 사용자 앞에 선다는 설명이 붙어 있습니다

실제로 이 repo에는 배포 워크플로우(deploy-pages.yml)도 함께 들어 있어서, main에 코드가 들어오면 라이브 데모가 자동으로 갱신됩니다. 테스트 게이트를 통과한 코드만 사용자 앞에 서는 작은 파이프라인이 완성되는 거죠.


실패를 증거로

upload-artifact로 Playwright 리포트를 남겨두면, CI 실패 시 Actions 페이지에서 리포트를 내려받아 트레이스까지 볼 수 있습니다. “CI에서만 깨져요"가 더 이상 미스터리가 아니게 돼요.

여기 함정이 하나 있는데, 실제로 밟아본 이야기입니다. playwright-report/ 폴더는 html 리포터가 설정돼 있어야 생겨요. 그걸 빼먹은 채 upload-artifact만 두면 CI가 이런 경고를 남기고 빈손으로 끝납니다.

bash
! No files were found with the provided path: playwright-report/.
  No artifacts will be uploaded.

이 repo도 처음엔 정확히 이 경고를 받았어요. 그래서 설정에 한 줄 들어갔습니다. (repo playwright.config.ts)

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는 미움받는 수호천사입니다.

레벨업: 머지 게이트를 세워 팀의 품질을 자동으로 지킬 수 있습니다.

다음 편: 커버리지 읽는 법과 품질 지표

낯선 용어가 있었다면 — 용어집에 전부 한 줄씩 정리돼 있어요.