iT邦幫忙

1

Agent 產生的 UI 能顯示,不代表它已經可以上線

  • 分享至 

  • xImage
  •  

Demo 裡,agent 呼叫 tool,畫面跳出一張卡片。PR 附了 screenshot,大家看過排版後就準備合併。

這時我通常會問幾個很掃興的問題:tool 少回一個欄位時會怎樣?使用者連按兩次重試,會不會多出兩張卡片?鍵盤能不能操作?iframe 裡的 action 到底拿得到哪些權限?

如果答案只剩「但畫面有 render 出來」,那還沒驗收。那只是證明某一組輸入,剛好走到某一個看起來正常的畫面。

Generative UI 麻煩的地方,不是 UI 會變,而是資料、renderer、瀏覽器行為與 release 證據常被揉成一團。最後團隊用 screenshot 替所有問題背書,直到 production 出現一個 screenshot 根本看不出來的錯誤。

先把「有 render」降級成一項觀察

我會把流程切成這四段:

tool result
    ↓
trusted renderer
    ↓
real browser behavior
    ↓
release evidence

每一段回答的問題不同。

  • Tool result:資料是不是符合協定?
  • Renderer:這份資料是不是只會落到允許的元件與操作?
  • Browser:使用者實際操作時,頁面是不是正常?
  • Release evidence:這次驗收留下的證據,能不能連回某個 commit 或 build?

順序也很重要。Renderer 畫得再漂亮,不能補救錯誤的 payload;瀏覽器自動化跑完 happy path,也不能證明 unknown component 有安全的 fallback。

Data contract 要測壞資料,不只測型別漂亮的資料

AI SDK 7 的 MCP tool 可以宣告 outputSchema,並用 structuredContent 回傳結構化結果。這讓 tool output 有機會從「模型大概看得懂的文字」變成可驗證的資料邊界。

但 schema 存在,不代表 UI 就正確。它只讓我們有地方拒絕不合約的輸入。

假設 renderer 接受這種結果:

{
  "version": 2,
  "type": "order-summary",
  "props": {
    "orderId": "A-1042",
    "total": 1280,
    "currency": "TWD"
  }
}

測試 fixture 不能只有這份成功範例。我至少會加上缺少 currency、未知 version、不存在的 type,以及 tool 執行錯誤。串流也要故意中斷,因為 UI stream 缺少 finish chunk 時,本來就不該假裝完成。

另外,把 toolCallId 留在 fixture 與 log 裡。當同一段對話出現兩次相似卡片時,這個 ID 能幫你分辨是 tool 被呼叫兩次、前端重複消費 message part,還是 retry 沒有去重。只存最後的 DOM,通常會把真正的原因洗掉。

Renderer contract 的重點,是讓失敗可預測

Generative UI 不該等於讓模型送一段任意 HTML 回來。比較能控制的作法,是 renderer 只認一份 component allowlist,再對每個 component 的 props 做驗證。

const renderers = {
  "order-summary": OrderSummary,
  "delivery-status": DeliveryStatus,
}

這張表看起來很普通,卻是安全與可測試性的分界。type 不在表內時要顯示什麼?props 版本落後時要拒絕、轉換,還是顯示簡化內容?app-only action 能不能被 model 直接呼叫?這些都要有固定答案。

AI SDK 的 MCP Apps renderer 會把 app UI 放進 sandboxed iframe,並透過受限的 handler 溝通。Sandbox 是隔離邊界,不是 correctness 證明。權限縮小了,錯誤的按鈕仍然可能送出兩次請求;未知狀態也還是可能只得到一片空白。

我比較在意 renderer 遇到壞輸入時能不能穩定失敗。Production 最難追的,往往不是明確的 error page,而是某個欄位缺失後,元件半殘地留在畫面上。

Fixture 要可重現,別讓測試素材自己漂移

動態 UI 常混著圖片、表格、檔案預覽與串流狀態。若每次測試都拿尺寸、格式不同的素材,visual diff 會充滿無關雜訊。

例如要準備固定尺寸的圖片 fixture,可以先用 Resize Image For 在瀏覽器本機完成 resize,再把輸出納入測試資料。這裡的價值只是把輸入正規化,而且來源圖片不必送到伺服器處理;它不會替 renderer 驗證任何行為。

元件與狀態的 fixture matrix 也別靠會議裡憑空想像。團隊可以從 生成式 UI 資源 對照不同 SDK、案例與 interaction pattern,再整理出自己產品真正支援的組合。資源索引可以補盲點,但不是測試標準。最後要驗收的,仍是你自己的 component catalog 與產品規則。

我會把 matrix 寫得很務實:

Component 必測資料狀態 必測互動
OrderSummary 完整、缺欄位、舊版本 確認、取消、重複送出
DeliveryStatus loading、完成、tool error refresh、retry
FilePreview 支援格式、損壞檔案、超大檔案 開啟、下載、鍵盤關閉

這張表不需要包山包海。它只要能回答「目前允許 agent 產生哪些 UI,以及每一種 UI 怎麼壞」。

Browser contract 要看使用者真的會撞到的東西

通過 schema 與 renderer 測試後,才輪到真實瀏覽器。

Chrome DevTools 149 的 WebMCP debugging 能檢查 tool schema、用自訂參數呼叫 tool,並追蹤 pending、active event 與 return payload。這功能目前仍屬 experimental early preview,我不會把它當成穩定的 release gate;不過它把 tool lifecycle 放回可檢查的 browser state,方向是對的。

Chrome DevTools 150 也把 Network、Sources、Performance 的資料帶進 agent 輔助介面,並加入 V8 heap snapshot 分析與 URL pattern 限制。對測試來說,重點不是「DevTools 也有 AI」,而是我們終於比較容易要求 agent 拿出可核對的瀏覽器證據。

一條能進 CI 的 browser flow,至少要故意碰到這些情境:

  1. 只用鍵盤完成主要操作,檢查焦點是否掉進 iframe 後出不來。
  2. 讀 accessibility tree,確認動態插入的元件有名稱、狀態與合理順序。
  3. 監看 network 與 console,確定 retry 沒有重複副作用,也沒有被 UI 吃掉的例外。
  4. 在窄螢幕、慢速回應、tool error 與 stream 中斷下重跑。
  5. 限制 agent 可瀏覽的 URL,再確認元件 action 沒有越界。
  6. 重複開關重量級元件後比較 heap snapshot,檢查 iframe、listener 或 Blob 是否被留住。

Screenshot 可以留,但它只是上述流程中的一個附件。它很適合回答間距有沒有跑掉,回答不了焦點順序、request 次數或 memory retention。

Vision 可以幫忙看,不該替你宣判

GitHub Copilot Vision 已能把 JPEG、PNG、GIF、WebP 與 PDF 當成開發工作的輸入。這類能力適合用來理解設計稿、錯誤截圖,或指出肉眼可見的差異。

它看見按鈕,不代表按鈕按得下去;它看見 loading 消失,也不知道背後是不是送了兩次訂單。

我會讓 Vision 協助找問題,不會讓「模型覺得畫面正常」成為 pass condition。真正的 assertion 還是要落在 DOM、accessibility tree、network、console、URL scope 與記憶體狀態。

Release evidence 要能回答「是哪一層壞了」

測試全綠還不夠,證據若只留在某個工程師的 terminal,過兩天就無法追。

每次 build 可以保留一個小型 evidence bundle:

evidence/
  tool-fixtures/
  dom-snapshot/
  accessibility-snapshot/
  network-summary.json
  console-summary.json
  screenshots/
  memory-summary.json
  manifest.json

manifest.json 記錄 commit、build ID、fixture 版本、browser 版本與各項結果。失敗時,CI 不只說「Generative UI test failed」,而是能指出 data contract 拒絕了未知版本,或 browser contract 發現 retry 送出兩次請求。

這會多花一些工,但比起上線後拿著 screenshot 猜問題發生在哪一層,便宜太多。

「看起來有 render」可以是 debug 的起點,不能是 release verdict。Agent 產生的 UI 真正能上線,是因為每一層都有自己的契約,壞掉時也留得下足夠證據,而不是因為 demo 那張卡片剛好長得很完整。

Source notes


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言