iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0

❯❯守門人也會犯錯!Playwright Pre-push 閘門、PR CI 快速防線與兩次自動化事故復盤
Day 24 沒過關的東西,進不了 main

📍 流水線位置|入口 → flow → 型別 → mock → 測試 → UI → vibe → 【上線】(守門)

流水線位置|入口 → flow → 型別 → mock → 測試 → UI → vibe → 【上線】(守門)

Day 20 的本地閘門(gate)作用於 git commit 階段,能在開發第一時間攔下各種意料之外的破壞。但 commit 說到底只是本機變更的起點,程式碼進入生產環境前,真正的防線始終是 main 分支,因為只要一合進 main 的變更,就會直接觸發自動部署管線。

也因此,在變更正式合進 main 分支前,系統設計了兩道不同職責的檢查關卡。


第一道:pre-push,本機的最後防線

執行 git push 時,pre-push hook 會自動跑系統檢查,它沿用 Day 20 的同一套設定檔,一口氣跑完 main spec 與 vibe spec 的自動化測試。只要環境支援 Docker,它甚至會拉起環境的映像檔進行隔離測試,它測的是真正打包出來的 production build,而不是拿 dev server 跑跑看就算過關。

既然commit 測過了,push 為什麼還要再測一次?因為大家開發時隨手 commit 是常態,中途改壞了也很容易沒發現;但 push 一送出去就是遠端的事了,影響範圍完全不同,本來就該用最嚴格的標準把關。這一關說穿了就是幫大家的人為疏失墊底:攔下那些「忘記跑測試就推 code」以及藏在邊邊角角的錯誤。

跑完整測試要花 5 至 6 分鐘。等待固然煩人,但比起事後去清洗被污染的 main 分支,這個時間成本划算太多了。

為了避免無謂的等待,hook 內建了變更範圍判斷機制(節錄自 .husky/pre-push 的註解):

# 最佳化:若本次 push 的 commit「只動到非程式碼檔」(.env*/docs/docker 設定等),
# 這些變更不會影響被測程式邏輯,因此跳過測試,不必空等。
# 保守原則:只要有任何一個程式碼檔被改到,就照常跑全套測試。

只要這次推送只涉及文件或設定檔,系統會立刻放行,把阻力降到最低,防止有人因為心煩而動念繞過 CI 檢查。


第二道:CI on PR,雲端的公證人

當 Pull Request 建立後,CI 會在完全乾淨的雲端環境裡跑校驗流程(節錄自 pull_request.yml):

- run: npm ci
# 快速守門先行(數十秒內),紅燈即擋 PR,不用等 build
- run: npm run test:unit
- run: npm run build
- run: npm run eslint      # ← 含 Day 20 那支自製的視覺層級檢查

留意這裡的順序巧思:單元測試(unit test)被特意擺在建置(build)前面。只要測試沒過,幾十秒內就會直接掛紅燈並中斷 PR,完全不用白白浪費時間等 Build 跑完。

只有這幾關全部 pass,才拿得到合進 main 的通行證。由於端對端測試(E2E)執行成本高,所以我安排在本地 pre-push 階段解決,雲端 CI 則專注在「乾淨環境」的相容性校驗。

既然本地都跑過 pre-push 了,幹嘛還要在雲端多此一舉?

答案很簡單:pre-push 終究是在你家電腦跑的,隨時可能被本地 node_modules、殘留環境變數或沒進版控的檔案給「開美顏」;而 CI 則是跑在零污染的雲端,驗證的是「程式碼一旦離開你的電腦,到底還建不建得起來、跑不跑得動」。

雙重 CI/CD 守門員架構圖

兩者職責切割明確:

pre-push 防範本機的人為疏失,CI 則防止本機環境的自信偏見。


守門機制的兩次翻車事故

這套守門流程其實也在實際運作中踩過兩次大坑。

第一次事故(Day 15):CI 漏勾測試項目。
當時 CI 設定檔中漏寫了 test:unit 項目,果即使有 5 個單元測試處於壞掉狀態,CI 卻依然一路綠燈把程式碼合進 main。檢查機制本身運作正常,但壞在我忘了把相應的測試腳本納入檢查範圍。事後才將單元測試補回 CI 的必要路徑,確立了「快速守門先行」的順序。

第二次事故:SSH 遠端連線 Time Out。
原因出在 git push 與 hook 執行的時間差。系統會先與 GitHub 建立 SSH 連線,連線建立後才執行本地的 pre-push hook;由於測試流程需耗時 5 至 6 分鐘,這段時間 SSH 連線完全沒有資料傳輸,結果直接觸發 GitHub 伺服器的逾時機制,被強制斷線。

SSH Timeout 假成功事故與心跳修復圖

這也是最坑的地方:Terminal 亮綠燈說測試通過,但 git push 其實根本沒成功。回去翻 log 才知道 GitHub 早就斷線了( Connection to github.com closed by remote host)。測試過關卻沒 push 上去,這種沒有警告的失敗,殺傷力比直接噴錯卡住大太多了。

修復分成兩個層次:

1. 治本(維持連線):
在本地 ~/.ssh/configgithub.com 加上 ServerAliveInterval 60 設定,確保在執行測試時每 60 秒持續送心跳(Heartbeat),不讓 GitHub 把連線當死連結切掉。

2. 驗收(雙重確認):
推送完成後,執行 git ls-remote 對比遠端與本地 HEAD 的 commit sha,確保變更真的有落到遠端伺服器上。

最後也在 pre-push 腳本補上了錯誤處置與修復指引:

→ gate 已全綠但 push 因 SSH timeout 中斷時,可 --no-verify 重推(驗證已完成非跳過)。
→ 其他情況勿用 --no-verify(CLAUDE.md 政策禁止)。

明確定義了跳過檢查(--no-verify)的唯一例外條件:僅限於測試已於本地完整驗證通過,卻卡在傳輸層斷線的情境,其餘情況仍嚴格禁止繞過檢查。

這兩次踩坑也提醒了我:自動化防線本身,也是需要被測試與驗證的程式碼。

守門機制徹底搞定後,下一個課題是:通過檢查的變更怎麼自動交付到生產環境,以及部署流程中藏著哪些架構風險?

🎒 最小一步|打開你的 CI pipeline 看執行順序:耗時最長的步驟是不是排在最前面?試著把數十秒就能出結果的檢查(單元測試、Linter)搬到最前面,壞掉時就不用多等幾分鐘白工。
📎 本篇證據|.husky/pre-push(skip 白名單註解與紅燈分流指引原文)・.github/workflows/pull_request.yml(「快速守門先行」註解原文)・issue #76(test:unit 補進 CI)・pre-push SSH 假成功事故(keepalive+ls-remote 修法,留在 pre-push#L70-L71 的錯誤處置指引)


上一篇
Day 23 一個變數,決定前端打誰
系列文
收到自己的紅色炸彈!婚期就是 Deadline:前端一人用 AI 規格流水線,30 天出貨婚禮 SaaS24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言