# GitHub Actions로 테스트·접근성 게이트 만들기

> PR을 열 때마다 단위·E2E·접근성(axe) 검사가 자동으로 도는 파이프라인을 GitHub Actions로 세팅합니다. 잡을 병렬로 나눠 속도를 올리고, branch protection으로 진짜 머지 게이트를 만드는 법까지 다룹니다.

**Published:** 2026-08-03 | **Updated:** 2026-08-03

---


> 「프론트엔드 테스트 제대로 하기」 시리즈의 **17편**입니다. [전체 목차 보기](/series/프론트엔드-테스트-제대로-하기/) · [용어집](/posts/frontend-testing-glossary/)

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

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

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

## 이번 편에서 배울 것

- 단위+빌드+E2E를 잡(job)으로 분리
- 접근성(axe) 검사를 머지 게이트로
- 실패 아티팩트(리포트) 남기기

{{< img src="images/contents/ci-pipeline.png" alt="PR에서 단위·E2E·접근성 검사가 순서대로 실행되어 머지 게이트를 이루는 파이프라인 다이어그램" >}}

---

## 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를 나눠두면 전체 시간이 절반이 돼요.

```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 스캔](/posts/frontend-testing-axe-e2e/)이 포함**돼 있으므로, 접근성 위반도 이 파이프라인이 잡습니다.

push하면 Actions 탭에서 이렇게 끝납니다 — 이 repo의 실제 실행 기록이에요.

```bash
✓ main test · 29518543463

JOBS
✓ unit in 23s
✓ e2e in 42s
```

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

---

## 게이트로 만들기

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

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

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

---

## 실패를 증거로

`upload-artifact`로 Playwright 리포트를 남겨두면, CI 실패 시 Actions 페이지에서 리포트를 내려받아 [트레이스](/posts/frontend-testing-playwright-debug/)까지 볼 수 있습니다. "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는 미움받는 수호천사입니다.

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

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

> 낯선 용어가 있었다면 — [용어집](/posts/frontend-testing-glossary/)에 전부 한 줄씩 정리돼 있어요.

