Day 27|本機測試都通過了,放到 GitHub Actions 卻失敗
前言
昨天的測試跑在你的機器上,今天把它接進 GitHub Actions,讓相關程式碼更新時自動跑。同時把報告、操作紀錄和截圖留下來,免得 CI 紅了,大家只能對著一行錯誤訊息猜。
開始之前
本機跑測試有兩個問題,而且都不是「麻煩」而已。
第一,它只在你想到的時候跑。改完一段程式碼、趕著下班的那次,通常就是最該跑的那次。
第二,也是更嚴重的,本機的綠燈不能拿來說服任何人。你說「我這邊是綠的」,對方無從查證,也無從重播。CI 的價值不只是自動化,是它產生了一份別人可以檢視的紀錄。
但只把測試搬上去還不夠。CI 紅了,你能拿到的預設訊息可能只有一行 exit code 1,或一段沒有上下文的逾時。那種紅燈跟沒有紅燈差不多,因為沒有人能從它往下走。
所以這一天有兩件事:把測試接上去,以及讓紅燈自帶證據。第二件事的規格從 Day 8 到 Day 11 就立好了,今天是把它搬進 pipeline。
動手試試
workflow 的核心是 matrix,也就是把同一份測試分成兩個工作,分別跑 clean 與 with-bugs。
下面只節錄觸發條件和 matrix 設定,還不能整段貼上就跑。安裝依賴、執行測試和上傳報告的步驟,放在配套專案的完整 e2e.yml 裡:
name: e2e
on:
push:
branches: ['**']
paths: ['tests/**', 'package.json', '.github/workflows/e2e.yml']
pull_request:
workflow_dispatch:
inputs:
repeat_flaky:
description: '額外跑 flaky 量測(重複次數,0 = 不跑)'
default: '0'
concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: true
jobs:
e2e:
name: e2e (${{ matrix.sut }})
strategy:
fail-fast: false
matrix:
sut: [clean, with-bugs]fail-fast: false 是關鍵。預設值會在第一格紅掉時取消其他格,那正好毀掉這條 pipeline 的用途:我們要的就是「clean 綠、with-bugs 紅」這個對照,少一邊就判不出來。
跑完之後
實跑五次的結果:
| run | e2e (clean) |
e2e (with-bugs) |
|---|---|---|
30710272941 |
✗ 紅 | ✗ 紅 |
30710272941(rerun --failed) |
✗ 紅 | ✗ 紅 |
30710677728 |
✓ 3 passed | ✗ 紅 |
30710750654 |
✓ | 本次驗穩紀錄未列 |
30710799963 |
✓ | 本次驗穩紀錄未列 |

表中有記錄的 with-bugs 紅燈,是預期中的金額斷言失敗(Received: 0)。clean 前兩次紅才是這次要追的問題,明天會把整個過程攤開來看。
後三輪的驗穩目標是修復後的 clean 測試,原始 reruns-2026-08-02.yaml 記下它連續三次通過。最後兩列沒有列出 with-bugs 的結果,所以只能拿來支持 clean 的驗穩,不能說這五輪都有完整的兩環境對照。
順帶一提,clean 是 3 passed 不是 4:需要正確帳密的那支登入測試,在 CI 上因為沒設 secrets 而跳過。這次少驗了一支,要明講;之後補好 secrets,就能把它加回來,不需要把帳密寫進程式碼。
讀紅燈:artifact 是契約
artifact 就是 CI 執行後留下、可以下載的檔案,例如測試報告、trace 和截圖。這份 workflow 的報告用 if: always() 上傳,失敗時的證據包則用 if: failure() 上傳,紅燈亮了才有東西可以查。
名稱也要讓接手的人看得懂。本例用 playwright-report-clean、playwright-report-with-bugs 區分兩個環境。之後如果加入 API 測試,也要和 UI 測試分開命名,免得下載了一大包,還得猜每份報告在測什麼。
改名要連契約表一起改,這條跟 Day 11 的證據包命名是同一個道理:能被自動處理的前提是名字可預測。
拉證據的指令固定兩條:
gh run view <run-id> --log-failed # 只看失敗那幾行
gh run download <run-id> # 把 artifact 撈下來先看失敗步驟的 log,比一開始就把整份紀錄塞給模型容易找重點。如果看不出原因,再補讀相關步驟和下載證據。
背後怎麼做
上面那份是本次實驗用的 workflow。之後要擴成日常使用的 pipeline,可以再考慮下面幾件事,並不是這一輪全都已經做了。
不同觸發時機可以跑不同範圍。 PR 要快,先跑必要範圍;nightly 是夜間排程,可以跑全量;workflow_dispatch 留給手動執行與帶參數的實驗,本例也用它啟動 flaky 量測。
API 測試切成獨立 job,而且不裝瀏覽器。 安裝瀏覽器通常是整條裡最慢的一步,API 測試不需要它。讓後端規則的紅燈在幾十秒內先亮,比等 UI 測試跑完十分鐘才知道有價值。
但這裡有個坑:UI job 掛 needs: api-test 只能用在 PR。 nightly 也掛的話,API 一紅整批 UI 會變成 skipped,隔天早上你看到的是一份缺一半的報告,而不是壞消息。
祕密只走 GitHub secrets,不寫死在 workflow、不進知識庫。
workflow 會影響整個專案何時跑測試、花多少資源。交給代理人修改時,要把變更內容和目標分支交代清楚,依專案授權執行。
今天學到什麼
今天把測試接進 GitHub Actions,用 matrix 跑兩個受測版本,並留下能下載的報告與失敗證據。這樣別人也能看到同一份結果,不必只相信一句「我本機有過」。
實際跑起來,乾淨版反而先紅了兩次,修復後才達到連續三次通過的驗收門檻。紀錄裡哪些是完整對照、哪些只用來驗穩,以及哪支測試因為沒設帳密而跳過,都要交代清楚。
明天就來看那兩次紅燈。先別急著改測試,我們會沿著證據找原因,也會看到我怎麼連續猜錯兩次。
參考資料
- Martin Fowler(2006)《Continuous Integration》 - 每次提交都自動驗證,以及為什麼壞掉的 build 要立刻被看見
- Jez Humble、David Farley(2010)《Continuous Delivery》 - deployment pipeline 的分層概念:快的先跑、慢的後跑,本篇的 API 快 lane 出自這裡
- Nicole Forsgren、Jez Humble、Gene Kim(2018)《Accelerate: The Science of Lean Software and DevOps》 - 回饋速度與交付效能的量化關聯,說明為什麼要為了幾十秒去拆 job
- GitHub Docs — Storing and sharing data from a workflow - artifact 的上傳條件、命名與保留天數,
if: always()的官方說明 - 本專案
.github/workflows/e2e.yml(實跑的 workflow)、skills/infra/ci-pipeline/SKILL.md、tests/README.md的 CI 那一節 - 五次 run 的原始紀錄