Day 27|本機測試都通過了,放到 GitHub Actions 卻失敗

分享
Day 27|本機測試都通過了,放到 GitHub Actions 卻失敗
Photo by Aman Upadhyay / Unsplash

前言

昨天的測試跑在你的機器上,今天把它接進 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 ✓ 本次驗穩紀錄未列
CI 流程從三種觸發、matrix 兩格、到 if always 上傳 artifact,下方是五次實跑的結果表與兩條拉證據的指令

表中有記錄的 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 跑兩個受測版本,並留下能下載的報告與失敗證據。這樣別人也能看到同一份結果,不必只相信一句「我本機有過」。

實際跑起來,乾淨版反而先紅了兩次,修復後才達到連續三次通過的驗收門檻。紀錄裡哪些是完整對照、哪些只用來驗穩,以及哪支測試因為沒設帳密而跳過,都要交代清楚。

明天就來看那兩次紅燈。先別急著改測試,我們會沿著證據找原因,也會看到我怎麼連續猜錯兩次。


參考資料

  1. Martin Fowler(2006)《Continuous Integration》 - 每次提交都自動驗證,以及為什麼壞掉的 build 要立刻被看見
  2. Jez Humble、David Farley(2010)《Continuous Delivery》 - deployment pipeline 的分層概念:快的先跑、慢的後跑,本篇的 API 快 lane 出自這裡
  3. Nicole Forsgren、Jez Humble、Gene Kim(2018)《Accelerate: The Science of Lean Software and DevOps》 - 回饋速度與交付效能的量化關聯,說明為什麼要為了幾十秒去拆 job
  4. GitHub Docs — Storing and sharing data from a workflow - artifact 的上傳條件、命名與保留天數,if: always() 的官方說明
  5. 本專案 .github/workflows/e2e.yml(實跑的 workflow)、skills/infra/ci-pipeline/SKILL.md、tests/README.md 的 CI 那一節 - 五次 run 的原始紀錄