Skip to main content
E2E 測試:AI 時代的自動化測試與驗收(七)如何保護關鍵使用者旅程、前端 API 與非同步流程?

Architecture Journal

TestingE2EAcceptance

E2E 測試:AI 時代的自動化測試與驗收(七)如何保護關鍵使用者旅程、前端 API 與非同步流程?

上一篇:契約測試的三種方案:OpenAPI + Schemathesis、Pact 與 Spring Cloud Contract

前幾篇把測試責任逐層拆開:單元測試驗證規則,整合測試驗證元件接在一起後能不能運作,契約測試驗證服務之間約定的邊界沒有被悄悄改掉。

但這些測試即使全部通過,還是可能漏掉一個使用者真正會遇到的問題:

從登入、操作畫面、呼叫 API,到資料寫入、非同步處理與最後的結果呈現,整條流程真的走得完嗎?

這就是 E2E(End-to-End)測試要回答的問題。

E2E 不應該是「把所有測試再跑一次」,也不應該是測試金字塔最上面堆滿測試案例。它的價值在於,用少量測試保護少數最重要、跨越最多系統邊界的使用者旅程。

當 AI 可以快速產生更多程式碼與測試時,真正稀缺的,是團隊是否知道哪些旅程值得保護,以及什麼結果才算完成。

E2E 測試保護的是旅程,不是所有功能

把前幾篇的分層放在一起,責任大致可以這樣看:

測試關注點主要問題常見範圍
單元測試這個規則算對嗎?函式、類別、單一決策
整合測試元件接在一起能運作嗎?資料庫、佇列、序列化、設定
契約測試兩個系統對邊界的約定一致嗎?API、事件、Schema、錯誤格式
E2E 測試使用者的關鍵旅程走得完嗎?前端、後端、資料與部署拓撲
驗收測試這次交付值得接受嗎?業務目標、使用者價值、風險

E2E 關心的是跨邊界後的整體結果。例如「使用者成功下單」可能同時經過:

  1. 瀏覽器載入前端頁面。
  2. 前端取得商品與庫存資料。
  3. API 驗證使用者並建立訂單。
  4. 訂單服務寫入資料庫、發布事件,背景工作處理付款或庫存流程。
  5. 前端重新取得訂單狀態並顯示結果。

單元、整合、契約測試可以分別替其中幾段提供信心,但它們無法單獨證明這些段落在實際拓撲中接起來後,仍然符合使用者的預期。完整旅程測試才會直接驗證這件事。

這裡也要區分 E2E 與驗收測試。E2E 通常是一種技術驗證方式;驗收測試則是在判斷交付是否符合產品或業務條件。一個驗收條件可能用 API、瀏覽器或人工操作驗證,不一定都要寫成 E2E。反過來,一條 E2E 流程也可能只是部署後的健康檢查,還沒有涵蓋完整的業務驗收。

為什麼 E2E 不能什麼都測

E2E 的範圍越大,成本通常會一起增加:

  • 需要更多服務、資料庫、佇列與外部依賴。
  • 執行時間較長,開發者等待回饋的成本較高。
  • 失敗時不容易立即知道是哪一層出問題。
  • 測試資料、環境狀態與非同步處理會增加不穩定因素。
  • UI、API 與部署設定的任何一處改動,都可能讓流程需要維護。

因此,E2E 測試不適合拿來覆蓋每個欄位、每個錯誤訊息與每一種邊界輸入。這些細節應該由較低層的測試快速涵蓋。

更實際的做法是:先找出失敗代價最高的使用者旅程,再確認其中哪些跨服務風險無法由低層測試取代。剩下的案例交給單元、整合或契約測試。

這樣做不代表降低測試標準,而是把較昂貴的測試用在低層測試無法回答的問題上。

怎麼挑值得保護的旅程

一條旅程是否值得成為 E2E,通常可以從幾個角度判斷:

判斷角度要問的問題
業務影響這條流程失敗,會不會直接影響收入、留存或核心服務?
跨邊界程度它是否經過多個服務、資料儲存或非同步流程?
失敗代價問題是否可能直到使用者操作時才被發現?
使用頻率這是不是使用者最常走的主流程?
代表性通過這條流程,能否代表一整類重要能力仍然可用?

例如,電商系統可以優先保護:

  • 使用者可以登入並查看自己的訂單。
  • 使用者可以把商品加入購物車並完成下單。
  • 使用者付款後,訂單會進入正確狀態。
  • 使用者可以在前端看到非同步處理完成的結果。

不一定需要為每一個商品篩選條件都建立一條完整 E2E。篩選規則可以由單元測試覆蓋,查詢與資料庫整合可以由整合測試覆蓋,API 欄位格式可以由契約測試覆蓋。

E2E 要留下的是「如果這裡壞掉,使用者就無法完成重要事情」的案例。

Exploratory Testing 用來發現風險,E2E 用來固定保護旅程

只寫 happy path,確實容易讓 E2E 看起來通過了,卻沒有覆蓋真正危險的情境。這時可以使用 Exploratory Testing,針對一條核心旅程探索權限、異常資料、非同步延遲、外部服務失敗、重試與恢復等風險。

例如,可以針對下單旅程進行一次限時探索,專注於付款失敗、重複送出,以及庫存於付款期間變更等情境。探索後,只有高風險且可重現的問題,才需要沉澱成自動化回歸測試。

但 Exploratory Testing 與 E2E 不是同一件事。E2E 描述要驗證的系統邊界與使用者旅程;Exploratory Testing 描述測試人員如何在未知風險中學習、提問與找問題。探索可以從瀏覽器、API 或其他測試層開始,不一定都要成為 E2E。

探索結果也不應全部直接轉成自動化 E2E。比較好的做法是先分類:

  • 需求或業務規則不清楚:補回驗收條件。
  • 高風險且可重現的缺陷:加入適當層級的回歸測試。
  • 只有跨系統串接才看得出的問題:考慮加入 E2E。
  • 低風險或一次性的觀察:保留探索紀錄即可。

換句話說,Exploratory Testing 用來發現未知風險;E2E 用來長期守住已確認的重要旅程。兩者分開管理,才能讓 E2E 保持少量、穩定,而且有明確目的。

用下單流程畫出一條關鍵旅程

延續前幾篇的訂單例子,一條「使用者下單並看到處理結果」的 E2E 旅程,可以先畫成下面這樣:

步驟使用者或系統行為E2E 要觀察的結果
1使用者登入頁面顯示已登入狀態,後續請求使用正確身分
2使用者選擇可購買商品商品資訊與可用狀態能從實際服務取得
3使用者送出訂單頁面顯示訂單已建立,不是只顯示按鈕被點擊
4訂單服務寫入資料並發布事件訂單編號可被查詢,訂單進入可觀察的處理狀態
5付款或庫存流程完成訂單狀態符合這個測試環境的預期
6使用者重新查看訂單前端顯示與後端狀態一致

這條測試不需要斷言每一個 HTTP header,也不需要檢查資料庫的每一個欄位或直接驗證內部事件是否發布。那些是契約或整合測試更適合處理的責任。

E2E 應該斷言的是使用者能辨認的結果,以及足以代表旅程完成的業務狀態。例如:

  • 頁面顯示訂單編號。
  • 訂單狀態從「處理中」進入「已確認」。
  • 重新整理頁面後,結果仍然存在。
  • 失敗時顯示可理解的狀態,而不是畫面停在無限載入。

如果流程包含付款、寄信或第三方物流,測試不必在每次執行時連到真實服務。可以使用 sandbox、測試替身或可控的測試帳號,重點是保留這條旅程真正需要驗證的系統邊界。sandbox 可以驗證與第三方的實際整合;測試替身則適合驗證本系統面對成功、失敗或 timeout 時的行為。使用測試替身不代表已經驗證第三方服務本身,這部分仍需要另外安排 sandbox 或契約層級的檢查。

E2E 的執行入口,以及 deployment smoke

「E2E」不是只有一種執行入口;deployment smoke 則是另一個依目的與執行時機定義的測試類型。團隊可以依照要驗證的邊界,選擇 API 或瀏覽器作為入口。

API-level E2E

API-level E2E 直接透過公開 API 完成完整業務流程。例如登入、建立訂單、查詢訂單狀態。它跳過瀏覽器渲染,因此通常比 browser-level E2E 快,也比較容易在服務層定位問題。

它適合驗證:

  • 多個後端服務串起來後,完整業務流程是否成立。
  • 身分、資料、事件與非同步狀態是否能正確傳遞。
  • 前端以外的使用者端,例如行動 App 或外部整合,是否仍能使用這條流程。

Browser-level E2E

Browser-level E2E 從使用者操作畫面開始,驗證瀏覽器、前端路由、API 呼叫與後端流程的連接。

它適合保護少數最重要的使用者旅程,但不適合把每個 UI 狀態都塞進去。定位元素時,應優先使用使用者看得到的角色、標籤或明確的測試識別,而不是依賴容易變動的 CSS 結構。Playwright 官方文件也建議以面向使用者的定位方式與自動等待、web-first assertions 來降低時序問題。[1]

Deployment smoke test

Deployment smoke test 通常在部署完成後執行,只驗證最小但關鍵的路徑,例如:

  • 首頁可以載入。
  • 使用者可以登入。
  • 一個核心 API 可以成功回應。
  • 一條最重要的讀取或寫入流程可以完成。

它不一定涵蓋完整業務流程,也不一定需要跑完整瀏覽器矩陣。它要快速回答的是:「這個環境目前還活著,而且最基本的入口沒有壞掉。」

這些做法可以同時存在。API-level E2E 驗證後端旅程,browser-level E2E 驗證使用者入口,deployment smoke 驗證部署後的最小可用性。deployment smoke 可以使用 API 或瀏覽器執行,也不一定涵蓋完整業務旅程。與其糾結名稱,每條測試都要清楚說明自己保護哪個風險。

E2E 的資料與環境隔離

很多 E2E 失敗,表面上看起來像產品問題,實際上是測試之間互相污染。

常見情況包括:

  • 多個測試共用同一個帳號,前一個測試改變了後一個測試的資料。
  • 測試依賴資料庫裡某筆長期存在的商品或訂單。
  • 測試依賴固定的訂單編號、時間或排序結果。
  • 測試清理資料失敗,下一次執行才暴露問題。
  • 測試依賴一個沒有明確 readiness check 的服務。

一條 E2E 流程應該盡量具備自己的前置條件與識別方式:

  1. 建立專用測試使用者或測試租戶。
  2. 建立本次測試專用的商品、訂單或其他資料。
  3. 使用唯一的 correlation ID 或測試標籤。
  4. 執行完成後清理可清理的資料,或使用一次性的測試環境。
  5. 不依賴其他測試先跑完,也不依賴測試執行順序。

理想上,測試環境應該接近實際部署拓撲,但仍然要可控。資料可以是真實格式的測試資料,外部副作用則應該明確隔離,例如使用付款 sandbox 或不會寄出真實郵件的通知服務。

非同步流程不要用 sleep 撐過去

訂單、付款、事件與背景工作通常不是同步完成。最容易出現的寫法,是在測試裡加一個固定的 sleep(5000),期待五秒後結果就會出現。

這種寫法同時有兩個問題:

  • 服務很快時,測試白白等待。
  • 服務變慢時,五秒又不夠,測試仍然失敗。

更可靠的做法是等待可觀察的業務條件,例如輪詢訂單狀態直到進入預期狀態,或讓測試框架使用帶有 timeout 的 assertion。Playwright 的 web-first assertions 會在條件未滿足時自動重試,適合用來等待畫面上的最終狀態,而不是等待一段沒有業務意義的時間。[2]

示意上,測試應該表達「等待訂單變成已確認」:

test('customer can complete the order journey', async ({ page }) => {
  // 建立測試資料與登入狀態的方式依專案而定。
  await page.goto('/orders/new');
  await page.getByLabel('商品').selectOption('test-product');
  await page.getByRole('button', { name: '送出訂單' }).click();

  await expect(page.getByTestId('order-status'))
    .toHaveText('已確認', { timeout: 15_000 });
});

這段程式碼是表達測試意圖的示意碼,商品送出訂單order-status 與測試資料建立方式都必須依實際產品調整。它保留在文章裡的價值,是讓讀者看到 E2E assertion 應該對準「使用者可觀察的最終結果」,而不是複製一份看似可以直接執行的專案程式碼。

如果非同步狀態只能透過 API 觀察,也可以讓測試輪詢 API 的業務結果。重點仍然是等待明確條件,並讓 timeout 反映合理的系統上限,而不是任意加長等待時間。

E2E 失敗時,先判斷是不是 flaky

E2E 失敗不代表一定是產品 bug。收到失敗結果時,可以先按來源分類:

失敗類型常見訊號下一步
產品邏輯錯誤相同資料與環境下穩定重現回到負責該行為的服務或前端修正
環境未準備好服務啟動慢、連線拒絕、依賴尚未 ready補 readiness check 或調整環境啟動流程
資料互相污染單獨執行通過,平行或重跑失敗隔離 fixture、帳號與測試資料
定位或時序問題元素偶爾找不到、固定等待後仍不穩改用穩定 locator 與條件式等待
外部依賴不穩第三方 sandbox timeout 或限流使用可控替身,或把外部依賴風險獨立監控

每次失敗至少應保留足夠的診斷資料:測試步驟、瀏覽器截圖、console 與 network log、請求 correlation ID,以及服務端 log。Playwright 的 Trace Viewer 可以在測試失敗後重看操作步驟、DOM、network 與 assertion 狀態;官方文件也提供在第一次 retry 時保留 trace 的設定方式。[3]

Retry 可以幫助我們判斷偶發性,但不能拿來掩蓋不穩定。若一個測試常常第一次失敗、第二次成功,這本身就是系統或測試設計需要處理的訊號。

AI 可以產生骨架,但人要決定通過條件

AI 很適合協助建立 E2E 的初稿,尤其是已有使用者故事、驗收條件與 API 文件的專案。它可以協助:

  • 從使用者故事整理出可能的關鍵旅程。
  • 把旅程轉成測試步驟與 fixture 骨架。
  • 根據頁面結構提出 locator 候選。
  • 產生失敗摘要、trace 與 log 的初步分類。
  • 找出目前只有低層測試、但缺少完整旅程保護的功能。

但 AI 不應該自行決定:

  • 哪些旅程是產品真正不能失敗的核心路徑。
  • 非同步流程在什麼狀態才算完成。
  • 付款、通知或資料建立等副作用可以做到什麼程度。
  • 測試失敗時要阻擋部署,還是只發出警告。
  • 某個測試只是 smoke,還是足以代表一條完整驗收條件。

這些決定需要產品、開發、測試與維運共同確認。AI 可以把測試寫得很快,但「通過」的定義仍然是團隊的責任。

E2E 應該怎麼放進 CI

很多團隊的實際做法是:CI 先跑完單元、整合與契約測試,部署到測試環境後,再由腳本執行 E2E。這是合理的部署後 E2E 模式,因為它驗證的是已經組合好的服務、設定、網路、佇列與外部依賴。

它的代價是回饋比較晚。問題可能要等到部署完成後才會被發現,甚至已經進入某個環境。因此,部署後 E2E 適合驗證實際部署拓撲;部署前則可以保留少量、快速的核心旅程,提早阻擋明顯回歸。

E2E 的執行策略可以依回饋速度與風險分層:

時機建議執行內容目的
Pull Request單元、整合、契約測試,加上少量最關鍵的 API 或 browser journey快速阻擋核心流程回歸
合併後/部署到測試環境較完整的服務旅程與主要瀏覽器驗證整合環境中的跨邊界行為
Nightly 或排程更多旅程、瀏覽器版本與資料組合找出較低頻但重要的回歸
部署到正式環境後最小 deployment smoke確認新環境真的可用
發布前與本次變更相關的高風險旅程提供發布決策所需的信心

如果團隊目前的流程是 CI 完成、部署後再執行 E2E,不需要為了追求新的分層方式而全部重寫。比較重要的是確認腳本真的驗證了完整業務結果,而不是只檢查首頁或一個 API 有回應。若部署後 E2E 失敗,管線也要事先定義是阻擋後續發布、觸發 rollback,還是只發出警告。

不要把所有 E2E 都放進 Pull Request,也不要把所有 E2E 都延後到發布後。PR 上應該留下能快速回饋的核心路徑;較慢、較廣的矩陣可以排程執行,但失敗結果仍然要有明確的負責人與處理期限。

E2E 的執行結果最好能和部署版本、測試資料、環境與 commit 綁在一起。否則看到「下單流程失敗」時,團隊還要先猜是哪個版本、哪個環境或哪批資料造成的。

結語:E2E 的價值在於守住少數不能失敗的事

E2E 測試不是信心的總量,也不是測試案例數量的競賽。

它真正要做的是,守住幾條使用者最重要、跨越最多系統邊界的旅程,確認這些旅程在真實部署拓撲中仍然走得完。細節交給單元、整合與契約測試;環境健康交給 smoke test;產品是否值得交付,則回到驗收條件與人的判斷。

在 AI 加速產生程式碼的時代,E2E 的價值反而更清楚。它要確認系統仍然完成了使用者真正要完成的事;逐行程式碼是否被操作過,並不是它要回答的問題。

下一篇會從測試往驗收再往前走:當 AI 能參與需求拆解、實作與測試產生時,團隊要怎麼把「可以交付」定義成可檢查、可追蹤的驗收流程?

參考資料