如何使用 Protractor 寫 Radio 的驗收測試 ?
HTML 的 <input type="radio"> 是常見的控制項,該如何使用 Protractor 對 radio 寫驗收測試呢 ?
Version
Protractor 5.1.2
Requirement

畫面上共有 3 個 radio,各為 AWS、Azure 與 GCP。
下方會顯示目前所選擇的 value。
當選擇為 AWS 時,下方顯示 0。

當選擇為 Azure 時,下方顯示 1。

Acceptance Test (紅燈)
測試案例 :
- 應該有
3個<label>- 應該有
3個<input type="radio">- 當選擇
AWS,下方應該出現0- 當選擇
Azure,下方應該出現1- 當選擇
GCP,下方應該出現2
e2e/app.e2e-spec.ts
1 | import { AppPage } from './app.po'; |
11 行
1 | it(`should have '3' labels`, () => { |
測試案例 : 應該有
3個<label>
page.getLabelCount() 將由 page object 傳回 <label> 的個數。
15 行
1 | it(`should have '3' radios`, () => { |
測試案例 : 應該有
3個<input type="radio">
page.getRadioCount() 將由 page object 傳回 radio 的個數。
19 行
1 | it(`should get '0' when select 'AWS'`, () => { |
測試案例 : 當選擇
AWS,下方應該出現0
要模擬 user 選擇 radio,有兩種方式,一種是使用 index 選擇,一種是使用文字選擇。
page.selectCloudByIndex() 將使用 index 方式選擇 radio。
page.getSelectedCloud() 將傳回 user 所選擇 radio 的 value。
page.getSelectedId() 將傳回下方所顯示的 value。
25 行
1 | it(`should get '1' when select 'Azure'`, () => { |
測試案例 : 當選擇
Azure,下方應該出現1
page.selectCloudByText() 將使用文字方式選擇 radio。
e2e/app.po.ts
1 | import { browser, by, element } from 'protractor'; |
*.e2e-spec.ts 負責描述測試案例,不包含 HTML 與 CSS 部分。
*.po.ts 則負責描述 HTML 與 CSS 部分。
Page object 主要目的在於讓測試與 HTML/CSS 解耦合,不要 designer 若變動了 HTML 或 CSS,則所有驗收測試都要修改,只要修改 page object 即可。
驗收測試應該只根據需求變動而修改,不應該因為 HTML/CSS 變動而修改。
第 8 行
1 | getLabelCount(): any { |
回傳所有 <label> 的個數。
count()的型別不是number,而是wdpromise.Promise<number>,因為型別比較複雜,所以迴船型別使用any代替。
12 行
1 | getRadioCount(): any { |
使用 CSS selector 方式找到所有 radio。all() 回傳為陣列,加上 count() 可獲得陣列的筆數。
16 行
1 | selectCloudByIndex(index: number): AppPage { |
使用 index 選擇 radio。
因為 radio 已經被包在 <label> 內,所以 click <label>,就相當於 click radio。
先用 element(by.tagName('label')) 找到所有 <label> 的陣列,再透過 get(index) 選擇 <label>,最後 click()。
24 行
1 | selectCloudByText(cloud: string): AppPage { |
使用文字選擇 radio。
因為 radio 已經被包在 <label> 內,所以 click <label>,就相當於 click radio。
因為文字是屬於 <label>,而不是屬於 radio,所以使用 by.cssContainingText('label', cloud) 直接找到符合條件的 <label>,然後 click()。
30 行
1 | getSelectedCloud(): any { |
獲得所選擇 radio 的 value。
Click 時可以針對 <label>,但要獲得所選擇 radio 的 value ,就必須扎實的針對 radio。
若為同一組 radio,其 name 必定相同,所以可以 input[name="cloud"] 作為搜尋條件。
被選擇的 radio,必有 checked attribute ,因此可加上 :checked 為搜尋條件。
最後使用 getAttribute('value') 獲得 radio 的 value。

因為我們還沒實作任何功能,得到預期的驗收測試 紅燈。
Acceptance Test (綠燈)
測試案例 :
- 應該有
3個<label>- 應該有
3個<input type="radio">- 當選擇
AWS,下方應該出現0- 當選擇
Azure,下方應該出現1- 當選擇
GCP,下方應該出現2
src/app/app.component.html
1 | <label *ngFor="let cloud of clouds" [for]="cloud.name|lowercase"> |
1 | <label *ngFor="let cloud of clouds" [for]="cloud.name|lowercase">{{ cloud.name }}</label> |
<label> 與 <input type="radio"> 會依賴後端的資料顯示,故適合使用 *ngFor 產生。
for 會與 radio 的 id 先對應,使用 lowercase pipe 將 cloud.name 轉成小寫。
<label> 所顯示的值則使用 cloud.name 直接 interpolation binding。
1 | <input type="radio" name="cloud" [id]="cloud.name|lowercase" [value]="cloud.id" [checked]="cloud.checked" (change)="onChange(myRadio)" #myRadio> |
為了讓 radio 同一組,故 name 都使用 cloud。
id 使用 lowercase pipe 將 cloud.name 轉成小寫。
checked 根據 cloud.checked 決定 radio 一開始是否選取。
將 change event 綁訂到 onChange(),並將 #myRadio 傳入 onChange()。
src/app/app.component.ts
1 | import { Component } from '@angular/core'; |
第 10 行
1 | selectedId = '0'; |
selectedId field 預設為 0。
11 行
1 | clouds: Cloud[] = [ |
建立 cloud field 與設定初始陣列。
17 行
1 | onChange(element: HTMLInputElement) { |
將 HTMLInputElement.value 值指定到 selectedId field。

功能都實作出來了,重新跑一次驗收測試確認都為 綠燈。
Conclusion
- 實務上整個 ATDD 循環應該是驗收測試 (紅燈) -> 整合測試 (紅燈) -> 單元測試 (紅燈) -> 單元測試 (綠燈) -> 整合測試 (綠燈) -> 驗收測試 (綠燈),因為本文重點在於 Protract 驗收測試的 radio 寫法,所以省略了整合測試與單元測試部分。
- 本文特別針對 radio 展示了
selectCloudByIndex()與selectCloudByText()兩種寫法。
Sample Code
完整的範例可以在我的 GitHub 找到。