iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0

❯❯ 本日真實慘案:因為元件與 stepper 重組,意外撞斷了 accessibility name 的承重合約。
Day 22 畫面變漂亮了!然後 gate 全紅

📍 流水線位置|入口 → flow → 型別 → mock → 測試 → UI → 【vibe】 → 上線(事故現場)

流水線位置|入口 → flow → 型別 → mock → 測試 → UI → 【vibe】 → 上線(事故現場)

前三篇把 vibe 治理架構建立起來(紅線、閘門、自動補測)。這篇記錄它第一次在實戰中替我擋下地雷的過程。

那只是一次很單純的視覺優化:
賓客名單頁加一排「男方/女方」篩選膠囊、RSVP 表單的攜伴人數改用 ± 步進鈕(stepper)、接待台模組把數據表格(table)改成卡片式呈現(card)。每一項都落在「視覺與互動調整」的許可範圍裡。

結果 gate 一跑,凍結區的主規格測試(main spec)直接爆了一排紅燈。

這裡先回頭對齊 Day 21 的機制:分層與自動補測處理的是「新增的互動有沒有自己的測試」,但這次踩雷是另一個故事,新元素撞上了主 spec 原本的舊斷言。這種衝突本來就不在補測機制的守備範圍,只有 gate 把凍結區全量重跑時才會被抓出來。


事故一:新增膠囊造成可及性名稱(Accessible Name)碰撞

賓客名單頁的舊測試我一行都沒動,只是在頁面頂部加了一排篩選膠囊,結果紅燈卻卡在舊測試上。

Accessible Name 語意碰撞示意圖

入坑排查才發現,問題出在 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 輸出,錯誤成因完全一致:)

gate 紅燈:strict mode violation resolved to 2 elements(重現示範)
還原 aria-label 後重跑同一支測試:1 passed(重現示範)


事故二:元件重構遺漏語意屬性

表格改卡片那一批,出事的又是另一種情境。原本的 HTML 表格標籤(<table><tr>)自帶結構語意;改成卡片之後,DOM 變成純 <div> 容器,因為當時漏補了rolearia-label,測試框架瞬間失去了辨識實體邊界的能力。

這直接踩到紅線第一條:實體必須可識別。雖然畫面上文字都有正常渲染出來,但對測試斷言來說,DOM 裡面根本找不到任何可以定位的實體節點。

DOM 語意退化與修復對照圖

當時共有 4 個頁面把表格改成卡片。其中 1 個頁面有補 rolearia-label,其餘 3 個頁面全漏了,導致 findEntity 傻傻等到 30 秒 timeout 宣告失敗。事後我也把「表格改卡片必須補齊 rolearia-label」寫入開發規範,直接交給 gate 自動攔截。


修復:一行 CSS 沒動,只修 HTML 語意

這次的修復完全集中在語意屬性,連一行 CSS 都沒改:

1. 解決撞名:

  • 篩選膠囊: 標籤補上動態數量(變成「男方 8」),一舉跟表單裡單純的「男方」按鈕做出區隔。
  • Stepper ± 鈕: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「少一位/多一位」、receptionrole="article"


上一篇
Day 21 新增互動功能:讓 AI 自動生成對應測試
下一篇
Day 23 一個變數,決定前端打誰
系列文
收到自己的紅色炸彈!婚期就是 Deadline:前端一人用 AI 規格流水線,30 天出貨婚禮 SaaS24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言