# Github Actions 설정

## 깃허브 액션 핸즈온

* 깃허브 액션은 빌드, 테스트, 배포 파이프라인을 자동화할 수 있는 CI 및 CD 플랫폼
* 깃허브 액션은 깃허브 저장소에 `yaml`파일을 배치시키는것 만으로 테스트를 자동화할 수 있음
* [example](https://github.com/frontend-testing-book-kr/github-actions-example)

### 워크플로우 파일 작성

* 깃허브 액션에서 워크프로는 <mark style="color:purple;">**일련의 작업들로 구성된 자동화 프로세스**</mark>를 의미
* `.github/workflows` 라는 디렉터리를 생성하고 워크플로 파일을 배치한다.
  * 이 작업만으로 깃허브 액션을 실행시킬 수 있음

```yaml
name: CI

on: push # 저장소에 푸시할 때마다 실행됨

jobs:
  test:
    runs-on: ubuntu-latest # 운영체제는 최신 버전의 우분투 사용
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3 # CI환경에선 Node.js 사용
        with:
          node-version: 18
      - name: Install dependencies # CI 환경에 package.json을 설치
        run: npm ci
      - name: Jest unit test # CI 환경에서 npm test 실행
        run: npm test # 테스트 실행 스크립트
```

### 테스트가 실패한 풀 리퀘스트를 병합할 수 없도록 설정하기

* 새로 생성한 저장소는 테스트가 실패한 상태여도 병합이 가능하다.
* 의도치 않은 병합이 일어나지 않게 하려면 <mark style="color:purple;">**\[Settings] -> \[Branches]**</mark> 페이지에서 **브랜치 보호 규칙**을 설정해야함
* 규칙을 적용하려는 브랜치명 패턴을 `Branch name pattern` 에 입력
* `Require status checks to pass before merging` 에 체크
* 상태 검사 검색 박스에 작업 이름을 입력해서 해당 작업을 상태 검사 대상으로 선택할 수 있음
  * yml 파일에 지정했던 작업 이름을 입력하고 목록에서 선택
* 페이지 하단에 <mark style="color:purple;">**\[Create]**</mark> 버튼을 클릭해서 **설정 완료**

## 워크플로 파일 작성법

* 워크플로를 실행시킬 트리거와 워크플로의 내용을 기술하는 작업을 조합하면 CI/CD 파이프라인을 구축 가능

### 이름(name)

* 워크플로 파일 맨 위에 있는 `name` 은 워크플로를 잘 나타내는 이름으로 작성
* 이름은 실행 결과 등을 쉽게 식별할 수 있도록 지어야함
* PR이나 액션 이력에서 식별을 위한 레이블로 사용됨

```yaml
name: Test UI
```

### 환경 변수(env)

* 워크플로 파일 상단에 위치한 `env`
* 워크플로에 있는 작업에서 참조할 수 있는 환경 변수들을 정의한다.
* 주로 ID, 비밀번호, 토큰 등 민감한 크리덴셜 정보를 참조할 때 사용함
* 저장소마다 있는 **`Actions secrets`** 에 환경 변수를 설정하면 **워크플로 파일에서 참조 가능**
  * `${{ secrets.환경 변수명 }}` 형식의 템플릿 문자열
  * 해당 방식으로 `Actions.secrets` 에 저장된 정보를 참조함

```yaml
env:
  REG_NOTIFY_CLIENT_ID: asdf
  AWS_BUCKET_NAME: asdf
```

### 트리거(on)

* 워크플로를 실행할 타이밍을 지정

```yaml
on: push # 푸시할 때 마다 워크플로 실행
```

* 모노레포로 구성된 저장소에서는 다르게 지정해야 한다.
  * 테스트 대상과 상관없는 파일이 푸시됐을 때에도 워크플로가 실행되기 떄문
  * 특정 패키지에 포함된 소스코드가 푸시됐을 때만 워크플로가 실행되도록 세부 경로를 지정해야함

```yaml
on:
  push:
    paths:
      - packages/app/**
```

### 작업(jobs)

* 워크플로에서 수행할 작업들을 단계별로 지정함

#### runs-on

* 작업을 실행할 가상 환경의 운영체제를 지정
* 상황에 맞는 여러 운영체제를 선택할 수 있으며 선택 가능한 운영체제는 공식문서에서 확인

```yaml
runs-on: ubuntu-latest
```

#### steps

* 작업에서 실행할 모든 스텝을 그룹화함
* 그룹에는 개별 액션, 또는 셸 스크립트를 포함시킬 수 있음

```yaml
steps:
```

#### uses

* 스탭에서 액션을 사용할 때 선언한다.
* `actions/checkout` 은 가장 많이 사용되는 액션
  * CI 환경에서 저장소에 접근할 때 사용하는 액션
  * 대부분 작업에서 사용됨

```yaml
- uses: actions/checkout@v3
```

* 프론트엔드 프로젝트는 `actions/setup-node` 도 대부분의 작업에서 사용함
* 버전을 같이 지정하면 특정 버전의 Node.js가 CI 환경에서 설치됨

```yaml
- uses: actions/setup-node@v3
  with:
    node-version: 18
```

#### run

* CI 환경에서 실행할 커맨드를 작성
* 프로젝트에서 실행할 수 있는 커맨드를 모두 사용할 수 있으므로 package.json에 작성한 **npm scripts**도 실행 가능

```yaml
run: npm test
```

{% hint style="info" %}
액션을 조합하거나 셸 스크립트를 등록해 CD 파이프라인도 만들 수 있다.

빌드된 애플리케이션을 배포하는것 뿐만 아니라 빌드된 스토리북도 호스팅 환경에서 전송하는 등 다양한 작업이 가능
{% endhint %}

### 작업을 병렬처리해서 워크플로 최적화하기

* 작업을 실행한 결과물(설치한 모듈 혹은 빌드된 파일 등)은 캐싱이 가능하다.
* 캐시를 활용하면 실행 시간을 단축시킬 수 있고 여러 작업에서 결과물을 공유할 수 있음
* 깃허브 액션의 워크플로는 여러 작업의 병렬처리도 가능함
  * 모든 작업이 완료될 때까지 걸리는 시간을 단축 가능
* 병렬 및 캐싱처리하여 작업을 재조합하면 실행 시간을 단축시킬 수 있음
* 테스트 코드가 많아질수록 작업을 병렬처리한 효과도 극대화 된다.

#### 한개의 작업만 가진 워크플로

```yaml
name: Test UI

on: push

env: 
  REG_NOTIFY_CLIENT_ID: ${{ secrets.REG_NOTIFY_CLIENT_ID }}
  AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }}
  
jobs:
  tests:
    runs-on: ubuntu-latest
  steps:
     - uses: actions/checkout@v3
       with:
         fetch-depth: 0
     - uses: actions/setup-node@v3
       with:
         node-version: 18
     - name: Configure AWS Credentials
       uses: aws-actions/configure-aws-credentials@master
       with: 
         aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
         aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
         aws-region: ap-northeast-2
     - name: Install dependencies # 의존 모듈 설치
       run: npm ci
     - name: Run Type Check # 타입 검사 실행
       run: npm run typecheck
     - name: Run Lint
       run: npm run lint
     - name: Run Unit Tests
       run: npm test
     - name: Build Storybook
       run: npm run storybook:build --quiet
     - name: RUn Storybook Tests
       run: npm run storybook:ci
     - name: Run Storycap
       run: npm run vrt:snapshot
     - name: Run reg-suit
       run: npm run vrt:run
```

#### 작업 간 병렬처리 계획하기

> **의존관계**에 따라 병렬처리

* 개발환경과 동일하게 CI 환경에 `node_modules` 를 설치하면 다음과 같은 테스크를 실행할 수 있게 됨
  * 세 가지 테스크에는 의존 관계가 없기에 각각을 하나의 작업으로 분할해서 병렬처리 가능
    * 타입 검사, Lint 실행
    * 단위 테스트 실행
    * 스토리북 빌드
* 빌드된 스토리북을 대상으로 테스트하는 작업
  * UI 테스트와 시각적 회귀 테스트는 서로 의존하지 않기 때문에 병렬처리
    * 스토리북을 활용한 UI 테스트
    * 스토리북을 활용한 시각적 회귀 테스트

```yaml
# 스텝별로 병렬처리 가능
jobs:
  install: ... # 1.의존 모듈 설치
  check: ... # 2.타입 검사, Lint
  unit-test: ... # 2.단위 테스트
  build-storybook: ... # 2.스토리북 빌드
  test-storybook: ... # 3.스토리북을 활용한 UI 테스트
  vrt-storybook: ... # 3.스토리북을 활용한 시각적 회귀 테스트
```

#### 작업 간 의존관계 구성하기

* 워크플로를 구성하려면 작업들 간 의존관계를 설정해야 함
* 작업의 **`needs`** 라는 프로퍼티에 의존하는 작업 명칭을 지정하면 의존하는 작업이 완료될 때까지 해당작업을 실행하지 않고 대기한다.
  * 이것이 작업간 의존관계 설정

```yaml
jobs: 
  install: ...
  check: ...
    needs: install
    steps: ...
  unit-test: ...
    needs: install
    steps: ...
  build-storybook: ...
    needs: install
    steps: ...
  test-storybook: ...
    needs: build-storybook
    steps: ...
  vrt-storybook: ...
    needs: build-storybook
    steps: ...
```

#### 의존하는 작업들 간에 캐시 활용하기

* 모든 작업에는 프로젝트에 설치된 `node_moduels` 가 필요함
* 따라서 `npm ci` 로 `node_modules` 를 먼저 설치해야 하지만 단순히 설치한다고 해서 후속 작업에서 공유할 수 없음
* 깃허브 액션이 지원하는 **`actions/cache`** 를 사용하면 지정하 명칭의 에셋을 캐싱하거나 캐싱된 에셋을 불러올 수 있음
  * 한 작업에서 캐싱한 에셋을 다른 작업에서 불러올 수 있게되면 독립된 여러 작업 간에 에셋을 공유할 수 있게됨
* `install` 부분에 `if` 문으로 작성한 조건은 '`node_modules_cache`라는 ID로 특정 가능한 캐시가 없다면' 이다.
  * 즉, 캐시가 없으면 설치를 시작하고 캐시가 있으면 스텝을 종료한다는 의미
* `path` 에는 캐시의 경로를, `key` 에는 캐시에 사용할 키를 지정
  * `hasFiles` 함수를 사용해 `package-lock.json` 내용으로 **해시 키**를 생성함
  * `package-lock.json` 에 갱신된 내용이 없으면 캐시를 재활용하게 됨

```yaml
jobs:
  install:
    runs-on: ubuntu:latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Cache node_modules # 캐시가 있는지 검사
        uses: actions/cache@v3
        id: node_modules_cache
        with:
          path: node_modules # 캐싱할 의존 모듈
          key: ${{ runner.os }}-${{ hasFiles('**/package-lock.json') }}
      - name: Install dependencies # 캐시가 없으면 설치
        if: steps.node_modules_cache.outputs.cache-hit != 'true'
        run: npm ci
```

#### 캐싱한 node\_modules 불러오기

* `check` 라고 명명한 작업에는 타입 검사와 Lint를 실행하고 있음
* `Restore node_modeuls` 에서는 `install`에서 캐싱했던 `node_modules` 를 불러온다.
  * <mark style="color:red;">**캐싱했을 때와 코드는 동일**</mark>하지만 지금은 워킹 디렉터리에 캐싱한 에셋을 불러오는 역할임
  * 동시에 병렬처리하는 `unit-test` 에서도 같은 방식으로 캐시를 불러와 테스트 실행

```yaml
jobs:
  install: ...
  check:
    nddes: install
    runs-on: ubuntu:latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Restore node_modules # 캐싱한 의존 모듈을 불러옴
        id: node_modules_cache
        uses: actions/cache@v3
        with:
          path: node_modules 
          key: ${{ runner.os }}-${{ hasFiles('**/package-lock.json') }}
      - name: Run Type Check # 타입검사 실행
        run: npm run typecheck
      - name: Run Lint # Lint 실행
        run: npm run lint
```

#### 빌드된 스토리북 캐싱하기

* 스토리북 테스트를 안정적으로 실행하려면 미리 CI 환경에서 스토리북을 빌드 한 후, `node_modules` 설치와 동일하게 후속 작업을 위해 빌드된 스토리북을 캐싱
  * 지금은 캐시용 키로 `github.sha` 를 사용해서 같은 커밋 내에서만 캐시를 공유 가능

```yaml
jobs:
  install: ...
  check: ...
  unit-test: ...
  build-storybook: 
    needs: install
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Restore node_modules # 캐싱한 의존 모듈을 불러옴
        id: node_modules_cache
        uses: actions/cache@v3
        with:
          path: node_modules 
          key: ${{ runner.os }}-${{ hasFiles('**/package-lock.json') }}
      - name: Cache Storybook # 빌드된 스토리북을 캐싱
        uses: actions/cache@v3
        id: storybook_cache
        with:
          path: storybook-static
          key: ${{ runner.os }}-${{ github.sha }}   
      - name: Build Storybook # 캐싱한 빌드 결과물을 불러옴
        if: steps.storybook_cache.outputs.cache-hit != 'true'
        run: npm run storybook:build --quiet
```

#### 빌드된 스토리북을 불러와서 테스트에 활용

* 끝으로 빌드된 스토리북을 불러와서 테스트를 실행
* 빌드된 스토리북은 UI 테스트 및 시각적 회귀 테스트에 사용됨

```yaml
jobs:
  install: ...
  check: ...
  unit-test: ...
  build-storybook: ...
  test-storybook:
    needs: build-storybook
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Restore node_modules # 캐싱한 의존 모듈을 불러옴
        id: node_modules_cache
        uses: actions/cache@v3
        with:
          path: node_modules 
          key: ${{ runner.os }}-${{ hasFiles('**/package-lock.json') }}
      - name: Restore Storybook # 캐싱한 빌드된 스토리북을 불러옴
        id: storybook_cache
        uses: actions/cache@v3
        with:
          path: storybook-static
          key: ${{ runner.os }}-${{ github.sha }}   
      - name: Install Playwright # 스토리북을 활용한 UI 테스트에 필요한 의존 모듈
        run: npx playwright install --with-deps chromium
      - name: Run Storybook Tests
        run: npm run storybook:ci
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/dev_note/testing/undefined-6/github-actions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
