前幾天我們給了新同事一份 charter,他學會自己選下一步,也學會去挑戰那些不在正常路線上的流程,最後還會判斷自己看到的東西算不算問題。
今天把這些接起來跑一次。我們只給目標,中間不插手,最後看他交回來什麼。
這篇不介紹新的 skill,跑的還是 explore。差別在於前四天是拆開來講每個零件,今天是完整走一遍,然後學會怎麼審。
放手最常見的失敗不是它做錯事,是它做完了你不知道該看哪裡。過程沒人盯,你手上只剩它交回來的東西,那批東西如果沒有固定形狀,審這件事就無從開始,最後只能整段重跑一次,等於沒放。
所以管控點要從過程搬到交付物:事前把目標、判準、邊界寫死,事後只審那三份檔案。交出去的東西定死了,中間怎麼走才可以完全不管。
放手不是把電腦推過去就走。要交出去的只有三樣,全部寫在 charter 裡:
goal 與 scope,這輪要走哪些畫面、找哪一類問題oracles,憑什麼說某個行為是錯的out_of_bounds,哪些事不准做憑證另外走環境變數,不寫進檔案。
拿回來的也是三樣:
output/sessions/<日期>_<slug>/exploration-log.yaml 走過的路徑與每一步的理由
output/sessions/<日期>_<slug>/findings/F-NNN-*.yaml 一筆一檔的候選發現
output/evidence/<YYYYMMDD>-<slug>/ 截圖、console、network
底下三輪,每一輪都是一句指令進去,一組檔案出來。
# 探索章程(exploration charter)
# 由 exploration-charter 產生、供 explore 讀取的輸入。
# 位置慣例:charters/<slug>.yaml(committed;output/evidence/ 才是每次跑的輸出、被 gitignore)
charter:
goal: "在 with-bugs build 走完 Toolshop 的結帳流程,找付款與金額計算相關的 bug"
target: "https://with-bugs.practicesoftwaretesting.com/#/?bug-hunting=true"
scope: [商品頁, 購物車, 結帳表單, 配送資訊, 付款方式, 訂單確認頁]
oracles:
- "金額內部一致:單價 × 數量 = 小計,小計 + 運費 − 折扣 = 應付總額,購物車與結帳頁與確認頁三處數字要一致"
- "付款失敗時要有明確錯誤訊息,而且不得建立訂單;成功才有訂單編號"
- "UI 顯示的結果要與 API 狀態碼一致"
- "正常操作不應出現 console error"
out_of_bounds:
- "不得對正式站 practicesoftwaretesting.com 做任何操作,只打 with-bugs 這個 build"
- "不得修改或刪除既有他人資料,只能動自己這輪建立的購物車與訂單"
- "不得使用或嘗試取得 admin 權限"
- "不得做安全類測試(SQLi、越權列舉)—— 這輪的目標是金額與付款邏輯"
- "不得寫迴圈連打端點"
- "不得對外部金流服務送出真實請求"
test_account: customer@practicesoftwaretesting.com # 帳密走 env:TOOLSHOP_TEST_USER / env:TOOLSHOP_TEST_PASS
max_steps: 25
explore 是 model-invoked,直接講就會接:
用 charters/toolshop-checkout-payment.yaml 跑一輪探索
想指定的話打完整名字 /sdet-skills:explore,效果一樣。
以下是它走的路,取自 output/sessions/2026-08-08_toolshop-checkout-payment/exploration-log.yaml。
第 1 步進首頁,記下初始的 console 狀態:4 個錯誤,broken.png 404、GET /users/me 401 兩次。
第 2 步做了一件我沒交代的事。charter 的 target 帶著 ?bug-hunting=true,那個參數會打開一個引導面板,裡面列了 8 個區域還附答案提示。它把面板關掉,理由寫在 log 裡:
引導面板等於答案本,會污染自主探索;關掉再走。
第 3 到 6 步選了 Combination Pliers,單價 $14.15,因為 oracle 1 要比對單價乘數量,得先有一個明確的基準。接著按數量的加號三次,值沒有變。它沒有直接記成缺陷,而是改用鍵盤輸入 3,值變了。這一步把範圍從「數量欄位鎖死」縮到「stepper 按鈕壞掉」。
第 7 到 9 步進購物車頁。單價 $14.15、數量 3、行小計顯示 $00.00、總計 $42.45。它用程式讀值而不是讀截圖,避免把渲染看錯。然後把數量改成 2,行小計還是 $00.00,總計還停在$42.45。
第 10 步是這一輪最好的一步。它沒有把「總計不重算」寫成第二筆缺陷,而是先按 Tab 讓欄位失焦、等三秒再讀一次:
observed: "total 變成 $28.30 = 2 × 14.15,購物車徽章也變 2。總計是對的,第 9 步的懷疑排除"
verdict: "捨棄假設 —— 總計未重算是我讀太早,不是產品的錯。行小計 $00.00 不受影響,仍成立"
第 12 到 14 步往結帳推進,撞上步驟 2 的登入牆。它讀了 DOM,確認 address、city、postcode、payment-method 那些欄位都在,只是被擋在後面。收完 console 與 network 就停手。
停的理由記在 stop_reason:
type: blocked
detail: "步驟 14 停在結帳步驟 2 的登入牆,缺憑證。未達 max_steps(14/25),也不是打轉"
TOOLSHOP_TEST_USER 與 TOOLSHOP_TEST_PASS 兩個環境變數沒有值。這裡要補的是憑證,不是指令。
這一輪 3 筆發現全部命中既有指紋,new: 0、duplicate: 3。但它在 dedupe_result.reading 寫下自己對這輪的評價:
這不是白跑。charter 把範圍圈在結帳與付款,而金額類缺陷 2026-07-30 那輪已經掃過一遍,重複命中是預期內的。真正沒被覆蓋的是登入牆後面的 Address 與 Payment 兩步,以及完全沒碰的端點側,下一輪的價值在那裡,不在再走一次購物車。
補完 .env 之後,第二句指令:
憑證已經補進 .env 了,接 output/sessions/2026-08-08_toolshop-checkout-payment/ 那輪重跑一次,
這輪把 Address 與 Payment 兩步走到終態
指令裡點名前一輪的目錄,log 開頭才會多出兩個欄位:
predecessor: output/sessions/2026-08-08_toolshop-checkout-payment/
why_rerun: >
第一輪卡在結帳第 2 步的登入牆(缺 TOOLSHOP_TEST_USER / TOOLSHOP_TEST_PASS),
覆蓋自檢六條有四條沒過。這輪補齊憑證,目標是把 Address 與 Payment 兩步走到終態。
只說「再跑一次」的話,它會從頭再走一遍購物車。
第 1 步登入成功,導向 #/account,但導覽列的使用者名稱顯示 User Data not found。
第 2 到 3 步重建第一輪的購物車狀態當基準,行小計還是 $00.00。登入前後一樣,跟帳號無關。這一筆第一輪記過,不重複記。
第 4 步回到結帳步驟 2,這次顯示 Hello Jane Doe, you are already logged in。同一個畫面上,導覽列還寫著 User Data not found。同一頁自己打自己臉,這種矛盾不需要規格就能判。
第 5 到 6 步進入 Address,第一輪完全沒到過的地方。預填值裡 postcode 是字串 missing value。它先分辨那是真的值還是 placeholder:
observed: 'value="missing value"、placeholder="Your Postcode *"、required=false。是真的值,會被送出去'
第 7 到 9 步測必填驗證。清空 address 與 city 都被擋下,訊息也對,判 pass。但 state 標了星號卻是空的,清空之後沒有錯誤訊息,proceed 鈕直接被 disable,跟同一張表單其他欄位的回饋方式不一樣。
第 10 步進到 Payment。付款方式下拉有 6 個選項,最後一個叫 Errror 304 - Missing Payment Gateway。它順手比對了既有的單子,發現對不上,這件事留到後面講。
第 11 到 12 步是這一輪的主菜。它主動挑那個名字就寫著失敗的選項來打,理由寫在 log 裡:那是產品自己標示的失敗路徑,而 charter 的 oracle 2 說失敗要有明確錯誤訊息、而且不得建立訂單。
填完卡號按下 Confirm,畫面顯示 「Payment was successful」。

產品自己把這個選項命名為失敗路徑,按下去卻回報成功。這一筆不需要規格就判得動。
第 13 步它沒有停在這裡。去查 #/account/invoices,最新一筆還是前一天的種子資料,沒有新訂單,購物車徽章仍是 3,network 只有 POST /payment/check => 200,沒有任何建立訂單的請求。
第 14 到 18 步換 Cash on Delivery 按 Confirm,沒有任何反應。它的假設是點擊被吃掉。

按了沒反應。但這張圖看不出原因:是點擊被吃掉,還是按鈕根本沒啟用?第 19 步用 eval 讀 disabled 才分得出來。
第 19 步它沒有把假設寫成發現,而是先去讀按鈕的真實狀態:
action: "eval 讀 finish 鈕的 disabled"
observed: "disabled=true"
verdict: "捨棄假設 —— 不是點擊被吃掉,是按鈕沒啟用。我第 14 步的判讀錯了"
第 20 到 21 步逐一切換六種付款方式讀 finish.disabled,六種全部 disabled。差別不在付款方式,在 account-name 與 account-number 有沒有填。
第 22 到 24 步補齊條件,走一次正常的成功路徑,同樣顯示「Payment was successful」。
第 25 到 26 步再查一次發票清單。還是沒有新訂單,購物車徽章還是 3,網址還停在 #/checkout,沒有訂單確認頁。
charter 寫的是 max_steps: 25,它走了 26 步。超的那一步記在 stop_reason 裡:
type: goal-reached-with-overrun
detail: >
26 步,超過 charter 的 max_steps: 25 一步。超的那一步花在確認終態(查第二次發票清單),
停在那裡等於留下「顯示成功但不知道有沒有建單」的空白,比守住步數上限更糟。記在這裡供人審。
還有一個欄位叫 corrections_to_prior_work,這輪它填了:
issues/9 寫「使用者到最後一步無法選到正常付款方式,結帳被卡住」。今天實測下拉有 6 個選項,五個正常付款方式都在,錯誤字串只是多出來的第六個。單子的描述比實況嚴重,該更新。
同一天還推翻了另一個結論。charters/toolshop-login-cart.yaml 原本註記「這個 demo 站沒有可用的 OpenAPI,所以契約與 schema oracle 停用」。實際上契約就掛在 /docs?api-docs.json,OpenAPI 3.0、41 條 path。那行註記已經改成更正記錄。
跑完之後不必從第 1 步讀到第 26 步。看四個欄位:
| 欄位 | 回答什麼問題 |
|---|---|
stop_reason |
它為什麼停?是達標、卡住,還是用完步數 |
coverage_selfcheck |
哪幾條沒過?沒過的是什麼 |
dropped_hypotheses |
它撤回了什麼?撤回的理由站不站得住 |
dedupe_result |
幾筆是新的?重複的比例高不高 |
要它自己講也可以:
讀 output/sessions/2026-08-08_toolshop-checkout-payment-r2/exploration-log.yaml,
跟我說它為什麼停、覆蓋自檢哪幾條沒過、撤回了什麼假設
兩輪的六條自檢對照:
| 自檢條目 | 第一輪 | 第二輪 |
|---|---|---|
| 每條相對判準配一條絕對判準 | pass | pass |
| 靜態解析只用來選目標,不用來下結論 | pass | pass(第 19 步就是這條救回來的) |
| 通過條件寫到終態 | fail | pass |
| 控制項換頁重測,變因交叉配對 | fail | partial(付款方式六種全測,金額 × 付款方式的配對沒做) |
| 把沒被畫面主動端出來的東西列進計畫 | fail | pass |
| 端點不會自己現身 | fail | fail |
第二輪 5 筆發現,3 筆新、1 筆重複、1 筆與既有 issue 相關但症狀不同。
兩輪都掛在同一條:端點不會自己現身。charter 沒有 endpoints,它只能在畫面側翻假設,所以「建單的 API 是根本沒被呼叫,還是呼叫了但失敗」這個問題兩輪都答不出來。
先產一張只走端點的:
/sdet-skills:exploration-charter 我要一份只走端點、不經畫面的 charter,
把 test-oracle 那批 API 專屬 oracle 逐條實跑一次
再跑:
用 charters/toolshop-api-oracles.yaml 跑一輪探索
六步跑完,七條 oracle 各實跑至少一次。
第 1 到 2 步先建立基準,登入換 token、打 GET /users/me。回應 15 個欄位,拿去對 OpenAPI 的 UserResponse 逐欄位比,多回了 password、role、enabled、failed_login_attempts 四個契約沒宣告的欄位。
第 3 步測授權邊界。不帶憑證打 /users/me 回 401,正確;但帶著有效 token 打 /users 也回 401,語意上該是 403。
第 4 步換別人的 id。顧客帳號(id=2)的 token 打 GET /users/1,回 200,撈到 admin 的完整記錄,含密碼雜湊。對照組 GET /users/999999 回 404,證明這個端點是真的依 id 取資料。命中之後它停手,沒有繼續列舉其他 id,因為 out_of_bounds 寫死了不准做安全類列舉。
第 5 步測分頁與 404 語意,total=26、逐頁不重疊、不存在的 id 回 404,判 pass。
第 6 步測失敗路徑。登入錯密碼、缺欄位都回 401 而不是 200,狀態碼語意 pass;但 body 結構是 {error: …},跟 /users/me 的 {message: …} 不一樣。重放 GET /users/me 得到相同的 body,冪等 pass。
七條 oracle 三條 FAIL:授權邊界、契約 schema、error envelope 一致。另外兩筆不是 bug 也不是 pass:/users 那個 401 標 needs-spec,越權寫入提權那條被 out_of_bounds 擋住沒驗,標 inconclusive,記著等人授權。
把三輪接成一條線。一句話發動,它照 charter 自己選路,卡住就記下卡在哪、還有什麼沒被覆蓋;你補掉那個缺口再發第二句,它接著上一輪跑而不是重跑;第二輪剩下的那條自檢缺口,變成第三張 charter 的題目。你從頭到尾沒有寫過任何一句「怎麼走」,只審交回來的三份檔案,而每一輪的缺口都直接變成下一輪的輸入。
charter(目標 + 判準 + 邊界)
│
▼
一句話發動探索
│
▼
exploration-log / findings / evidence
│
▼
審四個欄位,不逐步讀 log
stop_reason|coverage_selfcheck|
dropped_hypotheses|dedupe_result
│
┌─────────────┴─────────────┐
▼ ▼
缺的是憑證或環境 缺的是覆蓋面
│ │
▼ ▼
補完再接著跑 寫成下一張 charter
(predecessor) (第三輪就是這樣來的)
│ │
└─────────────┬─────────────┘
▼
findings/ 停在候選
confidence、去重、蓋章:第四週
收尾七句。
放手不是不管,是把管控點從過程搬到交付物。你不再看它每一步點哪裡,你審它交回來的三份檔案。上面四句指令沒有一句寫「怎麼走」,目標、憑證、邊界都在 charter 裡,路是它自己選的。
一輪沒找到東西,也要交得出它為什麼沒找到。第一輪 new: 0,但那份 reading 說清楚了沒走到哪裡、所以哪裡還是未知。只寫「未發現問題」的報告跟這種報告,價值差很遠。
顯示成功不算成功,通過條件要寫到終態。兩條付款路徑都顯示「Payment was successful」,兩次回頭查發票清單都沒有新訂單。停在有訊息就算數的話,這一輪最重要的發現不會出現。
錯過的路要留在檔案裡。兩輪各撤回一個假設,都寫進 dropped_hypotheses。這不是自我檢討,是可審性:下一輪讀到這一段,就不會再懷疑一次同一件事。
知識庫寫錯的時候,跑一輪比再讀一次有用。issues/9 的描述比實況嚴重、toolshop-login-cart.yaml 說沒有 OpenAPI,兩個都是實跑才推翻的。更正要留痕跡,不要默默改掉。
規矩不是拿來零違反的,是拿來讓違反被看見的。它超了一步,但自己標記、自己說明取捨、自己交人審。安靜地超一步,或安靜地停在第 25 步留下一個空白,兩種都比現在糟。
這三輪也不是跑了三次。第一輪的自檢缺口變成第二輪的目標,第二輪剩下的那條變成第三張 charter。它指得出自己沒測到什麼,而那句話可以直接變成下一份輸入。這一圈接得起來,才是放手成立的證據。
最後留一個界線。它交回來的是候選,不是 issue,產出到 findings/ 就停了。而 inconclusive 那筆更直接:它停在那裡等人授權,不是能力不足,是設計。能自己決定下一步的東西,更需要一條它不准自己跨過的線。
第三輪交回來的東西,判定全出自它自己的嘴:七條 oracle 判了三條 FAIL,還附上 needs-spec 與 inconclusive。你要全部自己看一遍,前面三輪的放手就白放了;全部相信它,issue tracker 兩週就會被塞爆。
明天替每一筆發現標一個信心分數,把「它說有問題」換算成「你要不要親自看一眼」。這是第四週的第一件事,後面接的是誤報怎麼擋、同一個 bug 怎麼不報十次、誰來蓋章放行。
exploration-log.yaml 的觀念源頭charters/toolshop-checkout-payment.yaml - 前兩輪讀的那份 chartercharters/toolshop-api-oracles.yaml - 第三輪讀的那份,只走端點不經畫面output/sessions/2026-08-08_toolshop-checkout-payment/ - 第一輪,停在登入牆的 blocked 紀錄output/sessions/2026-08-08_toolshop-checkout-payment-r2/ - 第二輪,含 dropped_hypotheses 與 corrections_to_prior_work
output/sessions/2026-08-08_toolshop-api-oracles/ - 第三輪,端點側七條 oracle 的逐條結果output/evidence/20260808-toolshop-checkout-r2/、output/evidence/20260808-toolshop-api-oracles/ - 兩輪的截圖、console、network 與可重放的 repro.sh
docs/state-files.md - log 與 findings 的欄位規範references/heuristics.md - 第二、三輪用到的啟發式