在 ASP.NET WebForms 中,ClientID 會因命名容器巢狀而產生冗長動態 ID(如 ctl00_ContentPlaceHolder1_GridView1_ctl02_TextBox1),導致 Selenium 等自動化工具難以穩定定位元素。本文介紹透過 ControlAdapter 注入唯讀 testid 屬性,保留原始邏輯 ID,大幅提升測試腳本穩定性。
System.Web.UI.Adapters.ControlAdapterBeginRender,將控制項的 ID 寫入 testid 屬性.browser 定義檔指定套用的控制項類型using System.Web.UI;
using System.Web.UI.Adapters;
using System.Web.UI.HtmlControls;
public class HtmlControlAdapter : ControlAdapter
{
protected override void BeginRender(HtmlTextWriter writer)
{
if (Control.ID != null)
{
writer.AddAttribute("testid", Control.ID);
}
base.BeginRender(writer);
}
}
在 App_Browsers 資料夾新增 CustomAdapter.browser:
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.WebControls.TextBox"
adapterType="YourNamespace.HtmlControlAdapter" />
<adapter controlType="System.Web.UI.WebControls.Button"
adapterType="YourNamespace.HtmlControlAdapter" />
<adapter controlType="System.Web.UI.WebControls.DropDownList"
adapterType="YourNamespace.HtmlControlAdapter" />
<!-- 依需求擴充其他控制項 -->
</controlAdapters>
</browser>
</browsers>
<input type="text" id="ctl00_MainContent_UserName" name="ctl00$MainContent$UserName"
testid="UserName" />
<input type="submit" id="ctl00_MainContent_SubmitBtn" name="ctl00$MainContent$SubmitBtn"
testid="SubmitBtn" value="送出" />
// Selenium 範例
var userName = driver.FindElement(By.CssSelector("input[testid='UserName']"));
var submitBtn = driver.FindElement(By.CssSelector("input[testid='SubmitBtn']"));
| 優點 | 限制 |
|---|---|
| ID 語意化、可讀、不隨版面調整而變動 | 需部署 .browser 定義檔,生產環境需同步 |
| 零侵入業務邏輯,不影響既有程式碼 | 僅適用 WebForms,MVC/Razor 原生 ID 穩定無此問題 |
| 支援所有繼承 WebControl 的控制項 | 動態新增控制項需確保 ID 正確設定 |
可結合 IAttributeAccessor 於執行期動態決定是否輸出 testid,或擴充支援 data-testid 符合現代測試框架慣例。此模式同樣適用於無障礙存取(ARIA)屬性注入。
透過這項技巧,測試團隊能以固定選擇器替代脆弱的 XPath,顯著降低維護成本,加速 CI/CD 流程中的 UI 測試環節。