[Angular] Protractor

Protractor 也是一個出自 Google 之手的 E2E 測試框架,當初是為了測試 AngularJS 所開發出來的 E2E,當然也可以用來測試其他的一般網站,跟 Angular 2 (廢言)。雖然 Protractor 出來很久了,但是一直都沒有仔細的去研究怎麼使用,這篇文章就來記錄一些。

(不談怎麼設定環境,因為 Angular CLI 已經幫我們處理好了)

目的

E2E 是利用程式來模擬使用者操作網頁的動作,測試網頁程式有符合預期的實際操作流程與結果

語法

browser

跟瀏覽器有關的行為,可透過這物件控制,列出幾個常用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// at the top of the test spec:
const fs = require('fs');

// abstract writing screen shot to a file
function writeScreenShot(data, filename) {
const stream = fs.createWriteStream(filename);
stream.write(new Buffer(data, 'base64'));
stream.end();
}

...
it('should have selected', () => {
page.navigateTo();
browser.takeScreenshot().then(function(png) {
writeScreenShot(png, 'exception.png');
});
})

by

by 是定位器 (locator),用來搜尋 element 使用的,常用的方法有

  • 單一 element
    • css:使用 CSS Selector 來定位 element
    • id:利用 Id 來定位 element
  • 一個或多個 elements
    • linkText:利用連結文字來定位 elements
    • partialLinkText:利用部分連結文字來定位 elements
    • name:利用 name 來定位 elements
    • className:利用 css class 來定位 elements
    • tagName:利用 tag name 來定位 elements
    • xpath:利用 xpath 來定位 elements (可透過瀏覽器取得該元件的 xpath)
  • 尋找按鈕(button) - 建議使用 by.css 取代

element

element 需要搭配 locator 使用,進而取得想要的 HTMLElement。常用方法(完整文件)如下

  • element(locator)
    • isPresent:是否有符合條件的 element
    • click:執行 click 動作
    • sendKeys:送出鍵盤動作。例如:打字到 Input 上
    • getAttribute: 取得 element 的 屬性值
    • getText: 取得 innerText
    • isEnabled:判斷 element 是否有 disabled 屬性
    • isSelected:判斷 element 是否 selected 屬性
    • submit: 執行 Form submit 動作
    • clear:清除 value 欄位
    • isDisplayed:判斷 element 是否有顯示在畫面上 ,可能被設定 visibility 被設定為 false
  • element.all(locator)
    • get:使用 ElementArrayIndex 取得特定位置的 element
    • count:符合條件的 element 數量
    • isPresent:是否有符合條件的 element

小技巧

加快 E2E 的測試速度

關掉 serve

預設的 E2E 是會先執行 serve 後在進行測試,可是這樣子的預設行為,都會因為 server 的建置時間而拖慢測試速度。所以可以將這兩個動作分別執行。 E2E 可以關掉 serve的動作,指令如下

ng e2e --serve=false

以上的設定方式,當執行 ng e2e時,就只會單純跑測試了。

使用 chrome headless

修改 protractor.conf.js,新增 chromeOptions 區塊內容,這樣的設定在執行 e2e 時,就不會跳出瀏覽器了

1
2
3
4
5
6
7
8
...
capabilities: {
'browserName': 'chrome',
chromeOptions: {
args: [ "--headless", "--disable-gpu", "--window-size=1920, 1080" ]
}
},
...

詳細說明請參閱燈哥的文章

使用 PageObject

一個頁面上的 Element 很多,大多數的時間,這些 Element 都因為不同的操作流程而被重複操作著,這時候利用 PageObject 的方式將 Element 封裝。

1
2
3
4
5
6
7
8
9
10
11
import {browser, by, element} from 'protractor';

export class NgRPage {
navigateTo() {
return browser.get('/');
}

getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

使用方式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {NgRPage} from './app.po';

describe('ng-r App', () => {
let page: NgRPage;

beforeEach(() => {
page = new NgRPage();
});

it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('todos');
});
});

element 與 element.all 的混搭

element(locator) 只能取回單一元素,但是可以串接下去的。例如說,我想要取得某個 select options 的值,寫法可以有兩種

1
2
3
4
// method 2
element(by.css('ul>li'));
// method 2
element(by.name('sel')).all(by.css('li'))

我個人是比較喜歡第二種的寫法,比較清爽一點

參考資料