❯❯ 本日真實慘案:因為元件與 stepper 重組,意外撞斷了 accessibility name 的承重合約。
📍 流水線位置|入口 → flow → 型別 → mock → 測試 → UI → 【vibe】 → 上線(事故現場)

前三篇把 vibe 治理架構建立起來(紅線、閘門、自動補測)。這篇記錄它第一次在實戰中替我擋下地雷的過程。
那只是一次很單純的視覺優化:
賓客名單頁加一排「男方/女方」篩選膠囊、RSVP 表單的攜伴人數改用 ± 步進鈕(stepper)、接待台模組把數據表格(table)改成卡片式呈現(card)。每一項都落在「視覺與互動調整」的許可範圍裡。
結果 gate 一跑,凍結區的主規格測試(main spec)直接爆了一排紅燈。
這裡先回頭對齊 Day 21 的機制:分層與自動補測處理的是「新增的互動有沒有自己的測試」,但這次踩雷是另一個故事,新元素撞上了主 spec 原本的舊斷言。這種衝突本來就不在補測機制的守備範圍,只有 gate 把凍結區全量重跑時才會被抓出來。
賓客名單頁的舊測試我一行都沒動,只是在頁面頂部加了一排篩選膠囊,結果紅燈卻卡在舊測試上。

入坑排查才發現,問題出在 Spec 的定位邏輯。原本舊測試是用getByRole('button', { name: /^男方$/ }) 來抓表單裡的「男方」按鈕。但我新加的篩選膠囊文字同樣包含「男方」,導致 Playwright 的 Strict Mode 在檢索元素時抓到兩個同名按鈕,直接判定為 strict mode violation 並中斷執行。
自動化測試與螢幕閱讀器(screen reader)都依賴 DOM 的可及性語意(Accessibility Semantics)來辨識元素。元素的 Accessible Name 必須具備唯一性;當多個元素共有同一個角色(role)與名稱時,對 Playwright 來說就等於「找不到目標」,當場噴錯。
攜伴人數的步進鈕(stepper)也中了同一招。攜伴人數 ± 鈕採用的 aria-label="減少攜伴人數",結果觸發了 Spec 中 getByLabel(/加一|攜伴|人數/) 的正則表達式,一次匹配到 3 個 DOM 元素而導致測試失敗。
這些問題在畫面上看起來完全正常,手動點擊也順到不行。如果沒有 gate 在關口把守,這批藏著迴歸風險的程式碼就真的悄悄溜上線了。
(下圖是我事後還原事故當天 stepper 的 aria-label,重跑同一支測試錄下的真實 Console 輸出,錯誤成因完全一致:)


表格改卡片那一批,出事的又是另一種情境。原本的 HTML 表格標籤(<table>、<tr>)自帶結構語意;改成卡片之後,DOM 變成純 <div> 容器,因為當時漏補了role 與 aria-label,測試框架瞬間失去了辨識實體邊界的能力。
這直接踩到紅線第一條:實體必須可識別。雖然畫面上文字都有正常渲染出來,但對測試斷言來說,DOM 裡面根本找不到任何可以定位的實體節點。

當時共有 4 個頁面把表格改成卡片。其中 1 個頁面有補 role 與 aria-label,其餘 3 個頁面全漏了,導致 findEntity 傻傻等到 30 秒 timeout 宣告失敗。事後我也把「表格改卡片必須補齊 role 與 aria-label」寫入開發規範,直接交給 gate 自動攔截。
這次的修復完全集中在語意屬性,連一行 CSS 都沒改:
1. 解決撞名:
aria-label 改為「少一位/多一位」,精準繞開 Spec 原本太寬鬆的正則匹配。2. 補齊結構語意:
role="article" 與 :aria-label="guest.name" 屬性,讓原本退化的 <div> 重新具備可定位的實體邊界。修改完重跑 gate,全數綠燈通過。
畫面的視覺長相完全沒變,但 Accessibility Tree 裡的每個 DOM 實體,都重新找回了自己的唯一識別性。
這次被 gate 攔下的,正是視覺人工檢查完全看不見的盲點。雖然畫面渲染正常、操作起來也很順,但 DOM 的語意結構早已默默爛掉。
這起事故也打破了我對「凍結測試」的刻板印象。測試不是只有被刪掉程式碼時才會紅,當你新加的元件侵入全域語意命名空間時,它一樣會亮起紅燈。
合約不是圍欄,而是戶政事務所:長相隨你變,但註冊過的身分證字號不能重複註冊。
撞名等於搶走別人的身分證字號,漏掉屬性則像造出一個沒上報的無戶籍黑戶,不管哪種都會讓系統的識別機制直接癱瘓。
規範就算列得再詳細,也永遠窮舉不出所有可能出錯的情境。這也證明了門禁不能只仰賴文件宣導。Day 20 提到「人會忘記規則」,然而這次經驗也告訴我們,規則本身也列不完。
另外,DOM 語意壞掉,第一時間受害的就是使用螢幕閱讀器的用戶。測試框架尋找元素的方式跟輔助科技(assistive technology)完全一致;守住了測試合約,其實也順手守住了產品最基本的無障礙友善度。
Arc 4 的 vibe 治理到這裡劃下句點。從邊界定義、自動化閘門到自動補測,讓視覺無論怎麼改都有機制在背後兜底。
下一階段我們要進入發布管線(Release Pipeline)。這套被機制保護好的程式碼,接下來要怎麼順利送上正式環境部署?
🎒 最小一步|只用 Tab 鍵走一遍你產品的主要操作流程;鍵盤聚焦不到、選不到的區塊,就是 DOM 角色(role)與名稱(accessible name)缺失的位置。
📎 本篇證據|vibe 改版承重陷阱紀錄(膠囊撞getByRole('button', { name: /^男方$/ })、± 鈕撞getByLabel(/加一|攜伴|人數/)、卡片補 role+aria-label)・修法可在 repo 對照(guests.vue 膠囊帶數量、RsvpForm.vue「少一位/多一位」、reception 的role="article")