visionA/local-tool/.autoflow/06-testing/m9-5-validation-plan.md
jim800121chen 8c27da7cca test(local-tool): M9-5 — three-platform validation plan + e2e scripts + MJ3 fix
A 階段最後 milestone、出測試計畫 + 自動化腳本 + 三平台人工 checklist、使用者下週手動跑實機驗證。

Testing artifacts (8 檔、2630 行):
- .autoflow/06-testing/m9-5-validation-plan.md: 656 行(4 情境 × 3 平台 × 2 chip = 24 combo)
- 4 e2e specs (vitest + RTL + mock WS / mock fetch):
  - firmware-upgrade-happy-path.spec.ts (357 / 4 cases)
  - firmware-upgrade-error-recovery.spec.ts (356 / 4 cases + 8 reason it.each)
  - firmware-r-fw-11-modal-not-closable.spec.ts (303 / 6 cases)
  - wails-onbeforeclose-firmware-active.spec.ts (217 / 9 cases、含 5 todo 占位 M9-12)
- 3 manual checklists: macOS 264 / Windows 234 / Linux 243 行

設計取捨:
- 不引入 Playwright/Cypress (visionA-local frontend 沒裝、屬 architect 決策)、走 vitest + mock
- E2E 腳本放 06-testing/scripts/ 作 spec doc + 可選實作參考
- 實機驗證走人工 checklist (dongle 插拔 / kill process / SIGTERM 等需要實體互動)

MJ3 修復 (M9-4 reviewer round 1 留的 follow-up):
- server/internal/api/ws/firmware_ws_test.go: +16/-8
- "type": "firmware:progress" → "firmware_progress" (對齊 firmwareProgressMessage.Type)
- "phase" → "stage" (對齊 TDD §4.2 + FirmwareProgress.Stage)
- 不動 production code、只 test schema 對齊

執行建議 (給你下週):
- Day 1 P0: macOS+Win+Linux × KL520+KL720 happy path (~3h)
- Day 2 P1: R-FW-11 + disconnect_during_op + upgrade_mid_failed + 失敗注入 (4h)
- Day 3 P2: SIGTERM 延遲關閉 + Wails OnBeforeClose force-quit modal (2-3h)

測試:
- go test ./... -race 全綠 (server / wails / frontend 60 tests)
- MJ3 修復不破壞既有測試

A 階段開發 6/7 完成 (M9 文件 + M9-1 ~ M9-4.5)、剩 M9-5 實機驗證 (你下週跑)、跑完依結果決定 A 階段交付或派 sub-agent 修。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 15:34:17 +08:00

657 lines
29 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# M9-5 三平台實機驗證計畫 — Kneron Dongle FW 升降版
> 對應 PRD`.autoflow/02-prd/features/feature-firmware-management.md`AC-FW-1.1 ~ AC-FW-1.9
> 對應 TDD`.autoflow/04-architecture/v2/firmware-management.md` §11測試策略
> 對應 Design`.autoflow/03-design/v2/firmware-management.md` §7失敗復原 UX 8 種)
> 階段:**A 階段最後 milestone**M9-5、1 人天、Testing 出 plan + 自動化腳本、使用者下週實機驗證)
> 版本v1.02026-05-25
> 作者Testing Agent
---
## 0. 文件結構導覽
| 章節 | 主題 |
|------|------|
| §1 | 範圍與驗證目標 |
| §2 | 測試環境前置(硬體 + 軟體) |
| §3 | 三平台 × 兩 chip × 四情境 × 一致驗證表24 combo |
| §4 | 功能性驗證Functional — happy path、4 情境 |
| §5 | 可靠性驗證Reliability — 失敗注入 8 種 reason |
| §6 | 效能驗證Performance — 升級時長護欄 |
| §7 | UX 驗證 — badge / modal / 復原 UI / R-FW-11 modal 不可關 |
| §8 | 整合驗證 — SIGTERM / Wails OnBeforeClose / force-quit modal |
| §9 | 自動化腳本對照表E2E unit-style + 手動 checklist |
| §10 | 通過 / 失敗判定規則 |
| §11 | Bug 回報格式 |
| §12 | 下週執行建議順序(哪平台先 / 哪情境先) |
| §13 | Pass → next-step / Fail → 派工建議 |
---
## 1. 範圍與驗證目標
### 1.1 範圍邊界
**在範圍**M9-5 必驗):
- KL520 + KL720 兩 chip 的 FW 升級流程A 階段、KDP1 → KDP2 + KDP2 → KDP2 short-circuit
- macOS / Windows / Linux 三平台
- 4 個升級情境§4.1
- 8 種失敗注入路徑§5.2
- AC-FW-1.1 ~ AC-FW-1.9 全部 acceptance criteria
- §8.6 graceful shutdown + Wails OnBeforeClose force-quit modal
**不在範圍**B 階段或 post-launch
- KL630 / KL730 升降版B 階段 M9-10 才開、AC-FW-3.5
- 手動降版B 階段 M9-11/12
- 多版本目錄結構B 階段 M9-9
- KneronPLUS wheel 升 3.1.2 跨主版本回歸B 階段 M9-13
- Beta usability testUR-1~3、post-launch
### 1.2 驗證目標pass criteria
| 類別 | 目標 |
|------|------|
| Functional | 4 情境 × 6 combo3 平台 × 2 chip= 24 個升級實驗、success rate ≥ 95% |
| Reliability | 8 種 reason 注入測試、UI friendly message 正確顯示、no server crash |
| Performance | KL520 ≤ 60s 上界、KL720 ≤ 200s 上界AC-FW-1.7 + TDD §7.2 護欄) |
| UX | Badge 4 色顯示正確、modal 不可關R-FW-11、復原 UI 對應 8 種 reason |
| Integration | SIGTERM 延遲關閉、Wails OnBeforeClose force-quit modal 阻擋成功 |
---
## 2. 測試環境前置
### 2.1 硬體(使用者已備齊)
| 項目 | 說明 |
|------|------|
| KL520 dongle | USB Boot 版KDP1 legacy+ KDP2 standard 版各 1 根;若只有 KDP2 dongle、可從 KDP2 跑 short-circuit 路徑 |
| KL720 dongle | 至少 1 根(已預燒 KDP2、會走 short-circuit |
| macOS 機器 | Apple Silicon 或 Intel x86_64兩者皆可 |
| Windows 機器 | Windows 10/11、x86_64、有 admin 權限KneronPLUS WinUSB driver |
| Linux 機器 | Ubuntu 20.04+ 或同等 distro、udev rules 已綁定 |
| USB hub | 推薦(拔插測試方便、不必碰主機 port |
### 2.2 軟體
每平台需要:
- visionA-local 最新 A 階段 build含 M9-1 ~ M9-4 commits
- Chrome / Edge / Firefox 任一(測 UI、瀏覽器 tab
- Terminal / Console看 server log、跑 `git log` 等)
### 2.3 測試資料
- 純淨環境:每平台跑前刪 `~/Library/Application Support/visiona-local/` 或同等位置、確保沒有 leftover state
- KL520 KDP1 dongle 是必要的(最重要的 happy path若沒有、降級為「KDP2 short-circuit only」+ §11 bug-report 指出此 gap
---
## 3. 三平台 × 兩 chip × 四情境驗證表
每個 cell = 1 個驗證實驗。理想全部 ✅,部分 ⚠ partial 可接受、❌ 阻擋發布。
| # | 平台 | Chip | 情境 | 預期 stage 序列 | timeout 上界 | 驗證標準 |
|---|------|------|------|---------------|------------|---------|
| 1 | macOS | KL520 | KDP1 → KDP2 完整升級4 stage | preparing → loading → flashing → verifying → done | 60s | §4.1 情境 A |
| 2 | macOS | KL520 | KDP2 → KDP2 short-circuit3 stage | preparing → flashing → verifying → done | 60s | §4.1 情境 B |
| 3 | macOS | KL720 | KDP2 升級 / load_firmware_from_file | preparing → flashing → verifying → done | 200s | §4.1 情境 C |
| 4 | Windows | KL520 | KDP1 → KDP2 完整升級 | 同 1 | 60s | §4.1 情境 A |
| 5 | Windows | KL520 | KDP2 short-circuit | 同 2 | 60s | §4.1 情境 B |
| 6 | Windows | KL720 | KDP2 升級 | 同 3 | 200s | §4.1 情境 C |
| 7 | Linux | KL520 | KDP1 → KDP2 完整升級 | 同 1 | 60s | §4.1 情境 A |
| 8 | Linux | KL520 | KDP2 short-circuit | 同 2 | 60s | §4.1 情境 B |
| 9 | Linux | KL720 | KDP2 升級 | 同 3 | 200s | §4.1 情境 C |
| 10 | macOS | KL520 | 來回升級(情境 A 完成後再跑 A | 兩次 1 stage 序列 | 各 60s | §4.1 情境 D |
| 11 | Windows | KL520 | 來回升級 | 同 10 | 各 60s | §4.1 情境 D |
| 12 | Linux | KL520 | 來回升級 | 同 10 | 各 60s | §4.1 情境 D |
**Functional combo = 12**3 平台 × 2 chip × 2 主情境 = 12情境 D 是延伸驗證、加 3 combo = 15、但 reliability + UX + integration 各加部分 combo、合計約 24 個實驗)。
**Reliability 額外實驗**
- 13-20每平台跑「拔 USB 中斷」「kill bridge.py」「kill server」「modal R-FW-11 不可關」4 種注入,至少 macOS + Windows 各 4 = 8 comboLinux 補上 = 12
**Integration 額外實驗**
- 21-24每平台跑「升級期間關 Wails 視窗→force-quit modal」「升級期間 SIGTERM→延遲關閉」2 種、合計 6 combo
合計 ~24 個 combo使用者可依時間裁切§12 給優先順序)。
---
## 4. 功能性驗證Functional
### 4.1 4 個升級情境(每平台 × 每 chip 都跑)
#### 情境 AKDP1 → KDP2 完整升級4 stage、AC-FW-1.2
**前置條件**:插一根 KL520 KDP1 legacy donglefirmware 字串 `KDP Comp/U` 或類似)。
**測試步驟**
1. 確認 visionA-local 已啟動、瀏覽器開 Devices 頁
2. 確認偵測到 dongle、卡片右上角 FW badge **紅色** + 文字「KDP1 (legacy)」+ tooltip「此韌體為舊版 KDP1...」
3. 點卡片內「升級到最新」按鈕labels`升級到 v2.2.0`
4. 確認 confirm modal 顯示:
- 標題「升級韌體」
- from/tofrom = `KDP1`、to = `v2.2.0`
- 警告:「升級過程約需 30 秒、期間切勿拔除裝置或關閉應用程式」
- 「開始升級」按鈕primary、不是 destructive
5. 點「開始升級」、modal 切到 upgrading phase
6. 確認 progress modal
- **不顯示** ✕ / 取消按鈕R-FW-11 緩解)
- progress bar 從 0% 開始
- 階段文字依序變化:「階段 1 / 4準備偵測 + 連接裝置)」→「階段 2 / 4載入引導程式」→「階段 3 / 4寫入韌體」→「階段 4 / 4驗證完成」
- 紅色 banner「請勿拔除裝置」全程顯示
- 「已耗時」秒數遞增、「預估剩餘」秒數遞減(依 etaMs 計算)
- ESC / 點外部不關 modal捕獲、抖動或無反應
7. 升級成功30 秒內、modal 短暫顯示綠勾 ✓ 「完成」+ afterVersion `v2.2.0`
8. 約 1.5 秒後 modal 自動關閉
9. 右上角 toast 出現「KL520 #1 升級成功 — 從 KDP1 升級到 v2.2.0(耗時 X 秒)」、停留 6 秒
10. Devices 卡片 FW badge **變綠**、firmware version `v2.2.0`
**Pass 條件**
- ✅ 全部步驟完成、所有 UI 元素如預期
- ✅ 升級在 60s 內完成KL520 timeout 上界)
- ✅ 4 stage 全部出現、順序正確
- ✅ Server log 沒有 panic、error
#### 情境 BKDP2 → KDP2 short-circuit3 stage
**前置條件**KL520 dongle 已是 KDP2、firmware 字串為 `v2.1.0``v2.2.x` 但比 bundled current 舊(觸發 yellow badge。或情境 A 跑完後重新跑情境 B。
**測試步驟**
1. Devices 頁卡片 FW badge **黃色**older
2. 點升級按鈕、confirm modal
3. 點「開始升級」、modal 切 upgrading
4. 確認 progress stage 序列:**3 stage**(不是 4「階段 1 / 3準備」→「階段 2 / 3寫入韌體」→「階段 3 / 3驗證完成」**沒有** loading 階段)
5. 升級成功、badge 變綠
**Pass 條件**
- ✅ 3 stage 而不是 4 stage核心差異、`stageOrdinal` helper 邏輯驗證)
- ✅ 升級在 60s 內完成
#### 情境 CKL720 升級
**前置條件**KL720 donglefirmware 為 KDP2可能與 bundled current 一樣或舊)。
**測試步驟**
1. Devices 頁卡片 badge綠 (若 already current) 或黃 (older 可升)
2. 若是黃色,點升級
3. 同情境 A/B但 estimated duration 標 `3 分鐘`180 秒)
4. progress modal stage 序列同 KDP2 short-circuit
**Pass 條件**
- ✅ 升級在 200s 內完成KL720 timeout 上界)
- ✅ progress 跑滿且沒有卡死
#### 情境 D來回升級regression
**前置條件**:情境 A 已跑完、再跑一次完整 KDP1 → KDP2 升級流程(如果有第二根 KL520 KDP1 legacy dongle建議用第二根否則只能跑一次
**測試步驟**
1. 重新跑情境 A
2. 確認第二次升級不會:
- 被「Error 15 SEND_DATA_TOO_LARGE」打斷既有 KL520 reset bug fix 應該已處理 `needsReset=true`
- bridge.py 帶舊 firmware state 連線
3. 第二次升級流程跟第一次完全一樣
**Pass 條件**
- ✅ 沒有 Error 15
- ✅ 連續兩次升級都成功
### 4.2 多裝置同時升級per-device 隔離、firmware-store activeDeviceId 機制)
**前置條件**:兩根 dongle 同時連接(建議 1× KL520 + 1× KL720
**測試步驟**
1. Devices 頁顯示兩張卡片
2. 點 KL520 卡片的升級按鈕modal 開)
3. 升級進行中、**切回 Devices 頁** 看另一張 KL720 卡片狀態
4. 點 KL720 卡片的升級按鈕 — 預期:**會被擋住**(因為 firmware-store 一次只允許一個 activeDeviceId、其他 device 的事件被忽略)
- 或者 modal 開但顯示「裝置正在進行其他作業」409 FW_DEVICE_BUSY
5. 等 KL520 完成
6. 點 KL720 升級、流程正常
**Pass 條件**
- ✅ 第一個升級進行中、不會被第二個 modal 蓋掉
- ✅ Frontend store 不出現「兩個 progress event 互相覆蓋」
---
## 5. 可靠性驗證Reliability、失敗注入
### 5.1 對應 Design §7.1 的 8 種失敗類型
| # | 失敗類型 | 注入方法 | 預期 reason | 預期 UI |
|---|---------|---------|------------|--------|
| 1 | scan_not_found | 升級前拔 USB → 立刻點升級 | `scan_not_found` | error modal「找不到裝置」+「重新插拔後重試」按鈕 |
| 2 | connect_failed | 多開 visionA-local兩 instance 搶 device→ 點升級 | `connect_failed` | 「無法連接裝置」+「重試」按鈕 |
| 3 | loader_write_failed | 升級到 loading stage 時 `pkill -9 python` | `loader_write_failed` | 「引導程式載入失敗」+「拔插後重試」 |
| 4 | upgrade_mid_failed | 升級到 flashing stage 時 `pkill -9 python` | `upgrade_mid_failed` | 「韌體寫入失敗、聯絡技術支援」+「複製錯誤訊息」+「取得協助」mailto: |
| 5 | disconnect_during_op | 升級到 flashing stage 時拔 USB | `disconnect_during_op` | brick warning banner + 「Contact Support」**不顯示 retry 按鈕** |
| 6 | timeout | 修改 bridge.py 加 `time.sleep(70)` 模擬 KL520 ≥ 60s或 KL720 ≥ 200s | `timeout` | 「操作超時」+「拔插後重新掃描」 |
| 7 | verify_mismatch | 升級成功但 backend 偽造 firmware 字串不對(需 mock 或實機難以重現) | `verify_mismatch` | brick warning + ContactSupport |
| 8 | verify_not_found | 升級成功但 rescan 找不到 device | `verify_not_found` | brick warning + ContactSupport |
**實機限制**#7 verify_mismatch / #8 verify_not_found 在實機很難穩定重現、建議用 E2E 自動化mock backend覆蓋。
### 5.2 注入測試步驟模板
對每個失敗類型:
1. 啟動 visionA-local
2. 跑情境 A 或 B 升級流程到 confirm modal
3. 點「開始升級」、進入 upgrading phase
4. 等到指定 stage、執行注入動作拔 USB / kill process / etc.
5. 觀察 UI 變化
6. 截圖 + 紀錄 timestamp
### 5.3 重點驗證項
對每個失敗:
- [ ] error modal 顯示對應 friendly message中文 / 英文視 i18n 設定)
- [ ] 顯示 errorCode`FW_E102`
- [ ] 技術資訊區可展開、包含 stage / reason / deviceId / rawError
- [ ] 「複製錯誤訊息」按鈕可點、複製成功變「已複製 ✓」
- [ ] 若是 destructive reasondisconnect/verify_mismatch/verify_not_found
- [ ] 顯示 brick warning role=note
- [ ] 不顯示 Retry 按鈕
- [ ] 顯示「Contact Support」destructive 按鈕enabled、點擊開 mailto:
- [ ] 若是 recoverable reason其他
- [ ] 顯示 Retry / ReplugRetry / Rescan 按鈕
- [ ] 點 Retry 後流程重啟store 回 confirming phase + 重新打 API
- [ ] Server log 無 panic、無 crash
- [ ] Devices 頁卡片狀態不會卡住、re-plug 後可繼續用
---
## 6. 效能驗證Performance
### 6.1 升級時長護欄AC-FW-1.7、TDD §7.2
| Chip | 預估 | 護欄上界 | 護欄超過行為 |
|------|------|---------|-------------|
| KL520 | ~30swarrenchen 實測) | 60s | timeout、UI 顯示 `Reason="timeout"` |
| KL720 | ~180swarrenchen 實測) | 200s | timeout、UI 顯示 `Reason="timeout"` + `FW_UPGRADE_BRICK_RISK` |
### 6.2 量測方法
對每個 functional 升級實驗§3 表格 1-12
1. 開瀏覽器 DevTools Network tab、觀察 WS 「firmware_progress」event 時序
2. 紀錄:
- `stage=preparing` event 第一次出現的 timestamp
- `stage=done` event 出現的 timestamp
- 差距 = 升級實測時長
3. 對照表格:
| Combo | 預期時長 | 實測時長 | Pass / Fail |
|-------|---------|---------|------------|
| macOS × KL520 × KDP1→KDP2 | < 60s | | |
| macOS × KL520 × short-circuit | < 60s | | |
| macOS × KL720 | < 200s | | |
| Windows × KL520 × KDP1KDP2 | < 60s | | |
| ... | | | |
**Pass 條件**所有 combo 升級時長 護欄上界
**Warn 條件**時長 > 預估但 < 護欄log P3 改善項
**Fail 條件**時長 護欄阻擋發布 Backend 派工調 timeout / 修流程
### 6.3 progress event 推送頻率
- 預期 stage 切換時推 1 event每秒至少 1 event ETA 更新
- stage > 10s 沒有 eventUI ETA 失準、log 為 Backend P2 改善項
---
## 7. UX 驗證
### 7.1 Badge 4 色狀態AC-FW-1.1
| State | 條件 | 顏色 | 文字 |
|-------|------|------|------|
| current | firmwareVersion = bundled current | 綠 | 版本字串(如 `v2.2.0` |
| older | firmwareVersion 比 current 舊(同 KDP2 系列)| 黃 | 版本字串 |
| legacy | firmwareIsLegacy = trueKDP1| 紅 | `KDP1` |
| unknown | firmwareVersion 或 bundled 為空 / `unknown` | 灰 | `Unknown``Loading...` |
**測試步驟**
1. 在三平台分別跑:插一根 KDP1 legacy dongle、確認紅 badge
2. 升級到 v2.2.0 後、確認綠 badge
3. 修改 bundle CURRENT_VERSION如果 A 階段沒實作則此步驟跳過、由 B 階段 M9-9 驗)→ 確認黃 badge
4. 拔 USB → 卡片消失(不該顯示灰 badge、直接 device 不在清單)
5. 連線中 bridge.py 沒回 firmware 字串 → 顯示灰 badge過渡狀態
### 7.2 升級 modal R-FW-11「不可關」驗證AC-FW-1.9
升級 upgrading phase 期間驗證:
- [ ] ✕ 按鈕不存在DialogContent showCloseButton={false}
- [ ] ESC 不關閉 modal
- [ ] 點 modal 外部(黑色 overlay不關閉
- [ ] 「取消」按鈕不存在
- [ ] 即使刷新瀏覽器頁面後再回來、升級期間仍在進行progress modal 由 store 重新 hydrate
### 7.3 失敗 modal 復原 UIDesign §7.2
| Reason | 主要按鈕 | 次要按鈕 |
|--------|---------|---------|
| scan_not_found | 「重新插拔後重試」 | 關閉 |
| connect_failed | 「重試」 | 關閉 |
| loader_write_failed | 「拔插後重試」 | 關閉 |
| upgrade_mid_failed | 「重試」 | 關閉 |
| timeout | 「拔插後重新掃描」 | 關閉 |
| disconnect_during_op | **「Contact Support」**destructive、mailto: | 關閉、**無 retry** |
| verify_mismatch | **「Contact Support」** | 關閉、**無 retry** |
| verify_not_found | **「Contact Support」** | 關閉、**無 retry** |
每種失敗都驗:
- [ ] 主要按鈕文案正確
- [ ] destructive reason 不顯示 Retry
- [ ] Contact Support 點擊開 mailto: handlersubject + body 帶 errorCode + 技術資訊)
- [ ] 「複製錯誤訊息」可複製、變「已複製 ✓」2 秒後復原
- [ ] errorCode 以 mono 字型顯示
### 7.4 Success toast 行為
- [ ] toast 顯示「{deviceName} 升級成功 — 從 X 升級到 Y耗時 N 秒)」
- [ ] toast 停留 6 秒(不是預設 4 秒)
- [ ] toast 自動消失、不需手動關
- [ ] 升級成功後 Devices 頁自動 refresh、badge 變綠
---
## 8. 整合驗證Integration
### 8.1 升級期間 SIGTERM 延遲關閉TDD §8.6.1
**測試步驟**
1. macOS / Linux找出 server PID`ps aux | grep visiona-local-server` 或 visionA-local 控制台顯示)
2. 啟動升級流程(情境 A 或 C、長一點的
3. 升級到 flashing stage 時、執行 `kill -TERM <PID>`
4. 預期:
- server 不立即退出
- 透過 WS 廣播 `server:shutdown-pending` event 到 `"system"` room
- shutdown 延遲到 firmware task 完成(最多 `firmware.MaxShutdownWait = 220s`
- firmware task 跑完才走原本 graceful shutdown 流程
5. 觀察 server log、應有
- `firmware: N active firmware task(s) detected, delaying shutdown up to ...`
- firmware task 完成後、`firmware: all firmware tasks completed, proceeding to shutdown`
6. 升級結束後、server 才真正退出
7. Frontend modal 在 server 退出前該完成正常的 upgrade success toast 流程
**Pass 條件**
- ✅ server 不在升級進行中退出(不 brick
- ✅ SIGTERM event log 有 `delaying shutdown` 紀錄
- ✅ 升級成功完成、device 升級後狀態正常
### 8.2 Wails OnBeforeClose force-quit modalTDD §8.6.2 + visiona-local/firmware_close_guard.go
**測試步驟**
1. 任一平台 + 任一 chip
2. 啟動升級流程(情境 A 或 C
3. 升級到 flashing stage 時、點 Wails 視窗的 ✕ 關閉按鈕macOS紅色關閉、Windows右上 ✕、Linuxwindow manager close
4. 預期:
- Wails 視窗 **不關閉**
- Wails 收到 `OnBeforeClose` 後查 `/api/firmware/active-tasks``hasActive: true`
- emit Wails event `app:firmware-in-progress` 帶 task payload
- Frontend modal 攔截、顯示「韌體切換進行中、為避免裝置損毀、無法關閉應用程式」
- modal 顯示 task infodevice name / chip / direction / stage / elapsedMs / etaSeconds
- modal 顯示「繼續等待」primary、回到原本升級流程+ 「強制關閉」destructive按鈕
5. 點「強制關閉」、預期第二層 FORCE 確認 modal要使用者輸入「FORCE」字串
6. 不輸入 / 取消 → 升級繼續、modal 關閉、Wails 視窗回到正常
7. 輸入「FORCE」+ 確認 → `ConfirmForceClose` binding 被叫、Wails 走 graceful shutdown會 brick 裝置、風險已接受、Design §6a
8. 升級正常結束(不點強制關閉的情境)後、再點 ✕ → Wails 正常關閉
**Pass 條件**
- ✅ 升級進行中視窗無法關
- ✅ force-quit modal 顯示 + payload 正確
- ✅ 第二層 FORCE 確認可運作
- ✅ 升級完成後視窗正常可關
### 8.3 升級期間 server crash 容忍
**測試步驟**
1. 升級進行中、`kill -9 <server-PID>`(強制殺、不走 graceful shutdown
2. Frontend WS 應於 3 秒內偵測到斷線、自動重連嘗試
3. server 重啟後(手動 / Wails 自動、Frontend bootId 機制應觸發 reload
4. Devices 頁應重新 scan、卡片回到「device 連線中」或「沒有 device」狀態
5. 觀察 dongle 實體狀態 — 可能 brick取決於升級 stage、屬接受風險、TDD §8.6.1 hard timeout 後也走 shutdown
**Pass 條件**
- ✅ Frontend 不卡死、UI 可繼續操作
- ✅ Server 重啟後狀態正常
- ⚠ Dongle 可能 brick屬接受風險、需給技術支援 SOP 救磚)
---
## 9. 自動化腳本對照表
| 測試類型 | 涵蓋方式 | 對應檔 |
|---------|---------|--------|
| Backend smoke schema 對齊MJ3 | 已修、go test 全綠 | `server/internal/api/ws/firmware_ws_test.go` |
| Frontend store 邏輯 | 已有 vitest 60 tests 全綠 | `frontend/src/tests/stores/firmware-store.test.ts` |
| Frontend componentbadge / error view | 已有 vitest 全綠 | `frontend/src/tests/components/firmware-*.test.tsx` |
| **E2E happy pathmock WS + UI** | M9-5 新增 vitest 風格 | `.autoflow/06-testing/scripts/firmware-upgrade-happy-path.spec.ts` |
| **E2E 失敗注入8 reason × UI** | M9-5 新增 vitest 風格 | `.autoflow/06-testing/scripts/firmware-upgrade-error-recovery.spec.ts` |
| **E2E R-FW-11 modal 不可關** | M9-5 新增 vitest 風格 | `.autoflow/06-testing/scripts/firmware-r-fw-11-modal-not-closable.spec.ts` |
| **E2E Wails OnBeforeClose 攔截邏輯** | M9-5 新增unit-style on Go | `.autoflow/06-testing/scripts/wails-onbeforeclose-firmware-active.spec.ts` |
| 三平台實機驗證 | 人工 checklist | `.autoflow/06-testing/scripts/manual-checklist-{macos,windows,linux}.md` |
**注意**:因為 visionA-local 沒有 Playwright / Cypress、實機驗證需要實體 dongle、E2E 腳本走 **vitest + RTL + mock WS / mock fetch** 路線,模擬 backend 整條 stage 流轉、frontend 跑真實 component build。實機驗證走人工 checklist§12
---
## 10. 通過 / 失敗判定規則
### 10.1 P0阻擋發布
任一發生 → A 階段不可進入發布流程:
- Functional 升級成功率 < 90%24 combo 3 個失敗
- 升級期間 server crash / panic
- 升級期間 dongle 100% brick無法復原
- §8.1 SIGTERM 期間 server 不延遲關閉直接中斷 firmware task
- §8.2 Wails 視窗在升級進行中可直接關閉OnBeforeClose 沒生效
### 10.2 P1嚴重但可繞過
任一發生 P1 bug ticket release window 決定是否阻擋
- 升級時長 > 護欄但 < 護欄 × 1.5KL520 60-90s / KL720 200-300s
- Frontend store 多裝置隔離失效兩個 progress modal 互相覆蓋
- destructive reason 仍顯示 retry 按鈕
- force-quit modal 第二層 FORCE 確認字串可繞過
### 10.3 P2一般問題
- Badge 顏色顯示錯誤 older 顯示成 current
- toast 持續時間錯誤
- i18n key 漏譯 / 文案錯誤
### 10.4 P3改善建議
- progress event 推送頻率太低卡頓 > 5s
- error modal 「技術資訊」展開後排版錯亂
---
## 11. Bug 回報格式
對每個發現的問題,使用以下格式回報到 `.autoflow/06-testing/bugs/M9-5-BUG-{NN}.md`
```markdown
# M9-5-BUG-NN: [標題]
## 嚴重程度P0 / P1 / P2 / P3
## 發現方式Functional / Reliability / Performance / UX / Integration
## 環境macOS 14.3 / Windows 11 / Ubuntu 22.04
## 對應驗證§4.1 情境 A / §5.1 #3 loader_write_failed / etc.
## 對應 ACAC-FW-1.X / R-FW-11 / R-FW-13
## 重現步驟
1. ...
2. ...
## 預期行為
(依 PRD / TDD / Design Spec、引用具體章節
## 實際行為
(觀察到的差異)
## 截圖
(路徑:.autoflow/06-testing/screenshots/M9-5-bug-NN-{step}.png
## Server log 片段
```
(從 ~/Library/Application Support/visiona-local/wails.log 或對應位置)
```
## Frontend WS event log
```
(從 browser DevTools Network → WS frames
```
## 建議修復方向(如有)
(不一定要、但 testing 觀察過程的線索)
```
---
## 12. 下週執行建議順序(給使用者)
### 12.1 第 1 天smoke + happy path必驗、~3 小時)
優先順序:
1. **macOS × KL520 × KDP2 short-circuit**(最快、不需 KDP1 dongle、5 分鐘)
2. **macOS × KL520 × KDP1 → KDP2 完整升級**happy path 核心、10 分鐘)
3. **macOS × KL720 × 升級**(單一 chip 驗證、5 分鐘 + 3 分鐘升級 = 10 分鐘)
4. 重複 1-3 在 Windows
5. 重複 1-3 在 Linux
→ 若 macOS 1-3 全綠、且 Windows / Linux 至少 1 個 combo 綠、可進入第 2 天。
→ 若 macOS 任一失敗:停下、回報 bug、不繼續其他平台。
### 12.2 第 2 天reliability + UX4 小時)
對 macOS或第 1 天最穩的平台)跑:
1. R-FW-11 modal 不可關5 分鐘)
2. Badge 4 色顯示10 分鐘)
3. 失敗注入 #1 scan_not_found5 分鐘)
4. 失敗注入 #3 loader_write_failed10 分鐘)
5. 失敗注入 #4 upgrade_mid_failed10 分鐘)
6. 失敗注入 #5 disconnect_during_op10 分鐘)
7. 失敗注入 #6 timeout10 分鐘)
8. UX checklisttoast / 復原 UI / mailto: 開啟30 分鐘)
再對 Windows + Linux 跑 #5 disconnect_during_op + #4 upgrade_mid_failed兩個 brick risk 最高的)作為 sanity check。
### 12.3 第 3 天integration2-3 小時)
對任一最穩平台跑:
1. §8.1 SIGTERM 延遲關閉macOS / Linux 推薦、Windows SIGTERM 不直觀)
2. §8.2 Wails OnBeforeClose force-quit modal三平台都要驗
### 12.4 整體建議優先順序
```
P0 must-have必跑
- §4.1 情境 A × 三平台(核心 happy path
- §4.1 情境 B × 三平台short-circuit 驗證)
- §8.2 Wails OnBeforeClose × 三平台(避免 brick
P1 should-have強烈建議
- §4.1 情境 C × 三平台KL720 驗證)
- §5.1 #5 disconnect_during_op × 三平台brick 風險最高)
- §8.1 SIGTERM × macOS + Linux
- §7.3 失敗 modal 復原 UI × 至少一個平台
P2 nice-to-have時間允許
- §4.1 情境 D 來回升級 × 任一平台
- §5.1 #1-4 + #6 其他失敗注入
- §6 effective 時長量測
```
---
## 13. Pass → Next-step / Fail → 派工建議
### 13.1 Pass所有 P0 都綠 + P1 大部分綠)
→ A 階段可進入發布流程:
1. 給 Orchestrator 「A 階段交付」訊號
2. Orchestrator 啟動 Reviewer agent 對 M9-5 plan + scripts + manual checklist 做最終 review
3. Reviewer 通過後、A 階段五人天宣告完成、可進入:
- 法律合規確認Kneron firmware redistribution 授權、R-FW-5 / Q-FW-1
- 發布 A 階段 visionA-local含 KL520/KL720 升級 only、KL630/KL730 延 B 階段)
- **或** 進入 B 階段 M9-6 強驗證 + M9-7 啟動
### 13.2 Fail任一 P0 失敗 / 多個 P1 失敗)
依失敗類型分流派工建議:
| Fail 類型 | 派誰 |
|----------|------|
| §4.1 升級流程崩潰、stage 不正確 | Backend修 firmware_handler / bridge.py / driver |
| §5 reason 對應錯誤、UI friendly message 沒顯示 | Frontend修 firmware-store errorMessageKeyFor / firmware-error-view |
| §6 時長 ≥ 護欄 | Backend調 bridge.py timeout 或優化流程) |
| §7.1 Badge 4 色錯誤 | Frontend修 firmware-badge computeBadgeState |
| §7.2 R-FW-11 modal 可關 | Frontend修 firmware-upgrade-dialog onOpenChange |
| §8.1 SIGTERM 不延遲 | Backend修 firmware/shutdown.go + server/main.go signal handler |
| §8.2 Wails 視窗可關 | Wails layer修 visiona-local/firmware_close_guard.go + frontend modal |
**回報給 Orchestrator 的 escalation 範本**
```
[M9-5 驗證失敗]
- 失敗 combo[macOS × KL520 × KDP1→KDP2 / 情境 A]
- 失敗類型:[P0 升級流程崩潰]
- bug ticket.autoflow/06-testing/bugs/M9-5-BUG-NN.md
- 建議派工Backend修 bridge.py upgrade_mid 處理)
- A 階段交付狀態:阻擋
```
---
## 14. 附錄
### 14.1 重要 log 位置
| 平台 | wails.log | server.log |
|------|-----------|-----------|
| macOS | `~/Library/Application Support/visiona-local/wails.log` | `~/Library/Application Support/visiona-local/server.log` |
| Linux | `~/.local/share/visiona-local/wails.log` | `~/.local/share/visiona-local/server.log` |
| Windows | `%APPDATA%\visiona-local\wails.log` | `%APPDATA%\visiona-local\server.log` |
### 14.2 重要 WS endpoint
- `ws://127.0.0.1:<port>/ws/devices/:id/firmware-progress` — firmware progress room
- broadcast `type=firmware_progress``stage=preparing|loading|flashing|verifying|done|error`
### 14.3 重要 API endpoint
- `POST /api/devices/:id/firmware/upgrade` — 觸發升級
- `GET /api/firmware/active-tasks` — 查詢進行中 taskWails 用)
### 14.4 主要相依檔(給 bug 回報時引用)
| 檔 | 角色 |
|----|------|
| `server/internal/firmware/service.go` | 升級 service goroutine |
| `server/internal/firmware/shutdown.go` | SIGTERM graceful shutdown |
| `server/internal/api/handlers/firmware_handler.go` | API + WS broadcast |
| `server/scripts/kneron_bridge.py` | bridge.py upgrade handler |
| `visiona-local/firmware_close_guard.go` | Wails OnBeforeClose |
| `frontend/src/components/firmware/firmware-upgrade-dialog.tsx` | modal lifecycle |
| `frontend/src/stores/firmware-store.ts` | phase / reason mapping |
---
## 變更紀錄
| 日期 | 版本 | 變更 | 作者 |
|------|------|------|------|
| 2026-05-25 | v1.0 | 初版 — M9-5 三平台實機驗證 plan + 自動化腳本對照表 + 人工 checklist 索引 + 下週執行建議順序 | Testing Agent |