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。
也就是說,還沒有任何自動化測試確認:
這些事情都不是純狀態函式能完全代表的。
所以今天補 browser test,照 Day 19 結尾列的三條路走:壞檔、取消、確定。
但我一開始先提醒自己一件事:
Browser test 不是拿來偷看內部 state 的。
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 的測試刻意只驗這些東西:
這些才是使用者真的會遇到的結果。
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,變更會突然變大:
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。什麼時候該換,後面再說。
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 還是原來那份。
第二條測試是合法檔案,但使用者取消:
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 如果壞了,使用者最怕的事情就會發生:只是選到檔案,工作就被蓋掉。
第三條測試是合法檔案,而且使用者確認:
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、檔案輸入這些外部條件。
到今天為止,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 完成。
明天見。