React | Vue | Angular | Web Components | 그 외(More)
Vitest 애드온(Vitest addon)은 스토리북 안에서 UI 테스트를 자동화하는 데 아주 탁월한 도구입니다. 하지만 테스트가 뒷받침해 주는 완벽한 자신감을 얻으려면, 이 자동화된 테스트들을 여러분의 CI(지속적 통합, Continuous Integration) 환경에서도 쌩쌩 돌려야만 합니다.
다행히도 그 과정은 아주 쉽답니다!
ℹ️ 만약 프로젝트 환경상 Vitest 애드온을 쓸 수 없는 상황이더라도, 테스트 러너(test-runner)를 사용하면 여러분의 스토리들을 CI 환경에서 훌륭한 테스트로 활용할 수 있습니다. CI 세팅 방법은 테스트 러너 공식 문서의 'CI 설정하기(Set up CI to run tests)' 섹션을 참고해 주세요.
CI에서 스토리북 테스트를 돌리는 건, 여러분 컴퓨터의 터미널(CLI)에서 테스트를 돌리는 것과 거의 똑같습니다. 그냥 실행되는 '장소'만 다를 뿐, 똑같은 명령어를 사용하거든요.
차근차근 하나씩 설정해 볼까요?
package.json 스크립트 정의하기편의를 위해 package.json 파일에 스토리북 테스트를 실행할 스크립트를 하나 만들어 주세요. 이건 여러분이 로컬에서 실행할 때 쓰는 명령어와 똑같지만, CI 워크플로우에 편하게 등록해 두기 위해서 미리 정의해 두는 겁니다.
// package.json
{
"scripts": {
"test-storybook": "vitest --project=storybook"
}
}
이 스크립트는 vitest 명령어를 호출하면서, Vitest 애드온을 설치할 때 여러분의 Vitest 설정 파일에 추가되었던 "storybook" 프로젝트만 쏙 골라서 실행하라고 제한(--project=storybook)을 둡니다. (만약 프로젝트 이름을 바꾸셨다면 스크립트도 그에 맞게 수정해 주세요!) 물론 여기에 필요한 추가적인 vitest CLI 옵션들을 마음껏 덧붙여도 좋습니다.
이제 CI 환경에서 씽씽 돌아갈 새로운 "UI Tests" 워크플로우를 만들어보겠습니다. 만약 기존에 쓰던 워크플로우가 있다면, 거기에 설정을 살짝 얹어주셔도 좋아요.
가장 많이 쓰이는 대표적인 CI 서비스들의 설정 예시를 보여드릴게요:
GitHub Actions저장소 루트에 .github/workflow/test-ui.yml 파일을 만들고 아래 코드를 넣어주세요:
# .github/workflows/test-ui.yml
name: UI Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
container:
# 꼭 Playwright 이미지의 최신 버전을 가져오도록 확인해 주세요!
# [https://playwright.dev/docs/docker#pull-the-image](https://playwright.dev/docs/docker#pull-the-image)
image: [mcr.microsoft.com/playwright:v1.58.2-noble](https://mcr.microsoft.com/playwright:v1.58.2-noble)
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.12.0
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test-storybook
GitLab Pipelines
저장소 루트에 .gitlab-ci.yml 파일을 만들고 아래 코드를 넣어주세요:
# .gitlab-ci.yml
image: node:jod
stages:
- UI_Tests
cache:
key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
paths:
- .npm/
before_script:
# 의존성(Dependencies) 설치
- npm ci
Test:
stage: UI_Tests
# 꼭 Playwright 이미지의 최신 버전을 가져오도록 확인해 주세요!
# [https://playwright.dev/docs/docker#pull-the-image](https://playwright.dev/docs/docker#pull-the-image)
image: [mcr.microsoft.com/playwright:v1.58.2-noble](https://mcr.microsoft.com/playwright:v1.58.2-noble)
script:
- npm run test-storybook
Bitbucket Pipelines
저장소 루트에 bitbucket-pipelines.yml 파일을 만들고 아래 코드를 넣어주세요:
# bitbucket-pipelines.yml
image: node:jod
definitions:
caches:
npm: $HOME/.npm
pipelines:
default:
- stage:
name: "UI Tests"
steps:
- step:
name: "Run Tests"
# 꼭 Playwright 이미지의 최신 버전을 가져오도록 확인해 주세요!
# [https://playwright.dev/docs/docker#pull-the-image](https://playwright.dev/docs/docker#pull-the-image)
image: [mcr.microsoft.com/playwright:v1.58.2-noble](https://mcr.microsoft.com/playwright:v1.58.2-noble)
caches:
- npm
- node
script:
# 의존성(Dependencies) 설치
- npm ci
- npm run test-storybook
Circle CI
저장소 루트에 .circleci/config.yml 파일을 만들고 아래 코드를 넣어주세요:
# .circleci/config.yml
version: 2.1
executors:
ui-testing:
docker:
# 꼭 Playwright 이미지의 최신 버전을 가져오도록 확인해 주세요!
# [https://playwright.dev/docs/docker#pull-the-image](https://playwright.dev/docs/docker#pull-the-image)
- image: [mcr.microsoft.com/playwright:v1.58.2-noble](https://mcr.microsoft.com/playwright:v1.58.2-noble)
working_directory: ~/repo
jobs:
Test:
executor: ui-testing
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package-lock.json" }}
- v1-dependencies-
# 의존성(Dependencies) 설치
- run: npm ci
- run: npm run test-storybook
- save_cache:
name: Save NPM cache
paths:
- ~/.npm
key: v1-dependencies-{{ checksum "package-lock.json" }}
workflows:
UI_Tests:
jobs:
- Test
Travis CI
저장소 루트에 .travis.yml 파일을 만들고 아래 코드를 넣어주세요:
# .travis.yml
language: node_js
os: linux
dist: jammy
node_js:
- 20
before_script:
# Vitest의 browser 모드가 스토리 테스트를 실행할 수 있도록 의존성과 Playwright 브라우저들을 설치합니다.
- npm ci && npm run playwright install chromium --with-deps
cache: npm
jobs:
include:
- stage: "UI Tests"
name: "Run tests"
script: npm run test-storybook
Jenkins
저장소 루트에 JenkinsFile 파일을 만들고 아래 코드를 넣어주세요:
// JenkinsFile
pipeline {
agent any
tools {nodejs "node"}
stages {
stage('UI Tests'){
agent {
docker {
/*
* 꼭 Playwright 이미지의 최신 버전을 가져오도록 확인해 주세요!
* [https://playwright.dev/docs/docker#pull-the-image](https://playwright.dev/docs/docker#pull-the-image)
*/
image '[mcr.microsoft.com/playwright:v1.58.2-noble](https://mcr.microsoft.com/playwright:v1.58.2-noble)'
reuseNode true
}
}
steps {
/* 의존성(Dependencies) 설치 */
sh 'npm ci'
sh "npm run test-storybook"
}
}
}
}
Azure Pipelines
저장소 루트에 azure-pipelines.yml 파일을 만들고 아래 코드를 넣어주세요:
# azure-pipelines.yml
trigger:
- main
pool:
vmImage: "ubuntu-latest"
stages:
- stage: UI_Tests
displayName: "UI Tests"
jobs:
- job: Test
displayName: "Storybook tests"
# 꼭 Playwright 이미지의 최신 버전을 가져오도록 확인해 주세요!
# [https://playwright.dev/docs/docker#pull-the-image](https://playwright.dev/docs/docker#pull-the-image)
container: [mcr.microsoft.com/playwright:v1.58.2-noble](https://mcr.microsoft.com/playwright:v1.58.2-noble)
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm
steps:
- task: UseNode@1
displayName: "Install Node.js"
inputs:
version: "22.12.0"
- task: Cache@2
displayName: "Install and cache dependencies"
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(npm_config_cache)
- script: npm ci
condition: ne(variables.CACHE_RESTORED, 'true')
- task: CmdLine@2
displayName: "Run tests"
inputs:
script: npm run test-storybook
ℹ️ 기본적으로 Storybook Test는 Playwright를 사용해서 스토리를 렌더링합니다. 따라서 가장 빠른 테스트 속도를 원하신다면 (위의 대부분의 예시 코드에서처럼) Playwright가 이미 설치되어 있는 머신 이미지를 사용하시는 게 좋습니다.
스토리북 테스트가 실패하면, 보통은 결과 화면에 실패한 스토리로 바로 가는 링크가 함께 출력됩니다. 로컬 컴퓨터에서 테스트를 돌렸다면 그 링크는 여러분의 로컬에 띄워진 스토리북(localhost:6006)을 가리키겠죠.
하지만 CI 환경에는 켜져 있는 스토리북 서버가 없잖아요? 그래서 CI에서는 약간의 꼼수가 필요합니다. 스토리북을 먼저 빌드해서 어딘가에 배포(publish)한 다음, Vitest 애드온에게 "야, 스토리북 여기에 배포해 놨어!"라고 알려줘야, 애드온이 디버깅하기 유용한(실제 접속 가능한) 스토리 링크를 뱉어낼 수 있습니다.
GitHub Actions를 예로 들어 설명해 드릴게요. 다른 CI 서비스들도 문법이나 설정 위치만 조금 다를 뿐 원리는 거의 똑같습니다.
Vercel, GitHub Pages 같은 배포 서비스들은 새로운 배포가 완료되면 보통 deployment_status라는 이벤트를 발생시키고, 이 이벤트 정보 안에는 새로 생성된 접속 URL이 deployment_status.environment_url이라는 이름으로 들어있습니다. 바로 이게 우리가 찾던 배포된 스토리북 주소입니다!
이 URL을 SB_URL이라는 환경 변수(environment variable)에 담아서 테스트 명령어에 전달해 주면 됩니다.
// .github/workflows/test-storybook.yml
name: Storybook Tests
+ # 👇 반드시 deployment_status 이벤트가 발생했을 때만 실행되도록 업데이트해 주세요!
+ on: deployment_status
- on: [push]
jobs:
test:
runs-on: ubuntu-latest
container:
image: [mcr.microsoft.com/playwright:v1.58.2-noble](https://mcr.microsoft.com/playwright:v1.58.2-noble)
+ # 👇 성공적으로 배포(deploy)되었을 때만 실행되도록 조건을 걸어줍니다.
+ if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success'
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.12.0
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test-storybook
+ # 👇 받아온 스토리북 URL을 환경 변수로 전달해 줍니다.
+ env:
+ SB_URL: '${{ github.event.deployment_status.environment_url }}'
이제 마지막으로, Vitest 설정 파일에서 방금 만든 환경 변수(SB_URL)를 플러그인 설정의 storybookUrl 옵션으로 읽어오도록 세팅해 줍니다.
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
// ...
test: {
// ...
projects: [
{
plugins: [
storybookTest({
// ...
// 👇 환경 변수로 전달된 URL을 여기에 쏙 넣어줍니다.
storybookUrl: process.env.SB_URL,
}),
],
},
],
},
});
짜잔! 이제 CI에서 테스트가 실패하면 터미널 창에 실제 배포된 스토리북으로 연결되는 URL이 깔끔하게 출력되어서, 디버깅이 정말 한결 편해질 거예요!
ℹ️ 코드 커버리지에 대한 더 자세한 이야기는 전용 가이드 문서(full guide)에 아주 상세하게 나와있으니 한 번 확인해 보세요.
스토리북 테스트의 코드 커버리지를 계산하려면 vitest 명령어 뒤에 --coverage 플래그만 딱 붙여주시면 됩니다. 커버리지는 프로젝트 내의 모든 테스트(스토리 + 일반 단위 테스트 등)를 싹 다 포함해서 포괄적(comprehensively)으로 계산할 때 가장 의미가 있지만, 원한다면 스토리북 테스트만의 커버리지를 따로 뽑아볼 수도 있어요.
명령어를 수정하는 방법은 두 가지입니다. 하나는 package.json 스크립트 자체를 바꾸는 거예요:
프로젝트의 모든 테스트를 한 번에 실행할 때 (For all tests):
// package.json
{
"scripts": {
+ "test": "vitest --coverage"
- "test": "vitest"
}
}
스토리북 테스트만 단독으로 실행할 때 (For only Storybook tests):
// package.json
{
"scripts": {
+ "test-storybook": "vitest --project=storybook --coverage"
- "test-storybook": "vitest --project=storybook"
}
}
아니면, 로컬에서는 그냥 테스트만 돌리고 CI 환경에서 돌아갈 때만 커버리지를 계산하도록 CI 설정 파일을 수정할 수도 있어요:
프로젝트의 모든 테스트를 한 번에 실행할 때 (For all tests):
// .github/workflows/test-storybook.yml
- name: Run tests
run: |
+ npm run test -- --coverage
- npm run test
스토리북 테스트만 단독으로 실행할 때 (For only Storybook tests):
// .github/workflows/test-storybook.yml
- name: Run tests
run: |
+ npm run test-storybook -- --coverage
- npm run test-storybook
방금 만든 CI 설정은 여러분이 Pull Request(PR)에 코드를 푸시(push)할 때마다 씽씽 돌아가도록 구성되어 있습니다. 한번 새로운 PR을 만들어서(예를 들면, Storybook Test가 찾아낸 접근성 문제를 고치는 PR 같은 거요!) 워크플로우가 제대로 작동하는지 테스트해 보세요.
PR을 올리고 나면 PR 화면의 하단 상태 체크(status check) 영역에 방금 만든 테스트 결과가 뜰 겁니다. GitHub을 예로 들자면, 테스트가 실패했을 때 이렇게 보이게 됩니다:

저 실패 메시지(Details)를 클릭하고 들어가면 상세한 테스트 결과 로그를 볼 수 있고, 앞서 설정해 둔 (SB_URL 환경 변수 덕분에) 실패한 스토리로 바로 꽂아주는 편리한 링크도 확인할 수 있을 거예요.

어떤 프로젝트들은 스토리북 테스트 말고도 다른 종류의 테스트(예: 순수 단위 테스트(unit tests) 등)들을 Vitest로 돌리기도 합니다.
이런 다른 테스트들은 프로젝트 필터(project filter) 옵션을 이용해서 별도의 스크립트로 분리해 주면 서로 간섭 없이 독립적으로 실행할 수 있습니다. 예를 들어, 단위 테스트를 모아놓은 Vitest 프로젝트 이름이 "unit"이라면 이렇게 설정할 수 있죠:
// package.json
{
"scripts": {
"test-storybook": "vitest --project=storybook",
"test-unit": "vitest --project=unit"
}
}
그리고 CI 워크플로우 파일에서는 이 스크립트들을 각각 나란히 호출해주면 됩니다:
// .github/workflows/test.yml
- name: Run tests
run: |
npm run test-unit
npm run test-storybook
아니면, package.json 스크립트에서 --project=storybook 필터를 아예 지워버리면 Vitest가 알아서 모든 프로젝트의 테스트를 한 방에 묶어서 돌려주기도 합니다!
// package.json
{
"scripts": {
"test": "vitest"
}
}
그러면 워크플로우에서는 아주 심플하게 한 줄만 적으면 되죠:
// .github/workflows/test.yml
- name: Run tests
run: |
npm run test
더 유용한 테스팅 관련 자료들