使用 Protractor作
AngularJS End to End 測試

Idea behind E2E
- 使用者觀點
- 後端與前端
- 增加釋出程式碼的信心度
- 不可以替代單元或整合測試
End to End
- Web-level 測試
- 必須有特定的server
- Selenium / Protractor
Unit Test
- 片斷程式碼測試
- 測試services, classes and objects
- Karma

Selenium vs Protractor ?

Protractor 輸出全域element(), 包含一個Locator參數, 回傳ElementFinder物件,然後selenium會去browser裡面找到該element。
安裝
-
Download Node.JS
npm install protractor -g
webdriver-manager start
webdriver-manager update
Step0 -
準備兩個檔案
-
conf.js
-
spec.js
設定 conf.js
//conf.js
exports.config = {
framework: 'jasmine2',
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['spec1.js']
}
開始撰寫測試檔案
// spec.js
//Jasmine 語法 - describe: 描述這個測試
describe('Protractor Demo App', function() {
//Jasmine語法 - it: 這個測試會做什麼?
it('should have a title', function() {
//Protractor 提供的global變數與方法, 告訴selenium去載入頁面
browser.get('http://localhost:1232/');
//This is wrong
expect(browser.getTitle()).toEqual('易发国际a');
});
});
Step 1 -
與elements(locator)互動
Element(Locator)
// 使用css選擇器
element( by.css('.myclass') );
// 使用id
element( by.id('myid') );
// 使用 ng-model
element( by.model('name') );
// 綁定某變數的元素
element( by.binding('bindingname') );
Element & Locator
// spec2
describe('Protractor Demo App', function() {
it('should add one and two', function() {
browser.get('http://juliemr.github.io/protractor-demo/');
element(by.model('first')).sendKeys(1);
element(by.model('second')).sendKeys(2);
element(by.id('gobutton')).click();
expect(element(by.binding('latest')).getText()).
toEqual('5'); // This is wrong!
});
});
後面加上Action
var el = element(locator);
// Click on the element
el.click();
// Send keys to the element (usually an input)
el.sendKeys('my text');
// Clear the text in an element (usually an input)
el.clear();
// Get the value of an attribute, for example, get the value of an input
el.getAttribute('value');
所有的方法都是非同步
且回傳Promise
// Example of getText() promise
element( by.model('zipcode') ).getText()
.then(function(val) {
var num = val.substring(0, 4);
var isNum = !isNaN(num);
expect( isNum ).toBeTruthy();
});
Control Flow
it('should find an element by text input model', function() {
browser.get('#/home'); // (1) method browser.get
// (2) method by.binding
var login = element(by.binding('login'));
// (3) method getText
expect(login.getText()).toEqual('User');
});
WebDriverJS 會控制Control Flow
測試lobby/Live中
路徑是否正確
// test-live.js
describe('Protractor Demo App', function() {
//在每個it 共用的code
afterEach(function() {
browser.igNoreSynchronization = false;
});
//測Xtd
it('should test XTD link', function() {
browser.get('http://localhost:1232/Lobby/Live');
browser.ignoreSynchronization = true;
element(by.css('[data-box="01"]')).click().then(function () {
browser.getAllWindowHandles().then(function (handles) {
browser.switchTo().window(handles[1]).then(function () {
expect(browser.getCurrentUrl()).toEqual('http://localhost:1232/Account/LoginToXTD');
browser.driver.switchTo().window(handles[0]);
});
})
});
});
//BB
it('should test BB link is valid', function() {
browser.get('http://localhost:1232/Lobby/Live');
browser.ignoreSynchronization = true;
element(by.css('[data-box="02"]')).click().then(function () {
browser.getAllWindowHandles().then(function (handles) {
browser.switchTo().window(handles[1]).then(function () {
expect(browser.getCurrentUrl()).toEqual('http://777.gpk-online.com/');
browser.driver.switchTo().window(handles[0]);
});
})
});
});
browser.igNoreSynchronization = false;
//AG
it('should test AG link is valid', function() {
browser.get('http://localhost:1232/Lobby/Live');
browser.ignoreSynchronization = true;
element(by.css('[data-box="03"]')).click().then(function () {
browser.getAllWindowHandles().then(function (handles) {
browser.switchTo().window(handles[1]).then(function () {
expect(browser.getCurrentUrl()).toEqual('http://localhost:1232/Account/LoginToAg?lunchGame=0');
browser.driver.switchTo().window(handles[0]);
});
})
});
});
browser.igNoreSynchronization = false;
//GPK
it('should test GPK link is valid', function() {
browser.get('http://localhost:1232/Lobby/Live');
browser.ignoreSynchronization = true;
element(by.css('[data-box="04"]')).click().then(function () {
browser.getAllWindowHandles().then(function (handles) {
browser.switchTo().window(handles[1]).then(function () {
expect(browser.getCurrentUrl()).toEqual('http://gpk-gameinfo.com/');
browser.driver.switchTo().window(handles[0]);
});
})
});
});
browser.igNoreSynchronization = false;
//MG
it('should test MG link is valid', function() {
browser.get('http://localhost:1232/Lobby/Live');
browser.ignoreSynchronization = true;
element(by.css('[data-box="05"]')).click().then(function () {
browser.getAllWindowHandles().then(function (handles) {
browser.switchTo().window(handles[1]).then(function () {
expect(browser.getCurrentUrl()).toEqual('http://localhost:1232/Account/LoginToMgLiveDealer?startTab=0');
browser.driver.switchTo().window(handles[0]);
});
})
});
});
browser.igNoreSynchronization = false;
});
//refernce: http://stackoverflow.com/questions/28511013/non-angular-page-opened-after-a-click
可維護的測試
Best practices
Page Objects
Exports; Require
Test specs
Page Object
- Duplication
- Coupling
- Maintenance
Design Pattern
interface
If UI changes, only PO changes
Page Object
//test-live.po.js
'use strict';
var LivePage = function() {
this.xtd = element(by.css('[data-box="01"]'));
this.bb = element(by.css('[data-box="02"]'));
this.ag = element(by.css('[data-box="03"]'));
this.gpk = element(by.css('[data-box="04"]'));
this.mg = element(by.css('[data-box="05"]'));
this.get = function(){
browser.get('http://localhost:1232/Lobby/Live');
}
};
module.exports = new LivePage();
Spec
//test-live2.js
describe('Protractor Live test', function() {
var testLive = require('./test-live.po.js');
afterEach(function() {
browser.igNoreSynchronization = false;
});
//測XTD
it('should test XTD link', function() {
testLive.get();
browser.ignoreSynchronization = true;
testLive.xtd.click().then(function () {
browser.getAllWindowHandles().then(function (handles) {
browser.switchTo().window(handles[1]).then(function () { expect(browser.getCurrentUrl()).toEqual('http://localhost:1232/Account/LoginToXTD');
browser.driver.switchTo().window(handles[0]);
});
})
});
});
});
測試不同瀏覽器 - conf2.js
exports.config = {
framework: 'jasmine2',
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['test-live.js'],
multiCapabilities: [{
browserName:'firefox'
},{
browserName:'chrome'
}]
}
結語
-
E2E測試不可取代單元測試
-
注意擴充性
-
其他工具
elementexplorer
測試註冊頁面
Protractor
By Eric Wang
Protractor
An Introduction to AngularJS End to End Testing using Protractor
- 1,086