最近在重寫公司的專案,在整合 storybook 到 CI 時好好研究了一番,發現有些方法可以大幅降低在 CI 上跑的時間,於是就稍微整理了一下。
本篇文章的 repo 在這裡,可以直接 clone 下來看 commit 會比較清楚!
Table of Contents
1. 專案建置
可以直接切到 project-initialization
的 branch,會看到專案初始化後的環境(vite + react + storybook)。
首先建立一個簡單的專案,讓我們等等可以跑這個專案的 storybook。我這邊直接用 pnpm + vite + react + github actions 來建立這個專案。
1.1 vite
create-vite 提供了許多 template 可以快速建立一個新的專案,因此只要輸入以下指令:
$ pnpm create vite storybook-github-actions-optimization --template react-swc-ts
就可以快速建立一個 react + TypeScript + swc 的專案。
1.2 storybook
直接根據 storybook 的官網給的指令:
$ pnpm create storybook@latest
╭──────────────────────────────────────────────────────╮
│ │
│ Adding Storybook version 8.6.0 to your project.. │
│ │
╰──────────────────────────────────────────────────────╯
? What do you want to use Storybook for? ›
Instructions:
↑/↓: Highlight option
←/→/[space]: Toggle selection
a: Toggle all
enter/return: Complete answer
◯ Documentation: MDX, auto-generated component docs
◯ Testing: Fast browser-based component tests, watch mode
這時候會出現一些提示:

直接按 enter,storybook 就會幫你初始化環境了。
1.3 Install storybook test-runner
接著要安裝 storybook test-runner,test-runner 可以讓你確認是否有 story 是整個壞掉,無法呈現 UI 的狀態,因此很適合整合到 CI 上,確保不會意外改壞 storybook。
$ pnpm add --save-dev @storybook/test-runner
這麼一來,初步的環境就建立好了,跟開頭說的一樣,可以參考 project-initialization
這個 branch。
2. 建立 CI 的 workflow
接著我們用 github actions 來建立 test storybook 的 CI workflow。
name: Continuous Integration
on:
push:
permissions:
contents: read
jobs:
run-storybook-test-runner:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Cache node modules
id: cache-npm
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-build-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-build-
${{ runner.os }}-
- if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Playwright
run: pnpx playwright install --with-deps
- name: Build Storybook
run: pnpm build-storybook --quiet
- name: Serve Storybook and run tests
run: |
pnpx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \\
"pnpx http-server storybook-static --port 6006 --silent" \\
"pnpx wait-on tcp:127.0.0.1:6006 && pnpm test-storybook"
一開始的 CI 是參考 storybook 官方文件上與 github actions 整合的範例,只有差在官網的範例 package manager 是 yarn
,而我這裡用的是 pnpm
而已。
3. 最佳化
前置作業都完成後,我們就可以開始來最佳化流程了!
3.1 playwright install chromium only
可以看到 CI 中,在安裝完 dependencies 後,會執行這個指令:
$ pnpx playwright install --with-deps
如果不太知道 pnpx 做了什麼是,可以參考這篇:你可能不懂npx
原理和 npx 是類似的,差在 package manager 不同而已
這個指令代表的是用 playwright
這個 package 來安裝 rendering engine,會需要這個的原因是上述提到的 storybook test-runner 會用到 rendering engine (ex: Chromium、WebKit、Firefox) 來執行,而 rendering engine 通常是安裝在系統上的,因此沒辦法透過 pnpm install 來安裝在 node_modules 裡。
但如果看仔細一點 storybook 的文件會看到,test-runner 用到的是 jest-playwright
來執行,而在 jest-playwright
的 README 有提到:browsers 這個 config 可以指定要用哪些 rendering engine 來執行測試,預設其實只會有 chromium
browsers <[(string | object)[]]>. Define browsers to run tests in.
* chromium Each test runs Chromium (default).
* firefox Each test runs Firefox.
* webkit Each test runs Webkit.
但如果照 storybook 官網提供的範例執行:pnpx playwright install --with-deps
其實會直接安裝所有的 rendering engine,可以看到跑 CI 的時候,光是透過 playwright 安裝這些東西就花了 64s
。
因此其實可以只安裝 chromium 就好,將剛剛的指令改成:
$ pnpx playwright install chromium --with-deps
playwright 就會只安裝 chromium 這個 rendering engine 了。
可以看到這個 commit 跑的 CI,Install Playwright
的部分從 64s 下降為 22s。
3.2 cache playwright
剛有提到 playwright 是用來執行 test-runner 的必要工具,但如果沒有特別 cache 的話,就必須每次跑 CI 的時候都要重新安裝,因此我們可以將已經安裝過的 playwright 版本給 cache 住:
# 先透過 pnpx 取得 playwright 最新的版本輸出到 $GITHUB_OUTPUT
- name: Get installed Playwright version
id: playwright-version
run: echo "version=$(pnpx playwright --version | sed 's/Version //')" >> $GITHUB_OUTPUT
# 用上一步輸出到 $GITHUB_OUTPUT 的版本作為 cache key
- name: Cache playwright
id: cache-playwright
uses: actions/cache@v4
with:
path: '~/.cache/ms-playwright'
key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}
restore-keys: |
${{ runner.os }}-playwright-
# 如果 cache-hit 的話,代表這個版本的 playwright 已經安裝過了,不需要重新安裝
- name: Install Playwright
if: steps.cache-playwright.outputs.cache-hit != 'true'
run: pnpx playwright install chromium --with-deps
這麼一來,除非 playwright 的版本有更新,否則就會直接拿已經安裝過的 cache,可以大幅降低跑 CI 的時間。
可以看到這個 commit push 上去後,再重新跑一次 CI,這次就不會重新安裝 playwright 了!
3.3 cache build storybook
storybook 的文件有提到,要用 test-runner 跑 storybook 的話,需要先 build storybook 變成靜態檔案,才能用 test-runner 跑。但我們在 push commit 的時候可能根本沒有改到 storybook 相關的檔案,卻要重新 build storybook 好像不太合理,因此在這個步驟我們也可以做 cache。
- name: Cache Storybook
id: cache-storybook
uses: actions/cache@v4
with:
path: storybook-static
# src/stories 底下的檔案有變動的話,cache key 才會變,才會需要重 build storybook
key: ${{ runner.os }}-storybook-${{ hashFiles('src/stories') }}
restore-keys: |
${{ runner.os }}-storybook-
- name: Build Storybook
if: steps.cache-storybook.outputs.cache-hit != 'true'
run: pnpm build-storybook --quiet
這麼一來,push commit 時,如果該 commit 沒有改到 src/stories 裡面的檔案,在跑 CI 時就不會重新 build storybook 了!
對應的 commit (不過這個 repo 的 stories 很少,因此成效會不太明顯,但隨著專案越來越大,減少的時間也會越來越可觀)。
4. 成效 ↓ 70%
可以觀察到,最一開始完全按照官網的範例的話,整個 CI 執行了 98s

而在我們做完最佳化後,整個 CI 只跑了 30s
整整下降了將近 70%

5. Reference
Test runner | Storybook docs
Running tests using Jest & Playwright
如果覺得我的文章有幫助的話,歡迎幫我的粉專按讚哦~謝謝你!