iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0

Day 18 我說還沒有 browser test。

Day 19 我又說 browser test 一樣先放著。

今天 Day 20,終於輪到它了。

不過今天補的不是一條很大的 end-to-end 測試,也不是把整個 ClarifyBuild 從 Idea Entry 一路點到 Prompt Preview。

今天的目標比較窄:

browser-level coverage for draft import confirmation

也就是把 Day 19 做完的 import confirmation flow,放進真正的瀏覽器裡跑一次。

因為 Day 19 雖然已經把匯入流程拆成:

prepare
↓
confirm
↓
apply

也補了 app-flow 層的測試。

但那些測試沒有真的執行 src/main.js。

也就是說,還沒有任何自動化測試確認:

  • 使用者選檔案時,file input 的事件真的會接到 import action
  • 壞檔真的不會跳 confirmation
  • 好檔真的會跳 native confirm
  • Cancel 真的會保留畫面上的 Spec / Prompt
  • Confirm 之後,畫面真的回到 Specification Setup
  • 重新產生的 Spec 真的來自 imported draft

這些事情都不是純狀態函式能完全代表的。

所以今天補 browser test,照 Day 19 結尾列的三條路走:壞檔、取消、確定。

但我一開始先提醒自己一件事:

Browser test 不是拿來偷看內部 state 的。


為什麼今天不直接測 generated output clearing

Day 19 做 import confirmation 時,有一條很重要的產品規則:

匯入 draft 之後,舊的 Project Specification 和 AI Coding Prompt 不能繼續沿用。

在程式裡,這件事有兩層在守。

第一層是 storage 還原。Day 17 開始,draft 匯出檔本來就不帶 generated output。

第二層是 Day 19 的 applyPreparedDraftImport()。它在套用 imported draft 前,會再清一次 generated output。

守的是這兩個欄位:

generatedSpec
generatedPrompt

如果只看 internal state,這很好測。

Day 19 也已經測了。

但今天要做的是 browser-level coverage。

使用者在瀏覽器裡看不到 state.generatedSpec === ""。

使用者真正看得到的是:

匯入成功後,App 回到 Specification Setup
↓
使用者需要重新按一次「產生 Project Specification」
↓
重新產生出來的 Spec 內容,應該反映 imported draft

state 是 main.js 裡的 module 變數,從瀏覽器外面讀不到。

要讀,就得先改 production code,把它掛到 window 上。那樣測到的就成了一個為了測試改過的 App。

所以 Day 20 的測試刻意只驗這些東西:

  • stage 對應的畫面
  • confirmation dialog 的文字
  • toast 文字
  • Prompt textarea 裡的可見內容
  • Spec preview 裡的可見內容

這些才是使用者真的會遇到的結果。


先決定要不要裝 Playwright

Day 19 結尾我說,browser test 繞不過 dependency 的決定。

原本最直覺的選項,是裝 Playwright。

Playwright 很適合做 browser test。

如果 ClarifyBuild 已經有前端 build tool、測試環境、CI 設定,那我大概會直接用它。

但目前這個專案刻意很小:

npm run dev  -> python3 -m http.server 5173
npm test     -> node --test

沒有 Vite。

沒有 bundler。

沒有 jsdom。

沒有任何 npm dependency。

在這個狀態下,Day 20 如果新增 Playwright,變更會突然變大:

  • 新增 npm dependency
  • 產生 lockfile
  • 決定要用 Playwright 自己的 test runner,還是接進 node --test

這些都不是錯。

只是今天真正要驗證的範圍很小。

我需要的是:

打開 index.html
↓
按幾個按鈕
↓
選一個 JSON 檔
↓
處理 native confirm
↓
讀畫面文字

所以最後我選了比較小的方案:

Node 內建 node:test
Node 內建 http server
本機 Google Chrome headless
Chrome DevTools Protocol
Node 內建 WebSocket

也就是說,沒有新增任何 npm dependency。

jsdom 我也沒用。它只模擬 DOM,今天要驗證的是「真的在瀏覽器裡跑一次」。原生 confirm、真實 file input、index.html 載入 src/main.js 這些邊界,用 jsdom 反而會離 Day 19 留下的問題太遠。

Day 20 的切片,先用最小工具把風險鎖住就夠了。

我省下的是 dependency,付出的是自己寫的 harness。什麼時候該換,後面再說。


測試真的有跑到 main.js

Day 18 和 Day 19 的測試都在 app-flow 層:給它一份 state,看函式回傳什麼。

好處是快、穩、容易定位。

但「使用者做了動作 → main.js 的 action → helper → render」這一段,它走不到。

Day 20 新增的檔案是 test/browserFlow.test.js。

它會啟動一個很小的 static server,然後用 headless Chrome 打開 index.html。

index.html 裡載入的還是 ./src/main.js。

這次測試從瀏覽器的角度進去:

載入 App
↓
localStorage 放入一份完整 draft
↓
讓 App 從真實 storage restore
↓
按「產生 Project Specification」
↓
按「產生 AI Coding Prompt」
↓
透過 file input 選擇 draft JSON

這樣就會真的走到 renderApp() 的事件綁定,也會走到 src/main.js 裡的 import action。

這是 Day 18/19 沒有覆蓋到的地方。


第一條:壞檔不能跳確認

第一條測試是壞檔:

browser import rejects invalid draft without confirmation and preserves current output

流程是:

先載入目前 draft
↓
產生 Spec
↓
產生 Prompt
↓
選擇一個壞掉的 JSON 檔

測試檔內容故意是:

{bad json

這種檔案連 JSON 都不是。

這時候 browser test 要確認三件事。

第一,沒有跳 confirmation dialog。

因為檔案不合法,就不應該問使用者要不要覆蓋目前 draft。

如果壞檔也跳確認,使用者會以為這是一份可以套用的 draft。

第二,畫面上出現錯誤 toast:

Draft 匯入失敗:檔案格式不符合 ClarifyBuild draft。

第三,目前 Prompt 還在,而且內容仍然反映目前 draft。

這裡看的是使用者眼前的輸出:壞檔進來之後,Prompt 還是原來那份。


第二條:好檔 Cancel,要保留目前 Spec / Prompt

第二條測試是合法檔案,但使用者取消:

browser import cancel keeps the current draft, spec, and prompt

這條先建立兩份不同的 draft。

目前 draft 裡有:

目前 draft idea
搜尋目前論文

要匯入的 draft 裡有:

從檔案匯入的 draft
整理匯入論文

這樣測試才看得出來畫面到底保留了哪一份。

流程是:

目前 draft 產生 Spec
↓
目前 draft 產生 Prompt
↓
選擇合法 imported draft
↓
看到 native confirm
↓
按 Cancel

測試先確認 confirmation 視窗真的跳出來,文字就是 Day 19 加的那句:

匯入會取代目前 draft,且需要重新產生輸出

取消之後,畫面應該出現:

Draft 匯入已取消,已保留目前 draft。

然後 Prompt textarea 裡應該還有目前 draft 的內容。

再按「回到 Spec」,Spec preview 裡也應該還是目前 draft 的內容。

同時,畫面不應該出現 imported draft 的內容。

這條測試很重要。

因為 Day 19 的核心就是:

合法 draft 也不能在使用者確認前進 App。

Cancel path 如果壞了,使用者最怕的事情就會發生:只是選到檔案,工作就被蓋掉。


第三條:好檔 Confirm,要用 imported draft 重新產生 Spec

第三條測試是合法檔案,而且使用者確認:

browser import confirm returns to setup and regenerated spec uses imported draft

流程前半段和第二條一樣:

目前 draft 產生 Spec / Prompt
↓
選擇合法 imported draft
↓
看到 native confirm

差別是這次按 Confirm。

確認後,畫面上應該出現:

Draft 已匯入,請重新產生輸出。

而且 App 應該回到 Specification Setup。

這裡測 stage,看的是畫面:「產生 Project Specification」按鈕又出現了,進度列停在 Specify。

接著測試按下「產生 Project Specification」。

重新產生後,Spec preview 裡應該出現 imported draft 的內容:

從檔案匯入的 draft
整理匯入論文

也不應該再出現原本 current draft 的內容:

目前 draft idea
搜尋目前論文

這條測試問的是:

使用者重新產生後,看到的是不是 imported draft 的 Spec?

它沒有問 generatedSpec 是不是被清空。

這件事在今天的畫面上看不出來:清空也好、沒清空也好,使用者都得先重新按一次「產生 Project Specification」才進得了預覽頁。

清空本身,今天之前就有測試在守:storage 那一層,和 applyPreparedDraftImport() 那一層。

main.js 裡那些「改了來源資料就清空」的呼叫點,到現在還沒有任何測試。


這次測試的代價

今天沒有新增 npm dependency。

但這不是零代價。

自己用 Chrome DevTools Protocol 寫 browser harness,有幾個限制。

第一,它假設本機有 Google Chrome,而且路徑是:

/Applications/Google Chrome.app/Contents/MacOS/Google Chrome

找不到 Chrome 的時候,三條測試會直接失敗,不會跳過。

它用的 WebSocket 是 Node 內建的,要 Node 22 以上才預設有。今天執行測試的 Node 是 v24,但 package.json 的 engines 還寫 >=20,這個我還沒改。

第二,它只適合目前這種小範圍測試。

測試檔 522 行,三個情境本身大約 90 行。自己寫的 static server、Chrome 啟動和 CDP client,佔了約 240 行。

npm test 也多了大約 1.5 到 2 秒:三條 browser test 各開一次 Chrome,其餘 24 條加起來不到 0.1 秒。

如果之後 browser tests 變多,或要跨瀏覽器、截圖、trace、CI 整合,自己維護 CDP helper 就不划算了。

第三,它只跑在本機 Chrome。

Safari、Firefox、行動瀏覽器都沒有被涵蓋。

它也沒有真的用滑鼠按。按鈕是 element.click(),檔案是直接塞進隱藏的 file input,「按匯入 Draft → 開檔案選擇器」這一段沒有走到。

所以 Day 20 的結論不是:

我們不需要 Playwright。

而是:

今天先不需要 Playwright。

這兩句差很多。


測試結果

Day 19 結束時:

tests 24
pass 24
fail 0

Day 20 新增三條 browser test。

跑單一 browser test 檔案:

node --test test/browserFlow.test.js

結果:

tests 3
pass 3
fail 0

跑完整測試:

npm test

結果:

tests 27
pass 27
fail 0

有一個小插曲。

我跑測試的環境會限制本機網路權限。第一次跑 npm test 時,本地 server 不能 bind 到 127.0.0.1,browser test 被擋住。

放寬權限後重跑,測試通過。

這也提醒我一件事:browser test 一旦進來,測試環境就不再只是純 JavaScript runtime。它開始需要瀏覽器、port、檔案輸入這些外部條件。


Day 20 做完後的狀態

到今天為止,ClarifyBuild 的測試層次變成這樣:

domain / engine tests
↓
storage lifecycle tests
↓
app-flow state tests
↓
browser-level import confirmation tests

這不是一開始就設計好的完美金字塔。

它比較像是每天跟著風險往上補。

Day 15 守 MVP 資料流。

Day 16 守 draft storage。

Day 17 守 export/import lifecycle。

Day 18 守按鈕背後的 app flow。

Day 19 守 import confirmation boundary。

Day 20 終於用瀏覽器守住使用者真的會遇到的 import confirmation 行為。

今天沒有改產品。

沒有改 UI。

沒有碰 Day 10~13 定下來的產品規則。

只是把 Day 19 的產品承諾,放到真正的瀏覽器裡驗證一次。

對這種小工具來說,這一步很重要。

因為使用者不會呼叫 prepareDraftImport()。

使用者只會選檔案、按取消、按確定,然後看畫面是不是還是他以為的那份 draft。

Day 20 測的就是這件事。

Day 19 結尾我留了一個問題:要不要把 window.confirm() 換成 App 內 modal。

現在測試有了,我還沒決定要不要換。

真的要換的時候,這三條路可以直接當 regression target。換掉的會是「怎麼按確定、按取消」,畫面上該看到什麼,大部分不用重寫。

Day 21 如果還要加 browser test,我會先把今天的 browser harness 抽乾淨,再補 case。

Day 20 完成。

明天見。


上一篇
Day 19|匯入的 Draft,在按下確定之前不能先進 App
系列文
AI 寫不好,可能是我沒說清楚:30 天打造 ClarifyBuild,讓 Vibe Coding 從需求開始 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言